@icij/murmur-next 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/.github/workflows/deploy-github-pages.yaml +50 -0
  2. package/.storybook/app.scss +14 -0
  3. package/.storybook/doc_variables.scss +20 -0
  4. package/.storybook/main.ts +35 -0
  5. package/.storybook/preview-head.html +2 -0
  6. package/.storybook/preview.ts +32 -0
  7. package/README.md +71 -0
  8. package/deploy.js +15 -0
  9. package/docs/components/ApiTable.vue +171 -0
  10. package/docs/components/App.vue +146 -0
  11. package/docs/components/CollapsibleBlock.vue +122 -0
  12. package/docs/components/DocsHeader.vue +68 -0
  13. package/docs/components/DocsMenu.vue +201 -0
  14. package/docs/components/DocsMenuSection.vue +109 -0
  15. package/docs/components/EditLink.vue +49 -0
  16. package/docs/components/OutboundLink.vue +13 -0
  17. package/docs/components/PalettePresenter.vue +96 -0
  18. package/docs/components/RepositoryLink.vue +28 -0
  19. package/docs/components/SampleCard.vue +119 -0
  20. package/docs/main.js +42 -0
  21. package/docs/pages/components/accordion/doc.md +96 -0
  22. package/docs/pages/components/active-text-truncate/doc.md +44 -0
  23. package/docs/pages/components/advanced-link-form/doc.md +105 -0
  24. package/docs/pages/components/brand/doc.md +30 -0
  25. package/docs/pages/components/brand-expansion/doc.md +70 -0
  26. package/docs/pages/components/confirm-button/doc.md +91 -0
  27. package/docs/pages/components/content-placeholder/doc.md +16 -0
  28. package/docs/pages/components/custom-pagination/doc.md +61 -0
  29. package/docs/pages/components/digits-input/doc.md +28 -0
  30. package/docs/pages/components/donate-form/doc.md +20 -0
  31. package/docs/pages/components/embed-form/doc.md +22 -0
  32. package/docs/pages/components/embeddable-footer/doc.md +60 -0
  33. package/docs/pages/components/follow-us-popover/doc.md +5 -0
  34. package/docs/pages/components/generic-footer/doc.md +21 -0
  35. package/docs/pages/components/generic-header/doc.md +24 -0
  36. package/docs/pages/components/haptic-copy/doc.md +27 -0
  37. package/docs/pages/components/imddb-header/doc.md +23 -0
  38. package/docs/pages/components/ordinal-legend/doc.md +44 -0
  39. package/docs/pages/components/range-picker/doc.md +86 -0
  40. package/docs/pages/components/responsive-iframe/doc.md +13 -0
  41. package/docs/pages/components/scale-legend/doc.md +65 -0
  42. package/docs/pages/components/secret-input/doc.md +12 -0
  43. package/docs/pages/components/selectable-dropdown/doc.md +156 -0
  44. package/docs/pages/components/sharing-options/doc.md +13 -0
  45. package/docs/pages/components/sharing-options-link/doc.md +36 -0
  46. package/docs/pages/components/sign-up-form/doc.md +13 -0
  47. package/docs/pages/components/slide-up-down/doc.md +28 -0
  48. package/docs/pages/components/textured-deck/doc.md +78 -0
  49. package/docs/pages/components/tiny-pagination/doc.md +92 -0
  50. package/docs/pages/datavisualisation/bars/doc.md +110 -0
  51. package/docs/pages/datavisualisation/columns/doc.md +165 -0
  52. package/docs/pages/datavisualisation/lines/doc.md +139 -0
  53. package/docs/pages/datavisualisation/stacked-bar/doc.md +160 -0
  54. package/docs/pages/datavisualisation/stacked-column/doc.md +191 -0
  55. package/docs/pages/getting-started/about-icij/doc.md +13 -0
  56. package/docs/pages/getting-started/custom-bootstrap/doc.md +36 -0
  57. package/docs/pages/getting-started/installation-guide/doc.md +59 -0
  58. package/docs/pages/getting-started/internationalization/doc.md +74 -0
  59. package/docs/pages/maps/choropleth-map/doc.md +420 -0
  60. package/docs/pages/maps/choropleth-map-annotation/doc.md +373 -0
  61. package/docs/pages/maps/symbol-map/doc.md +203 -0
  62. package/docs/pages/structure/breakpoints/doc.md +3 -0
  63. package/docs/pages/structure/grid/doc.md +3 -0
  64. package/docs/pages/utilities/assets/doc.md +138 -0
  65. package/docs/pages/utilities/config/doc.md +52 -0
  66. package/docs/pages/utilities/iframes/doc.md +3 -0
  67. package/docs/pages/visual/colors/doc.md +31 -0
  68. package/docs/pages/visual/iconography/doc.md +56 -0
  69. package/docs/pages/visual/states/doc.md +77 -0
  70. package/docs/pages/visual/themes/doc.md +3 -0
  71. package/docs/pages/visual/typography/doc.md +71 -0
  72. package/docs/routes.js +25 -0
  73. package/docs/store/index.js +21 -0
  74. package/docs/styles/app.scss +36 -0
  75. package/docs/styles/variables.scss +20 -0
  76. package/lib/assets/images/icij-full-white.svg +6 -0
  77. package/lib/assets/images/icij-full.svg +6 -0
  78. package/lib/assets/images/icij.png +0 -0
  79. package/lib/assets/images/icij.svg +46 -0
  80. package/lib/assets/images/icij@2x.png +0 -0
  81. package/lib/assets/images/murmur-dark.png +0 -0
  82. package/lib/assets/images/murmur-dark.svg +79 -0
  83. package/lib/assets/images/murmur-white.png +0 -0
  84. package/lib/assets/images/murmur-white.svg +68 -0
  85. package/lib/components/AccordionStep.vue +128 -0
  86. package/lib/components/AccordionWrapper.vue +138 -0
  87. package/lib/components/ActiveTextTruncate.vue +258 -0
  88. package/lib/components/AdvancedLinkForm.vue +273 -0
  89. package/lib/components/Brand.vue +150 -0
  90. package/lib/components/BrandExpansion.vue +237 -0
  91. package/lib/components/ConfirmButton.vue +204 -0
  92. package/lib/components/ContentPlaceholder.vue +100 -0
  93. package/lib/components/CustomPagination.vue +225 -0
  94. package/lib/components/DigitsInput.vue +180 -0
  95. package/lib/components/DonateForm.vue +367 -0
  96. package/lib/components/EmbedForm.vue +173 -0
  97. package/lib/components/EmbeddableFooter.vue +201 -0
  98. package/lib/components/Fa.js +3 -0
  99. package/lib/components/FollowUsPopover.vue +117 -0
  100. package/lib/components/GenericFooter.vue +218 -0
  101. package/lib/components/GenericHeader.vue +259 -0
  102. package/lib/components/HapticCopy.vue +256 -0
  103. package/lib/components/ImddbHeader.vue +336 -0
  104. package/lib/components/OrdinalLegend.vue +164 -0
  105. package/lib/components/RangePicker.vue +430 -0
  106. package/lib/components/ResponsiveIframe.vue +48 -0
  107. package/lib/components/ScaleLegend.vue +230 -0
  108. package/lib/components/SecretInput.vue +132 -0
  109. package/lib/components/SelectableDropdown.vue +368 -0
  110. package/lib/components/SharingOptions.vue +230 -0
  111. package/lib/components/SharingOptionsLink.vue +259 -0
  112. package/lib/components/SignUpForm.vue +181 -0
  113. package/lib/components/SlideUpDown.vue +131 -0
  114. package/lib/components/TexturedDeck.vue +101 -0
  115. package/lib/components/TinyPagination.vue +268 -0
  116. package/lib/components/index.js +31 -0
  117. package/lib/composables/chart.ts +182 -0
  118. package/lib/composables/resizeObserver.ts +37 -0
  119. package/lib/composables/sendEmail.ts +50 -0
  120. package/lib/config.default.ts +33 -0
  121. package/lib/config.ts +70 -0
  122. package/lib/d3-geo-projection.d.ts +1 -0
  123. package/lib/datavisualisations/BarChart.vue +275 -0
  124. package/lib/datavisualisations/ColumnChart.vue +527 -0
  125. package/lib/datavisualisations/LineChart.vue +274 -0
  126. package/lib/datavisualisations/StackedBarChart.vue +614 -0
  127. package/lib/datavisualisations/StackedColumnChart.vue +640 -0
  128. package/lib/datavisualisations/index.js +5 -0
  129. package/lib/enums.ts +25 -0
  130. package/lib/i18n.ts +16 -0
  131. package/lib/keys.ts +2 -0
  132. package/lib/locales/en.json +140 -0
  133. package/lib/locales/fr.json +117 -0
  134. package/lib/locales/locales/en.json +140 -0
  135. package/lib/locales/locales/fr.json +117 -0
  136. package/lib/main.ts +87 -0
  137. package/lib/maps/ChoroplethMap.vue +825 -0
  138. package/lib/maps/ChoroplethMapAnnotation.vue +336 -0
  139. package/lib/maps/SymbolMap.vue +628 -0
  140. package/lib/maps/index.js +3 -0
  141. package/lib/querystring-es3.d.ts +1 -0
  142. package/lib/shims-bootstrap-vue.d.ts +5 -0
  143. package/lib/shims-tsx.d.ts +11 -0
  144. package/lib/shims-vue.d.ts +14 -0
  145. package/lib/styles/functions.scss +20 -0
  146. package/lib/styles/lib.scss +19 -0
  147. package/lib/styles/mixins.scss +37 -0
  148. package/lib/styles/utilities.scss +18 -0
  149. package/lib/styles/variables.scss +94 -0
  150. package/lib/styles/variables_dark.scss +1 -0
  151. package/lib/types.ts +46 -0
  152. package/lib/utils/animation.ts +24 -0
  153. package/lib/utils/assets.ts +46 -0
  154. package/lib/utils/clipboard.ts +41 -0
  155. package/lib/utils/iframe-resizer.ts +49 -0
  156. package/lib/utils/placeholder.ts +66 -0
  157. package/lib/utils/placeholderTypes.ts +21 -0
  158. package/lib/utils/strings.ts +8 -0
  159. package/loaders/highlight-loader.js +13 -0
  160. package/loaders/markdown-loader.js +91 -0
  161. package/loaders/metadata-loader.js +18 -0
  162. package/loaders/sass-extract-loader.js +14 -0
  163. package/loaders/vue-docgen-loader.js +14 -0
  164. package/package.json +96 -0
  165. package/plugins/MdPluginTypes.ts +10 -0
  166. package/plugins/docs.ts +50 -0
  167. package/plugins/front-matter.ts +36 -0
  168. package/plugins/highlight.ts +27 -0
  169. package/plugins/markdown-it/api-table.ts +25 -0
  170. package/plugins/markdown-it/sample-card.ts +31 -0
  171. package/plugins/plugin-delete.ts +47 -0
  172. package/plugins/plugin-docgen.ts +23 -0
  173. package/plugins/sass-vars.ts +25 -0
  174. package/plugins/vue-docgen.ts +29 -0
  175. package/public/android-chrome-192x192.png +0 -0
  176. package/public/android-chrome-512x512.png +0 -0
  177. package/public/apple-touch-icon.png +0 -0
  178. package/public/assets/img/arrow-bottom.svg +3 -0
  179. package/public/assets/img/texture-brick-black.jpg +0 -0
  180. package/public/assets/img/texture-brick.jpg +0 -0
  181. package/public/assets/img/texture-carbon-black.jpg +0 -0
  182. package/public/assets/img/texture-carbon.jpg +0 -0
  183. package/public/assets/img/texture-crack-black.jpg +0 -0
  184. package/public/assets/img/texture-crack.jpg +0 -0
  185. package/public/assets/img/texture-rock-black.jpg +0 -0
  186. package/public/assets/img/texture-rock.jpg +0 -0
  187. package/public/assets/img/texture-sand-black.jpg +0 -0
  188. package/public/assets/img/texture-sand.jpg +0 -0
  189. package/public/assets/img/texture-silk-black.jpg +0 -0
  190. package/public/assets/img/texture-silk.jpg +0 -0
  191. package/public/assets/topojson/france-departments.json +1 -0
  192. package/public/assets/topojson/paris-arrondissements.json +1 -0
  193. package/public/assets/topojson/world-countries-sans-antarctica.json +1 -0
  194. package/public/favicon-16x16.png +0 -0
  195. package/public/favicon-32x32.png +0 -0
  196. package/public/favicon.ico +0 -0
  197. package/public/site.webmanifest +1 -0
  198. package/stories/assets/code-brackets.svg +1 -0
  199. package/stories/assets/colors.svg +1 -0
  200. package/stories/assets/comments.svg +1 -0
  201. package/stories/assets/direction.svg +1 -0
  202. package/stories/assets/flow.svg +1 -0
  203. package/stories/assets/plugin.svg +1 -0
  204. package/stories/assets/repo.svg +1 -0
  205. package/stories/assets/stackalt.svg +1 -0
  206. package/stories/getting-started/about-icij.mdx +14 -0
  207. package/stories/getting-started/custom-bootstrap.mdx +23 -0
  208. package/stories/getting-started/installation-guide.mdx +62 -0
  209. package/stories/getting-started/internationalization.mdx +63 -0
  210. package/stories/murmur/components/AccordionStep.stories.ts +33 -0
  211. package/stories/murmur/components/AccordionWrapper.stories.ts +69 -0
  212. package/stories/murmur/components/ActiveTextTruncate.stories.ts +32 -0
  213. package/stories/murmur/components/AdvancedLinkForm.stories.ts +77 -0
  214. package/stories/murmur/components/Brand.stories.ts +30 -0
  215. package/stories/murmur/components/BrandExpansion.stories.ts +41 -0
  216. package/stories/murmur/components/ConfirmButton.stories.ts +40 -0
  217. package/stories/murmur/components/ContentPlaceholder.stories.ts +41 -0
  218. package/stories/murmur/components/CustomPagination.stories.ts +42 -0
  219. package/stories/murmur/components/DigitsInput.stories.ts +29 -0
  220. package/stories/murmur/components/DonateForm.stories.ts +29 -0
  221. package/stories/murmur/components/EmbedForm.stories.ts +35 -0
  222. package/stories/murmur/components/EmbeddableFooter.stories.ts +59 -0
  223. package/stories/murmur/components/FollowUsPopover.stories.ts +24 -0
  224. package/stories/murmur/components/GenericFooter.stories.ts +27 -0
  225. package/stories/murmur/components/GenericHeader.stories.ts +27 -0
  226. package/stories/murmur/components/HapticCopy.stories.ts +40 -0
  227. package/stories/murmur/components/ImddbHeader.stories.ts +27 -0
  228. package/stories/murmur/components/OrdinalLegend.stories.ts +49 -0
  229. package/stories/murmur/components/RangePicker.stories.ts +98 -0
  230. package/stories/murmur/components/ResponsiveIframe.stories.ts +24 -0
  231. package/stories/murmur/components/ScaleLegend.stories.ts +65 -0
  232. package/stories/murmur/components/SecretInput.stories.ts +60 -0
  233. package/stories/murmur/components/SelectableDropdown.stories.ts +143 -0
  234. package/stories/murmur/components/SharingOptions.stories.ts +32 -0
  235. package/stories/murmur/components/SharingOptionsLink.stories.ts +53 -0
  236. package/stories/murmur/components/SignUpForm.stories.ts +51 -0
  237. package/stories/murmur/components/SlideUpDown.stories.ts +32 -0
  238. package/stories/murmur/components/TexturedDeck.stories.ts +83 -0
  239. package/stories/murmur/components/TinyPagination.stories.ts +65 -0
  240. package/stories/murmur/datavisualisations/BarChart.stories.ts +54 -0
  241. package/stories/murmur/datavisualisations/ColumnChart.stories.ts +88 -0
  242. package/stories/murmur/datavisualisations/LineChart.stories.ts +139 -0
  243. package/stories/murmur/datavisualisations/StackedBarChart.stories.ts +199 -0
  244. package/stories/murmur/datavisualisations/StackedColumnChart.stories.ts +136 -0
  245. package/stories/murmur/decorators.ts +108 -0
  246. package/stories/murmur/maps/ChoroplethMap.stories.ts +440 -0
  247. package/stories/murmur/maps/ChoroplethMapAnnotation.stories.ts +26 -0
  248. package/stories/murmur/maps/SymbolMap.stories.ts +24 -0
  249. package/stories/murmur/utils.ts +7 -0
  250. package/tests/unit/components/AccordionStep.spec.ts +157 -0
  251. package/tests/unit/components/AccordionWrapper.spec.ts +57 -0
  252. package/tests/unit/components/ActiveTextTruncate.spec.js +30 -0
  253. package/tests/unit/components/AdvancedLinkForm.spec.js +124 -0
  254. package/tests/unit/components/Brand.spec.js +50 -0
  255. package/tests/unit/components/ContentPlaceholder.spec.js +29 -0
  256. package/tests/unit/components/CustomPagination.spec.js +72 -0
  257. package/tests/unit/components/DigitsInput.spec.ts +157 -0
  258. package/tests/unit/components/DonateForm.spec.js +149 -0
  259. package/tests/unit/components/EmbedForm.spec.js +108 -0
  260. package/tests/unit/components/EmbeddableFooter.spec.js +11 -0
  261. package/tests/unit/components/Fa.spec.js +18 -0
  262. package/tests/unit/components/FollowUsPopover.spec.js +29 -0
  263. package/tests/unit/components/GenericFooter.spec.js +29 -0
  264. package/tests/unit/components/GenericHeader.spec.js +104 -0
  265. package/tests/unit/components/HapticCopy.spec.js +123 -0
  266. package/tests/unit/components/ImddbHeader.spec.js +96 -0
  267. package/tests/unit/components/OrdinalLegend.spec.js +120 -0
  268. package/tests/unit/components/RangePicker.spec.ts +87 -0
  269. package/tests/unit/components/ResponsiveIframe.spec.js +20 -0
  270. package/tests/unit/components/ScaleLegend.spec.js +139 -0
  271. package/tests/unit/components/SecretInput.spec.js +81 -0
  272. package/tests/unit/components/SelectableDropdown.spec.js +160 -0
  273. package/tests/unit/components/SharingOptions.spec.js +125 -0
  274. package/tests/unit/components/SharingOptionsLink.spec.js +184 -0
  275. package/tests/unit/components/SignUpForm.spec.js +145 -0
  276. package/tests/unit/components/SlideUpDown.spec.js +59 -0
  277. package/tests/unit/components/TinyPagination.spec.js +46 -0
  278. package/tests/unit/config.spec.js +136 -0
  279. package/tests/unit/datavisualisations/BarChart.spec.js +63 -0
  280. package/tests/unit/datavisualisations/ColumnChart.spec.js +344 -0
  281. package/tests/unit/datavisualisations/LineChart.spec.js +155 -0
  282. package/tests/unit/datavisualisations/StackedBarChart.spec.js +294 -0
  283. package/tests/unit/datavisualisations/StackedColumnChart.spec.js +443 -0
  284. package/tests/unit/i18n.spec.ts +19 -0
  285. package/tests/unit/main.spec.js +82 -0
  286. package/tests/unit/maps/ChoroplethMap.spec.js +214 -0
  287. package/tests/unit/maps/ChoroplethMapAnnotation.spec.ts +186 -0
  288. package/tests/unit/maps/SymbolMap.spec.js +92 -0
  289. package/tests/unit/require.spec.js +22 -0
  290. package/tests/unit/setup.js +13 -0
  291. package/tests/unit/utils/assets.spec.js +61 -0
  292. package/tests/unit/utils/clipboard.spec.js +18 -0
  293. package/tests/unit/utils/iframe-resizer.spec.js +71 -0
  294. package/tsconfig.json +35 -0
  295. package/vite.config.ts +79 -0
  296. package/vitest.config.ts +19 -0
@@ -0,0 +1,614 @@
1
+ <script lang="ts">
2
+ import * as d3 from 'd3'
3
+ import find from 'lodash/find'
4
+ import get from 'lodash/get'
5
+ import identity from 'lodash/identity'
6
+ import kebabCase from 'lodash/kebabCase'
7
+ import keys from 'lodash/keys'
8
+ import without from 'lodash/without'
9
+ import sortBy from 'lodash/sortBy'
10
+ import {ComponentPublicInstance, computed, defineComponent, nextTick, PropType, ref, watch} from 'vue'
11
+ import {chartProps, getChartProps, useChart} from "@/composables/chart.js";
12
+ import {isArray} from "lodash";
13
+
14
+ export default defineComponent({
15
+ name: 'StackedBarChart',
16
+ props: {
17
+ /**
18
+ * Colors of each bar group
19
+ */
20
+ barColors: {
21
+ type: Array,
22
+ default: () => []
23
+ },
24
+ /**
25
+ * Enforce the height of the chart (regardless of the width or number of row)
26
+ */
27
+ fixedHeight: {
28
+ type: Number as PropType<number | null>,
29
+ default: null
30
+ },
31
+ /**
32
+ * Group name to display in the legend
33
+ */
34
+ groups: {
35
+ type: Array,
36
+ default: () => []
37
+ },
38
+ /**
39
+ * Hide bars that have no values.
40
+ */
41
+ hideEmptyValues: {
42
+ type: Boolean
43
+ },
44
+ /**
45
+ * Hide the legend.
46
+ */
47
+ hideLegend: {
48
+ type: Boolean
49
+ },
50
+ /**
51
+ * A list of highlighted groups
52
+ */
53
+ highlights: {
54
+ type: Array,
55
+ default: () => []
56
+ },
57
+ /**
58
+ * Delay to apply when set the first highlight
59
+ */
60
+ highlightDelay: {
61
+ type: Number,
62
+ default: 400
63
+ },
64
+ /**
65
+ * Field of each object containing data (for each group)
66
+ */
67
+ keys: {
68
+ type: Array,
69
+ default: () => []
70
+ },
71
+ /**
72
+ * Switch labels above bars
73
+ */
74
+ labelAbove: {
75
+ type: Boolean,
76
+ default: false
77
+ },
78
+ /**
79
+ * Field containing the label for each row
80
+ */
81
+ labelField: {
82
+ type: String,
83
+ default: 'label'
84
+ },
85
+ /**
86
+ * Set a minimal height for the bars
87
+ */
88
+ minBarHeight: {
89
+ type: Number,
90
+ default: 16
91
+ },
92
+ /**
93
+ * Set a maximal height for the bars
94
+ */
95
+ maxBarHeight: {
96
+ type: Number,
97
+ default: 60
98
+ },
99
+ /**
100
+ * Bar width is relative to each group's total
101
+ */
102
+ relative: {
103
+ type: Boolean,
104
+ default: false
105
+ },
106
+ /**
107
+ * Delay to apply when restoring hightlights to initial state
108
+ */
109
+ restoreHighlightDelay: {
110
+ type: Number,
111
+ default: 50
112
+ },
113
+ /**
114
+ * A list of entire row to highlight
115
+ */
116
+ rowHighlights: {
117
+ type: Array,
118
+ default: () => []
119
+ },
120
+ /**
121
+ * Sort groups by one or several keys.
122
+ */
123
+ sortBy: {
124
+ type: [Array, String],
125
+ default: null
126
+ },
127
+ /**
128
+ * Function to apply to format x axis ticks (bar value). It can be a
129
+ * function returning the formatted value or a d3's formatter string.
130
+ */
131
+ xAxisTickFormat: {
132
+ type: [Function, String] as PropType<Function | string>,
133
+ default: () => identity
134
+ },
135
+ ...chartProps()
136
+ },
137
+ setup(props, {emit}) {
138
+ const highlightedKeys = ref(props.highlights)
139
+ const highlightTimeout = ref<NodeJS.Timeout | undefined>(undefined)
140
+ const isLoaded = ref(false)
141
+ const el = ref<ComponentPublicInstance<HTMLElement> | null>(null)
142
+ const {
143
+ loadedData,
144
+ baseHeightRatio,
145
+ d3Formatter,
146
+ dataHasHighlights
147
+ } = useChart(el, getChartProps(props), {emit}, isLoaded)
148
+
149
+ const hasConstraintHeight = computed(() => {
150
+ return props.fixedHeight !== null || props.socialMode
151
+ })
152
+
153
+ const sortedData = computed(() => {
154
+ if (!isLoaded.value) {
155
+ return []
156
+ }
157
+ return !props.sortBy ? loadedData.value : sortBy(loadedData.value, props.sortBy)
158
+ })
159
+ const discoveredKeys = computed((): any[] => {
160
+ if (props.keys.length) {
161
+ return props.keys
162
+ }
163
+ if (!loadedData.value) {
164
+ return []
165
+ }
166
+ return without(keys(loadedData.value[0]), props.labelField)
167
+ })
168
+ const colorScale = computed(() => {
169
+ return d3.scaleOrdinal().domain(discoveredKeys.value).range(props.barColors)
170
+ })
171
+ const maxValue = computed(() => {
172
+ return d3.max(loadedData.value || [], (datum, i) => {
173
+ return totalRowValue(i)
174
+ })
175
+ })
176
+ const hasHighlights = computed(() => {
177
+ return !!highlightedKeys.value.length
178
+ })
179
+ const hasRowHighlights = computed(() => {
180
+ return !!props.rowHighlights.length
181
+ })
182
+ const height = computed(() => {
183
+ if (props.fixedHeight !== null) {
184
+ return `${props.fixedHeight}px`
185
+ }
186
+ return props.socialMode && el.value ? `${el.value.offsetWidth * baseHeightRatio.value}px` : 'auto'
187
+ })
188
+
189
+ function normalizeKey(key: string) {
190
+ return kebabCase(key)
191
+ }
192
+
193
+ function totalRowValue(i: number | string) {
194
+ return d3.sum(discoveredKeys.value, (key) => {
195
+ return sortedData.value[i][key]
196
+ })
197
+ }
198
+
199
+ function groupName(key: string) {
200
+ const index = discoveredKeys.value.indexOf(key)
201
+ return props.groups[index] || key
202
+ }
203
+
204
+ function highlight(key: string) {
205
+ highlightedKeys.value = [key]
206
+ }
207
+
208
+ function restoreHighlights() {
209
+ clearTimeout(highlightTimeout.value)
210
+ const delay = props.restoreHighlightDelay
211
+ // Delay the restoration so it can be cancelled by a new highlight
212
+ highlightTimeout.value = setTimeout(() => (highlightedKeys.value = props.highlights), delay)
213
+ }
214
+
215
+ function delayHighlight(key: string) {
216
+ clearTimeout(highlightTimeout.value)
217
+ // Reduce the delay to zero if there is already an highlighted key
218
+ const isDelayed = !hasHighlights.value
219
+ const delay = isDelayed ? props.highlightDelay : 0
220
+ highlightTimeout.value = setTimeout(() => highlight(key), delay)
221
+ }
222
+
223
+ function isHighlighted(key: string) {
224
+ return highlightedKeys.value.indexOf(key) > -1
225
+ }
226
+
227
+ function isRowHighlighted(i: number | string) {
228
+ const row = get(sortedData.value, [i, props.labelField], null)
229
+ return (isArray(props.rowHighlights) && props.rowHighlights?.includes(row)) && !highlightedKeys.value.length
230
+ }
231
+
232
+ function barStyle(i: number | string, key: string) {
233
+ const value = sortedData.value[i][key]
234
+ const totalWidth = props.relative ? totalRowValue(i) : maxValue.value
235
+ if (!totalWidth) {
236
+ throw new Error("Total width is not correct" + totalWidth)
237
+ }
238
+ const width = `${100 * (value / totalWidth)}%`
239
+ const backgroundColor = colorScale.value(key)
240
+ return {width, backgroundColor}
241
+ }
242
+
243
+ // function barHeightBounds(height:number) {
244
+ // return Math.min(Math.max(height, props.minBarHeight), props.maxBarHeight)
245
+ // }
246
+ async function stackBarAndValue(i: number | string) {
247
+ if (!sortedData.value) {
248
+ return []
249
+ }
250
+ await nextTick()
251
+ // Collect sizes first
252
+ const stack = discoveredKeys.value.map((key: string) => {
253
+ const {bar, row, value} = queryBarAndValue(i as number, key)
254
+ if (!bar || !row || !value) {
255
+ throw new Error("Values not retrieved")
256
+ }
257
+ const barEdge = bar.getBoundingClientRect().left + bar.offsetWidth
258
+ const barWidth = bar.offsetWidth
259
+ const rowEdge = row.getBoundingClientRect().left + row.offsetWidth
260
+ const valueWidth = value.offsetWidth
261
+ return {key, barEdge, barWidth, rowEdge, valueWidth}
262
+ })
263
+ // Infer value's display
264
+ return stack.map((desc, index) => {
265
+ desc.overflow = desc.valueWidth >= desc.barWidth
266
+ if (index > 0) {
267
+ const prevDesc = stack[index - 1]
268
+ const bothValuesWidth = desc.valueWidth + prevDesc.valueWidth
269
+ desc.overflow = desc.overflow || (prevDesc.overflow && desc.barWidth < bothValuesWidth)
270
+ }
271
+ desc.pushed = desc.barEdge + desc.valueWidth > desc.rowEdge && desc.overflow
272
+ return desc
273
+ })
274
+ }
275
+
276
+ function queryBarAndValue(i: number, key: string) {
277
+ if (!sortedData.value) {
278
+ return {}
279
+ }
280
+ const barClass = 'stacked-bar-chart__groups__item__bars__item'
281
+ const rowSelector = '.stacked-bar-chart__groups__item'
282
+ const row = el.value?.querySelectorAll(rowSelector)[i] as HTMLElement
283
+ const normalizedKey = normalizeKey(key)
284
+ const barSelector = `.${barClass}--${normalizedKey}`
285
+ const bar = row?.querySelector(barSelector) as HTMLElement
286
+ const valueSelector = `.${barClass}__value`
287
+ const value = bar?.querySelector(valueSelector) as HTMLElement
288
+ return {bar, row, value}
289
+ }
290
+
291
+ async function hasValueOverflow(i: number | string, key: string) {
292
+ const stack = await stackBarAndValue(i)
293
+ return get(find(stack, {key}), 'overflow')
294
+ }
295
+
296
+ async function hasValuePushed(i: number | string, key: string) {
297
+ const stack = await stackBarAndValue(i)
298
+ return get(find(stack, {key}), 'pushed')
299
+ }
300
+
301
+ async function hasValueHidden(i: number | string, key: string) {
302
+ const keyIndex = discoveredKeys.value.indexOf(key)
303
+ const nextKey = discoveredKeys.value[keyIndex + 1]
304
+ if (!nextKey) {
305
+ return false
306
+ }
307
+ const keyC = await hasValueOverflow(i, key)
308
+ const keyN = await hasValueOverflow(i, nextKey)
309
+ return keyC && keyN
310
+ }
311
+
312
+ function isHidden(i: number | string, key: string) {
313
+ return props.hideEmptyValues && !sortedData.value[i][key]
314
+ }
315
+
316
+ // async function barItemClasses(i,key){
317
+ // const stack = await stackBarAndValue(i)
318
+ // const hasOv = get(find(stack, {key}), 'overflow')
319
+ // const hasPu = get(find(stack, {key}), 'pushed')
320
+ //
321
+ // const keyIndex = discoveredKeys.value.indexOf(key)
322
+ // const nextKey = discoveredKeys.value[keyIndex + 1]
323
+ // let hasNextOv= false
324
+ // if(nextKey){
325
+ // const stackNext = await stackBarAndValue(keyIndex + 1)
326
+ // hasNextOv = get(find(stackNext, {key}), 'overflow')
327
+ // }
328
+ // const hasHiddenV = hasOv && hasNextOv
329
+ // const classes = {
330
+ // hiddenValue:isHidden(keyIndex, key),
331
+ // overflow:hasOv,
332
+ // pushed:hasPu,
333
+ // hidden:hasHiddenV
334
+ // }
335
+ // return classes
336
+ // }
337
+
338
+ function formatXDatum(d: string) {
339
+ return d3Formatter(d, props.xAxisTickFormat)
340
+ }
341
+
342
+ watch(() => props.highlights, (newHighlights) => {
343
+ highlightedKeys.value = newHighlights
344
+ })
345
+
346
+ return {
347
+ colorScale,
348
+ dataHasHighlights,
349
+ discoveredKeys,
350
+ el,
351
+ hasConstraintHeight,
352
+ hasHighlights,
353
+ hasRowHighlights,
354
+ height,
355
+ sortedData,
356
+ barStyle,
357
+ delayHighlight,
358
+ formatXDatum,
359
+ groupName,
360
+ hasValueHidden,
361
+ hasValueOverflow,
362
+ hasValuePushed,
363
+ isHidden,
364
+ isHighlighted,
365
+ isRowHighlighted,
366
+ loadedData,
367
+ normalizeKey,
368
+ restoreHighlights
369
+ }
370
+
371
+ }
372
+
373
+ // watch: {
374
+ // relative() {
375
+ // this.$nextTick(this.$forceUpdate)
376
+ // },
377
+ // height() {
378
+ // this.$nextTick(this.$forceUpdate)
379
+ // },
380
+ // sortBy() {
381
+ // this.$nextTick(this.$forceUpdate)
382
+ // },
383
+ // highlights:{
384
+ // deep:true,
385
+ // handler() {
386
+ // this.highlightedKeys = this.highlights
387
+ // }
388
+ // }
389
+ // },
390
+
391
+ })
392
+ </script>
393
+ <template>
394
+ <div
395
+ ref="el"
396
+ :class="{
397
+ 'stacked-bar-chart--social-mode': socialMode,
398
+ 'stacked-bar-chart--label-above': labelAbove,
399
+ 'stacked-bar-chart--has-highlights': hasHighlights || hasRowHighlights || dataHasHighlights,
400
+ 'stacked-bar-chart--has-constraint-height': hasConstraintHeight,
401
+ 'stacked-bar-chart--has-label-above': labelAbove
402
+ }"
403
+ :style="{ height }"
404
+ class="stacked-bar-chart d-flex flex-column"
405
+ >
406
+ <div class="d-flex align-items-center">
407
+ <slot name="header-left">
408
+ <ul v-if="!hideLegend" class="stacked-bar-chart__legend list-inline mx-0 mt-0 mb-2">
409
+ <li
410
+ v-for="key in discoveredKeys"
411
+ :key="key"
412
+ :class="{
413
+ 'stacked-bar-chart__legend__item--highlighted': isHighlighted(key)
414
+ }"
415
+ class="stacked-bar-chart__legend__item list-inline-item d-inline-flex"
416
+ @mouseleave="restoreHighlights()"
417
+ @mouseover="delayHighlight(key)"
418
+ >
419
+ <span :style="{ 'background-color': colorScale(key) }" class="stacked-bar-chart__legend__item__box"/>
420
+ {{ groupName(key) }}
421
+ </li>
422
+ </ul>
423
+ </slot>
424
+ <slot name="header-right"/>
425
+ </div>
426
+ <div class="stacked-bar-chart__groups">
427
+ <div
428
+ v-for="(datum, i) in sortedData"
429
+ :key="i"
430
+ :class="{ 'flex-column': labelAbove }"
431
+ class="stacked-bar-chart__groups__item border-bottom flex-fill d-flex align-items-center"
432
+ >
433
+ <div :class="{ 'w-100': labelAbove }" class="stacked-bar-chart__groups__item__label me-1 small">
434
+ {{ datum[labelField] }}
435
+ </div>
436
+ <div class="stacked-bar-chart__groups__item__bars my-1 d-flex flex-grow-1 align-items-center">
437
+ <div
438
+ v-for="(key, j) in discoveredKeys"
439
+ :key="j"
440
+ :class="{
441
+ [`stacked-bar-chart__groups__item__bars__item--${normalizeKey(key)}`]: true,
442
+ [`stacked-bar-chart__groups__item__bars__item--${j}n`]: true,
443
+ 'stacked-bar-chart__groups__item__bars__item--highlighted': isHighlighted(key) || isRowHighlighted(i),
444
+ 'stacked-bar-chart__groups__item__bars__item--hidden': isHidden(i, key),
445
+ 'stacked-bar-chart__groups__item__bars__item--value-overflow': hasValueOverflow(i, key),
446
+ 'stacked-bar-chart__groups__item__bars__item--value-pushed': hasValuePushed(i, key),
447
+ 'stacked-bar-chart__groups__item__bars__item--value-hidden': hasValueHidden(i, key)
448
+ }"
449
+ :style="barStyle(i, key)"
450
+ class="stacked-bar-chart__groups__item__bars__item"
451
+ @mouseleave="restoreHighlights()"
452
+ @mouseover="delayHighlight(key)"
453
+ >
454
+ <div class="stacked-bar-chart__groups__item__bars__item__value p-1">
455
+ {{ formatXDatum(datum[key]) }}
456
+ </div>
457
+ </div>
458
+ </div>
459
+ </div>
460
+ </div>
461
+ </div>
462
+ </template>
463
+ <style lang="scss">
464
+ @use 'sass:math';
465
+ @import '../styles/lib';
466
+
467
+ .stacked-bar-chart {
468
+ $muted-group-opacity: 0.2;
469
+ $muted-group-filter: grayscale(30%) brightness(10%);
470
+ $muted-group-transition: opacity 0.3s, filter 0.3s;
471
+ $colors: $primary, $info, $warning;
472
+ $quantile: 3;
473
+
474
+ @each $start-color in $colors {
475
+ $i: index($colors, $start-color) - 1;
476
+ $end-color: mix($start-color, text-contrast($start-color), 20%);
477
+
478
+ @for $j from ($quantile * $i) through ($quantile * $i + $quantile - 1) {
479
+ $amount: ($j % $quantile) * math.div(100%, $quantile);
480
+ --group-color-#{$j}n: #{mix($end-color, $start-color, $amount)};
481
+ }
482
+ }
483
+
484
+ &__legend {
485
+ &__item {
486
+ display: inline-flex;
487
+ flex-direction: row;
488
+ align-items: center;
489
+ padding-right: $spacer * 0.5;
490
+ transition: $muted-group-transition;
491
+
492
+ @for $i from 0 through ($quantile * length($colors)) {
493
+ &:nth-child(#{$i + 1}n) &__box {
494
+ background-color: var(--group-color-#{$i}n);
495
+ }
496
+ }
497
+
498
+ .stacked-bar-chart--has-highlights &:not(&--highlighted) {
499
+ opacity: $muted-group-opacity;
500
+ filter: $muted-group-filter;
501
+ }
502
+
503
+ &__box {
504
+ height: 1em;
505
+ width: 1em;
506
+ border-radius: 0.5em;
507
+ display: inline-block;
508
+ margin-right: $spacer * 0.5;
509
+ }
510
+ }
511
+ }
512
+
513
+ &__groups {
514
+ .stacked-bar-chart--has-constraint-height & {
515
+ height: 100%;
516
+ display: flex;
517
+ flex-direction: column;
518
+ }
519
+
520
+ &__item {
521
+ &__label {
522
+ width: 20%;
523
+ padding: $spacer * 0.5 0;
524
+
525
+ .stacked-bar-chart--label-above & {
526
+ width: 100%;
527
+ padding-bottom: 0;
528
+ }
529
+
530
+ &:empty {
531
+ display: none;
532
+ }
533
+ }
534
+
535
+ &__bars {
536
+ min-height: 100%;
537
+ width: 100%;
538
+
539
+ .stacked-bar-chart--has-constraint-height & {
540
+ height: calc(100% - 1.5rem);
541
+ min-height: calc(100% - 1.5rem);
542
+ }
543
+
544
+ .stacked-bar-chart--has-constraint-height.stacked-bar-chart--label-above & {
545
+ height: auto;
546
+ min-height: auto;
547
+ }
548
+
549
+ &__item {
550
+ text-align: right;
551
+ transition: $muted-group-transition;
552
+ min-width: 1px;
553
+ min-height: 10px;
554
+ height: 100%;
555
+ display: flex;
556
+ flex-direction: row;
557
+ align-items: center;
558
+ justify-content: flex-end;
559
+
560
+ .stacked-bar-chart--has-constraint-height & {
561
+ height: auto;
562
+ align-self: stretch;
563
+ }
564
+
565
+ &--hidden {
566
+ display: none !important;
567
+ }
568
+
569
+ @for $i from 0 through ($quantile * length($colors)) {
570
+ &--#{$i}n {
571
+ background: var(--group-color-#{$i}n);
572
+ }
573
+ }
574
+
575
+ .stacked-bar-chart--has-highlights &:not(&--highlighted) {
576
+ opacity: $muted-group-opacity;
577
+ filter: $muted-group-filter;
578
+ }
579
+
580
+ .stacked-bar-chart--has-highlights &:not(&--highlighted) &__value {
581
+ visibility: hidden;
582
+ }
583
+
584
+ .stacked-bar-chart:not(.stacked-bar-chart--has-highlights) &--value-hidden &__value,
585
+ .stacked-bar-chart:not(.stacked-bar-chart--has-highlights) &--value-pushed &__value {
586
+ visibility: hidden;
587
+ }
588
+
589
+ &--value-overflow &__value {
590
+ color: $body-color;
591
+ transform: translateX(100%);
592
+ }
593
+
594
+ &--value-pushed {
595
+ justify-content: flex-start;
596
+ }
597
+
598
+ &--value-pushed &__value {
599
+ color: $body-color;
600
+ transform: translateX(-100%);
601
+ }
602
+
603
+ &__value {
604
+ white-space: nowrap;
605
+ color: white;
606
+ pointer-events: none;
607
+ display: inline-block;
608
+ }
609
+ }
610
+ }
611
+ }
612
+ }
613
+ }
614
+ </style>