@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.
- package/CHANGELOG.md +19 -0
- package/build/cjs/__demo__/CalculationModal.stories.js +448 -0
- package/build/cjs/api/analytics/AnalyticsRequest.js +12 -1
- package/build/cjs/api/dimensions.js +1 -1
- package/build/cjs/api/expression.js +67 -0
- package/build/cjs/assets/DimensionItemIcons/CalculationIcon.js +25 -0
- package/build/cjs/assets/FormulaIcon.js +40 -0
- package/build/cjs/components/DataDimension/Calculation/CalculationModal.js +448 -0
- package/build/cjs/components/DataDimension/Calculation/DataElementOption.js +78 -0
- package/build/cjs/components/DataDimension/Calculation/DataElementSelector.js +309 -0
- package/build/cjs/components/DataDimension/Calculation/DndContext.js +213 -0
- package/build/cjs/components/DataDimension/Calculation/DragHandleIcon.js +23 -0
- package/build/cjs/components/DataDimension/Calculation/DraggingItem.js +58 -0
- package/build/cjs/components/DataDimension/Calculation/DropZone.js +58 -0
- package/build/cjs/components/DataDimension/Calculation/FormulaField.js +121 -0
- package/build/cjs/components/DataDimension/Calculation/FormulaItem.js +232 -0
- package/build/cjs/components/DataDimension/Calculation/MathOperatorSelector.js +58 -0
- package/build/cjs/components/DataDimension/Calculation/Operator.js +81 -0
- package/build/cjs/components/DataDimension/Calculation/styles/CalculationModal.style.js +13 -0
- package/build/cjs/components/DataDimension/Calculation/styles/DataElementOption.style.js +13 -0
- package/build/cjs/components/DataDimension/Calculation/styles/DataElementSelector.style.js +13 -0
- package/build/cjs/components/DataDimension/Calculation/styles/DraggingItem.style.js +13 -0
- package/build/cjs/components/DataDimension/Calculation/styles/DropZone.style.js +13 -0
- package/build/cjs/components/DataDimension/Calculation/styles/FormulaField.style.js +13 -0
- package/build/cjs/components/DataDimension/Calculation/styles/FormulaItem.style.js +13 -0
- package/build/cjs/components/DataDimension/Calculation/styles/MathOperatorSelector.style.js +13 -0
- package/build/cjs/components/DataDimension/Calculation/styles/Operator.style.js +13 -0
- package/build/cjs/components/DataDimension/DataDimension.js +22 -6
- package/build/cjs/components/DataDimension/DataTypeSelector.js +5 -3
- package/build/cjs/components/DataDimension/ItemSelector.js +111 -73
- package/build/cjs/components/LegendKey/LegendKey.js +1 -1
- package/build/cjs/components/TransferOption.js +13 -4
- package/build/cjs/components/styles/DimensionSelector.style.js +2 -2
- package/build/cjs/components/styles/TransferOption.style.js +2 -2
- package/build/cjs/index.js +6 -0
- package/build/cjs/locales/en/translations.json +32 -7
- package/build/cjs/modules/__tests__/expressions.spec.js +139 -0
- package/build/cjs/modules/__tests__/hash.spec.js +92 -0
- package/build/cjs/modules/__tests__/parseExpression.spec.js +46 -0
- package/build/cjs/modules/dataTypes.js +8 -1
- package/build/cjs/modules/dimensionListItem.js +82 -0
- package/build/cjs/modules/expressions.js +164 -0
- package/build/cjs/modules/hash.js +28 -0
- package/build/cjs/visualizations/config/generators/dhis/singleValue.js +112 -58
- package/build/es/__demo__/CalculationModal.stories.js +440 -0
- package/build/es/api/analytics/AnalyticsRequest.js +11 -1
- package/build/es/api/dimensions.js +1 -1
- package/build/es/api/expression.js +57 -0
- package/build/es/assets/DimensionItemIcons/CalculationIcon.js +13 -0
- package/build/es/assets/FormulaIcon.js +30 -0
- package/build/es/components/DataDimension/Calculation/CalculationModal.js +419 -0
- package/build/es/components/DataDimension/Calculation/DataElementOption.js +61 -0
- package/build/es/components/DataDimension/Calculation/DataElementSelector.js +283 -0
- package/build/es/components/DataDimension/Calculation/DndContext.js +194 -0
- package/build/es/components/DataDimension/Calculation/DragHandleIcon.js +11 -0
- package/build/es/components/DataDimension/Calculation/DraggingItem.js +40 -0
- package/build/es/components/DataDimension/Calculation/DropZone.js +43 -0
- package/build/es/components/DataDimension/Calculation/FormulaField.js +98 -0
- package/build/es/components/DataDimension/Calculation/FormulaItem.js +207 -0
- package/build/es/components/DataDimension/Calculation/MathOperatorSelector.js +42 -0
- package/build/es/components/DataDimension/Calculation/Operator.js +64 -0
- package/build/es/components/DataDimension/Calculation/styles/CalculationModal.style.js +4 -0
- package/build/es/components/DataDimension/Calculation/styles/DataElementOption.style.js +4 -0
- package/build/es/components/DataDimension/Calculation/styles/DataElementSelector.style.js +4 -0
- package/build/es/components/DataDimension/Calculation/styles/DraggingItem.style.js +4 -0
- package/build/es/components/DataDimension/Calculation/styles/DropZone.style.js +4 -0
- package/build/es/components/DataDimension/Calculation/styles/FormulaField.style.js +4 -0
- package/build/es/components/DataDimension/Calculation/styles/FormulaItem.style.js +4 -0
- package/build/es/components/DataDimension/Calculation/styles/MathOperatorSelector.style.js +4 -0
- package/build/es/components/DataDimension/Calculation/styles/Operator.style.js +4 -0
- package/build/es/components/DataDimension/DataDimension.js +21 -6
- package/build/es/components/DataDimension/DataTypeSelector.js +6 -4
- package/build/es/components/DataDimension/ItemSelector.js +111 -73
- package/build/es/components/LegendKey/LegendKey.js +1 -1
- package/build/es/components/TransferOption.js +14 -5
- package/build/es/components/styles/DimensionSelector.style.js +2 -2
- package/build/es/components/styles/TransferOption.style.js +2 -2
- package/build/es/index.js +1 -1
- package/build/es/locales/en/translations.json +32 -7
- package/build/es/modules/__tests__/expressions.spec.js +136 -0
- package/build/es/modules/__tests__/hash.spec.js +88 -0
- package/build/es/modules/__tests__/parseExpression.spec.js +43 -0
- package/build/es/modules/dataTypes.js +6 -0
- package/build/es/modules/dimensionListItem.js +61 -0
- package/build/es/modules/expressions.js +131 -0
- package/build/es/modules/hash.js +12 -0
- package/build/es/visualizations/config/generators/dhis/singleValue.js +112 -58
- 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
|
-
|
|
31
|
+
containerWidth,
|
|
32
|
+
containerHeight
|
|
23
33
|
} = _ref;
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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('
|
|
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('
|
|
65
|
-
subTextNode.setAttribute('
|
|
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
|
-
|
|
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;
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
+
svgWrapper.appendChild(subtitle);
|
|
210
243
|
}
|
|
211
244
|
|
|
212
|
-
|
|
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
|
-
|
|
251
|
+
icon,
|
|
252
|
+
containerWidth: width,
|
|
253
|
+
containerHeight: height
|
|
218
254
|
}));
|
|
219
|
-
return
|
|
255
|
+
return svgContainer;
|
|
220
256
|
};
|
|
221
257
|
|
|
222
|
-
const shouldUseContrastColor =
|
|
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
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
338
|
+
noData,
|
|
339
|
+
icon,
|
|
340
|
+
fontStyle
|
|
287
341
|
});
|
|
288
342
|
}
|
|
289
343
|
}
|