decidim-assemblies 0.24.3 → 0.25.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/assemblies/assembly_m_cell.rb +1 -1
  3. data/app/cells/decidim/assemblies/assembly_member/show.erb +1 -1
  4. data/app/cells/decidim/assemblies/content_blocks/highlighted_assemblies/show.erb +1 -1
  5. data/app/commands/decidim/assemblies/admin/copy_assembly.rb +11 -3
  6. data/app/commands/decidim/assemblies/admin/create_assembly.rb +2 -1
  7. data/app/commands/decidim/assemblies/admin/create_assembly_member.rb +5 -1
  8. data/app/commands/decidim/assemblies/admin/update_assembly.rb +7 -11
  9. data/app/controllers/decidim/assemblies/admin/assemblies_controller.rb +1 -5
  10. data/app/forms/decidim/assemblies/admin/assembly_form.rb +3 -2
  11. data/app/forms/decidim/assemblies/admin/assembly_import_form.rb +1 -0
  12. data/app/forms/decidim/assemblies/admin/assembly_member_form.rb +1 -1
  13. data/app/helpers/decidim/assemblies/admin/assemblies_admin_menu_helper.rb +0 -5
  14. data/app/models/decidim/assembly.rb +5 -5
  15. data/app/models/decidim/assembly_member.rb +1 -1
  16. data/app/packs/entrypoints/decidim_assemblies.js +5 -0
  17. data/app/packs/entrypoints/decidim_assemblies_admin.js +3 -0
  18. data/app/{assets/images/decidim/assemblies/assembly.svg → packs/images/decidim/assemblies/decidim_assemblies.svg} +0 -0
  19. data/app/packs/src/decidim/assemblies/admin/assemblies.js +63 -0
  20. data/app/{assets/javascripts/decidim/assemblies/admin/assembly_members.js.es6 → packs/src/decidim/assemblies/admin/assembly_members.js} +3 -3
  21. data/app/packs/src/decidim/assemblies/assemblies.js +14 -0
  22. data/app/packs/src/decidim/assemblies/orgchart.js +695 -0
  23. data/app/presenters/decidim/assemblies/assembly_presenter.rb +2 -12
  24. data/app/presenters/decidim/assemblies/assembly_stats_presenter.rb +3 -13
  25. data/app/queries/decidim/assemblies/admin/admin_users.rb +24 -11
  26. data/app/serializers/decidim/assemblies/assembly_importer.rb +3 -2
  27. data/app/serializers/decidim/assemblies/assembly_serializer.rb +4 -2
  28. data/app/views/decidim/assemblies/admin/assemblies/_form.html.erb +6 -3
  29. data/app/views/decidim/assemblies/admin/assembly_copies/_form.html.erb +1 -1
  30. data/app/views/decidim/assemblies/admin/assembly_imports/_form.html.erb +1 -1
  31. data/app/views/decidim/assemblies/admin/assembly_members/_form.html.erb +2 -2
  32. data/app/views/decidim/assemblies/assemblies/_promoted_assembly.html.erb +1 -1
  33. data/app/views/decidim/assemblies/assemblies/index.html.erb +2 -2
  34. data/app/views/decidim/assemblies/assemblies/show.html.erb +6 -4
  35. data/app/views/layouts/decidim/_assembly_header.html.erb +1 -1
  36. data/app/views/layouts/decidim/admin/assemblies.html.erb +1 -1
  37. data/app/views/layouts/decidim/admin/assembly.html.erb +4 -73
  38. data/app/views/layouts/decidim/assembly.html.erb +2 -2
  39. data/config/assets.rb +9 -0
  40. data/config/locales/ar.yml +0 -17
  41. data/config/locales/ca.yml +4 -38
  42. data/config/locales/cs.yml +4 -38
  43. data/config/locales/de.yml +4 -38
  44. data/config/locales/el.yml +0 -31
  45. data/config/locales/en.yml +5 -39
  46. data/config/locales/es-MX.yml +4 -38
  47. data/config/locales/es-PY.yml +4 -38
  48. data/config/locales/es.yml +4 -38
  49. data/config/locales/eu.yml +0 -17
  50. data/config/locales/fi-plain.yml +5 -39
  51. data/config/locales/fi.yml +5 -39
  52. data/config/locales/fr-CA.yml +1 -39
  53. data/config/locales/fr-LU.yml +449 -0
  54. data/config/locales/fr.yml +0 -38
  55. data/config/locales/gl.yml +4 -38
  56. data/config/locales/hu.yml +0 -31
  57. data/config/locales/id-ID.yml +0 -17
  58. data/config/locales/is-IS.yml +0 -14
  59. data/config/locales/it.yml +6 -39
  60. data/config/locales/ja.yml +4 -33
  61. data/config/locales/lb-LU.yml +1 -0
  62. data/config/locales/lv.yml +0 -30
  63. data/config/locales/nl.yml +5 -39
  64. data/config/locales/no.yml +0 -38
  65. data/config/locales/pl.yml +4 -41
  66. data/config/locales/pt-BR.yml +82 -17
  67. data/config/locales/pt.yml +0 -31
  68. data/config/locales/ro-RO.yml +5 -36
  69. data/config/locales/ru.yml +0 -17
  70. data/config/locales/sk.yml +0 -16
  71. data/config/locales/sl.yml +0 -17
  72. data/config/locales/sr-CS.yml +0 -16
  73. data/config/locales/sv.yml +3 -36
  74. data/config/locales/tr-TR.yml +0 -31
  75. data/config/locales/uk.yml +0 -17
  76. data/config/locales/zh-CN.yml +0 -31
  77. data/db/migrate/20210507063604_add_announcement_to_assemblies.rb +7 -0
  78. data/lib/decidim/api/assembly_type.rb +10 -1
  79. data/lib/decidim/assemblies/admin_engine.rb +122 -30
  80. data/lib/decidim/assemblies/engine.rb +6 -9
  81. data/lib/decidim/assemblies/participatory_space.rb +46 -8
  82. data/lib/decidim/assemblies/test/factories.rb +1 -0
  83. data/lib/decidim/assemblies/version.rb +1 -1
  84. metadata +21 -22
  85. data/app/assets/config/admin/decidim_assemblies_manifest.js +0 -2
  86. data/app/assets/config/decidim_assemblies_manifest.js +0 -2
  87. data/app/assets/javascripts/decidim/assemblies/admin/assemblies.js.es6 +0 -67
  88. data/app/assets/javascripts/decidim/assemblies/assemblies.js.es6 +0 -18
  89. data/app/assets/javascripts/decidim/assemblies/orgchart.js.es6 +0 -698
  90. data/app/cells/decidim/assemblies/statistic/show.erb +0 -9
  91. data/app/cells/decidim/assemblies/statistic_cell.rb +0 -20
  92. data/app/cells/decidim/assemblies/statistics/show.erb +0 -17
  93. data/app/cells/decidim/assemblies/statistics_cell.rb +0 -18
  94. data/config/locales/ja-JP.yml +0 -471
@@ -0,0 +1,695 @@
1
+ /* eslint-disable require-jsdoc, max-lines, no-return-assign, func-style, id-length, no-plusplus, no-use-before-define, no-negated-condition, init-declarations, no-invalid-this, no-param-reassign, no-ternary, multiline-ternary, no-nested-ternary, no-eval, no-extend-native, prefer-reflect */
2
+ /* eslint dot-location: ["error", "property"], no-negated-condition: "error" */
3
+ /* eslint no-unused-expressions: ["error", { "allowTernary": true }] */
4
+ /* eslint no-unused-vars: 0 */
5
+ /* global d3 */
6
+
7
+ import * as d3 from "d3"
8
+ import renderChart from "src/decidim/vizzs/renders"
9
+
10
+ // lib
11
+ const renderOrgCharts = () => {
12
+ const $orgChartContainer = $(".js-orgchart")
13
+ const $btnReset = $(".js-reset-orgchart")
14
+
15
+ let dataDepicted = null
16
+ let fake = false
17
+ let orgchart = {}
18
+
19
+ // lib - https://bl.ocks.org/bumbeishvili/b96ba47ea21d14dfce6ebb859b002d3a
20
+ const renderChartCollapsibleNetwork = (params) => {
21
+
22
+ // exposed variables
23
+ let attrs = {
24
+ id: `id${Math.floor(Math.random() * 1000000)}`,
25
+ svgWidth: 960,
26
+ svgHeight: 600,
27
+ marginTop: 0,
28
+ marginBottom: 5,
29
+ marginRight: 0,
30
+ marginLeft: 30,
31
+ container: "body",
32
+ distance: 150,
33
+ hiddenChildLevel: 1,
34
+ hoverOpacity: 0.2,
35
+ maxTextDisplayZoomLevel: 1,
36
+ lineStrokeWidth: 1.5,
37
+ fakeRoot: false,
38
+ nodeGutter: { x: 16, y: 8 },
39
+ childrenIndicatorRadius: 15,
40
+ fakeBorderWidth: 32,
41
+ data: null
42
+ }
43
+
44
+ /* ############### IF EXISTS OVERWRITE ATTRIBUTES FROM PASSED PARAM ####### */
45
+
46
+ let attrKeys = Object.keys(attrs)
47
+ attrKeys.forEach(function (key) {
48
+ if (params && params[key]) {
49
+ attrs[key] = params[key]
50
+ }
51
+ })
52
+
53
+ // innerFunctions which will update visuals
54
+ let updateData
55
+ let collapse, expand
56
+ let filter
57
+ let hierarchy = {}
58
+
59
+ // main chart object
60
+ let main = function (selection) {
61
+ selection.each(function scope() {
62
+
63
+ // calculated properties
64
+ let calc = {}
65
+ calc.chartLeftMargin = attrs.marginLeft
66
+ calc.chartTopMargin = attrs.marginTop
67
+ calc.chartWidth = attrs.svgWidth - attrs.marginRight - calc.chartLeftMargin
68
+ calc.chartHeight = attrs.svgHeight - attrs.marginBottom - calc.chartTopMargin
69
+
70
+ // ########################## HIERARCHY STUFF #########################
71
+ hierarchy.root = d3.hierarchy(attrs.data.root)
72
+
73
+ // ########################### BEHAVIORS #########################
74
+ let behaviors = {}
75
+ // behaviors.zoom = d3.zoom().scaleExtent([0.75, 100, 8]).on("zoom", zoomed)
76
+ behaviors.drag = d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended)
77
+
78
+ // ########################### LAYOUTS #########################
79
+ let layouts = {}
80
+
81
+ // custom radial layout
82
+ layouts.radial = radial()
83
+
84
+ // ########################### FORCE STUFF #########################
85
+ let force = {}
86
+ force.link = d3.forceLink().id((d) => d.id)
87
+ force.charge = d3.forceManyBody().strength(-240)
88
+ force.center = d3.forceCenter(calc.chartWidth / 2, calc.chartHeight / 2)
89
+
90
+ // prevent collide
91
+ force.collide = d3.forceCollide().radius((d) => {
92
+ // Creates an invented radius based on element measures: diagonal = 2 * radius = sqrt(width^2, height^2)
93
+ let base = (d.bbox || {}).width + (attrs.nodeGutter.x * 2)
94
+ let height = (d.bbox || {}).height + (attrs.nodeGutter.y * 2)
95
+ let diagonal = Math.sqrt(Math.pow(base, 2) + Math.pow(height, 2))
96
+ let fakeRadius = (diagonal / 2)
97
+
98
+ // return d3.max([attrs.nodeDistance * 3, fakeRadius])
99
+ return fakeRadius * 1.5
100
+ })
101
+
102
+ // manually set x positions (which is calculated using custom radial layout)
103
+ force.x = d3.forceX()
104
+ .strength(0.5)
105
+ .x(function (d) {
106
+
107
+ // if node does not have children and is channel (depth=2) , then position it on parent's coordinate
108
+ if (!d.children && d.depth > 2) {
109
+ if (d.parent) {
110
+ d = d.parent
111
+ }
112
+ }
113
+
114
+ // custom circle projection - radius will be - (d.depth - 1) * 150
115
+ return projectCircle(d.proportion, (d.depth - 1) * attrs.distance)[0]
116
+ })
117
+
118
+ // manually set y positions (which is calculated using d3.cluster)
119
+ force.y = d3.forceY()
120
+ .strength(0.5)
121
+ .y(function (d) {
122
+
123
+ // if node does not have children and is channel (depth=2) , then position it on parent's coordinate
124
+ if (!d.children && d.depth > 2) {
125
+ if (d.parent) {
126
+ d = d.parent
127
+ }
128
+ }
129
+
130
+ // custom circle projection - radius will be - (d.depth - 1) * 150
131
+ return projectCircle(d.proportion, (d.depth - 1) * attrs.distance)[1]
132
+ })
133
+
134
+ // --------------------------------- INITIALISE FORCE SIMULATION ----------------------------
135
+
136
+ // get based on top parameter simulation
137
+ force.simulation = d3.forceSimulation()
138
+ .force("link", force.link)
139
+ .force("charge", force.charge)
140
+ .force("center", force.center)
141
+ .force("collide", force.collide)
142
+ .force("x", force.x)
143
+ .force("y", force.y)
144
+
145
+ // ########################### HIERARCHY STUFF #########################
146
+
147
+ // flatten root
148
+ let arr = flatten(hierarchy.root)
149
+
150
+ // hide members based on their depth
151
+ arr.forEach((d) => {
152
+ // Hide fake root node
153
+ if ((attrs.fakeRoot) && (d.depth === 1)) {
154
+ d.hidden = true
155
+ }
156
+
157
+ if (d.depth > attrs.hiddenChildLevel) {
158
+ d._children = d.children
159
+ d.children = null
160
+ }
161
+ })
162
+
163
+ // #################################### DRAWINGS #######################
164
+
165
+ // drawing containers
166
+ let container = d3.select(this)
167
+
168
+ // add svg
169
+ let svg = container.patternify({ tag: "svg", selector: "svg-chart-container" })
170
+ .attr("width", attrs.svgWidth)
171
+ .attr("height", attrs.svgHeight)
172
+ // .call(behaviors.zoom)
173
+
174
+ // add container g element
175
+ let chart = svg.patternify({ tag: "g", selector: "chart" })
176
+ .attr("transform", `translate(${calc.chartLeftMargin},${calc.chartTopMargin})`)
177
+
178
+ // ################################ Chart Content Drawing ##################################
179
+
180
+ // link wrapper
181
+ let linksWrapper = chart.patternify({ tag: "g", selector: "links-wrapper" })
182
+
183
+ // node wrapper
184
+ let nodesWrapper = chart.patternify({ tag: "g", selector: "nodes-wrapper" })
185
+ let links, nodes
186
+
187
+ // reusable function which updates visual based on data change
188
+ update()
189
+
190
+ // update visual based on data change
191
+ function update(clickedNode) {
192
+
193
+ // Show/hide reset button
194
+ (clickedNode) ? $btnReset.removeClass("invisible") : $btnReset.addClass("invisible")
195
+
196
+ // set xy and proportion properties with custom radial layout
197
+ layouts.radial(hierarchy.root)
198
+
199
+ // nodes and links array
200
+ let nodesArr = flatten(hierarchy.root, true)
201
+ .orderBy((d) => d.depth)
202
+ .filter((d) => !d.hidden)
203
+
204
+ let linksArr = hierarchy.root.links()
205
+ .filter((d) => !d.source.hidden)
206
+ .filter((d) => !d.target.hidden)
207
+
208
+ // make new nodes to appear near the parents
209
+ nodesArr.forEach(function (d) {
210
+ if (clickedNode && clickedNode.id === (d.parent && d.parent.id)) {
211
+ d.x = d.parent.x
212
+ d.y = d.parent.y
213
+ }
214
+ })
215
+
216
+ // links
217
+ links = linksWrapper.selectAll(".link")
218
+ .data(linksArr, (d) => d.target.id)
219
+ links.exit().remove()
220
+
221
+ links = links.enter()
222
+ .append("line")
223
+ .attr("class", "link")
224
+ .merge(links)
225
+
226
+ // node groups
227
+ nodes = nodesWrapper.selectAll(".node")
228
+ .data(nodesArr, (d) => d.id)
229
+ nodes.exit().remove()
230
+
231
+ let enteredNodes = nodes.enter()
232
+ .append("g")
233
+ .attr("class", "node")
234
+
235
+ // bind event handlers
236
+ enteredNodes
237
+ .on("click", nodeClick)
238
+ .on("mouseenter", nodeMouseEnter)
239
+ .on("mouseleave", nodeMouseLeave)
240
+ .call(behaviors.drag)
241
+
242
+ // channels grandchildren
243
+ enteredNodes.append("rect")
244
+ .attr("class", "as-card")
245
+ .attr("rx", 4)
246
+ .attr("ry", 4)
247
+
248
+ enteredNodes.append("text")
249
+ .attr("class", "as-text")
250
+ .text((d) => d.data.name)
251
+
252
+ enteredNodes.selectAll("text").each(function(d) {
253
+ d.bbox = this.getBBox()
254
+ })
255
+
256
+ enteredNodes.selectAll("rect")
257
+ .attr("x", (d) => d.bbox.x - attrs.nodeGutter.x)
258
+ .attr("y", (d) => d.bbox.y - attrs.nodeGutter.y)
259
+ .attr("width", (d) => d.bbox.width + (2 * attrs.nodeGutter.x))
260
+ .attr("height", (d) => d.bbox.height + (2 * attrs.nodeGutter.y))
261
+
262
+ // append circle & text only when there are children
263
+ enteredNodes
264
+ .append("circle")
265
+ .filter((d) => Boolean(d.children) || Boolean(d._children))
266
+ .attr("class", "as-circle")
267
+ .attr("r", attrs.childrenIndicatorRadius)
268
+ .attr("cx", (d) => d.bbox.x + d.bbox.width + attrs.nodeGutter.x)
269
+ .attr("cy", (d) => d.bbox.y + d.bbox.height + attrs.nodeGutter.y)
270
+
271
+ enteredNodes
272
+ .append("text")
273
+ .filter((d) => Boolean(d.children) || Boolean(d._children))
274
+ .attr("class", "as-text")
275
+ .attr("dx", (d) => d.bbox.x + d.bbox.width + attrs.nodeGutter.x)
276
+ .attr("dy", attrs.childrenIndicatorRadius + 3)
277
+ .text((d) => d3.max([(d.children || {}).length, (d._children || {}).length]))
278
+
279
+ // merge node groups and style it
280
+ nodes = enteredNodes.merge(nodes)
281
+
282
+ // force simulation
283
+ force.simulation.nodes(nodesArr).on("tick", ticked)
284
+
285
+ // links simulation
286
+ force.simulation.force("link").links(links).id((d) => d.id).distance(attrs.distance * 2).strength(2)
287
+ }
288
+
289
+ // ####################################### EVENT HANDLERS ########################
290
+
291
+ // zoom handler
292
+ // function zoomed() {
293
+ // // get transform event
294
+ // let transform = d3.event.transform
295
+ // attrs.lastTransform = transform
296
+ //
297
+ // // apply transform event props to the wrapper
298
+ // chart.attr("transform", transform)
299
+ //
300
+ // svg.selectAll(".node").attr("transform", (d) => `translate(${d.x},${d.y}) scale(${1 / (attrs.lastTransform ? attrs.lastTransform.k : 1)})`)
301
+ // svg.selectAll(".link").attr("stroke-width", attrs.lineStrokeWidth / (attrs.lastTransform ? attrs.lastTransform.k : 1))
302
+ // }
303
+
304
+ // tick handler
305
+ function ticked() {
306
+ const fakeBorderWidth = attrs.fakeBorderWidth
307
+ const maxXValueAvailable = (value) => Math.max(Math.min(calc.chartWidth - fakeBorderWidth, value), fakeBorderWidth)
308
+ const maxYValueAvailable = (value) => Math.max(Math.min(calc.chartHeight - fakeBorderWidth, value), fakeBorderWidth)
309
+ // set links position
310
+ links
311
+ .attr("x1", (d) => maxXValueAvailable(d.source.x))
312
+ .attr("y1", (d) => maxYValueAvailable(d.source.y))
313
+ .attr("x2", (d) => maxXValueAvailable(d.target.x))
314
+ .attr("y2", (d) => maxYValueAvailable(d.target.y))
315
+
316
+ // set nodes position
317
+ svg.selectAll(".node")
318
+ .attr("transform", (d) => `translate(${maxXValueAvailable(d.x)},${maxYValueAvailable(d.y)})`)
319
+ }
320
+
321
+ // handler drag start event
322
+ function dragstarted() {
323
+ // disable node fixing
324
+ nodes.each((d) => {
325
+ d.fx = null
326
+ d.fy = null
327
+ })
328
+ }
329
+
330
+ // handle dragging event
331
+ function dragged(d) {
332
+ // make dragged node fixed
333
+ d.fx = d3.event.x
334
+ d.fy = d3.event.y
335
+ }
336
+
337
+ // -------------------- handle drag end event ---------------
338
+ function dragended() {
339
+ // we are doing nothing, here , aren't we?
340
+ }
341
+
342
+ // -------------------------- node mouse hover handler ---------------
343
+ function nodeMouseEnter(d) {
344
+ // get links
345
+ let _links = hierarchy.root.links()
346
+
347
+ // get hovered node connected links
348
+ let connectedLinks = _links.filter((l) => l.source.id === d.id || l.target.id === d.id)
349
+
350
+ // get hovered node linked nodes
351
+ let linkedNodes = connectedLinks.map((s) => s.source.id).concat(connectedLinks.map((c) => c.target.id))
352
+
353
+ // reduce all other nodes opacity
354
+ nodesWrapper.selectAll(".node")
355
+ .filter((n) => linkedNodes.indexOf(n.id) === -1)
356
+ .attr("opacity", attrs.hoverOpacity)
357
+
358
+ // reduce all other links opacity
359
+ linksWrapper.selectAll(".link")
360
+ .attr("opacity", attrs.hoverOpacity)
361
+
362
+ // highlight hovered nodes connections
363
+ linksWrapper.selectAll(".link")
364
+ .filter((l) => l.source.id === d.id || l.target.id === d.id)
365
+ .attr("opacity", 1)
366
+ }
367
+
368
+ // --------------- handle mouseleave event ---------------
369
+ function nodeMouseLeave() {
370
+ // return things back to normal
371
+ nodesWrapper.selectAll(".node")
372
+ .attr("opacity", 1)
373
+ linksWrapper.selectAll(".link")
374
+ .attr("opacity", 1)
375
+ }
376
+
377
+ // --------------- handle node click event ---------------
378
+ function nodeClick(d) {
379
+ // free fixed nodes
380
+ nodes.each((di) => {
381
+ di.fx = null
382
+ di.fy = null
383
+ })
384
+
385
+ // collapse or expand node
386
+ if (d.children) {
387
+ collapse(d)
388
+ } else if (d._children) {
389
+ expand(d)
390
+ } else {
391
+ // nothing is to collapse or expand
392
+ }
393
+
394
+ freeNodes()
395
+ }
396
+
397
+ // ######################################### UTIL FUNCS ##################################
398
+ updateData = function () {
399
+ main.run()
400
+ }
401
+
402
+ collapse = function (d, deep = false) {
403
+ if (d.children) {
404
+ if (deep) {
405
+ d.children.forEach((e) => collapse(e, true))
406
+ }
407
+
408
+ d._children = d.children
409
+ d.children = null
410
+ }
411
+
412
+ update(d)
413
+ force.simulation.restart()
414
+ force.simulation.alphaTarget(0.15)
415
+ }
416
+
417
+ expand = function (d, deep = false) {
418
+ if (d._children) {
419
+ if (deep) {
420
+ d._children.forEach((e) => expand(e, true))
421
+ }
422
+
423
+ d.children = d._children
424
+ d._children = null
425
+ }
426
+
427
+ update(d)
428
+ force.simulation.restart()
429
+ force.simulation.alphaTarget(0.15)
430
+ }
431
+
432
+ // function slowDownNodes() {
433
+ // force.simulation.alphaTarget(0.05)
434
+ // }
435
+
436
+ // function speedUpNodes() {
437
+ // force.simulation.alphaTarget(0.45)
438
+ // }
439
+
440
+ function freeNodes() {
441
+ d3.selectAll(".node").each((n) => {
442
+ n.fx = null
443
+ n.fy = null
444
+ })
445
+ }
446
+
447
+ function projectCircle(value, radius) {
448
+ let r = radius || 0
449
+ let corner = value * 2 * Math.PI
450
+ return [Math.sin(corner) * r, -Math.cos(corner) * r]
451
+ }
452
+
453
+ // recursively loop on children and extract nodes as an array
454
+ function flatten(root, clustered) {
455
+ let nodesArray = []
456
+ let i = 0
457
+ function recurse(node, depth) {
458
+ if (node.children) {
459
+ node.children.forEach(function (child) {
460
+ recurse(child, depth + 1)
461
+ })
462
+ }
463
+
464
+ if (!node.id) {
465
+ node.id = ++i
466
+ } else {
467
+ ++i
468
+ }
469
+
470
+ node.depth = depth
471
+ if (clustered) {
472
+ if (!node.cluster) {
473
+ // if cluster coordinates are not set, set it
474
+ node.cluster = { x: node.x, y: node.y }
475
+ }
476
+ }
477
+ nodesArray.push(node)
478
+ }
479
+ recurse(root, 1)
480
+ return nodesArray
481
+ }
482
+
483
+ function debug() {
484
+ if (attrs.isDebug) {
485
+ // stringify func
486
+ let stringified = String(scope)
487
+
488
+ // parse variable names
489
+ let groupVariables = stringified
490
+ // match var x-xx= {}
491
+ .match(/var\s+([\w])+\s*=\s*{\s*}/gi)
492
+ // match xxx
493
+ .map((d) => d.match(/\s+\w*/gi).filter((s) => s.trim()))
494
+ // get xxx
495
+ .map((v) => v[0].trim())
496
+
497
+ // assign local variables to the scope
498
+ groupVariables.forEach((v) => {
499
+ main[`P_${v}`] = eval(v)
500
+ })
501
+ }
502
+ }
503
+
504
+ debug()
505
+
506
+ })
507
+ }
508
+
509
+ // ----------- PROTOTYEPE FUNCTIONS ----------------------
510
+ d3.selection.prototype.patternify = function (_params) {
511
+ let selector = _params.selector
512
+ let elementTag = _params.tag
513
+ let _data = _params.data || [selector]
514
+
515
+ // pattern in action
516
+ let selection = this.selectAll(`.${selector}`).data(_data)
517
+ selection.exit().remove()
518
+ selection = selection.enter().append(elementTag).merge(selection)
519
+ selection.attr("class", selector)
520
+
521
+ return selection
522
+ }
523
+
524
+ // custom radial layout
525
+ function radial() {
526
+ return function (root) {
527
+
528
+ recurse(root, 0, 1)
529
+
530
+ function recurse(node, min, max) {
531
+ node.proportion = (max + min) / 2
532
+ if (!node.x) {
533
+
534
+ // if node has parent, match entered node positions to it's parent
535
+ if (node.parent) {
536
+ node.x = node.parent.x
537
+ } else {
538
+ node.x = 0
539
+ }
540
+ }
541
+
542
+ // if node had parent, match entered node positions to it's parent
543
+ if (!node.y) {
544
+ if (node.parent) {
545
+ node.y = node.parent.y
546
+ } else {
547
+ node.y = 0
548
+ }
549
+ }
550
+
551
+ // recursively do the same for children
552
+ if (node.children) {
553
+ let offset = (max - min) / node.children.length
554
+ node.children.forEach(function (child, i) {
555
+ let newMin = min + (offset * i)
556
+ let newMax = newMin + offset
557
+
558
+ recurse(child, newMin, newMax)
559
+ })
560
+ }
561
+ }
562
+ }
563
+ }
564
+
565
+ // https://github.com/bumbeishvili/d3js-boilerplates#orderby
566
+ Array.prototype.orderBy = function (func) {
567
+ this.sort((_a, _b) => {
568
+ let a = func(_a)
569
+ let b = func(_b)
570
+ if (typeof a === "string" || a instanceof String) {
571
+ return a.localeCompare(b)
572
+ }
573
+ return a - b
574
+ })
575
+
576
+ return this
577
+ }
578
+
579
+ // ########################## BOILEPLATE STUFF ################
580
+
581
+ // dinamic keys functions
582
+ Object.keys(attrs).forEach((key) => {
583
+ // Attach variables to main function
584
+ return main[key] = function (_) {
585
+ let string = `attrs['${key}'] = _`
586
+
587
+ if (!arguments.length) {
588
+ return eval(` attrs['${key}'];`)
589
+ }
590
+
591
+ eval(string)
592
+
593
+ return main
594
+ }
595
+ })
596
+
597
+ // set attrs as property
598
+ main.attrs = attrs
599
+
600
+ // debugging visuals
601
+ main.debug = function (isDebug) {
602
+ attrs.isDebug = isDebug
603
+ if (isDebug) {
604
+ if (!window.charts) {
605
+ window.charts = []
606
+ }
607
+ window.charts.push(main)
608
+ }
609
+ return main
610
+ }
611
+
612
+ // exposed update functions
613
+ main.data = function (value) {
614
+ if (!arguments.length) {
615
+ return attrs.data
616
+ }
617
+
618
+ attrs.data = value
619
+ if (typeof updateData === "function") {
620
+ updateData()
621
+ }
622
+ return main
623
+ }
624
+
625
+ // run visual
626
+ main.run = function () {
627
+ d3.selectAll(attrs.container)
628
+ .call(main)
629
+ return main
630
+ }
631
+
632
+ main.filter = function (filterParams) {
633
+ if (!arguments.length) {
634
+ return attrs.filterParams
635
+ }
636
+
637
+ attrs.filterParams = filterParams
638
+ if (typeof filter === "function") {
639
+ filter()
640
+ }
641
+ return main
642
+ }
643
+
644
+ main.reset = function () {
645
+
646
+ hierarchy.root.children.forEach((e) => collapse(e, true))
647
+ main.run()
648
+
649
+ return main
650
+ }
651
+
652
+ return main
653
+ }
654
+
655
+ // initialization
656
+ $orgChartContainer.each((i, container) => {
657
+
658
+ let $container = $(container)
659
+ let width = $container.width()
660
+ let height = width / (16 / 9)
661
+
662
+ d3.json($container.data("url")).then((data) => {
663
+ // Make a fake previous node if the data entry is not hierarchical
664
+ if (data instanceof Array) {
665
+ fake = true
666
+ dataDepicted = {
667
+ name: null,
668
+ children: data
669
+ }
670
+ } else {
671
+ dataDepicted = data
672
+ }
673
+
674
+ orgchart = renderChartCollapsibleNetwork()
675
+ .svgHeight(height)
676
+ .svgWidth(width)
677
+ .fakeRoot(fake)
678
+ .container(`#${container.id}`)
679
+ .data({
680
+ root: dataDepicted
681
+ })
682
+ .debug(true)
683
+ .run()
684
+ })
685
+ })
686
+
687
+ // reset
688
+ $btnReset.click(function() {
689
+ orgchart.reset()
690
+ })
691
+ }
692
+
693
+ $(() => {
694
+ renderChart(renderOrgCharts);
695
+ })