UNPKG

jointjs

Version:

JavaScript diagramming library

358 lines (294 loc) 18.3 kB
<!DOCTYPE html> <html> <head> <link rel="canonical" href="http://www.jointjs.com/" /> <meta name="description" content="Create interactive diagrams in JavaScript easily. JointJS plugins for ERD, Org chart, FSA, UML, PN, DEVS, LDM diagrams are ready to use." /> <meta name="keywords" content="JointJS, JavaScript, diagrams, diagramming library, UML, charts" /> <link href="http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700" rel="stylesheet" type="text/css" /> <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet"> <link rel="stylesheet" href="css/tutorial.css" /> <link rel="stylesheet" href="../node_modules/prismjs/themes/prism.css"> <!-- Dependencies: --> <script src="../node_modules/jquery/dist/jquery.js"></script> <script src="../node_modules/lodash/lodash.js"></script> <script src="../node_modules/backbone/backbone.js"></script> <link rel="stylesheet" type="text/css" href="../build/joint.min.css" /> <script type="text/javascript" src="../build/joint.min.js"></script> <title>JointJS - JavaScript diagramming library - Getting started.</title> </head> <body class="language-javascript tutorial-page"> <script> SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(toElement) { return toElement.getScreenCTM().inverse().multiply(this.getScreenCTM()); }; </script> <div id="custom-elements" class="tutorial"> <h2>Custom Elements</h2> <p>This is the fourth article of the intermediate section of the JointJS tutorial. <a href="serialization.html">Go back to serialization.</a> Alternatively, you can <a href="intermediate.html">return to index</a> of all articles.</p> <p>JointJS comes with several collections of built-in element shapes. We already saw two members of the <code>joint.shapes.standard</code> collection in the basic <a href="elements.html">element</a> and <a href="links.html">links</a> demos.</p> <p>However, even though there are many default shapes to choose from, you might find that you are missing one and want to create your own shape definition. Creating new shapes is very simple in JointJS if you already know SVG. The most important SVG elements are <code>rect</code>, <code>text</code>, <code>circle</code>, <code>ellipse</code>, <code>image</code> and <code>path</code>. Their thorough description with examples and illustrations can be found elsewhere on the Internet, e.g. on <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element" target="_blank">MDN</a>. What is important for us is to realize that combining the basic SVG shapes, we can define any 2D shape we need.</p> <p>For that, we use a builtin JointJS function:</p> <ul> <li><a href="/docs/jointjs#dia.Cell.define" target="_blank"><code>Element.define(type [, defaultInstanceProperties, prototypeProperties, staticProperties])</code></a> - define a new subtype of this <code>Element</code> class.</li> </ul> <p>If you want to create an Element subtype from scratch, you should inherit from the default <code>joint.dia.Element</code> class by calling <code>joint.dia.Element.define()</code>. You can also inherit from any shape in the predefined JointJS shape collections (e.g. <code>joint.shapes.standard.Rectangle.define()</code>) and even any custom element subtype you have previously defined.</p> <p>Here is how the parameters of the <code>define()</code> function map to familiar building blocks of elements:</p> <ul> <li><code>type</code> - the <a href="#name">name</a> of the subtype class.</li> <li><code>defaultInstanceProperties</code> - object that contains properties to be assigned to every constructed instance of the subtype. Used for specifying <a href="#default-attributes">default attributes</a>.</li> <li><code>prototypeProperties</code> - object that contains properties to be assigned on the subtype prototype. Intended for properties intrinsic to the subtype, not usually modified. Used for specifying shape <a href="#markup">markup</a>.</li> <li><code>staticProperties</code> - object that contains properties to be assigned on the subtype constructor. Not very common, used mostly for <a href="#static-properties">alternative constructor functions</a>.</li> </ul> <p>Let us use the familiar <a href="/docs/jointjs#shapes.standard.Rectangle" target="_blank"><code>joint.shapes.standard.Rectangle</code></a> shape definition as an example:</p> <pre><code>joint.dia.Element.define('standard.Rectangle', { attrs: { body: { refWidth: '100%', refHeight: '100%', strokeWidth: 2, stroke: '#000000', fill: '#FFFFFF' }, label: { textVerticalAnchor: 'middle', textAnchor: 'middle', refX: '50%', refY: '50%', fontSize: 14, fill: '#333333' } } }, { markup: [{ tagName: 'rect', selector: 'body', }, { tagName: 'text', selector: 'label' }] });</code></pre> <h3 id="name">Name</h3> <p>The first argument of the <code>define()</code> function is a unique identifier under which you want to be able to find the new type. The first part of the name, <code>joint.shapes</code>, is implied. Thus, we can see that the name of a type accessed as <code>joint.shapes.standard.Rectangle</code> has to be <code>'standard.Rectangle'</code>.</p> <h3 id="markup">Markup</h3> <p>Markup is usually provided inside the third argument of the <code>define()</code> function (prototype properties) for improved performance. (This is because markup is something that all instances of the element type are expected to have in common, so inheriting from subtype prototype is more efficient. Nevertheless, it is still possible to provide custom markup to individual instances of your class by providing individual <code>markup</code> later.)</p> <p>The <code>markup</code> property is specified as a JSON array. Each member of the array is taken to define one subelement of the new shape. Subelements are defined with objects containing a <code>tagName</code> (a string with the SVG tag name of the subelement) and a <code>selector</code> (a string identifier for this subelement in the shape). More advanced <code>markup.attributes</code> are explored in the <a href="custom-links.html">custom links tutorial</a>. Although JointJS can also understand SVG markup in string form, that approach is noticeably slower due to the need for parsing and lack of capacity for custom selectors.</p> <p>We can see that <code>joint.shapes.standard.Rectangle</code> is composed of two subelements - an SVGRectElement named <code>body</code> and an SVGTextElement named <code>label</code>:</p> <pre><code>{ markup: [{ tagName: 'rect', selector: 'body', }, { tagName: 'text', selector: 'label' }] }</code></pre> <p>Selectors are important for the targeting of subelements by element attributes. Although providing a selector to identify a subelement is not strictly required, it makes JointJS noticeably faster because it can avoid querying the DOM. If you really do not want to invent a custom name for the subelement selector, you can use the subelement's tagName. (For up to one subelement per tagName; selector names have to be unique.)</p> <pre><code>{ markup: [{ tagName: 'rect', selector: 'rect', }, { tagName: 'text', selector: 'text' }] }</code></pre> <h3 id="default-attributes">Default Attributes</h3> <p>Default attributes are usually provided inside the second argument of the <code>define()</code> function (default instance properties). (This is because all instances of the element type are expected to have their own individual attributes, so inheriting from the prototype is likely to cause unnecessary overhead.)</p> <p>In the <code>attrs</code> object, keys correspond to subelement selectors, as defined in the <a href="#markup">markup</a>. For each subelement, an object with attribute name-value pairs is then expected. Each of these attributes can be a native SVG attribute or a <a href="special-attributes.html">JointJS special attribute</a>.</p> <p>In <code>joint.shapes.standard.Rectangle</code>, we can see that the subelement referenced by <code>body</code> (i.e. the SVGRectElement component of the shape) has its default width and height set in terms of the model size (using the <a href="special-attributes.html#relative-dimensions">relative-dimension special attributes</a> <code>refWidth</code>/<code>refHeight</code>), alongside the fill and stroke styling. The <code>label</code> subelement (the SVGTextElement component of the shape) has its text anchor set to the center of the text bbox; that point is then positioned into the center of the model bbox by the <a href="special-attributes.html#relative-dimensions">two relative-position special attributes</a> <code>refX</code>/<code>refY</code>. Its font size is specified, and the font fill color:</p> <pre><code>{ attrs: { body: { refWidth: '100%', refHeight: '100%', strokeWidth: 2, stroke: '#000000', fill: '#FFFFFF' }, label: { textVerticalAnchor: 'middle', textAnchor: 'middle', refX: '50%', refY: '50%', fontSize: 14, fill: '#333333' } } }</code></pre> <p>We mentioned model size - the dimensions of the <code>body</code> depend on it - but what is it? Model size is set by the user, whenever they call the <a href="/docs/jointjs#dia.Element.prototype.resize"><code>element.resize()</code></a> function on an instance of <code>joint.shapes.standard.Rectangle</code>. For example, if we used <code>element.resize(100, 100)</code>, the referenced size would be 100px by 100px; consequently, if no transformations were applied, the <code>body</code> would have the dimensions (100,100). The model bbox is invisible on its own; it only becomes visible through subelements that reference it. Here, that subelement is the <code>body</code> subelement.</p> <p>There does not need to be a direct mapping between model size and any visible subelements. It is perfectly possible to replace the <code>refWidth</code>/<code>refHeight</code> relative attributes with the native SVG (absolute) <code>width</code>/<code>height</code>; this would create a subelement with constant dimensions - 20px by 20px, for example - regardless of model size. That may be ideal for control buttons.</p> <p>Similarly, the position of <code>label</code> anchor is defined in terms of model size and position. Model position is modified by calling the <a href="/docs/jointjs#dia.Element.prototype.position"><code>element.position()</code></a> function on an instance of <code>joint.shapes.standard.Rectangle</code>. For example, if we called <code>element.position(-100, -100)</code>, the origin of the reference coordinates would move to the point (-100,-100) on the paper.</p> <p>We can see that the anchor of the <code>label</code> of <code>joint.shapes.standard.Rectangle</code> is placed at <code>refX: '50%'</code>,<code>refY: '50%'</code> by default. The percentages refer to model size; thus, if model size were kept at (100,100), they would mean that the anchor of the <code>label</code> will be offset from the reference origin by (50,50). Thus, if model position were (-100,-100) and size were (100, 100), the label anchor would be positioned at (-50,-50).</p> <p>Relative positioning and sizing of elements is explained in more detail in a <a href="special-attributes.html#relative-dimensions">separate section of the tutorial</a>.</p> <h3 id="static-properties">Static Properties</h3> <p>Static properties are not used by <code>joint.shapes.standard.Rectangle</code>, but let us discuss them a little bit to gain a complete overview of custom elements.</p> <p>Imagine we wanted to define our own subtype of <code>joint.shapes.standard.Rectangle</code> (which we name <code>'examples.CustomRectangle'</code>), with the added benefit of a constructor function that chose a random style for the rectangle body - maybe because we need to add a lot of diverse rectangle shapes quickly. We could do this with a static function <code>createRandom</code>; then, we would have two ways to obtain an instance of CustomRectangle:</p> <p>With the standard constructor ...</p> <pre><code>var customRectangle = new joint.shapes.examples.CustomRectangle();</code></pre> <p>... or with the new static function:</p> <pre><code>var customRectangle = joint.shapes.examples.CustomRectangle.createRandom();</code></pre> <p>Both of these functions are demonstrated in our <a href="#example">example</a>.</p> <h3 id="example">Example</h3> <p>Let us apply everything we learned so far and create a new <cdoe>joint.shapes.examples.CustomRectangle</cdoe> class based on <code>joint.shapes.standard.Rectangle</code>. Keep in mind that the provided instance properties are mixined with the parent definition, but prototype and static properties are not. This means that it is enough for us to record only the attributes that changed in the definition of the custom element subtype (however, if we wanted to change the markup, we would have to do so explicitly).</p> <pre><code>joint.shapes.standard.Rectangle.define('examples.CustomRectangle', { attrs: { body: { rx: 10, // add a corner radius ry: 10, strokeWidth: 1, fill: 'cornflowerblue' }, label: { textAnchor: 'left', // align text to left refX: 10, // offset text from right edge of model bbox fill: 'white', fontSize: 18 } } }, { // inherit joint.shapes.standard.Rectangle.markup }, { createRandom: function() { var rectangle = new this(); var fill = '#' + ('000000' + Math.floor(Math.random() * 16777215).toString(16)).slice(-6); var stroke = '#' + ('000000' + Math.floor(Math.random() * 16777215).toString(16)).slice(-6); var strokeWidth = Math.floor(Math.random() * 6); var strokeDasharray = Math.floor(Math.random() * 6) + ' ' + Math.floor(Math.random() * 6); var radius = Math.floor(Math.random() * 21); rectangle.attr({ body: { fill: fill, stroke: stroke, strokeWidth: strokeWidth, strokeDasharray: strokeDasharray, rx: radius, ry: radius }, label: { // ensure visibility on dark backgrounds fill: 'black', stroke: 'white', strokeWidth: 1, fontWeight: 'bold' } }); return rectangle; } });</code></pre> <p>The following example shows the default look of <code>joint.shapes.standard.Rectangle</code> (i.e. with no instance attributes set) alongside the default look of our custom element and the randomized results of the <code>createRandom()</code> constructor function. Try <a onClick="window.location.reload()" style="cursor: pointer">refreshing the page</a> to see <code>createRandom()</code> in action.</p> <div class="paper" id="paper-custom-elements"></div> <p>JointJS source code: <a href="js/custom-elements.js" target="_blank">custom-elements.js</a></p> <p><a href="custom-links.html">Now that we know how to create custom element models, let us learn about custom links.</a></p> </div><!--end tutorial--> <script src="../node_modules/prismjs/prism.js"></script> <script src="js/custom-elements.js"></script> </body> </html>