decidim-core 0.13.1 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of decidim-core might be problematic. Click here for more details.

Files changed (236) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/decidim_core_manifest.js +1 -1
  3. data/app/assets/images/decidim/gamification/badges/invitations.svg +117 -0
  4. data/app/assets/javascripts/decidim.js.es6 +4 -1
  5. data/app/assets/javascripts/decidim/ajax_modals.js.es6 +17 -0
  6. data/app/assets/javascripts/decidim/conferences.js.es6 +16 -0
  7. data/app/assets/javascripts/decidim/input_hashtags.js.es6 +115 -0
  8. data/app/assets/javascripts/decidim/input_mentions.js.es6 +2 -3
  9. data/app/assets/javascripts/decidim/vizzs/areachart.js.es6 +226 -0
  10. data/app/assets/javascripts/decidim/vizzs/metrics.js.es6 +26 -0
  11. data/app/assets/javascripts/decidim/vizzs/orgchart.js.es6 +701 -0
  12. data/app/assets/javascripts/decidim/vizzs/renders.js.es6 +11 -0
  13. data/app/assets/stylesheets/decidim/extras/_proposal_form.scss +3 -1
  14. data/app/assets/stylesheets/decidim/layouts/_home.scss +1 -1
  15. data/app/assets/stylesheets/decidim/modules/_areachart.scss +74 -0
  16. data/app/assets/stylesheets/decidim/modules/_badges.scss +116 -0
  17. data/app/assets/stylesheets/decidim/modules/_buttons.scss +5 -0
  18. data/app/assets/stylesheets/decidim/modules/_cards.scss +21 -4
  19. data/app/assets/stylesheets/decidim/modules/_chart-tooltip.scss +42 -0
  20. data/app/assets/stylesheets/decidim/modules/_collapsible-list.scss +12 -8
  21. data/app/assets/stylesheets/decidim/modules/_conference-nav.scss +31 -0
  22. data/app/assets/stylesheets/decidim/modules/_conference-programme.scss +110 -0
  23. data/app/assets/stylesheets/decidim/modules/_conference-speaker.scss +86 -0
  24. data/app/assets/stylesheets/decidim/modules/_conversation.scss +58 -0
  25. data/app/assets/stylesheets/decidim/modules/_help.scss +38 -0
  26. data/app/assets/stylesheets/decidim/modules/_hover-section.scss +29 -0
  27. data/app/assets/stylesheets/decidim/modules/_icons.scss +10 -4
  28. data/app/assets/stylesheets/decidim/modules/_input-hashtags.scss +124 -0
  29. data/app/assets/stylesheets/decidim/modules/_loading-spinner.scss +12 -0
  30. data/app/assets/stylesheets/decidim/modules/_margins.scss +2 -2
  31. data/app/assets/stylesheets/decidim/modules/_modules.scss +15 -0
  32. data/app/assets/stylesheets/decidim/modules/_navbar.scss +9 -0
  33. data/app/assets/stylesheets/decidim/modules/_orgchart.scss +62 -0
  34. data/app/assets/stylesheets/decidim/modules/_status-labels.scss +2 -1
  35. data/app/assets/stylesheets/decidim/modules/_typography.scss +9 -0
  36. data/app/assets/stylesheets/decidim/utils/_helpers.scss +28 -0
  37. data/app/assets/stylesheets/decidim/utils/_mixins.scss +63 -0
  38. data/app/cells/decidim/author/withdraw.erb +1 -1
  39. data/app/cells/decidim/author_cell.rb +1 -1
  40. data/app/cells/decidim/badge/show.erb +36 -0
  41. data/app/cells/decidim/badge_cell.rb +53 -0
  42. data/app/cells/decidim/badges/show.erb +6 -0
  43. data/app/cells/decidim/badges_cell.rb +14 -0
  44. data/app/cells/decidim/card_m/header.erb +1 -1
  45. data/app/cells/decidim/card_m/show.erb +1 -2
  46. data/app/cells/decidim/card_m/top.erb +7 -0
  47. data/app/cells/decidim/card_m_cell.rb +14 -17
  48. data/app/cells/decidim/coauthorships_cell.rb +77 -0
  49. data/app/cells/decidim/collapsible_authors/show.erb +0 -1
  50. data/app/cells/decidim/collapsible_authors_cell.rb +4 -4
  51. data/app/cells/decidim/collapsible_list/show.erb +12 -4
  52. data/app/cells/decidim/collapsible_list_cell.rb +14 -12
  53. data/app/cells/decidim/content_blocks/footer_sub_hero/show.erb +14 -0
  54. data/app/cells/decidim/content_blocks/footer_sub_hero_cell.rb +12 -0
  55. data/app/{views/decidim/pages/home/_hero.html.erb → cells/decidim/content_blocks/hero/show.erb} +4 -4
  56. data/app/cells/decidim/content_blocks/hero_cell.rb +25 -0
  57. data/app/cells/decidim/content_blocks/hero_settings_form/show.erb +7 -0
  58. data/app/cells/decidim/content_blocks/hero_settings_form_cell.rb +13 -0
  59. data/app/cells/decidim/content_blocks/highlighted_content_banner/show.erb +24 -0
  60. data/app/cells/decidim/content_blocks/highlighted_content_banner_cell.rb +16 -0
  61. data/app/{views/decidim/pages/home/_extended.html.erb → cells/decidim/content_blocks/how_to_participate/show.erb} +10 -10
  62. data/app/cells/decidim/content_blocks/how_to_participate_cell.rb +9 -0
  63. data/app/{views/decidim/pages/home/_statistics.html.erb → cells/decidim/content_blocks/stats/show.erb} +2 -2
  64. data/app/cells/decidim/content_blocks/stats_cell.rb +18 -0
  65. data/app/{views/decidim/pages/home/_sub_hero.html.erb → cells/decidim/content_blocks/sub_hero/show.erb} +2 -2
  66. data/app/cells/decidim/content_blocks/sub_hero_cell.rb +17 -0
  67. data/app/cells/decidim/conversation/show.erb +18 -0
  68. data/app/cells/decidim/conversation_cell.rb +23 -0
  69. data/app/cells/decidim/conversation_header/show.erb +17 -0
  70. data/app/cells/decidim/conversation_header_cell.rb +16 -0
  71. data/app/cells/decidim/conversations/show.erb +45 -0
  72. data/app/cells/decidim/conversations_cell.rb +24 -0
  73. data/app/cells/decidim/follow_button/show.erb +3 -3
  74. data/app/cells/decidim/follow_button_cell.rb +1 -5
  75. data/app/cells/decidim/following_cell.rb +1 -7
  76. data/app/cells/decidim/message/show.erb +15 -0
  77. data/app/cells/decidim/message_cell.rb +23 -0
  78. data/app/cells/decidim/new_conversation/show.erb +19 -0
  79. data/app/cells/decidim/new_conversation_cell.rb +19 -0
  80. data/app/cells/decidim/notifications/show.erb +1 -1
  81. data/app/cells/decidim/profile/show.erb +27 -0
  82. data/app/cells/decidim/profile_cell.rb +33 -0
  83. data/app/cells/decidim/profile_sidebar/show.erb +57 -0
  84. data/app/cells/decidim/profile_sidebar_cell.rb +31 -0
  85. data/app/cells/decidim/tos_page_cell.rb +0 -4
  86. data/app/cells/decidim/user_profile/header.erb +1 -1
  87. data/app/controllers/concerns/decidim/action_authorization.rb +13 -38
  88. data/app/controllers/concerns/decidim/needs_permission.rb +15 -6
  89. data/app/controllers/decidim/application_controller.rb +1 -0
  90. data/app/controllers/decidim/authorization_modals_controller.rb +35 -0
  91. data/app/controllers/decidim/components/base_controller.rb +0 -1
  92. data/app/controllers/decidim/devise/invitations_controller.rb +2 -1
  93. data/app/controllers/decidim/messaging/conversations_controller.rb +2 -11
  94. data/app/controllers/decidim/newsletters_controller.rb +4 -6
  95. data/app/controllers/decidim/notifications_controller.rb +4 -0
  96. data/app/controllers/decidim/pages_controller.rb +3 -7
  97. data/app/controllers/decidim/profiles_controller.rb +17 -7
  98. data/app/forms/decidim/notifications_settings_form.rb +1 -1
  99. data/app/forms/decidim/registration_form.rb +1 -1
  100. data/app/helpers/decidim/action_authorization_helper.rb +51 -46
  101. data/app/helpers/decidim/application_helper.rb +18 -0
  102. data/app/helpers/decidim/card_helper.rb +1 -1
  103. data/app/helpers/decidim/cells_helper.rb +6 -2
  104. data/app/helpers/decidim/resource_helper.rb +8 -1
  105. data/app/helpers/decidim/searches_helper.rb +5 -4
  106. data/app/helpers/decidim/traceability_helper.rb +5 -1
  107. data/app/models/decidim/authorization.rb +2 -2
  108. data/app/models/decidim/content_block.rb +144 -0
  109. data/app/models/decidim/gamification/badge_score.rb +13 -0
  110. data/app/models/decidim/messaging/message.rb +1 -1
  111. data/app/models/decidim/messaging/receipt.rb +1 -1
  112. data/app/models/decidim/organization.rb +1 -5
  113. data/app/models/decidim/resource_permission.rb +8 -0
  114. data/app/models/decidim/searchable_resource.rb +1 -1
  115. data/app/models/decidim/user.rb +17 -1
  116. data/app/permissions/decidim/default_permissions.rb +4 -3
  117. data/app/permissions/decidim/permissions.rb +33 -1
  118. data/app/presenters/decidim/hashtag_presenter.rb +32 -0
  119. data/app/presenters/decidim/resource_locator_presenter.rb +13 -0
  120. data/app/presenters/decidim/user_presenter.rb +1 -1
  121. data/app/queries/decidim/messaging/user_conversations.rb +1 -1
  122. data/app/resolvers/decidim/hashtags_resolver.rb +15 -0
  123. data/app/services/decidim/action_authorizer.rb +9 -8
  124. data/app/types/decidim/core/date_time_type.rb +1 -1
  125. data/app/types/decidim/core/hashtag_type.rb +13 -0
  126. data/app/uploaders/decidim/homepage_image_uploader.rb +1 -1
  127. data/app/uploaders/decidim/image_uploader.rb +1 -0
  128. data/app/views/decidim/authorization_modals/show.html.erb +32 -0
  129. data/app/views/decidim/messaging/conversations/create.js.erb +1 -1
  130. data/app/views/decidim/messaging/conversations/index.html.erb +1 -51
  131. data/app/views/decidim/messaging/conversations/new.html.erb +1 -5
  132. data/app/views/decidim/messaging/conversations/show.html.erb +1 -9
  133. data/app/views/decidim/messaging/conversations/update.js.erb +1 -1
  134. data/app/views/decidim/notifications/index.html.erb +1 -0
  135. data/app/views/decidim/pages/decidim_page.html.erb +9 -0
  136. data/app/views/decidim/pages/home.html.erb +12 -16
  137. data/app/views/decidim/pages/index.html.erb +8 -0
  138. data/app/views/decidim/profiles/_user_follow.erb +2 -2
  139. data/app/views/decidim/profiles/show.html.erb +1 -37
  140. data/app/views/decidim/searches/_results.html.erb +1 -1
  141. data/app/views/decidim/shared/_author_reference.html.erb +1 -1
  142. data/app/views/decidim/shared/_authorization_modal.html.erb +1 -0
  143. data/app/views/decidim/shared/_tags.html.erb +1 -1
  144. data/app/views/kaminari/decidim/_page.html.erb +1 -1
  145. data/app/views/layouts/decidim/_application.html.erb +6 -1
  146. data/app/views/layouts/decidim/_edit_link.html.erb +8 -0
  147. data/app/views/layouts/decidim/_impersonation_warning.html.erb +1 -1
  148. data/app/views/layouts/decidim/_user_menu.html.erb +2 -2
  149. data/app/views/layouts/decidim/_wrapper.html.erb +14 -1
  150. data/config/initializers/carrierwave.rb +15 -0
  151. data/config/locales/ca.yml +78 -30
  152. data/config/locales/en.yml +78 -30
  153. data/config/locales/es-PY.yml +78 -30
  154. data/config/locales/es.yml +78 -30
  155. data/config/locales/eu.yml +78 -30
  156. data/config/locales/fi.yml +262 -214
  157. data/config/locales/fr.yml +78 -30
  158. data/config/locales/gl.yml +78 -30
  159. data/config/locales/hu.yml +781 -0
  160. data/config/locales/it.yml +78 -30
  161. data/config/locales/nl.yml +78 -30
  162. data/config/locales/pl.yml +78 -30
  163. data/config/locales/pt-BR.yml +106 -58
  164. data/config/locales/pt.yml +78 -30
  165. data/config/locales/ru.yml +52 -32
  166. data/config/locales/sv.yml +183 -135
  167. data/config/locales/uk.yml +60 -40
  168. data/config/routes.rb +8 -6
  169. data/db/migrate/20180705091019_create_decidim_resource_permissions.rb +12 -0
  170. data/db/migrate/20180706104107_add_nickname_to_managed_users.rb +14 -0
  171. data/db/migrate/20180706111847_fix_result_follows.rb +9 -0
  172. data/db/migrate/20180724103814_add_content_blocks.rb +22 -0
  173. data/db/migrate/20180726112510_create_decidim_hashtags.rb +17 -0
  174. data/db/migrate/20180730071851_add_core_content_blocks.rb +28 -0
  175. data/db/migrate/20180802132147_rename_content_block_options_to_settings.rb +7 -0
  176. data/db/migrate/20180806095628_add_badge_scores.rb +11 -0
  177. data/db/migrate/20180808135006_add_images_to_content_blocks.rb +7 -0
  178. data/db/migrate/20180810092428_move_organization_fields_to_hero_content_block.rb +23 -0
  179. data/db/seeds.rb +10 -2
  180. data/lib/decidim/api/authorable_interface.rb +1 -1
  181. data/lib/decidim/coauthorable.rb +1 -0
  182. data/lib/decidim/content_block_manifest.rb +58 -0
  183. data/lib/decidim/content_block_registry.rb +87 -0
  184. data/lib/decidim/content_parsers.rb +1 -0
  185. data/lib/decidim/content_parsers/hashtag_parser.rb +36 -0
  186. data/lib/decidim/content_processor.rb +11 -0
  187. data/lib/decidim/content_renderers.rb +1 -0
  188. data/lib/decidim/content_renderers/hashtag_renderer.rb +43 -0
  189. data/lib/decidim/core.rb +28 -6
  190. data/lib/decidim/core/api.rb +1 -0
  191. data/lib/decidim/core/engine.rb +52 -1
  192. data/lib/decidim/core/test.rb +3 -0
  193. data/lib/decidim/core/test/factories.rb +32 -17
  194. data/lib/decidim/core/test/shared_examples/authorable_interface_examples.rb +10 -0
  195. data/lib/decidim/core/test/shared_examples/coauthorable.rb +3 -0
  196. data/lib/decidim/core/test/shared_examples/edit_link_shared_examples.rb +30 -0
  197. data/lib/decidim/core/test/shared_examples/has_space_in_mcell_examples.rb +15 -0
  198. data/lib/decidim/core/test/shared_examples/publicable.rb +1 -1
  199. data/lib/decidim/core/test/shared_examples/railtie_examples.rb +15 -0
  200. data/lib/decidim/core/test/shared_examples/scope_helper_examples.rb +1 -0
  201. data/lib/decidim/core/version.rb +1 -1
  202. data/lib/decidim/events/base_event.rb +2 -1
  203. data/lib/decidim/form_builder.rb +9 -3
  204. data/lib/decidim/friendly_dates.rb +1 -1
  205. data/lib/decidim/gamification.rb +109 -0
  206. data/lib/decidim/gamification/badge.rb +54 -0
  207. data/lib/decidim/gamification/badge_earned_event.rb +9 -0
  208. data/lib/decidim/gamification/badge_registry.rb +63 -0
  209. data/lib/decidim/gamification/badge_scorer.rb +118 -0
  210. data/lib/decidim/gamification/badge_status.rb +41 -0
  211. data/lib/decidim/gamification/base_event.rb +40 -0
  212. data/lib/decidim/gamification/level_up_event.rb +9 -0
  213. data/lib/decidim/hashtag.rb +15 -0
  214. data/lib/decidim/hashtaggable.rb +20 -0
  215. data/lib/decidim/query_extensions.rb +10 -0
  216. data/lib/decidim/resource_manifest.rb +10 -0
  217. data/lib/decidim/resourceable.rb +13 -0
  218. data/lib/decidim/search_resource_fields_mapper.rb +8 -3
  219. data/lib/decidim/searchable.rb +8 -0
  220. data/lib/decidim/translatable_attributes.rb +6 -18
  221. data/lib/decidim/view_model.rb +6 -0
  222. data/lib/devise/models/decidim_newsletterable.rb +1 -1
  223. data/vendor/assets/javascripts/d3.js +17813 -0
  224. metadata +125 -27
  225. data/app/cells/decidim/card_m/author.erb +0 -3
  226. data/app/cells/decidim/card_m/authors.erb +0 -9
  227. data/app/views/decidim/messaging/conversations/_message.html.erb +0 -14
  228. data/app/views/decidim/messaging/conversations/_reply.html.erb +0 -11
  229. data/app/views/decidim/messaging/conversations/_show.html.erb +0 -21
  230. data/app/views/decidim/messaging/conversations/_start.html.erb +0 -12
  231. data/app/views/decidim/pages/home/_footer_sub_hero.html.erb +0 -14
  232. data/app/views/decidim/pages/home/_highlighted_content_banner.html.erb +0 -26
  233. data/app/views/decidim/pages/home/_highlighted_processes.html.erb +0 -7
  234. data/app/views/decidim/profiles/_user.html.erb +0 -59
  235. data/app/views/decidim/shared/_action_authorization_modal.html.erb +0 -39
  236. data/app/views/layouts/decidim/_component_authorization_modals.html.erb +0 -5
@@ -0,0 +1,26 @@
1
+ /* eslint-disable no-unused-vars */
2
+
3
+ // Outside of the closure to make it public
4
+ let DATACHARTS = null;
5
+
6
+ const fetchDatacharts = () => {
7
+
8
+ const metrics = [{
9
+ key: "NAME_TO_BE_IN_THE_HTML-1",
10
+ query: "GRAPHQL_QUERY-1"
11
+ }, {
12
+ key: "NAME_TO_BE_IN_THE_HTML-2",
13
+ query: "GRAPHQL_QUERY-2"
14
+ }]
15
+
16
+ const fetch = (query) => $.post("<-- GRAPHQL_URL -->", query)
17
+
18
+ const promises = metrics.map((metric) => fetch(metric.query).then((response) => {
19
+ DATACHARTS[metric.key] = response
20
+
21
+ return DATACHARTS
22
+ }))
23
+
24
+ Promise.all(promises).then(() => DATACHARTS)
25
+
26
+ }
@@ -0,0 +1,701 @@
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: ["error", { "args": "none" }] */
5
+ /* global d3 */
6
+
7
+ // = require ./renders
8
+ // = require_self
9
+ // = require d3
10
+ ((exports) => {
11
+ const { Decidim: { Visualizations: render } } = exports;
12
+
13
+ // lib
14
+ const renderOrgCharts = () => {
15
+ const $orgChartContainer = $(".js-orgchart")
16
+ const $btnReset = $(".js-reset-orgchart")
17
+
18
+ let dataDepicted = null
19
+ let fake = false
20
+ let orgchart = {}
21
+
22
+ // lib - https://bl.ocks.org/bumbeishvili/b96ba47ea21d14dfce6ebb859b002d3a
23
+ const renderChartCollapsibleNetwork = (params) => {
24
+
25
+ // exposed variables
26
+ let attrs = {
27
+ id: `id${Math.floor(Math.random() * 1000000)}`,
28
+ svgWidth: 960,
29
+ svgHeight: 600,
30
+ marginTop: 0,
31
+ marginBottom: 5,
32
+ marginRight: 0,
33
+ marginLeft: 30,
34
+ container: "body",
35
+ distance: 150,
36
+ hiddenChildLevel: 1,
37
+ hoverOpacity: 0.2,
38
+ maxTextDisplayZoomLevel: 1,
39
+ lineStrokeWidth: 1.5,
40
+ fakeRoot: false,
41
+ nodeGutter: { x: 16, y: 8 },
42
+ childrenIndicatorRadius: 15,
43
+ data: null
44
+ }
45
+
46
+ /* ############### IF EXISTS OVERWRITE ATTRIBUTES FROM PASSED PARAM ####### */
47
+
48
+ let attrKeys = Object.keys(attrs)
49
+ attrKeys.forEach(function (key) {
50
+ if (params && params[key]) {
51
+ attrs[key] = params[key]
52
+ }
53
+ })
54
+
55
+ // innerFunctions which will update visuals
56
+ let updateData
57
+ let collapse, expand
58
+ let filter
59
+ let hierarchy = {}
60
+
61
+ // main chart object
62
+ let main = function (selection) {
63
+ selection.each(function scope() {
64
+
65
+ // calculated properties
66
+ let calc = {}
67
+ calc.chartLeftMargin = attrs.marginLeft
68
+ calc.chartTopMargin = attrs.marginTop
69
+ calc.chartWidth = attrs.svgWidth - attrs.marginRight - calc.chartLeftMargin
70
+ calc.chartHeight = attrs.svgHeight - attrs.marginBottom - calc.chartTopMargin
71
+
72
+ // ########################## HIERARCHY STUFF #########################
73
+ hierarchy.root = d3.hierarchy(attrs.data.root)
74
+
75
+ // ########################### BEHAVIORS #########################
76
+ let behaviors = {}
77
+ // behaviors.zoom = d3.zoom().scaleExtent([0.75, 100, 8]).on("zoom", zoomed)
78
+ behaviors.drag = d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended)
79
+
80
+ // ########################### LAYOUTS #########################
81
+ let layouts = {}
82
+
83
+ // custom radial layout
84
+ layouts.radial = d3.radial()
85
+
86
+ // ########################### FORCE STUFF #########################
87
+ let force = {}
88
+ force.link = d3.forceLink().id((d) => d.id)
89
+ force.charge = d3.forceManyBody().strength(-240)
90
+ force.center = d3.forceCenter(calc.chartWidth / 2, calc.chartHeight / 2)
91
+
92
+ // prevent collide
93
+ force.collide = d3.forceCollide().radius((d) => {
94
+ // Creates an invented radius based on element measures: diagonal = 2 * radius = sqrt(width^2, height^2)
95
+ let base = (d.bbox || {}).width + (attrs.nodeGutter.x * 2)
96
+ let height = (d.bbox || {}).height + (attrs.nodeGutter.y * 2)
97
+ let diagonal = Math.sqrt(Math.pow(base, 2) + Math.pow(height, 2))
98
+ let fakeRadius = (diagonal / 2)
99
+
100
+ // return d3.max([attrs.nodeDistance * 3, fakeRadius])
101
+ return fakeRadius * 1.2
102
+ })
103
+
104
+ // manually set x positions (which is calculated using custom radial layout)
105
+ force.x = d3.forceX()
106
+ .strength(0.5)
107
+ .x(function (d) {
108
+
109
+ // if node does not have children and is channel (depth=2) , then position it on parent's coordinate
110
+ if (!d.children && d.depth > 2) {
111
+ if (d.parent) {
112
+ d = d.parent
113
+ }
114
+ }
115
+
116
+ // custom circle projection - radius will be - (d.depth - 1) * 150
117
+ return projectCircle(d.proportion, (d.depth - 1) * attrs.distance)[0]
118
+ })
119
+
120
+ // manually set y positions (which is calculated using d3.cluster)
121
+ force.y = d3.forceY()
122
+ .strength(0.5)
123
+ .y(function (d) {
124
+
125
+ // if node does not have children and is channel (depth=2) , then position it on parent's coordinate
126
+ if (!d.children && d.depth > 2) {
127
+ if (d.parent) {
128
+ d = d.parent
129
+ }
130
+ }
131
+
132
+ // custom circle projection - radius will be - (d.depth - 1) * 150
133
+ return projectCircle(d.proportion, (d.depth - 1) * attrs.distance)[1]
134
+ })
135
+
136
+ // --------------------------------- INITIALISE FORCE SIMULATION ----------------------------
137
+
138
+ // get based on top parameter simulation
139
+ force.simulation = d3.forceSimulation()
140
+ .force("link", force.link)
141
+ .force("charge", force.charge)
142
+ .force("center", force.center)
143
+ .force("collide", force.collide)
144
+ .force("x", force.x)
145
+ .force("y", force.y)
146
+
147
+ // ########################### HIERARCHY STUFF #########################
148
+
149
+ // flatten root
150
+ let arr = flatten(hierarchy.root)
151
+
152
+ // hide members based on their depth
153
+ arr.forEach((d) => {
154
+ // Hide fake root node
155
+ if ((attrs.fakeRoot) && (d.depth === 1)) {
156
+ d.hidden = true
157
+ }
158
+
159
+ if (d.depth > attrs.hiddenChildLevel) {
160
+ d._children = d.children
161
+ d.children = null
162
+ }
163
+ })
164
+
165
+ // #################################### DRAWINGS #######################
166
+
167
+ // drawing containers
168
+ let container = d3.select(this)
169
+
170
+ // add svg
171
+ let svg = container.patternify({ tag: "svg", selector: "svg-chart-container" })
172
+ .attr("width", attrs.svgWidth)
173
+ .attr("height", attrs.svgHeight)
174
+ // .call(behaviors.zoom)
175
+
176
+ // add container g element
177
+ let chart = svg.patternify({ tag: "g", selector: "chart" })
178
+ .attr("transform", `translate(${calc.chartLeftMargin},${calc.chartTopMargin})`)
179
+
180
+ // ################################ Chart Content Drawing ##################################
181
+
182
+ // link wrapper
183
+ let linksWrapper = chart.patternify({ tag: "g", selector: "links-wrapper" })
184
+
185
+ // node wrapper
186
+ let nodesWrapper = chart.patternify({ tag: "g", selector: "nodes-wrapper" })
187
+ let links, nodes
188
+
189
+ // reusable function which updates visual based on data change
190
+ update()
191
+
192
+ // update visual based on data change
193
+ function update(clickedNode) {
194
+
195
+ // Show/hide reset button
196
+ (clickedNode) ? $btnReset.removeClass("invisible") : $btnReset.addClass("invisible")
197
+
198
+ // set xy and proportion properties with custom radial layout
199
+ layouts.radial(hierarchy.root)
200
+
201
+ // nodes and links array
202
+ let nodesArr = flatten(hierarchy.root, true)
203
+ .orderBy((d) => d.depth)
204
+ .filter((d) => !d.hidden)
205
+
206
+ let linksArr = hierarchy.root.links()
207
+ .filter((d) => !d.source.hidden)
208
+ .filter((d) => !d.target.hidden)
209
+
210
+ // make new nodes to appear near the parents
211
+ nodesArr.forEach(function (d) {
212
+ if (clickedNode && clickedNode.id === (d.parent && d.parent.id)) {
213
+ d.x = d.parent.x
214
+ d.y = d.parent.y
215
+ }
216
+ })
217
+
218
+ // links
219
+ links = linksWrapper.selectAll(".link")
220
+ .data(linksArr, (d) => d.target.id)
221
+ links.exit().remove()
222
+
223
+ links = links.enter()
224
+ .append("line")
225
+ .attr("class", "link")
226
+ .merge(links)
227
+
228
+ // node groups
229
+ nodes = nodesWrapper.selectAll(".node")
230
+ .data(nodesArr, (d) => d.id)
231
+ nodes.exit().remove()
232
+
233
+ let enteredNodes = nodes.enter()
234
+ .append("g")
235
+ .attr("class", "node")
236
+
237
+ // bind event handlers
238
+ enteredNodes
239
+ .on("click", nodeClick)
240
+ .on("mouseenter", nodeMouseEnter)
241
+ .on("mouseleave", nodeMouseLeave)
242
+ .call(behaviors.drag)
243
+
244
+ // channels grandchildren
245
+ enteredNodes.append("rect")
246
+ .attr("class", "as-card")
247
+ .attr("rx", 4)
248
+ .attr("ry", 4)
249
+
250
+ enteredNodes.append("text")
251
+ .attr("class", "as-text")
252
+ .text((d) => d.data.name)
253
+
254
+ enteredNodes.selectAll("text").each(function(d) {
255
+ d.bbox = this.getBBox()
256
+ })
257
+
258
+ enteredNodes.selectAll("rect")
259
+ .attr("x", (d) => d.bbox.x - attrs.nodeGutter.x)
260
+ .attr("y", (d) => d.bbox.y - attrs.nodeGutter.y)
261
+ .attr("width", (d) => d.bbox.width + (2 * attrs.nodeGutter.x))
262
+ .attr("height", (d) => d.bbox.height + (2 * attrs.nodeGutter.y))
263
+
264
+ // append circle & text only when there are children
265
+ enteredNodes
266
+ .append("circle")
267
+ .filter((d) => Boolean(d.children) || Boolean(d._children))
268
+ .attr("class", "as-circle")
269
+ .attr("r", attrs.childrenIndicatorRadius)
270
+ .attr("cx", (d) => d.bbox.x + d.bbox.width + attrs.nodeGutter.x)
271
+ .attr("cy", (d) => d.bbox.y + d.bbox.height + attrs.nodeGutter.y)
272
+
273
+ enteredNodes
274
+ .append("text")
275
+ .filter((d) => Boolean(d.children) || Boolean(d._children))
276
+ .attr("class", "as-text")
277
+ .attr("dx", (d) => d.bbox.x + d.bbox.width + attrs.nodeGutter.x)
278
+ .attr("dy", attrs.childrenIndicatorRadius + 3)
279
+ .text((d) => d3.max([(d.children || {}).length, (d._children || {}).length]))
280
+
281
+ // merge node groups and style it
282
+ nodes = enteredNodes.merge(nodes)
283
+
284
+ // force simulation
285
+ force.simulation.nodes(nodesArr).on("tick", ticked)
286
+
287
+ // links simulation
288
+ force.simulation.force("link").links(links).id((d) => d.id).distance(attrs.distance * 2).strength(2)
289
+ }
290
+
291
+ // ####################################### EVENT HANDLERS ########################
292
+
293
+ // zoom handler
294
+ // function zoomed() {
295
+ // // get transform event
296
+ // let transform = d3.event.transform
297
+ // attrs.lastTransform = transform
298
+ //
299
+ // // apply transform event props to the wrapper
300
+ // chart.attr("transform", transform)
301
+ //
302
+ // svg.selectAll(".node").attr("transform", (d) => `translate(${d.x},${d.y}) scale(${1 / (attrs.lastTransform ? attrs.lastTransform.k : 1)})`)
303
+ // svg.selectAll(".link").attr("stroke-width", attrs.lineStrokeWidth / (attrs.lastTransform ? attrs.lastTransform.k : 1))
304
+ // }
305
+
306
+ // tick handler
307
+ function ticked() {
308
+ // set links position
309
+ links
310
+ .attr("x1", function (d) { return d.source.x; })
311
+ .attr("y1", function (d) { return d.source.y; })
312
+ .attr("x2", function (d) { return d.target.x; })
313
+ .attr("y2", function (d) { return d.target.y; })
314
+
315
+ // set nodes position
316
+ svg.selectAll(".node")
317
+ .attr("transform", (d) => {
318
+ let bounds = {
319
+ x: Math.max(Math.min(calc.chartWidth, d.x), 0),
320
+ y: Math.max(Math.min(calc.chartHeight, d.y), 0)
321
+ }
322
+
323
+ return `translate(${bounds.x},${bounds.y})`
324
+ })
325
+ }
326
+
327
+ // handler drag start event
328
+ function dragstarted() {
329
+ // disable node fixing
330
+ nodes.each((d) => {
331
+ d.fx = null
332
+ d.fy = null
333
+ })
334
+ }
335
+
336
+ // handle dragging event
337
+ function dragged(d) {
338
+ // make dragged node fixed
339
+ d.fx = d3.event.x
340
+ d.fy = d3.event.y
341
+ }
342
+
343
+ // -------------------- handle drag end event ---------------
344
+ function dragended() {
345
+ // we are doing nothing, here , aren't we?
346
+ }
347
+
348
+ // -------------------------- node mouse hover handler ---------------
349
+ function nodeMouseEnter(d) {
350
+ // get links
351
+ let _links = hierarchy.root.links()
352
+
353
+ // get hovered node connected links
354
+ let connectedLinks = _links.filter((l) => l.source.id === d.id || l.target.id === d.id)
355
+
356
+ // get hovered node linked nodes
357
+ let linkedNodes = connectedLinks.map((s) => s.source.id).concat(connectedLinks.map((c) => c.target.id))
358
+
359
+ // reduce all other nodes opacity
360
+ nodesWrapper.selectAll(".node")
361
+ .filter((n) => linkedNodes.indexOf(n.id) === -1)
362
+ .attr("opacity", attrs.hoverOpacity)
363
+
364
+ // reduce all other links opacity
365
+ linksWrapper.selectAll(".link")
366
+ .attr("opacity", attrs.hoverOpacity)
367
+
368
+ // highlight hovered nodes connections
369
+ linksWrapper.selectAll(".link")
370
+ .filter((l) => l.source.id === d.id || l.target.id === d.id)
371
+ .attr("opacity", 1)
372
+ }
373
+
374
+ // --------------- handle mouseleave event ---------------
375
+ function nodeMouseLeave() {
376
+ // return things back to normal
377
+ nodesWrapper.selectAll(".node")
378
+ .attr("opacity", 1)
379
+ linksWrapper.selectAll(".link")
380
+ .attr("opacity", 1)
381
+ }
382
+
383
+ // --------------- handle node click event ---------------
384
+ function nodeClick(d) {
385
+ // free fixed nodes
386
+ nodes.each((di) => {
387
+ di.fx = null
388
+ di.fy = null
389
+ })
390
+
391
+ // collapse or expand node
392
+ if (d.children) {
393
+ collapse(d)
394
+ } else if (d._children) {
395
+ expand(d)
396
+ } else {
397
+ // nothing is to collapse or expand
398
+ }
399
+
400
+ freeNodes()
401
+ }
402
+
403
+ // ######################################### UTIL FUNCS ##################################
404
+ updateData = function () {
405
+ main.run()
406
+ }
407
+
408
+ collapse = function (d, deep = false) {
409
+ if (d.children) {
410
+ if (deep) {
411
+ d.children.forEach((e) => collapse(e, true))
412
+ }
413
+
414
+ d._children = d.children
415
+ d.children = null
416
+ }
417
+
418
+ update(d)
419
+ force.simulation.restart()
420
+ force.simulation.alphaTarget(0.15)
421
+ }
422
+
423
+ expand = function (d, deep = false) {
424
+ if (d._children) {
425
+ if (deep) {
426
+ d._children.forEach((e) => expand(e, true))
427
+ }
428
+
429
+ d.children = d._children
430
+ d._children = null
431
+ }
432
+
433
+ update(d)
434
+ force.simulation.restart()
435
+ force.simulation.alphaTarget(0.15)
436
+ }
437
+
438
+ // function slowDownNodes() {
439
+ // force.simulation.alphaTarget(0.05)
440
+ // }
441
+
442
+ // function speedUpNodes() {
443
+ // force.simulation.alphaTarget(0.45)
444
+ // }
445
+
446
+ function freeNodes() {
447
+ d3.selectAll(".node").each((n) => {
448
+ n.fx = null
449
+ n.fy = null
450
+ })
451
+ }
452
+
453
+ function projectCircle(value, radius) {
454
+ let r = radius || 0
455
+ let corner = value * 2 * Math.PI
456
+ return [Math.sin(corner) * r, -Math.cos(corner) * r]
457
+ }
458
+
459
+ // recursively loop on children and extract nodes as an array
460
+ function flatten(root, clustered) {
461
+ let nodesArray = []
462
+ let i = 0
463
+ function recurse(node, depth) {
464
+ if (node.children) {
465
+ node.children.forEach(function (child) {
466
+ recurse(child, depth + 1)
467
+ })
468
+ }
469
+
470
+ if (!node.id) {
471
+ node.id = ++i
472
+ } else {
473
+ ++i
474
+ }
475
+
476
+ node.depth = depth
477
+ if (clustered) {
478
+ if (!node.cluster) {
479
+ // if cluster coordinates are not set, set it
480
+ node.cluster = { x: node.x, y: node.y }
481
+ }
482
+ }
483
+ nodesArray.push(node)
484
+ }
485
+ recurse(root, 1)
486
+ return nodesArray
487
+ }
488
+
489
+ function debug() {
490
+ if (attrs.isDebug) {
491
+ // stringify func
492
+ let stringified = String(scope)
493
+
494
+ // parse variable names
495
+ let groupVariables = stringified
496
+ // match var x-xx= {}
497
+ .match(/var\s+([\w])+\s*=\s*{\s*}/gi)
498
+ // match xxx
499
+ .map((d) => d.match(/\s+\w*/gi).filter((s) => s.trim()))
500
+ // get xxx
501
+ .map((v) => v[0].trim())
502
+
503
+ // assign local variables to the scope
504
+ groupVariables.forEach((v) => {
505
+ main[`P_${v}`] = eval(v)
506
+ })
507
+ }
508
+ }
509
+
510
+ debug()
511
+
512
+ })
513
+ }
514
+
515
+ // ----------- PROTOTYEPE FUNCTIONS ----------------------
516
+ d3.selection.prototype.patternify = function (_params) {
517
+ let selector = _params.selector
518
+ let elementTag = _params.tag
519
+ let _data = _params.data || [selector]
520
+
521
+ // pattern in action
522
+ let selection = this.selectAll(`.${selector}`).data(_data)
523
+ selection.exit().remove()
524
+ selection = selection.enter().append(elementTag).merge(selection)
525
+ selection.attr("class", selector)
526
+
527
+ return selection
528
+ }
529
+
530
+ // custom radial layout
531
+ d3.radial = function () {
532
+ return function (root) {
533
+
534
+ recurse(root, 0, 1)
535
+
536
+ function recurse(node, min, max) {
537
+ node.proportion = (max + min) / 2
538
+ if (!node.x) {
539
+
540
+ // if node has parent, match entered node positions to it's parent
541
+ if (node.parent) {
542
+ node.x = node.parent.x
543
+ } else {
544
+ node.x = 0
545
+ }
546
+ }
547
+
548
+ // if node had parent, match entered node positions to it's parent
549
+ if (!node.y) {
550
+ if (node.parent) {
551
+ node.y = node.parent.y
552
+ } else {
553
+ node.y = 0
554
+ }
555
+ }
556
+
557
+ // recursively do the same for children
558
+ if (node.children) {
559
+ let offset = (max - min) / node.children.length
560
+ node.children.forEach(function (child, i) {
561
+ let newMin = min + (offset * i)
562
+ let newMax = newMin + offset
563
+
564
+ recurse(child, newMin, newMax)
565
+ })
566
+ }
567
+ }
568
+ }
569
+ }
570
+
571
+ // https://github.com/bumbeishvili/d3js-boilerplates#orderby
572
+ Array.prototype.orderBy = function (func) {
573
+ this.sort((_a, _b) => {
574
+ let a = func(_a)
575
+ let b = func(_b)
576
+ if (typeof a === "string" || a instanceof String) {
577
+ return a.localeCompare(b)
578
+ }
579
+ return a - b
580
+ })
581
+
582
+ return this
583
+ }
584
+
585
+ // ########################## BOILEPLATE STUFF ################
586
+
587
+ // dinamic keys functions
588
+ Object.keys(attrs).forEach((key) => {
589
+ // Attach variables to main function
590
+ return main[key] = function (_) {
591
+ let string = `attrs['${key}'] = _`
592
+
593
+ if (!arguments.length) {
594
+ return eval(` attrs['${key}'];`)
595
+ }
596
+
597
+ eval(string)
598
+
599
+ return main
600
+ }
601
+ })
602
+
603
+ // set attrs as property
604
+ main.attrs = attrs
605
+
606
+ // debugging visuals
607
+ main.debug = function (isDebug) {
608
+ attrs.isDebug = isDebug
609
+ if (isDebug) {
610
+ if (!window.charts) {
611
+ window.charts = []
612
+ }
613
+ window.charts.push(main)
614
+ }
615
+ return main
616
+ }
617
+
618
+ // exposed update functions
619
+ main.data = function (value) {
620
+ if (!arguments.length) {
621
+ return attrs.data
622
+ }
623
+
624
+ attrs.data = value
625
+ if (typeof updateData === "function") {
626
+ updateData()
627
+ }
628
+ return main
629
+ }
630
+
631
+ // run visual
632
+ main.run = function () {
633
+ d3.selectAll(attrs.container)
634
+ .call(main)
635
+ return main
636
+ }
637
+
638
+ main.filter = function (filterParams) {
639
+ if (!arguments.length) {
640
+ return attrs.filterParams
641
+ }
642
+
643
+ attrs.filterParams = filterParams
644
+ if (typeof filter === "function") {
645
+ filter()
646
+ }
647
+ return main
648
+ }
649
+
650
+ main.reset = function () {
651
+
652
+ hierarchy.root.children.forEach((e) => collapse(e, true))
653
+ main.run()
654
+
655
+ return main
656
+ }
657
+
658
+ return main
659
+ }
660
+
661
+ // initialization
662
+ $orgChartContainer.each((i, container) => {
663
+
664
+ let width = $(container).width()
665
+ let height = width / (16 / 9)
666
+
667
+ d3.json("/orgchart.json").then((data) => {
668
+ // Make a fake previous node if the data entry is not hierarchical
669
+ if (data instanceof Array) {
670
+ fake = true
671
+ dataDepicted = {
672
+ name: null,
673
+ children: data
674
+ }
675
+ } else {
676
+ dataDepicted = data
677
+ }
678
+
679
+ orgchart = renderChartCollapsibleNetwork()
680
+ .svgHeight(height)
681
+ .svgWidth(width)
682
+ .fakeRoot(fake)
683
+ .container(`#${container.id}`)
684
+ .data({
685
+ root: dataDepicted
686
+ })
687
+ .debug(true)
688
+ .run()
689
+ })
690
+ })
691
+
692
+ // reset
693
+ $btnReset.click(function() {
694
+ orgchart.reset()
695
+ })
696
+ }
697
+
698
+ $(() => {
699
+ render(renderOrgCharts);
700
+ })
701
+ })(window);