1/** js sequence diagrams 2 * https://bramp.github.io/js-sequence-diagrams/ 3 * (c) 2012-2017 Andrew Brampton (bramp.net) 4 * Simplified BSD license. 5 */ 6/*global Diagram, Snap, WebFont _ */ 7// TODO Move defintion of font onto the <svg>, so it can easily be override at each level 8if (typeof Snap != 'undefined') { 9 10 var xmlns = 'http://www.w3.org/2000/svg'; 11 12 var LINE = { 13 'stroke': '#000000', 14 'stroke-width': 2, // BUG TODO This gets set as a style, not as a attribute. Look at eve.on("snap.util.attr"... 15 'fill': 'none' 16 }; 17 18 var RECT = { 19 'stroke': '#000000', 20 'stroke-width': 2, 21 'fill': '#fff' 22 }; 23 24 var LOADED_FONTS = {}; 25 26 /****************** 27 * SnapTheme 28 ******************/ 29 30 var SnapTheme = function(diagram, options, resume) { 31 _.defaults(options, { 32 'css-class': 'simple', 33 'font-size': 16, 34 'font-family': 'Andale Mono, monospace' 35 }); 36 37 this.init(diagram, options, resume); 38 }; 39 40 _.extend(SnapTheme.prototype, BaseTheme.prototype, { 41 42 init: function(diagram, options, resume) { 43 BaseTheme.prototype.init.call(this, diagram); 44 45 this.paper_ = undefined; 46 this.cssClass_ = options['css-class'] || undefined; 47 this.font_ = { 48 'font-size': options['font-size'], 49 'font-family': options['font-family'] 50 }; 51 52 var a = this.arrowTypes_ = {}; 53 a[ARROWTYPE.FILLED] = 'Block'; 54 a[ARROWTYPE.OPEN] = 'Open'; 55 56 var l = this.lineTypes_ = {}; 57 l[LINETYPE.SOLID] = ''; 58 l[LINETYPE.DOTTED] = '6,2'; 59 60 var that = this; 61 this.waitForFont(function() { 62 resume(that); 63 }); 64 }, 65 66 // Wait for loading of the font 67 waitForFont: function(callback) { 68 var fontFamily = this.font_['font-family']; 69 70 if (typeof WebFont == 'undefined') { 71 throw new Error('WebFont is required (https://github.com/typekit/webfontloader).'); 72 } 73 74 if (LOADED_FONTS[fontFamily]) { 75 // If already loaded, just return instantly. 76 callback(); 77 return; 78 } 79 80 WebFont.load({ 81 custom: { 82 families: [fontFamily] // TODO replace this with something that reads the css 83 }, 84 classes: false, // No need to place classes on the DOM, just use JS Events 85 active: function() { 86 LOADED_FONTS[fontFamily] = true; 87 callback(); 88 }, 89 inactive: function() { 90 // If we fail to fetch the font, still continue. 91 LOADED_FONTS[fontFamily] = true; 92 callback(); 93 } 94 }); 95 }, 96 97 addDescription: function(svg, description) { 98 var desc = document.createElementNS(xmlns, 'desc'); 99 desc.appendChild(document.createTextNode(description)); 100 svg.appendChild(desc); 101 }, 102 103 setupPaper: function(container) { 104 // Container must be a SVG element. We assume it's a div, so lets create a SVG and insert 105 var svg = document.createElementNS(xmlns, 'svg'); 106 container.appendChild(svg); 107 108 this.addDescription(svg, this.diagram.title || ''); 109 110 this.paper_ = Snap(svg); 111 this.paper_.addClass('sequence'); 112 113 if (this.cssClass_) { 114 this.paper_.addClass(this.cssClass_); 115 } 116 117 this.beginGroup(); 118 119 // TODO Perhaps only include the markers if we actually use them. 120 var a = this.arrowMarkers_ = {}; 121 var arrow = this.paper_.path('M 0 0 L 5 2.5 L 0 5 z'); 122 a[ARROWTYPE.FILLED] = arrow.marker(0, 0, 5, 5, 5, 2.5) 123 .attr({id: 'markerArrowBlock'}); 124 125 arrow = this.paper_.path('M 9.6,8 1.92,16 0,13.7 5.76,8 0,2.286 1.92,0 9.6,8 z'); 126 a[ARROWTYPE.OPEN] = arrow.marker(0, 0, 9.6, 16, 9.6, 8) 127 .attr({markerWidth: '4', id: 'markerArrowOpen'}); 128 }, 129 130 layout: function() { 131 BaseTheme.prototype.layout.call(this); 132 this.paper_.attr({ 133 width: this.diagram.width + 'px', 134 height: this.diagram.height + 'px' 135 }); 136 }, 137 138 textBBox: function(text, font) { 139 // TODO getBBox will return the bounds with any whitespace/kerning. This makes some of our aligments screwed up 140 var t = this.createText(text, font); 141 var bb = t.getBBox(); 142 t.remove(); 143 return bb; 144 }, 145 146 // For each drawn element, push onto the stack, so it can be wrapped in a single outer element 147 pushToStack: function(element) { 148 this._stack.push(element); 149 return element; 150 }, 151 152 // Begin a group of elements 153 beginGroup: function() { 154 this._stack = []; 155 }, 156 157 // Finishes the group, and returns the <group> element 158 finishGroup: function() { 159 var g = this.paper_.group.apply(this.paper_, this._stack); 160 this.beginGroup(); // Reset the group 161 return g; 162 }, 163 164 createText: function(text, font) { 165 text = _.invoke(text.split('\n'), 'trim'); 166 var t = this.paper_.text(0, 0, text); 167 t.attr(font || {}); 168 if (text.length > 1) { 169 // Every row after the first, set tspan to be 1.2em below the previous line 170 t.selectAll('tspan:nth-child(n+2)').attr({ 171 dy: '1.2em', 172 x: 0 173 }); 174 } 175 176 return t; 177 }, 178 179 drawLine: function(x1, y1, x2, y2, linetype, arrowhead) { 180 var line = this.paper_.line(x1, y1, x2, y2).attr(LINE); 181 if (linetype !== undefined) { 182 line.attr('strokeDasharray', this.lineTypes_[linetype]); 183 } 184 if (arrowhead !== undefined) { 185 line.attr('markerEnd', this.arrowMarkers_[arrowhead]); 186 } 187 return this.pushToStack(line); 188 }, 189 190 drawRect: function(x, y, w, h) { 191 var rect = this.paper_.rect(x, y, w, h).attr(RECT); 192 return this.pushToStack(rect); 193 }, 194 195 /** 196 * Draws text with a optional white background 197 * x,y (int) x,y top left point of the text, or the center of the text (depending on align param) 198 * text (string) text to print 199 * font (Object) 200 * align (string) ALIGN_LEFT or ALIGN_CENTER 201 */ 202 drawText: function(x, y, text, font, align) { 203 var t = this.createText(text, font); 204 var bb = t.getBBox(); 205 206 if (align == ALIGN_CENTER) { 207 x = x - bb.width / 2; 208 y = y - bb.height / 2; 209 } 210 211 // Now move the text into place 212 // `y - bb.y` because text(..) is positioned from the baseline, so this moves it down. 213 t.attr({x: x - bb.x, y: y - bb.y}); 214 t.selectAll('tspan').attr({x: x}); 215 216 this.pushToStack(t); 217 return t; 218 }, 219 220 drawTitle: function() { 221 this.beginGroup(); 222 BaseTheme.prototype.drawTitle.call(this); 223 return this.finishGroup().addClass('title'); 224 }, 225 226 drawActor: function(actor, offsetY, height) { 227 this.beginGroup(); 228 BaseTheme.prototype.drawActor.call(this, actor, offsetY, height); 229 return this.finishGroup().addClass('actor'); 230 }, 231 232 drawSignal: function(signal, offsetY) { 233 this.beginGroup(); 234 BaseTheme.prototype.drawSignal.call(this, signal, offsetY); 235 return this.finishGroup().addClass('signal'); 236 }, 237 238 drawSelfSignal: function(signal, offsetY) { 239 this.beginGroup(); 240 BaseTheme.prototype.drawSelfSignal.call(this, signal, offsetY); 241 return this.finishGroup().addClass('signal'); 242 }, 243 244 drawNote: function(note, offsetY) { 245 this.beginGroup(); 246 BaseTheme.prototype.drawNote.call(this, note, offsetY); 247 return this.finishGroup().addClass('note'); 248 }, 249 }); 250 251 /****************** 252 * SnapHandTheme 253 ******************/ 254 255 var SnapHandTheme = function(diagram, options, resume) { 256 _.defaults(options, { 257 'css-class': 'hand', 258 'font-size': 16, 259 'font-family': 'danielbd' 260 }); 261 262 this.init(diagram, options, resume); 263 }; 264 265 // Take the standard SnapTheme and make all the lines wobbly 266 _.extend(SnapHandTheme.prototype, SnapTheme.prototype, { 267 drawLine: function(x1, y1, x2, y2, linetype, arrowhead) { 268 var line = this.paper_.path(handLine(x1, y1, x2, y2)).attr(LINE); 269 if (linetype !== undefined) { 270 line.attr('strokeDasharray', this.lineTypes_[linetype]); 271 } 272 if (arrowhead !== undefined) { 273 line.attr('markerEnd', this.arrowMarkers_[arrowhead]); 274 } 275 return this.pushToStack(line); 276 }, 277 278 drawRect: function(x, y, w, h) { 279 var rect = this.paper_.path(handRect(x, y, w, h)).attr(RECT); 280 return this.pushToStack(rect); 281 } 282 }); 283 284 registerTheme('snapSimple', SnapTheme); 285 registerTheme('snapHand', SnapHandTheme); 286} 287