@dhis2/analytics 24.10.1 → 25.1.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 (88) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/build/cjs/__demo__/CalculationModal.stories.js +448 -0
  3. package/build/cjs/api/analytics/AnalyticsRequest.js +12 -1
  4. package/build/cjs/api/dimensions.js +1 -1
  5. package/build/cjs/api/expression.js +67 -0
  6. package/build/cjs/assets/DimensionItemIcons/CalculationIcon.js +25 -0
  7. package/build/cjs/assets/FormulaIcon.js +40 -0
  8. package/build/cjs/components/DataDimension/Calculation/CalculationModal.js +448 -0
  9. package/build/cjs/components/DataDimension/Calculation/DataElementOption.js +78 -0
  10. package/build/cjs/components/DataDimension/Calculation/DataElementSelector.js +309 -0
  11. package/build/cjs/components/DataDimension/Calculation/DndContext.js +213 -0
  12. package/build/cjs/components/DataDimension/Calculation/DragHandleIcon.js +23 -0
  13. package/build/cjs/components/DataDimension/Calculation/DraggingItem.js +58 -0
  14. package/build/cjs/components/DataDimension/Calculation/DropZone.js +58 -0
  15. package/build/cjs/components/DataDimension/Calculation/FormulaField.js +121 -0
  16. package/build/cjs/components/DataDimension/Calculation/FormulaItem.js +232 -0
  17. package/build/cjs/components/DataDimension/Calculation/MathOperatorSelector.js +58 -0
  18. package/build/cjs/components/DataDimension/Calculation/Operator.js +81 -0
  19. package/build/cjs/components/DataDimension/Calculation/styles/CalculationModal.style.js +13 -0
  20. package/build/cjs/components/DataDimension/Calculation/styles/DataElementOption.style.js +13 -0
  21. package/build/cjs/components/DataDimension/Calculation/styles/DataElementSelector.style.js +13 -0
  22. package/build/cjs/components/DataDimension/Calculation/styles/DraggingItem.style.js +13 -0
  23. package/build/cjs/components/DataDimension/Calculation/styles/DropZone.style.js +13 -0
  24. package/build/cjs/components/DataDimension/Calculation/styles/FormulaField.style.js +13 -0
  25. package/build/cjs/components/DataDimension/Calculation/styles/FormulaItem.style.js +13 -0
  26. package/build/cjs/components/DataDimension/Calculation/styles/MathOperatorSelector.style.js +13 -0
  27. package/build/cjs/components/DataDimension/Calculation/styles/Operator.style.js +13 -0
  28. package/build/cjs/components/DataDimension/DataDimension.js +22 -6
  29. package/build/cjs/components/DataDimension/DataTypeSelector.js +5 -3
  30. package/build/cjs/components/DataDimension/ItemSelector.js +111 -73
  31. package/build/cjs/components/LegendKey/LegendKey.js +1 -1
  32. package/build/cjs/components/TransferOption.js +13 -4
  33. package/build/cjs/components/styles/DimensionSelector.style.js +2 -2
  34. package/build/cjs/components/styles/TransferOption.style.js +2 -2
  35. package/build/cjs/index.js +6 -0
  36. package/build/cjs/locales/en/translations.json +32 -7
  37. package/build/cjs/modules/__tests__/expressions.spec.js +139 -0
  38. package/build/cjs/modules/__tests__/hash.spec.js +92 -0
  39. package/build/cjs/modules/__tests__/parseExpression.spec.js +46 -0
  40. package/build/cjs/modules/dataTypes.js +8 -1
  41. package/build/cjs/modules/dimensionListItem.js +82 -0
  42. package/build/cjs/modules/expressions.js +164 -0
  43. package/build/cjs/modules/hash.js +28 -0
  44. package/build/cjs/visualizations/config/generators/dhis/singleValue.js +112 -58
  45. package/build/es/__demo__/CalculationModal.stories.js +440 -0
  46. package/build/es/api/analytics/AnalyticsRequest.js +11 -1
  47. package/build/es/api/dimensions.js +1 -1
  48. package/build/es/api/expression.js +57 -0
  49. package/build/es/assets/DimensionItemIcons/CalculationIcon.js +13 -0
  50. package/build/es/assets/FormulaIcon.js +30 -0
  51. package/build/es/components/DataDimension/Calculation/CalculationModal.js +419 -0
  52. package/build/es/components/DataDimension/Calculation/DataElementOption.js +61 -0
  53. package/build/es/components/DataDimension/Calculation/DataElementSelector.js +283 -0
  54. package/build/es/components/DataDimension/Calculation/DndContext.js +194 -0
  55. package/build/es/components/DataDimension/Calculation/DragHandleIcon.js +11 -0
  56. package/build/es/components/DataDimension/Calculation/DraggingItem.js +40 -0
  57. package/build/es/components/DataDimension/Calculation/DropZone.js +43 -0
  58. package/build/es/components/DataDimension/Calculation/FormulaField.js +98 -0
  59. package/build/es/components/DataDimension/Calculation/FormulaItem.js +207 -0
  60. package/build/es/components/DataDimension/Calculation/MathOperatorSelector.js +42 -0
  61. package/build/es/components/DataDimension/Calculation/Operator.js +64 -0
  62. package/build/es/components/DataDimension/Calculation/styles/CalculationModal.style.js +4 -0
  63. package/build/es/components/DataDimension/Calculation/styles/DataElementOption.style.js +4 -0
  64. package/build/es/components/DataDimension/Calculation/styles/DataElementSelector.style.js +4 -0
  65. package/build/es/components/DataDimension/Calculation/styles/DraggingItem.style.js +4 -0
  66. package/build/es/components/DataDimension/Calculation/styles/DropZone.style.js +4 -0
  67. package/build/es/components/DataDimension/Calculation/styles/FormulaField.style.js +4 -0
  68. package/build/es/components/DataDimension/Calculation/styles/FormulaItem.style.js +4 -0
  69. package/build/es/components/DataDimension/Calculation/styles/MathOperatorSelector.style.js +4 -0
  70. package/build/es/components/DataDimension/Calculation/styles/Operator.style.js +4 -0
  71. package/build/es/components/DataDimension/DataDimension.js +21 -6
  72. package/build/es/components/DataDimension/DataTypeSelector.js +6 -4
  73. package/build/es/components/DataDimension/ItemSelector.js +111 -73
  74. package/build/es/components/LegendKey/LegendKey.js +1 -1
  75. package/build/es/components/TransferOption.js +14 -5
  76. package/build/es/components/styles/DimensionSelector.style.js +2 -2
  77. package/build/es/components/styles/TransferOption.style.js +2 -2
  78. package/build/es/index.js +1 -1
  79. package/build/es/locales/en/translations.json +32 -7
  80. package/build/es/modules/__tests__/expressions.spec.js +136 -0
  81. package/build/es/modules/__tests__/hash.spec.js +88 -0
  82. package/build/es/modules/__tests__/parseExpression.spec.js +43 -0
  83. package/build/es/modules/dataTypes.js +6 -0
  84. package/build/es/modules/dimensionListItem.js +61 -0
  85. package/build/es/modules/expressions.js +131 -0
  86. package/build/es/modules/hash.js +12 -0
  87. package/build/es/visualizations/config/generators/dhis/singleValue.js +112 -58
  88. package/package.json +6 -1
@@ -11,62 +11,94 @@ var _fontStyle = require("../../../../modules/fontStyle.js");
11
11
 
12
12
  var _legends = require("../../../../modules/legends.js");
13
13
 
14
- const svgNS = 'http://www.w3.org/2000/svg';
14
+ const svgNS = 'http://www.w3.org/2000/svg'; // Compute text width before rendering
15
+ // Not exactly precise but close enough
16
+
17
+ const getTextWidth = (text, font) => {
18
+ const canvas = document.createElement('canvas');
19
+ const context = canvas.getContext('2d');
20
+ context.font = font;
21
+ return context.measureText(text).width;
22
+ };
15
23
 
16
24
  const generateValueSVG = _ref => {
17
25
  let {
18
26
  formattedValue,
19
27
  subText,
20
28
  valueColor,
29
+ icon,
21
30
  noData,
22
- y
31
+ containerWidth,
32
+ containerHeight
23
33
  } = _ref;
24
- const textSize = 300;
25
- const svgValue = document.createElementNS(svgNS, 'svg');
26
- svgValue.setAttribute('xmlns', svgNS);
27
- svgValue.setAttribute('viewBox', "0 -".concat(textSize + 50, " ").concat(textSize * 0.75 * formattedValue.length, " ").concat(textSize + 200));
28
-
29
- if (y) {
30
- svgValue.setAttribute('y', y);
34
+ const ratio = containerHeight / containerWidth;
35
+ const iconSize = 300;
36
+ const iconPadding = 50;
37
+ const textSize = iconSize * 0.85;
38
+ const textWidth = getTextWidth(formattedValue, "".concat(textSize, "px Roboto"));
39
+ const subTextSize = 40;
40
+ const showIcon = icon && formattedValue !== noData.text;
41
+ let viewBoxWidth = textWidth;
42
+
43
+ if (showIcon) {
44
+ viewBoxWidth += iconSize + iconPadding;
31
45
  }
32
46
 
47
+ const viewBoxHeight = viewBoxWidth * ratio;
48
+ const svgValue = document.createElementNS(svgNS, 'svg');
49
+ svgValue.setAttribute('viewBox', "0 0 ".concat(viewBoxWidth, " ").concat(viewBoxHeight));
50
+ svgValue.setAttribute('width', '95%');
51
+ svgValue.setAttribute('height', '95%');
52
+ svgValue.setAttribute('x', '50%');
53
+ svgValue.setAttribute('y', '50%');
54
+ svgValue.setAttribute('style', 'overflow: visible');
33
55
  let fillColor = _ui.colors.grey900;
34
56
 
35
57
  if (valueColor) {
36
58
  fillColor = valueColor;
37
59
  } else if (formattedValue === noData.text) {
38
60
  fillColor = _ui.colors.grey600;
61
+ } // show icon if configured in maintenance app
62
+
63
+
64
+ if (showIcon) {
65
+ // embed icon to allow changing color
66
+ // (elements with fill need to use "currentColor" for this to work)
67
+ const iconSvgNode = document.createElementNS(svgNS, 'svg');
68
+ iconSvgNode.setAttribute('width', iconSize);
69
+ iconSvgNode.setAttribute('height', iconSize);
70
+ iconSvgNode.setAttribute('viewBox', '0 0 48 48');
71
+ iconSvgNode.setAttribute('y', "-".concat(iconSize / 2));
72
+ iconSvgNode.setAttribute('x', "-".concat((iconSize + iconPadding + textWidth) / 2));
73
+ iconSvgNode.setAttribute('style', "color: ".concat(fillColor));
74
+ const parser = new DOMParser();
75
+ const svgIconDocument = parser.parseFromString(icon, 'image/svg+xml');
76
+ Array.from(svgIconDocument.documentElement.children).forEach(node => iconSvgNode.appendChild(node));
77
+ svgValue.appendChild(iconSvgNode);
39
78
  }
40
79
 
41
80
  const textNode = document.createElementNS(svgNS, 'text');
42
- textNode.setAttribute('text-anchor', 'middle');
43
81
  textNode.setAttribute('font-size', textSize);
44
82
  textNode.setAttribute('font-weight', '300');
45
83
  textNode.setAttribute('letter-spacing', '-5');
46
- textNode.setAttribute('x', '50%');
84
+ textNode.setAttribute('text-anchor', 'middle');
85
+ textNode.setAttribute('x', showIcon ? "".concat((iconSize + iconPadding) / 2) : 0); // vertical align, "alignment-baseline: central" is not supported by Batik
86
+
87
+ textNode.setAttribute('y', '.35em');
47
88
  textNode.setAttribute('fill', fillColor);
48
89
  textNode.setAttribute('data-test', 'visualization-primary-value');
49
90
  textNode.appendChild(document.createTextNode(formattedValue));
50
91
  svgValue.appendChild(textNode);
51
92
 
52
93
  if (subText) {
53
- const svgSubText = document.createElementNS(svgNS, 'svg');
54
- const subTextSize = 40;
55
- svgSubText.setAttribute('viewBox', "0 -50 ".concat(textSize * 0.75 * formattedValue.length, " ").concat(textSize + 200));
56
-
57
- if (y) {
58
- svgSubText.setAttribute('y', y);
59
- }
60
-
61
94
  const subTextNode = document.createElementNS(svgNS, 'text');
62
95
  subTextNode.setAttribute('text-anchor', 'middle');
63
96
  subTextNode.setAttribute('font-size', subTextSize);
64
- subTextNode.setAttribute('x', '50%');
65
- subTextNode.setAttribute('x', '50%');
97
+ subTextNode.setAttribute('y', iconSize / 2);
98
+ subTextNode.setAttribute('dy', subTextSize);
66
99
  subTextNode.setAttribute('fill', _ui.colors.grey600);
67
100
  subTextNode.appendChild(document.createTextNode(subText));
68
- svgSubText.appendChild(subTextNode);
69
- svgValue.appendChild(svgSubText);
101
+ svgValue.appendChild(subTextNode);
70
102
  }
71
103
 
72
104
  return svgValue;
@@ -74,14 +106,27 @@ const generateValueSVG = _ref => {
74
106
 
75
107
  const generateDashboardItem = (config, _ref2) => {
76
108
  let {
109
+ svgContainer,
110
+ width,
111
+ height,
77
112
  valueColor,
78
113
  titleColor,
79
114
  backgroundColor,
80
- noData
115
+ noData,
116
+ icon
81
117
  } = _ref2;
118
+ svgContainer.appendChild(generateValueSVG({
119
+ formattedValue: config.formattedValue,
120
+ subText: config.subText,
121
+ valueColor,
122
+ noData,
123
+ icon,
124
+ containerWidth: width,
125
+ containerHeight: height
126
+ }));
82
127
  const container = document.createElement('div');
83
128
  container.setAttribute('style', "display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; background-color:".concat(backgroundColor, ";"));
84
- const titleStyle = "font-size: 12px; color: ".concat(titleColor || '#666', ";");
129
+ const titleStyle = "padding: 0 8px; text-align: center; font-size: 12px; color: ".concat(titleColor || '#666', ";");
85
130
  const title = document.createElement('span');
86
131
  title.setAttribute('style', titleStyle);
87
132
 
@@ -92,18 +137,12 @@ const generateDashboardItem = (config, _ref2) => {
92
137
 
93
138
  if (config.subtitle) {
94
139
  const subtitle = document.createElement('span');
95
- subtitle.setAttribute('style', titleStyle + ' margin-top: 4px; padding: 0 8px');
140
+ subtitle.setAttribute('style', titleStyle + ' margin-top: 4px;');
96
141
  subtitle.appendChild(document.createTextNode(config.subtitle));
97
142
  container.appendChild(subtitle);
98
143
  }
99
144
 
100
- container.appendChild(generateValueSVG({
101
- formattedValue: config.formattedValue,
102
- subText: config.subText,
103
- valueColor,
104
- noData,
105
- y: 40
106
- }));
145
+ container.appendChild(svgContainer);
107
146
  return container;
108
147
  };
109
148
 
@@ -137,33 +176,27 @@ const getXFromTextAlign = textAlign => {
137
176
 
138
177
  const generateDVItem = (config, _ref3) => {
139
178
  let {
179
+ svgContainer,
180
+ width,
181
+ height,
140
182
  valueColor,
183
+ noData,
141
184
  backgroundColor,
142
185
  titleColor,
143
- parentEl,
144
186
  fontStyle,
145
- noData
187
+ icon
146
188
  } = _ref3;
147
- const parentElBBox = parentEl.getBoundingClientRect();
148
- const width = parentElBBox.width;
149
- const height = parentElBBox.height;
150
- const svgNS = 'http://www.w3.org/2000/svg';
151
- const svg = document.createElementNS(svgNS, 'svg');
152
- svg.setAttribute('xmlns', svgNS);
153
- svg.setAttribute('viewBox', "0 0 ".concat(width, " ").concat(height));
154
- svg.setAttribute('width', width);
155
- svg.setAttribute('height', height);
156
- svg.setAttribute('data-test', 'visualization-container');
157
189
 
158
190
  if (backgroundColor) {
159
- svg.setAttribute('style', "background-color: ".concat(backgroundColor, ";"));
191
+ svgContainer.setAttribute('style', "background-color: ".concat(backgroundColor, ";"));
160
192
  const background = document.createElementNS(svgNS, 'rect');
161
193
  background.setAttribute('width', '100%');
162
194
  background.setAttribute('height', '100%');
163
195
  background.setAttribute('fill', backgroundColor);
164
- svg.appendChild(background);
196
+ svgContainer.appendChild(background);
165
197
  }
166
198
 
199
+ const svgWrapper = document.createElementNS(svgNS, 'svg');
167
200
  const title = document.createElementNS(svgNS, 'text');
168
201
  const titleFontStyle = (0, _fontStyle.mergeFontStyleWithDefault)(fontStyle && fontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_TITLE], _fontStyle.FONT_STYLE_VISUALIZATION_TITLE);
169
202
  title.setAttribute('x', getXFromTextAlign(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]));
@@ -183,7 +216,7 @@ const generateDVItem = (config, _ref3) => {
183
216
 
184
217
  if (config.title) {
185
218
  title.appendChild(document.createTextNode(config.title));
186
- svg.appendChild(title);
219
+ svgWrapper.appendChild(title);
187
220
  }
188
221
 
189
222
  const subtitleFontStyle = (0, _fontStyle.mergeFontStyleWithDefault)(fontStyle && fontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE], _fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE);
@@ -206,20 +239,24 @@ const generateDVItem = (config, _ref3) => {
206
239
 
207
240
  if (config.subtitle) {
208
241
  subtitle.appendChild(document.createTextNode(config.subtitle));
209
- svg.appendChild(subtitle);
242
+ svgWrapper.appendChild(subtitle);
210
243
  }
211
244
 
212
- svg.appendChild(generateValueSVG({
245
+ svgContainer.appendChild(svgWrapper);
246
+ svgContainer.appendChild(generateValueSVG({
213
247
  formattedValue: config.formattedValue,
214
248
  subText: config.subText,
215
249
  valueColor,
216
250
  noData,
217
- y: 20
251
+ icon,
252
+ containerWidth: width,
253
+ containerHeight: height
218
254
  }));
219
- return svg;
255
+ return svgContainer;
220
256
  };
221
257
 
222
- const shouldUseContrastColor = inputColor => {
258
+ const shouldUseContrastColor = function () {
259
+ let inputColor = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
223
260
  // based on https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color
224
261
  var color = inputColor.charAt(0) === '#' ? inputColor.substring(1, 7) : inputColor;
225
262
  var r = parseInt(color.substring(0, 2), 16); // hexToR
@@ -246,7 +283,8 @@ function _default(config, parentEl, _ref4) {
246
283
  legendSets,
247
284
  fontStyle,
248
285
  noData,
249
- legendOptions
286
+ legendOptions,
287
+ icon
250
288
  } = _ref4;
251
289
  const legendSet = legendOptions && legendSets[0];
252
290
  const legendColor = legendSet && (0, _legends.getColorByValueFromLegendSet)(legendSet, config.value);
@@ -264,26 +302,42 @@ function _default(config, parentEl, _ref4) {
264
302
  parentEl.style.overflow = 'hidden';
265
303
  parentEl.style.display = 'flex';
266
304
  parentEl.style.justifyContent = 'center';
305
+ const parentElBBox = parentEl.getBoundingClientRect();
306
+ const width = parentElBBox.width;
307
+ const height = parentElBBox.height;
308
+ const svgContainer = document.createElementNS(svgNS, 'svg');
309
+ svgContainer.setAttribute('xmlns', svgNS);
310
+ svgContainer.setAttribute('viewBox', "0 0 ".concat(width, " ").concat(height));
311
+ svgContainer.setAttribute('width', dashboard ? '100%' : width);
312
+ svgContainer.setAttribute('height', dashboard ? '100%' : height);
313
+ svgContainer.setAttribute('data-test', 'visualization-container');
267
314
 
268
315
  if (dashboard) {
269
316
  parentEl.style.borderRadius = _ui.spacers.dp8;
270
317
  return generateDashboardItem(config, {
318
+ svgContainer,
319
+ width,
320
+ height,
271
321
  valueColor,
272
322
  backgroundColor,
273
323
  noData,
274
- ...(shouldUseContrastColor(legendColor) ? {
324
+ icon,
325
+ ...(legendColor && shouldUseContrastColor(legendColor) ? {
275
326
  titleColor: _ui.colors.white
276
327
  } : {})
277
328
  });
278
329
  } else {
279
330
  parentEl.style.height = "100%";
280
331
  return generateDVItem(config, {
332
+ svgContainer,
333
+ width,
334
+ height,
281
335
  valueColor,
282
336
  backgroundColor,
283
337
  titleColor,
284
- parentEl,
285
- fontStyle,
286
- noData
338
+ noData,
339
+ icon,
340
+ fontStyle
287
341
  });
288
342
  }
289
343
  }