Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

knsv / mermaid / 753

24 May 2019 - 15:14 coverage increased (+0.06%) to 54.316%
753

Pull #845

travis-ci

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
Support styling of subgraphs
Pull Request #845: Support styling of subgraphs

933 of 1744 branches covered (53.5%)

Branch coverage included in aggregate %.

38 of 54 new or added lines in 3 files covered. (70.37%)

2062 of 3770 relevant lines covered (54.69%)

206.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.98
/src/diagrams/flowchart/flowDb.js
1
import * as d3 from 'd3'
2

3
import { logger } from '../../logger'
4
import utils from '../../utils'
5

6
let vertices = {}
3×
7
let edges = []
3×
8
let classes = []
3×
9
let subGraphs = []
3×
10
let subGraphLookup = {}
3×
11
let tooltips = {}
3×
12
let subCount = 0
3×
13
let direction
14
// Functions to be run after graph rendering
15
let funs = []
3×
16
/**
17
 * Function called by parser when a node definition has been found
18
 * @param id
19
 * @param text
20
 * @param type
21
 * @param style
22
 * @param classes
23
 */
24
export const addVertex = function (id, text, type, style, classes) {
3×
25
  let txt
26

27
  if (typeof id === 'undefined') {
Branches [[0, 0]] missed. 307×
28
    return
!
29
  }
30
  if (id.trim().length === 0) {
Branches [[1, 0]] missed. 307×
31
    return
!
32
  }
33

34
  if (typeof vertices[id] === 'undefined') {
307×
35
    vertices[id] = { id: id, styles: [], classes: [] }
283×
36
  }
37
  if (typeof text !== 'undefined') {
307×
38
    txt = text.trim()
43×
39

40
    // strip quotes if string starts and exnds with a quote
41
    if (txt[0] === '"' && txt[txt.length - 1] === '"') {
Branches [[4, 0], [5, 1]] missed. 43×
42
      txt = txt.substring(1, txt.length - 1)
!
43
    }
44

45
    vertices[id].text = txt
43×
46
  }
47
  if (typeof type !== 'undefined') {
307×
48
    vertices[id].type = type
43×
49
  }
50
  if (typeof style !== 'undefined') {
307×
51
    if (style !== null) {
Branches [[8, 1]] missed. 9×
52
      style.forEach(function (s) {
9×
53
        vertices[id].styles.push(s)
13×
54
      })
55
    }
56
  }
57
  if (typeof classes !== 'undefined') {
Branches [[9, 0]] missed. 307×
NEW
58
    if (classes !== null) {
Branches [[10, 0], [10, 1]] missed. !
NEW
59
      classes.forEach(function (s) {
!
NEW
60
        vertices[id].classes.push(s)
!
61
      })
62
    }
63
  }
64
}
65

66
/**
67
 * Function called by parser when a link/edge definition has been found
68
 * @param start
69
 * @param end
70
 * @param type
71
 * @param linktext
72
 */
73
export const addLink = function (start, end, type, linktext) {
3×
74
  logger.info('Got edge...', start, end)
143×
75
  const edge = { start: start, end: end, type: undefined, text: '' }
143×
76
  linktext = type.text
143×
77

78
  if (typeof linktext !== 'undefined') {
143×
79
    edge.text = linktext.trim()
37×
80

81
    // strip quotes if string starts and exnds with a quote
82
    if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
Branches [[12, 0], [13, 1]] missed. 37×
83
      edge.text = edge.text.substring(1, edge.text.length - 1)
!
84
    }
85
  }
86

87
  if (typeof type !== 'undefined') {
Branches [[14, 1]] missed. 143×
88
    edge.type = type.type
143×
89
    edge.stroke = type.stroke
143×
90
  }
91
  edges.push(edge)
143×
92
}
93

94
/**
95
 * Updates a link's line interpolation algorithm
96
 * @param pos
97
 * @param interpolate
98
 */
99
export const updateLinkInterpolate = function (pos, interp) {
3×
100
  if (pos === 'default') {
7×
101
    edges.defaultInterpolate = interp
2×
102
  } else {
103
    edges[pos].interpolate = interp
5×
104
  }
105
}
106

107
/**
108
 * Updates a link with a style
109
 * @param pos
110
 * @param style
111
 */
112
export const updateLink = function (pos, style) {
3×
113
  if (pos === 'default') {
8×
114
    edges.defaultStyle = style
1×
115
  } else {
116
    if (utils.isSubstringInArray('fill', style) === -1) {
7×
117
      style.push('fill:none')
6×
118
    }
119
    edges[pos].style = style
7×
120
  }
121
}
122

123
export const addClass = function (id, style) {
3×
124
  if (typeof classes[id] === 'undefined') {
Branches [[18, 1]] missed. 7×
125
    classes[id] = { id: id, styles: [] }
7×
126
  }
127

128
  if (typeof style !== 'undefined') {
Branches [[19, 1]] missed. 7×
129
    if (style !== null) {
Branches [[20, 1]] missed. 7×
130
      style.forEach(function (s) {
7×
131
        classes[id].styles.push(s)
16×
132
      })
133
    }
134
  }
135
}
136

137
/**
138
 * Called by parser when a graph definition is found, stores the direction of the chart.
139
 * @param dir
140
 */
141
export const setDirection = function (dir) {
3×
142
  direction = dir
138×
143
}
144

145
/**
146
 * Called by parser when a special node is found, e.g. a clickable element.
147
 * @param ids Comma separated list of ids
148
 * @param className Class to add
149
 */
150
export const setClass = function (ids, className) {
3×
151
  ids.split(',').forEach(function (id) {
2×
152
    if (typeof vertices[id] !== 'undefined') {
Branches [[21, 1]] missed. 3×
153
      vertices[id].classes.push(className)
3×
154
    }
155

156
    if (typeof subGraphLookup[id] !== 'undefined') {
Branches [[22, 0]] missed. 3×
NEW
157
      subGraphLookup[id].classes.push(className)
!
158
    }
159
  })
160
}
161

162
const setTooltip = function (ids, tooltip) {
3×
163
  ids.split(',').forEach(function (id) {
!
164
    if (typeof tooltip !== 'undefined') {
Branches [[23, 0], [23, 1]] missed. !
165
      tooltips[id] = tooltip
!
166
    }
167
  })
168
}
169

170
const setClickFun = function (id, functionName) {
3×
171
  if (typeof functionName === 'undefined') {
Branches [[24, 0], [24, 1]] missed. !
172
    return
!
173
  }
174
  if (typeof vertices[id] !== 'undefined') {
Branches [[25, 0], [25, 1]] missed. !
175
    funs.push(function (element) {
!
176
      const elem = d3.select(element).select(`[id="${id}"]`)
!
177
      if (elem !== null) {
Branches [[26, 0], [26, 1]] missed. !
178
        elem.on('click', function () {
!
179
          window[functionName](id)
!
180
        })
181
      }
182
    })
183
  }
184
}
185

186
/**
187
 * Called by parser when a link is found. Adds the URL to the vertex data.
188
 * @param ids Comma separated list of ids
189
 * @param linkStr URL to create a link for
190
 * @param tooltip Tooltip for the clickable element
191
 */
192
export const setLink = function (ids, linkStr, tooltip) {
3×
193
  ids.split(',').forEach(function (id) {
!
194
    if (typeof vertices[id] !== 'undefined') {
Branches [[27, 0], [27, 1]] missed. !
195
      vertices[id].link = linkStr
!
196
    }
197
  })
198
  setTooltip(ids, tooltip)
!
199
  setClass(ids, 'clickable')
!
200
}
201
export const getTooltip = function (id) {
3×
202
  return tooltips[id]
!
203
}
204

205
/**
206
 * Called by parser when a click definition is found. Registers an event handler.
207
 * @param ids Comma separated list of ids
208
 * @param functionName Function to be called on click
209
 * @param tooltip Tooltip for the clickable element
210
 */
211
export const setClickEvent = function (ids, functionName, tooltip) {
3×
212
  ids.split(',').forEach(function (id) { setClickFun(id, functionName) })
!
213
  setTooltip(ids, tooltip)
!
214
  setClass(ids, 'clickable')
!
215
}
216

217
export const bindFunctions = function (element) {
3×
218
  funs.forEach(function (fun) {
!
219
    fun(element)
!
220
  })
221
}
222
export const getDirection = function () {
3×
223
  return direction
4×
224
}
225
/**
226
 * Retrieval function for fetching the found nodes after parsing has completed.
227
 * @returns {{}|*|vertices}
228
 */
229
export const getVertices = function () {
3×
230
  return vertices
127×
231
}
232

233
/**
234
 * Retrieval function for fetching the found links after parsing has completed.
235
 * @returns {{}|*|edges}
236
 */
237
export const getEdges = function () {
3×
238
  return edges
124×
239
}
240

241
/**
242
 * Retrieval function for fetching the found class definitions after parsing has completed.
243
 * @returns {{}|*|classes}
244
 */
245
export const getClasses = function () {
3×
246
  return classes
5×
247
}
248

249
const setupToolTips = function (element) {
3×
250
  let tooltipElem = d3.select('.mermaidTooltip')
!
251
  if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
Branches [[28, 0], [28, 1], [29, 0], [29, 1]] missed. !
252
    tooltipElem = d3.select('body')
!
253
      .append('div')
254
      .attr('class', 'mermaidTooltip')
255
      .style('opacity', 0)
256
  }
257

258
  const svg = d3.select(element).select('svg')
!
259

260
  const nodes = svg.selectAll('g.node')
!
261
  nodes
!
262
    .on('mouseover', function () {
263
      const el = d3.select(this)
!
264
      const title = el.attr('title')
!
265
      // Dont try to draw a tooltip if no data is provided
266
      if (title === null) {
Branches [[30, 0], [30, 1]] missed. !
267
        return
!
268
      }
269
      const rect = this.getBoundingClientRect()
!
270

271
      tooltipElem.transition()
!
272
        .duration(200)
273
        .style('opacity', '.9')
274
      tooltipElem.html(el.attr('title'))
!
275
        .style('left', (rect.left + (rect.right - rect.left) / 2) + 'px')
276
        .style('top', (rect.top - 14 + document.body.scrollTop) + 'px')
277
      el.classed('hover', true)
!
278
    })
279
    .on('mouseout', function () {
280
      tooltipElem.transition()
!
281
        .duration(500)
282
        .style('opacity', 0)
283
      const el = d3.select(this)
!
284
      el.classed('hover', false)
!
285
    })
286
}
287
funs.push(setupToolTips)
3×
288

289
/**
290
 * Clears the internal graph db so that a new graph can be parsed.
291
 */
292
export const clear = function () {
3×
293
  vertices = {}
135×
294
  classes = {}
135×
295
  edges = []
135×
296
  funs = []
135×
297
  funs.push(setupToolTips)
135×
298
  subGraphs = []
135×
299
  subGraphLookup = {}
135×
300
  subCount = 0
135×
301
  tooltips = []
135×
302
}
303
/**
304
 *
305
 * @returns {string}
306
 */
307
export const defaultStyle = function () {
3×
308
  return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;'
!
309
}
310

311
/**
312
 * Clears the internal graph db so that a new graph can be parsed.
313
 */
314
export const addSubGraph = function (id, list, title) {
3×
315
  function uniq (a) {
316
    const prims = { 'boolean': {}, 'number': {}, 'string': {} }
10×
317
    const objs = []
10×
318

319
    return a.filter(function (item) {
10×
320
      const type = typeof item
39×
321
      if (item.trim() === '') {
39×
322
        return false
15×
323
      }
324
      if (type in prims) { return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true) } else { return objs.indexOf(item) >= 0 ? false : objs.push(item) }
Branches [[32, 1], [34, 0], [34, 1]] missed. 24×
325
    })
326
  }
327

328
  let nodeList = []
10×
329

330
  nodeList = uniq(nodeList.concat.apply(nodeList, list))
10×
331

332
  id = id || ('subGraph' + subCount)
10×
333
  title = title || ''
Branches [[36, 1]] missed. 10×
334
  subCount = subCount + 1
10×
335
  const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] }
10×
336
  subGraphs.push(subGraph)
10×
337
  subGraphLookup[id] = subGraph
10×
338
  return id
10×
339
}
340

341
const getPosForId = function (id) {
3×
342
  for (let i = 0; i < subGraphs.length; i++) {
!
343
    if (subGraphs[i].id === id) {
Branches [[37, 0], [37, 1]] missed. !
344
      return i
!
345
    }
346
  }
347
  return -1
!
348
}
349
let secCount = -1
3×
350
const posCrossRef = []
3×
351
const indexNodes2 = function (id, pos) {
3×
352
  const nodes = subGraphs[pos].nodes
!
353
  secCount = secCount + 1
!
354
  if (secCount > 2000) {
Branches [[38, 0], [38, 1]] missed. !
355
    return
!
356
  }
357
  posCrossRef[secCount] = pos
!
358
  // Check if match
359
  if (subGraphs[pos].id === id) {
Branches [[39, 0], [39, 1]] missed. !
360
    return {
!
361
      result: true,
362
      count: 0
363
    }
364
  }
365

366
  let count = 0
!
367
  let posCount = 1
!
368
  while (count < nodes.length) {
!
369
    const childPos = getPosForId(nodes[count])
!
370
    // Ignore regular nodes (pos will be -1)
371
    if (childPos >= 0) {
Branches [[40, 0], [40, 1]] missed. !
372
      const res = indexNodes2(id, childPos)
!
373
      if (res.result) {
Branches [[41, 0], [41, 1]] missed. !
374
        return {
!
375
          result: true,
376
          count: posCount + res.count
377
        }
378
      } else {
379
        posCount = posCount + res.count
!
380
      }
381
    }
382
    count = count + 1
!
383
  }
384

385
  return {
!
386
    result: false,
387
    count: posCount
388
  }
389
}
390

391
export const getDepthFirstPos = function (pos) {
3×
392
  return posCrossRef[pos]
!
393
}
394
export const indexNodes = function () {
3×
395
  secCount = -1
!
396
  if (subGraphs.length > 0) {
Branches [[42, 0], [42, 1]] missed. !
397
    indexNodes2('none', subGraphs.length - 1, 0)
!
398
  }
399
}
400

401
export const getSubGraphs = function () {
3×
402
  return subGraphs
3×
403
}
404

405
export default {
406
  addVertex,
407
  addLink,
408
  updateLinkInterpolate,
409
  updateLink,
410
  addClass,
411
  setDirection,
412
  setClass,
413
  getTooltip,
414
  setClickEvent,
415
  setLink,
416
  bindFunctions,
417
  getDirection,
418
  getVertices,
419
  getEdges,
420
  getClasses,
421
  clear,
422
  defaultStyle,
423
  addSubGraph,
424
  getDepthFirstPos,
425
  indexNodes,
426
  getSubGraphs
427
}
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2019 Coveralls, LLC