@dhis2/analytics 24.10.6 → 24.10.8
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 +14 -0
- package/build/cjs/components/Interpretations/InterpretationModal/InterpretationModal.js +7 -3
- package/build/cjs/visualizations/config/generators/dhis/singleValue.js +195 -96
- package/build/es/components/Interpretations/InterpretationModal/InterpretationModal.js +8 -4
- package/build/es/visualizations/config/generators/dhis/singleValue.js +196 -97
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [24.10.8](https://github.com/dhis2/analytics/compare/v24.10.7...v24.10.8) (2024-04-25)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* single value size and position issues (DHIS2-15344) (DHIS2-13077) [24.x] ([#1646](https://github.com/dhis2/analytics/issues/1646)) ([f052741](https://github.com/dhis2/analytics/commit/f052741ca0f5eafc93094cf53b9bf89f7c7244c3))
|
|
7
|
+
|
|
8
|
+
## [24.10.7](https://github.com/dhis2/analytics/compare/v24.10.6...v24.10.7) (2024-04-22)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **interpretations:** plugin flashes when interacting with Interpretations modal ([#1653](https://github.com/dhis2/analytics/issues/1653)) ([17d8e12](https://github.com/dhis2/analytics/commit/17d8e12da54def840b9f329ffe93b635342c2d2c))
|
|
14
|
+
|
|
1
15
|
## [24.10.6](https://github.com/dhis2/analytics/compare/v24.10.5...v24.10.6) (2024-04-19)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -133,6 +133,11 @@ const InterpretationModal = _ref2 => {
|
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
135
|
}, [interpretationId, refetch]);
|
|
136
|
+
const filters = (0, _react.useMemo)(() => {
|
|
137
|
+
return {
|
|
138
|
+
relativePeriodDate: interpretation === null || interpretation === void 0 ? void 0 : interpretation.created
|
|
139
|
+
};
|
|
140
|
+
}, [interpretation === null || interpretation === void 0 ? void 0 : interpretation.created]);
|
|
136
141
|
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, loadingInProgress && /*#__PURE__*/_react.default.createElement(_ui.Layer, null, /*#__PURE__*/_react.default.createElement(_ui.CenteredContent, null, /*#__PURE__*/_react.default.createElement(_ui.CircularLoader, null))), /*#__PURE__*/_react.default.createElement(_ui.Modal, {
|
|
137
142
|
fluid: true,
|
|
138
143
|
onClose: handleClose,
|
|
@@ -159,12 +164,11 @@ const InterpretationModal = _ref2 => {
|
|
|
159
164
|
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
160
165
|
className: _style.default.dynamic([["3175860552", [_ui.colors.grey900, _ui.spacers.dp24, _ui.spacers.dp4, _ui.spacers.dp4]]]) + " " + "visualisation-wrap"
|
|
161
166
|
}, /*#__PURE__*/_react.default.createElement(VisualizationPlugin, {
|
|
162
|
-
filters:
|
|
163
|
-
relativePeriodDate: interpretation.created
|
|
164
|
-
},
|
|
167
|
+
filters: filters,
|
|
165
168
|
visualization: visualization,
|
|
166
169
|
onResponsesReceived: onResponsesReceived,
|
|
167
170
|
displayProperty: (_currentUser$settings = currentUser.settings) === null || _currentUser$settings === void 0 ? void 0 : _currentUser$settings.keyAnalysisDisplayProperty,
|
|
171
|
+
isInModal: true,
|
|
168
172
|
className: _style.default.dynamic([["3175860552", [_ui.colors.grey900, _ui.spacers.dp24, _ui.spacers.dp4, _ui.spacers.dp4]]])
|
|
169
173
|
})), /*#__PURE__*/_react.default.createElement("div", {
|
|
170
174
|
className: _style.default.dynamic([["3175860552", [_ui.colors.grey900, _ui.spacers.dp24, _ui.spacers.dp4, _ui.spacers.dp4]]]) + " " + "thread-wrap"
|
|
@@ -11,62 +11,133 @@ 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'; // multiply text width with this factor
|
|
15
|
+
// to get very close to actual text width
|
|
16
|
+
// nb: dependent on viewbox etc
|
|
17
|
+
|
|
18
|
+
const ACTUAL_TEXT_WIDTH_FACTOR = 0.9; // multiply value text size with this factor
|
|
19
|
+
// to get very close to the actual number height
|
|
20
|
+
// as numbers don't go below the baseline like e.g. "j" and "g"
|
|
21
|
+
|
|
22
|
+
const ACTUAL_NUMBER_HEIGHT_FACTOR = 0.67; // do not allow text width to exceed this threshold
|
|
23
|
+
// a threshold >1 does not really make sense but text width vs viewbox is complicated
|
|
24
|
+
|
|
25
|
+
const TEXT_WIDTH_CONTAINER_WIDTH_FACTOR = 1.3; // do not allow text size to exceed this
|
|
26
|
+
|
|
27
|
+
const TEXT_SIZE_CONTAINER_HEIGHT_FACTOR = 0.6;
|
|
28
|
+
const TEXT_SIZE_MAX_THRESHOLD = 400; // multiply text size with this factor
|
|
29
|
+
// to get an appropriate letter spacing
|
|
30
|
+
|
|
31
|
+
const LETTER_SPACING_TEXT_SIZE_FACTOR = 1 / 35 * -1;
|
|
32
|
+
const LETTER_SPACING_MIN_THRESHOLD = -6;
|
|
33
|
+
const LETTER_SPACING_MAX_THRESHOLD = -1; // fixed top margin above title/subtitle
|
|
34
|
+
|
|
35
|
+
const TOP_MARGIN_FIXED = 16; // multiply text size with this factor
|
|
36
|
+
// to get an appropriate sub text size
|
|
37
|
+
|
|
38
|
+
const SUB_TEXT_SIZE_FACTOR = 0.5;
|
|
39
|
+
const SUB_TEXT_SIZE_MIN_THRESHOLD = 26;
|
|
40
|
+
const SUB_TEXT_SIZE_MAX_THRESHOLD = 40; // multiply text size with this factor
|
|
41
|
+
// to get an appropriate icon padding
|
|
42
|
+
|
|
43
|
+
const ICON_PADDING_FACTOR = 0.3; // Compute text width before rendering
|
|
44
|
+
// Not exactly precise but close enough
|
|
45
|
+
|
|
46
|
+
const getTextWidth = (text, font) => {
|
|
47
|
+
const canvas = document.createElement('canvas');
|
|
48
|
+
const context = canvas.getContext('2d');
|
|
49
|
+
context.font = font;
|
|
50
|
+
return Math.round(context.measureText(text).width * ACTUAL_TEXT_WIDTH_FACTOR);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const getTextHeightForNumbers = textSize => textSize * ACTUAL_NUMBER_HEIGHT_FACTOR;
|
|
54
|
+
|
|
55
|
+
const getIconPadding = textSize => Math.round(textSize * ICON_PADDING_FACTOR);
|
|
56
|
+
|
|
57
|
+
const getTextSize = (formattedValue, containerWidth, containerHeight, showIcon) => {
|
|
58
|
+
let size = Math.min(Math.round(containerHeight * TEXT_SIZE_CONTAINER_HEIGHT_FACTOR), TEXT_SIZE_MAX_THRESHOLD);
|
|
59
|
+
const widthThreshold = Math.round(containerWidth * TEXT_WIDTH_CONTAINER_WIDTH_FACTOR);
|
|
60
|
+
const textWidth = getTextWidth(formattedValue, "".concat(size, "px Roboto")) + (showIcon ? getIconPadding(size) : 0);
|
|
61
|
+
|
|
62
|
+
if (textWidth > widthThreshold) {
|
|
63
|
+
size = Math.round(size * (widthThreshold / textWidth));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return size;
|
|
67
|
+
};
|
|
15
68
|
|
|
16
69
|
const generateValueSVG = _ref => {
|
|
17
70
|
let {
|
|
18
71
|
formattedValue,
|
|
19
72
|
subText,
|
|
20
73
|
valueColor,
|
|
74
|
+
textColor,
|
|
75
|
+
icon,
|
|
21
76
|
noData,
|
|
22
|
-
|
|
77
|
+
containerWidth,
|
|
78
|
+
containerHeight,
|
|
79
|
+
topMargin = 0
|
|
23
80
|
} = _ref;
|
|
24
|
-
|
|
81
|
+
console.log('jj SV generateValueSVG', formattedValue, icon, subText);
|
|
82
|
+
const showIcon = icon && formattedValue !== noData.text;
|
|
83
|
+
const textSize = getTextSize(formattedValue, containerWidth, containerHeight, showIcon);
|
|
84
|
+
const textWidth = getTextWidth(formattedValue, "".concat(textSize, "px Roboto"));
|
|
85
|
+
const iconSize = textSize;
|
|
86
|
+
const subTextSize = textSize * SUB_TEXT_SIZE_FACTOR > SUB_TEXT_SIZE_MAX_THRESHOLD ? SUB_TEXT_SIZE_MAX_THRESHOLD : textSize * SUB_TEXT_SIZE_FACTOR < SUB_TEXT_SIZE_MIN_THRESHOLD ? SUB_TEXT_SIZE_MIN_THRESHOLD : textSize * SUB_TEXT_SIZE_FACTOR;
|
|
25
87
|
const svgValue = document.createElementNS(svgNS, 'svg');
|
|
26
|
-
svgValue.setAttribute('
|
|
27
|
-
svgValue.setAttribute('
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
88
|
+
svgValue.setAttribute('viewBox', "0 0 ".concat(containerWidth, " ").concat(containerHeight));
|
|
89
|
+
svgValue.setAttribute('width', '50%');
|
|
90
|
+
svgValue.setAttribute('height', '50%');
|
|
91
|
+
svgValue.setAttribute('x', '50%');
|
|
92
|
+
svgValue.setAttribute('y', '50%');
|
|
93
|
+
svgValue.setAttribute('style', 'overflow: visible');
|
|
33
94
|
let fillColor = _ui.colors.grey900;
|
|
34
95
|
|
|
35
96
|
if (valueColor) {
|
|
36
97
|
fillColor = valueColor;
|
|
37
98
|
} else if (formattedValue === noData.text) {
|
|
38
99
|
fillColor = _ui.colors.grey600;
|
|
100
|
+
} // show icon if configured in maintenance app
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if (showIcon) {
|
|
104
|
+
// embed icon to allow changing color
|
|
105
|
+
// (elements with fill need to use "currentColor" for this to work)
|
|
106
|
+
const iconSvgNode = document.createElementNS(svgNS, 'svg');
|
|
107
|
+
iconSvgNode.setAttribute('viewBox', '0 0 48 48');
|
|
108
|
+
iconSvgNode.setAttribute('width', iconSize);
|
|
109
|
+
iconSvgNode.setAttribute('height', iconSize);
|
|
110
|
+
iconSvgNode.setAttribute('y', (iconSize / 2 - topMargin / 2) * -1);
|
|
111
|
+
iconSvgNode.setAttribute('x', "-".concat((iconSize + getIconPadding(textSize) + textWidth) / 2));
|
|
112
|
+
iconSvgNode.setAttribute('style', "color: ".concat(fillColor));
|
|
113
|
+
const parser = new DOMParser();
|
|
114
|
+
const svgIconDocument = parser.parseFromString(icon, 'image/svg+xml');
|
|
115
|
+
Array.from(svgIconDocument.documentElement.children).forEach(node => iconSvgNode.appendChild(node));
|
|
116
|
+
svgValue.appendChild(iconSvgNode);
|
|
39
117
|
}
|
|
40
118
|
|
|
119
|
+
const letterSpacing = Math.round(textSize * LETTER_SPACING_TEXT_SIZE_FACTOR);
|
|
41
120
|
const textNode = document.createElementNS(svgNS, 'text');
|
|
42
|
-
textNode.setAttribute('text-anchor', 'middle');
|
|
43
121
|
textNode.setAttribute('font-size', textSize);
|
|
44
122
|
textNode.setAttribute('font-weight', '300');
|
|
45
|
-
textNode.setAttribute('letter-spacing',
|
|
46
|
-
textNode.setAttribute('
|
|
123
|
+
textNode.setAttribute('letter-spacing', letterSpacing < LETTER_SPACING_MIN_THRESHOLD ? LETTER_SPACING_MIN_THRESHOLD : letterSpacing > LETTER_SPACING_MAX_THRESHOLD ? LETTER_SPACING_MAX_THRESHOLD : letterSpacing);
|
|
124
|
+
textNode.setAttribute('text-anchor', 'middle');
|
|
125
|
+
textNode.setAttribute('x', showIcon ? "".concat((iconSize + getIconPadding(textSize)) / 2) : 0);
|
|
126
|
+
textNode.setAttribute('y', topMargin / 2 + getTextHeightForNumbers(textSize) / 2);
|
|
47
127
|
textNode.setAttribute('fill', fillColor);
|
|
48
128
|
textNode.setAttribute('data-test', 'visualization-primary-value');
|
|
49
129
|
textNode.appendChild(document.createTextNode(formattedValue));
|
|
50
130
|
svgValue.appendChild(textNode);
|
|
51
131
|
|
|
52
132
|
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
133
|
const subTextNode = document.createElementNS(svgNS, 'text');
|
|
62
134
|
subTextNode.setAttribute('text-anchor', 'middle');
|
|
63
135
|
subTextNode.setAttribute('font-size', subTextSize);
|
|
64
|
-
subTextNode.setAttribute('
|
|
65
|
-
subTextNode.setAttribute('
|
|
66
|
-
subTextNode.setAttribute('fill',
|
|
136
|
+
subTextNode.setAttribute('y', iconSize / 2 + topMargin / 2);
|
|
137
|
+
subTextNode.setAttribute('dy', subTextSize * 1.7);
|
|
138
|
+
subTextNode.setAttribute('fill', textColor);
|
|
67
139
|
subTextNode.appendChild(document.createTextNode(subText));
|
|
68
|
-
|
|
69
|
-
svgValue.appendChild(svgSubText);
|
|
140
|
+
svgValue.appendChild(subTextNode);
|
|
70
141
|
}
|
|
71
142
|
|
|
72
143
|
return svgValue;
|
|
@@ -74,14 +145,28 @@ const generateValueSVG = _ref => {
|
|
|
74
145
|
|
|
75
146
|
const generateDashboardItem = (config, _ref2) => {
|
|
76
147
|
let {
|
|
148
|
+
svgContainer,
|
|
149
|
+
width,
|
|
150
|
+
height,
|
|
77
151
|
valueColor,
|
|
78
152
|
titleColor,
|
|
79
153
|
backgroundColor,
|
|
80
|
-
noData
|
|
154
|
+
noData,
|
|
155
|
+
icon
|
|
81
156
|
} = _ref2;
|
|
157
|
+
svgContainer.appendChild(generateValueSVG({
|
|
158
|
+
formattedValue: config.formattedValue,
|
|
159
|
+
subText: config.subText,
|
|
160
|
+
valueColor,
|
|
161
|
+
textColor: titleColor,
|
|
162
|
+
noData,
|
|
163
|
+
icon,
|
|
164
|
+
containerWidth: width,
|
|
165
|
+
containerHeight: height
|
|
166
|
+
}));
|
|
82
167
|
const container = document.createElement('div');
|
|
83
|
-
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', ";");
|
|
168
|
+
container.setAttribute('style', "display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; padding-top: 8px; ".concat(backgroundColor ? "background-color:".concat(backgroundColor, ";") : ''));
|
|
169
|
+
const titleStyle = "padding: 0 8px; text-align: center; font-size: 12px; color: ".concat(titleColor || '#666', ";");
|
|
85
170
|
const title = document.createElement('span');
|
|
86
171
|
title.setAttribute('style', titleStyle);
|
|
87
172
|
|
|
@@ -92,18 +177,12 @@ const generateDashboardItem = (config, _ref2) => {
|
|
|
92
177
|
|
|
93
178
|
if (config.subtitle) {
|
|
94
179
|
const subtitle = document.createElement('span');
|
|
95
|
-
subtitle.setAttribute('style', titleStyle + ' margin-top: 4px;
|
|
180
|
+
subtitle.setAttribute('style', titleStyle + ' margin-top: 4px;');
|
|
96
181
|
subtitle.appendChild(document.createTextNode(config.subtitle));
|
|
97
182
|
container.appendChild(subtitle);
|
|
98
183
|
}
|
|
99
184
|
|
|
100
|
-
container.appendChild(
|
|
101
|
-
formattedValue: config.formattedValue,
|
|
102
|
-
subText: config.subText,
|
|
103
|
-
valueColor,
|
|
104
|
-
noData,
|
|
105
|
-
y: 40
|
|
106
|
-
}));
|
|
185
|
+
container.appendChild(svgContainer);
|
|
107
186
|
return container;
|
|
108
187
|
};
|
|
109
188
|
|
|
@@ -137,89 +216,92 @@ const getXFromTextAlign = textAlign => {
|
|
|
137
216
|
|
|
138
217
|
const generateDVItem = (config, _ref3) => {
|
|
139
218
|
let {
|
|
219
|
+
svgContainer,
|
|
220
|
+
width,
|
|
221
|
+
height,
|
|
140
222
|
valueColor,
|
|
223
|
+
noData,
|
|
141
224
|
backgroundColor,
|
|
142
225
|
titleColor,
|
|
143
|
-
parentEl,
|
|
144
226
|
fontStyle,
|
|
145
|
-
|
|
227
|
+
icon
|
|
146
228
|
} = _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
229
|
|
|
158
230
|
if (backgroundColor) {
|
|
159
|
-
|
|
231
|
+
svgContainer.setAttribute('style', "background-color: ".concat(backgroundColor, ";"));
|
|
160
232
|
const background = document.createElementNS(svgNS, 'rect');
|
|
161
233
|
background.setAttribute('width', '100%');
|
|
162
234
|
background.setAttribute('height', '100%');
|
|
163
235
|
background.setAttribute('fill', backgroundColor);
|
|
164
|
-
|
|
236
|
+
svgContainer.appendChild(background);
|
|
165
237
|
}
|
|
166
238
|
|
|
239
|
+
const svgWrapper = document.createElementNS(svgNS, 'svg'); // title
|
|
240
|
+
|
|
167
241
|
const title = document.createElementNS(svgNS, 'text');
|
|
168
242
|
const titleFontStyle = (0, _fontStyle.mergeFontStyleWithDefault)(fontStyle && fontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_TITLE], _fontStyle.FONT_STYLE_VISUALIZATION_TITLE);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
title
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
243
|
+
const titleYPosition = TOP_MARGIN_FIXED + parseInt(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE]) + 'px';
|
|
244
|
+
const titleAttributes = {
|
|
245
|
+
x: getXFromTextAlign(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]),
|
|
246
|
+
y: titleYPosition,
|
|
247
|
+
'text-anchor': getTextAnchorFromTextAlign(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]),
|
|
248
|
+
'font-size': "".concat(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE], "px"),
|
|
249
|
+
'font-weight': titleFontStyle[_fontStyle.FONT_STYLE_OPTION_BOLD] ? _fontStyle.FONT_STYLE_OPTION_BOLD : 'normal',
|
|
250
|
+
'font-style': titleFontStyle[_fontStyle.FONT_STYLE_OPTION_ITALIC] ? _fontStyle.FONT_STYLE_OPTION_ITALIC : 'normal',
|
|
251
|
+
'data-test': 'visualization-title',
|
|
252
|
+
fill: titleColor && titleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR] === _fontStyle.defaultFontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_TITLE][_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR] ? titleColor : titleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR]
|
|
253
|
+
};
|
|
254
|
+
Object.entries(titleAttributes).forEach(_ref4 => {
|
|
255
|
+
let [key, value] = _ref4;
|
|
256
|
+
return title.setAttribute(key, value);
|
|
257
|
+
});
|
|
183
258
|
|
|
184
259
|
if (config.title) {
|
|
185
260
|
title.appendChild(document.createTextNode(config.title));
|
|
186
|
-
|
|
187
|
-
}
|
|
261
|
+
svgWrapper.appendChild(title);
|
|
262
|
+
} // subtitle
|
|
188
263
|
|
|
189
|
-
const subtitleFontStyle = (0, _fontStyle.mergeFontStyleWithDefault)(fontStyle && fontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE], _fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE);
|
|
190
|
-
const subtitle = document.createElementNS(svgNS, 'text');
|
|
191
|
-
subtitle.setAttribute('x', getXFromTextAlign(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]));
|
|
192
|
-
subtitle.setAttribute('y', 28);
|
|
193
|
-
subtitle.setAttribute('dy', 22);
|
|
194
|
-
subtitle.setAttribute('text-anchor', getTextAnchorFromTextAlign(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]));
|
|
195
|
-
subtitle.setAttribute('font-size', "".concat(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE], "px"));
|
|
196
|
-
subtitle.setAttribute('font-weight', subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_BOLD] ? _fontStyle.FONT_STYLE_OPTION_BOLD : 'normal');
|
|
197
|
-
subtitle.setAttribute('font-style', subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_ITALIC] ? _fontStyle.FONT_STYLE_OPTION_ITALIC : 'normal');
|
|
198
|
-
|
|
199
|
-
if (titleColor && subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR] === _fontStyle.defaultFontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE][_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR]) {
|
|
200
|
-
subtitle.setAttribute('fill', titleColor);
|
|
201
|
-
} else {
|
|
202
|
-
subtitle.setAttribute('fill', subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR]);
|
|
203
|
-
}
|
|
204
264
|
|
|
205
|
-
subtitle.
|
|
265
|
+
const subtitle = document.createElementNS(svgNS, 'text');
|
|
266
|
+
const subtitleFontStyle = (0, _fontStyle.mergeFontStyleWithDefault)(fontStyle && fontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE], _fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE);
|
|
267
|
+
const subtitleAttributes = {
|
|
268
|
+
x: getXFromTextAlign(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]),
|
|
269
|
+
y: titleYPosition,
|
|
270
|
+
dy: "".concat(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE] + 10),
|
|
271
|
+
'text-anchor': getTextAnchorFromTextAlign(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]),
|
|
272
|
+
'font-size': "".concat(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE], "px"),
|
|
273
|
+
'font-weight': subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_BOLD] ? _fontStyle.FONT_STYLE_OPTION_BOLD : 'normal',
|
|
274
|
+
'font-style': subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_ITALIC] ? _fontStyle.FONT_STYLE_OPTION_ITALIC : 'normal',
|
|
275
|
+
fill: titleColor && subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR] === _fontStyle.defaultFontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE][_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR] ? titleColor : subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR],
|
|
276
|
+
'data-test': 'visualization-subtitle'
|
|
277
|
+
};
|
|
278
|
+
Object.entries(subtitleAttributes).forEach(_ref5 => {
|
|
279
|
+
let [key, value] = _ref5;
|
|
280
|
+
return subtitle.setAttribute(key, value);
|
|
281
|
+
});
|
|
206
282
|
|
|
207
283
|
if (config.subtitle) {
|
|
208
284
|
subtitle.appendChild(document.createTextNode(config.subtitle));
|
|
209
|
-
|
|
285
|
+
svgWrapper.appendChild(subtitle);
|
|
210
286
|
}
|
|
211
287
|
|
|
212
|
-
|
|
288
|
+
svgContainer.appendChild(svgWrapper);
|
|
289
|
+
svgContainer.appendChild(generateValueSVG({
|
|
213
290
|
formattedValue: config.formattedValue,
|
|
214
291
|
subText: config.subText,
|
|
215
292
|
valueColor,
|
|
293
|
+
textColor: titleColor,
|
|
216
294
|
noData,
|
|
217
|
-
|
|
295
|
+
icon,
|
|
296
|
+
containerWidth: width,
|
|
297
|
+
containerHeight: height,
|
|
298
|
+
topMargin: TOP_MARGIN_FIXED + ((config.title ? parseInt(title.getAttribute('font-size')) : 0) + (config.subtitle ? parseInt(subtitle.getAttribute('font-size')) : 0)) * 2.5
|
|
218
299
|
}));
|
|
219
|
-
return
|
|
300
|
+
return svgContainer;
|
|
220
301
|
};
|
|
221
302
|
|
|
222
|
-
const shouldUseContrastColor =
|
|
303
|
+
const shouldUseContrastColor = function () {
|
|
304
|
+
let inputColor = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
223
305
|
// based on https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color
|
|
224
306
|
var color = inputColor.charAt(0) === '#' ? inputColor.substring(1, 7) : inputColor;
|
|
225
307
|
var r = parseInt(color.substring(0, 2), 16); // hexToR
|
|
@@ -240,14 +322,15 @@ const shouldUseContrastColor = inputColor => {
|
|
|
240
322
|
return L <= 0.179;
|
|
241
323
|
};
|
|
242
324
|
|
|
243
|
-
function _default(config, parentEl,
|
|
325
|
+
function _default(config, parentEl, _ref6) {
|
|
244
326
|
let {
|
|
245
327
|
dashboard,
|
|
246
328
|
legendSets,
|
|
247
329
|
fontStyle,
|
|
248
330
|
noData,
|
|
249
|
-
legendOptions
|
|
250
|
-
|
|
331
|
+
legendOptions,
|
|
332
|
+
icon
|
|
333
|
+
} = _ref6;
|
|
251
334
|
const legendSet = legendOptions && legendSets[0];
|
|
252
335
|
const legendColor = legendSet && (0, _legends.getColorByValueFromLegendSet)(legendSet, config.value);
|
|
253
336
|
let valueColor, titleColor, backgroundColor;
|
|
@@ -264,26 +347,42 @@ function _default(config, parentEl, _ref4) {
|
|
|
264
347
|
parentEl.style.overflow = 'hidden';
|
|
265
348
|
parentEl.style.display = 'flex';
|
|
266
349
|
parentEl.style.justifyContent = 'center';
|
|
350
|
+
const parentElBBox = parentEl.getBoundingClientRect();
|
|
351
|
+
const width = parentElBBox.width;
|
|
352
|
+
const height = parentElBBox.height;
|
|
353
|
+
const svgContainer = document.createElementNS(svgNS, 'svg');
|
|
354
|
+
svgContainer.setAttribute('xmlns', svgNS);
|
|
355
|
+
svgContainer.setAttribute('viewBox', "0 0 ".concat(width, " ").concat(height));
|
|
356
|
+
svgContainer.setAttribute('width', dashboard ? '100%' : width);
|
|
357
|
+
svgContainer.setAttribute('height', dashboard ? '100%' : height);
|
|
358
|
+
svgContainer.setAttribute('data-test', 'visualization-container');
|
|
267
359
|
|
|
268
360
|
if (dashboard) {
|
|
269
|
-
parentEl.style.borderRadius =
|
|
361
|
+
parentEl.style.borderRadius = '3px';
|
|
270
362
|
return generateDashboardItem(config, {
|
|
363
|
+
svgContainer,
|
|
364
|
+
width,
|
|
365
|
+
height,
|
|
271
366
|
valueColor,
|
|
272
367
|
backgroundColor,
|
|
273
368
|
noData,
|
|
274
|
-
|
|
369
|
+
icon,
|
|
370
|
+
...(legendOptions.style === _legends.LEGEND_DISPLAY_STYLE_FILL && legendColor && shouldUseContrastColor(legendColor) ? {
|
|
275
371
|
titleColor: _ui.colors.white
|
|
276
372
|
} : {})
|
|
277
373
|
});
|
|
278
374
|
} else {
|
|
279
375
|
parentEl.style.height = "100%";
|
|
280
376
|
return generateDVItem(config, {
|
|
377
|
+
svgContainer,
|
|
378
|
+
width,
|
|
379
|
+
height,
|
|
281
380
|
valueColor,
|
|
282
381
|
backgroundColor,
|
|
283
382
|
titleColor,
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
383
|
+
noData,
|
|
384
|
+
icon,
|
|
385
|
+
fontStyle
|
|
287
386
|
});
|
|
288
387
|
}
|
|
289
388
|
}
|
|
@@ -4,7 +4,7 @@ import i18n from '@dhis2/d2-i18n';
|
|
|
4
4
|
import { Modal, ModalActions, ModalContent, NoticeBox, Button, spacers, colors, Layer, CenteredContent, CircularLoader } from '@dhis2/ui';
|
|
5
5
|
import cx from 'classnames';
|
|
6
6
|
import PropTypes from 'prop-types';
|
|
7
|
-
import React, { useEffect, useState } from 'react';
|
|
7
|
+
import React, { useEffect, useState, useMemo } from 'react';
|
|
8
8
|
import { InterpretationThread } from './InterpretationThread.js';
|
|
9
9
|
import { useModalContentWidth } from './useModalContentWidth.js';
|
|
10
10
|
const modalCSS = {
|
|
@@ -111,6 +111,11 @@ const InterpretationModal = _ref2 => {
|
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
}, [interpretationId, refetch]);
|
|
114
|
+
const filters = useMemo(() => {
|
|
115
|
+
return {
|
|
116
|
+
relativePeriodDate: interpretation === null || interpretation === void 0 ? void 0 : interpretation.created
|
|
117
|
+
};
|
|
118
|
+
}, [interpretation === null || interpretation === void 0 ? void 0 : interpretation.created]);
|
|
114
119
|
return /*#__PURE__*/React.createElement(React.Fragment, null, loadingInProgress && /*#__PURE__*/React.createElement(Layer, null, /*#__PURE__*/React.createElement(CenteredContent, null, /*#__PURE__*/React.createElement(CircularLoader, null))), /*#__PURE__*/React.createElement(Modal, {
|
|
115
120
|
fluid: true,
|
|
116
121
|
onClose: handleClose,
|
|
@@ -137,12 +142,11 @@ const InterpretationModal = _ref2 => {
|
|
|
137
142
|
}, /*#__PURE__*/React.createElement("div", {
|
|
138
143
|
className: _JSXStyle.dynamic([["3175860552", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "visualisation-wrap"
|
|
139
144
|
}, /*#__PURE__*/React.createElement(VisualizationPlugin, {
|
|
140
|
-
filters:
|
|
141
|
-
relativePeriodDate: interpretation.created
|
|
142
|
-
},
|
|
145
|
+
filters: filters,
|
|
143
146
|
visualization: visualization,
|
|
144
147
|
onResponsesReceived: onResponsesReceived,
|
|
145
148
|
displayProperty: (_currentUser$settings = currentUser.settings) === null || _currentUser$settings === void 0 ? void 0 : _currentUser$settings.keyAnalysisDisplayProperty,
|
|
149
|
+
isInModal: true,
|
|
146
150
|
className: _JSXStyle.dynamic([["3175860552", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]])
|
|
147
151
|
})), /*#__PURE__*/React.createElement("div", {
|
|
148
152
|
className: _JSXStyle.dynamic([["3175860552", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "thread-wrap"
|
|
@@ -1,62 +1,133 @@
|
|
|
1
|
-
import { colors
|
|
1
|
+
import { colors } from '@dhis2/ui';
|
|
2
2
|
import { FONT_STYLE_VISUALIZATION_TITLE, FONT_STYLE_VISUALIZATION_SUBTITLE, FONT_STYLE_OPTION_FONT_SIZE, FONT_STYLE_OPTION_TEXT_COLOR, FONT_STYLE_OPTION_TEXT_ALIGN, FONT_STYLE_OPTION_ITALIC, FONT_STYLE_OPTION_BOLD, TEXT_ALIGN_LEFT, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER, mergeFontStyleWithDefault, defaultFontStyle } from '../../../../modules/fontStyle.js';
|
|
3
3
|
import { getColorByValueFromLegendSet, LEGEND_DISPLAY_STYLE_FILL } from '../../../../modules/legends.js';
|
|
4
|
-
const svgNS = 'http://www.w3.org/2000/svg';
|
|
4
|
+
const svgNS = 'http://www.w3.org/2000/svg'; // multiply text width with this factor
|
|
5
|
+
// to get very close to actual text width
|
|
6
|
+
// nb: dependent on viewbox etc
|
|
7
|
+
|
|
8
|
+
const ACTUAL_TEXT_WIDTH_FACTOR = 0.9; // multiply value text size with this factor
|
|
9
|
+
// to get very close to the actual number height
|
|
10
|
+
// as numbers don't go below the baseline like e.g. "j" and "g"
|
|
11
|
+
|
|
12
|
+
const ACTUAL_NUMBER_HEIGHT_FACTOR = 0.67; // do not allow text width to exceed this threshold
|
|
13
|
+
// a threshold >1 does not really make sense but text width vs viewbox is complicated
|
|
14
|
+
|
|
15
|
+
const TEXT_WIDTH_CONTAINER_WIDTH_FACTOR = 1.3; // do not allow text size to exceed this
|
|
16
|
+
|
|
17
|
+
const TEXT_SIZE_CONTAINER_HEIGHT_FACTOR = 0.6;
|
|
18
|
+
const TEXT_SIZE_MAX_THRESHOLD = 400; // multiply text size with this factor
|
|
19
|
+
// to get an appropriate letter spacing
|
|
20
|
+
|
|
21
|
+
const LETTER_SPACING_TEXT_SIZE_FACTOR = 1 / 35 * -1;
|
|
22
|
+
const LETTER_SPACING_MIN_THRESHOLD = -6;
|
|
23
|
+
const LETTER_SPACING_MAX_THRESHOLD = -1; // fixed top margin above title/subtitle
|
|
24
|
+
|
|
25
|
+
const TOP_MARGIN_FIXED = 16; // multiply text size with this factor
|
|
26
|
+
// to get an appropriate sub text size
|
|
27
|
+
|
|
28
|
+
const SUB_TEXT_SIZE_FACTOR = 0.5;
|
|
29
|
+
const SUB_TEXT_SIZE_MIN_THRESHOLD = 26;
|
|
30
|
+
const SUB_TEXT_SIZE_MAX_THRESHOLD = 40; // multiply text size with this factor
|
|
31
|
+
// to get an appropriate icon padding
|
|
32
|
+
|
|
33
|
+
const ICON_PADDING_FACTOR = 0.3; // Compute text width before rendering
|
|
34
|
+
// Not exactly precise but close enough
|
|
35
|
+
|
|
36
|
+
const getTextWidth = (text, font) => {
|
|
37
|
+
const canvas = document.createElement('canvas');
|
|
38
|
+
const context = canvas.getContext('2d');
|
|
39
|
+
context.font = font;
|
|
40
|
+
return Math.round(context.measureText(text).width * ACTUAL_TEXT_WIDTH_FACTOR);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getTextHeightForNumbers = textSize => textSize * ACTUAL_NUMBER_HEIGHT_FACTOR;
|
|
44
|
+
|
|
45
|
+
const getIconPadding = textSize => Math.round(textSize * ICON_PADDING_FACTOR);
|
|
46
|
+
|
|
47
|
+
const getTextSize = (formattedValue, containerWidth, containerHeight, showIcon) => {
|
|
48
|
+
let size = Math.min(Math.round(containerHeight * TEXT_SIZE_CONTAINER_HEIGHT_FACTOR), TEXT_SIZE_MAX_THRESHOLD);
|
|
49
|
+
const widthThreshold = Math.round(containerWidth * TEXT_WIDTH_CONTAINER_WIDTH_FACTOR);
|
|
50
|
+
const textWidth = getTextWidth(formattedValue, "".concat(size, "px Roboto")) + (showIcon ? getIconPadding(size) : 0);
|
|
51
|
+
|
|
52
|
+
if (textWidth > widthThreshold) {
|
|
53
|
+
size = Math.round(size * (widthThreshold / textWidth));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return size;
|
|
57
|
+
};
|
|
5
58
|
|
|
6
59
|
const generateValueSVG = _ref => {
|
|
7
60
|
let {
|
|
8
61
|
formattedValue,
|
|
9
62
|
subText,
|
|
10
63
|
valueColor,
|
|
64
|
+
textColor,
|
|
65
|
+
icon,
|
|
11
66
|
noData,
|
|
12
|
-
|
|
67
|
+
containerWidth,
|
|
68
|
+
containerHeight,
|
|
69
|
+
topMargin = 0
|
|
13
70
|
} = _ref;
|
|
14
|
-
|
|
71
|
+
console.log('jj SV generateValueSVG', formattedValue, icon, subText);
|
|
72
|
+
const showIcon = icon && formattedValue !== noData.text;
|
|
73
|
+
const textSize = getTextSize(formattedValue, containerWidth, containerHeight, showIcon);
|
|
74
|
+
const textWidth = getTextWidth(formattedValue, "".concat(textSize, "px Roboto"));
|
|
75
|
+
const iconSize = textSize;
|
|
76
|
+
const subTextSize = textSize * SUB_TEXT_SIZE_FACTOR > SUB_TEXT_SIZE_MAX_THRESHOLD ? SUB_TEXT_SIZE_MAX_THRESHOLD : textSize * SUB_TEXT_SIZE_FACTOR < SUB_TEXT_SIZE_MIN_THRESHOLD ? SUB_TEXT_SIZE_MIN_THRESHOLD : textSize * SUB_TEXT_SIZE_FACTOR;
|
|
15
77
|
const svgValue = document.createElementNS(svgNS, 'svg');
|
|
16
|
-
svgValue.setAttribute('
|
|
17
|
-
svgValue.setAttribute('
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
78
|
+
svgValue.setAttribute('viewBox', "0 0 ".concat(containerWidth, " ").concat(containerHeight));
|
|
79
|
+
svgValue.setAttribute('width', '50%');
|
|
80
|
+
svgValue.setAttribute('height', '50%');
|
|
81
|
+
svgValue.setAttribute('x', '50%');
|
|
82
|
+
svgValue.setAttribute('y', '50%');
|
|
83
|
+
svgValue.setAttribute('style', 'overflow: visible');
|
|
23
84
|
let fillColor = colors.grey900;
|
|
24
85
|
|
|
25
86
|
if (valueColor) {
|
|
26
87
|
fillColor = valueColor;
|
|
27
88
|
} else if (formattedValue === noData.text) {
|
|
28
89
|
fillColor = colors.grey600;
|
|
90
|
+
} // show icon if configured in maintenance app
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if (showIcon) {
|
|
94
|
+
// embed icon to allow changing color
|
|
95
|
+
// (elements with fill need to use "currentColor" for this to work)
|
|
96
|
+
const iconSvgNode = document.createElementNS(svgNS, 'svg');
|
|
97
|
+
iconSvgNode.setAttribute('viewBox', '0 0 48 48');
|
|
98
|
+
iconSvgNode.setAttribute('width', iconSize);
|
|
99
|
+
iconSvgNode.setAttribute('height', iconSize);
|
|
100
|
+
iconSvgNode.setAttribute('y', (iconSize / 2 - topMargin / 2) * -1);
|
|
101
|
+
iconSvgNode.setAttribute('x', "-".concat((iconSize + getIconPadding(textSize) + textWidth) / 2));
|
|
102
|
+
iconSvgNode.setAttribute('style', "color: ".concat(fillColor));
|
|
103
|
+
const parser = new DOMParser();
|
|
104
|
+
const svgIconDocument = parser.parseFromString(icon, 'image/svg+xml');
|
|
105
|
+
Array.from(svgIconDocument.documentElement.children).forEach(node => iconSvgNode.appendChild(node));
|
|
106
|
+
svgValue.appendChild(iconSvgNode);
|
|
29
107
|
}
|
|
30
108
|
|
|
109
|
+
const letterSpacing = Math.round(textSize * LETTER_SPACING_TEXT_SIZE_FACTOR);
|
|
31
110
|
const textNode = document.createElementNS(svgNS, 'text');
|
|
32
|
-
textNode.setAttribute('text-anchor', 'middle');
|
|
33
111
|
textNode.setAttribute('font-size', textSize);
|
|
34
112
|
textNode.setAttribute('font-weight', '300');
|
|
35
|
-
textNode.setAttribute('letter-spacing',
|
|
36
|
-
textNode.setAttribute('
|
|
113
|
+
textNode.setAttribute('letter-spacing', letterSpacing < LETTER_SPACING_MIN_THRESHOLD ? LETTER_SPACING_MIN_THRESHOLD : letterSpacing > LETTER_SPACING_MAX_THRESHOLD ? LETTER_SPACING_MAX_THRESHOLD : letterSpacing);
|
|
114
|
+
textNode.setAttribute('text-anchor', 'middle');
|
|
115
|
+
textNode.setAttribute('x', showIcon ? "".concat((iconSize + getIconPadding(textSize)) / 2) : 0);
|
|
116
|
+
textNode.setAttribute('y', topMargin / 2 + getTextHeightForNumbers(textSize) / 2);
|
|
37
117
|
textNode.setAttribute('fill', fillColor);
|
|
38
118
|
textNode.setAttribute('data-test', 'visualization-primary-value');
|
|
39
119
|
textNode.appendChild(document.createTextNode(formattedValue));
|
|
40
120
|
svgValue.appendChild(textNode);
|
|
41
121
|
|
|
42
122
|
if (subText) {
|
|
43
|
-
const svgSubText = document.createElementNS(svgNS, 'svg');
|
|
44
|
-
const subTextSize = 40;
|
|
45
|
-
svgSubText.setAttribute('viewBox', "0 -50 ".concat(textSize * 0.75 * formattedValue.length, " ").concat(textSize + 200));
|
|
46
|
-
|
|
47
|
-
if (y) {
|
|
48
|
-
svgSubText.setAttribute('y', y);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
123
|
const subTextNode = document.createElementNS(svgNS, 'text');
|
|
52
124
|
subTextNode.setAttribute('text-anchor', 'middle');
|
|
53
125
|
subTextNode.setAttribute('font-size', subTextSize);
|
|
54
|
-
subTextNode.setAttribute('
|
|
55
|
-
subTextNode.setAttribute('
|
|
56
|
-
subTextNode.setAttribute('fill',
|
|
126
|
+
subTextNode.setAttribute('y', iconSize / 2 + topMargin / 2);
|
|
127
|
+
subTextNode.setAttribute('dy', subTextSize * 1.7);
|
|
128
|
+
subTextNode.setAttribute('fill', textColor);
|
|
57
129
|
subTextNode.appendChild(document.createTextNode(subText));
|
|
58
|
-
|
|
59
|
-
svgValue.appendChild(svgSubText);
|
|
130
|
+
svgValue.appendChild(subTextNode);
|
|
60
131
|
}
|
|
61
132
|
|
|
62
133
|
return svgValue;
|
|
@@ -64,14 +135,28 @@ const generateValueSVG = _ref => {
|
|
|
64
135
|
|
|
65
136
|
const generateDashboardItem = (config, _ref2) => {
|
|
66
137
|
let {
|
|
138
|
+
svgContainer,
|
|
139
|
+
width,
|
|
140
|
+
height,
|
|
67
141
|
valueColor,
|
|
68
142
|
titleColor,
|
|
69
143
|
backgroundColor,
|
|
70
|
-
noData
|
|
144
|
+
noData,
|
|
145
|
+
icon
|
|
71
146
|
} = _ref2;
|
|
147
|
+
svgContainer.appendChild(generateValueSVG({
|
|
148
|
+
formattedValue: config.formattedValue,
|
|
149
|
+
subText: config.subText,
|
|
150
|
+
valueColor,
|
|
151
|
+
textColor: titleColor,
|
|
152
|
+
noData,
|
|
153
|
+
icon,
|
|
154
|
+
containerWidth: width,
|
|
155
|
+
containerHeight: height
|
|
156
|
+
}));
|
|
72
157
|
const container = document.createElement('div');
|
|
73
|
-
container.setAttribute('style', "display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; background-color:".concat(backgroundColor, ";"));
|
|
74
|
-
const titleStyle = "font-size: 12px; color: ".concat(titleColor || '#666', ";");
|
|
158
|
+
container.setAttribute('style', "display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; padding-top: 8px; ".concat(backgroundColor ? "background-color:".concat(backgroundColor, ";") : ''));
|
|
159
|
+
const titleStyle = "padding: 0 8px; text-align: center; font-size: 12px; color: ".concat(titleColor || '#666', ";");
|
|
75
160
|
const title = document.createElement('span');
|
|
76
161
|
title.setAttribute('style', titleStyle);
|
|
77
162
|
|
|
@@ -82,18 +167,12 @@ const generateDashboardItem = (config, _ref2) => {
|
|
|
82
167
|
|
|
83
168
|
if (config.subtitle) {
|
|
84
169
|
const subtitle = document.createElement('span');
|
|
85
|
-
subtitle.setAttribute('style', titleStyle + ' margin-top: 4px;
|
|
170
|
+
subtitle.setAttribute('style', titleStyle + ' margin-top: 4px;');
|
|
86
171
|
subtitle.appendChild(document.createTextNode(config.subtitle));
|
|
87
172
|
container.appendChild(subtitle);
|
|
88
173
|
}
|
|
89
174
|
|
|
90
|
-
container.appendChild(
|
|
91
|
-
formattedValue: config.formattedValue,
|
|
92
|
-
subText: config.subText,
|
|
93
|
-
valueColor,
|
|
94
|
-
noData,
|
|
95
|
-
y: 40
|
|
96
|
-
}));
|
|
175
|
+
container.appendChild(svgContainer);
|
|
97
176
|
return container;
|
|
98
177
|
};
|
|
99
178
|
|
|
@@ -127,89 +206,92 @@ const getXFromTextAlign = textAlign => {
|
|
|
127
206
|
|
|
128
207
|
const generateDVItem = (config, _ref3) => {
|
|
129
208
|
let {
|
|
209
|
+
svgContainer,
|
|
210
|
+
width,
|
|
211
|
+
height,
|
|
130
212
|
valueColor,
|
|
213
|
+
noData,
|
|
131
214
|
backgroundColor,
|
|
132
215
|
titleColor,
|
|
133
|
-
parentEl,
|
|
134
216
|
fontStyle,
|
|
135
|
-
|
|
217
|
+
icon
|
|
136
218
|
} = _ref3;
|
|
137
|
-
const parentElBBox = parentEl.getBoundingClientRect();
|
|
138
|
-
const width = parentElBBox.width;
|
|
139
|
-
const height = parentElBBox.height;
|
|
140
|
-
const svgNS = 'http://www.w3.org/2000/svg';
|
|
141
|
-
const svg = document.createElementNS(svgNS, 'svg');
|
|
142
|
-
svg.setAttribute('xmlns', svgNS);
|
|
143
|
-
svg.setAttribute('viewBox', "0 0 ".concat(width, " ").concat(height));
|
|
144
|
-
svg.setAttribute('width', width);
|
|
145
|
-
svg.setAttribute('height', height);
|
|
146
|
-
svg.setAttribute('data-test', 'visualization-container');
|
|
147
219
|
|
|
148
220
|
if (backgroundColor) {
|
|
149
|
-
|
|
221
|
+
svgContainer.setAttribute('style', "background-color: ".concat(backgroundColor, ";"));
|
|
150
222
|
const background = document.createElementNS(svgNS, 'rect');
|
|
151
223
|
background.setAttribute('width', '100%');
|
|
152
224
|
background.setAttribute('height', '100%');
|
|
153
225
|
background.setAttribute('fill', backgroundColor);
|
|
154
|
-
|
|
226
|
+
svgContainer.appendChild(background);
|
|
155
227
|
}
|
|
156
228
|
|
|
229
|
+
const svgWrapper = document.createElementNS(svgNS, 'svg'); // title
|
|
230
|
+
|
|
157
231
|
const title = document.createElementNS(svgNS, 'text');
|
|
158
232
|
const titleFontStyle = mergeFontStyleWithDefault(fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_TITLE], FONT_STYLE_VISUALIZATION_TITLE);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
title
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
233
|
+
const titleYPosition = TOP_MARGIN_FIXED + parseInt(titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE]) + 'px';
|
|
234
|
+
const titleAttributes = {
|
|
235
|
+
x: getXFromTextAlign(titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]),
|
|
236
|
+
y: titleYPosition,
|
|
237
|
+
'text-anchor': getTextAnchorFromTextAlign(titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]),
|
|
238
|
+
'font-size': "".concat(titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE], "px"),
|
|
239
|
+
'font-weight': titleFontStyle[FONT_STYLE_OPTION_BOLD] ? FONT_STYLE_OPTION_BOLD : 'normal',
|
|
240
|
+
'font-style': titleFontStyle[FONT_STYLE_OPTION_ITALIC] ? FONT_STYLE_OPTION_ITALIC : 'normal',
|
|
241
|
+
'data-test': 'visualization-title',
|
|
242
|
+
fill: titleColor && titleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] === defaultFontStyle[FONT_STYLE_VISUALIZATION_TITLE][FONT_STYLE_OPTION_TEXT_COLOR] ? titleColor : titleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR]
|
|
243
|
+
};
|
|
244
|
+
Object.entries(titleAttributes).forEach(_ref4 => {
|
|
245
|
+
let [key, value] = _ref4;
|
|
246
|
+
return title.setAttribute(key, value);
|
|
247
|
+
});
|
|
173
248
|
|
|
174
249
|
if (config.title) {
|
|
175
250
|
title.appendChild(document.createTextNode(config.title));
|
|
176
|
-
|
|
177
|
-
}
|
|
251
|
+
svgWrapper.appendChild(title);
|
|
252
|
+
} // subtitle
|
|
178
253
|
|
|
179
|
-
const subtitleFontStyle = mergeFontStyleWithDefault(fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_SUBTITLE], FONT_STYLE_VISUALIZATION_SUBTITLE);
|
|
180
|
-
const subtitle = document.createElementNS(svgNS, 'text');
|
|
181
|
-
subtitle.setAttribute('x', getXFromTextAlign(subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]));
|
|
182
|
-
subtitle.setAttribute('y', 28);
|
|
183
|
-
subtitle.setAttribute('dy', 22);
|
|
184
|
-
subtitle.setAttribute('text-anchor', getTextAnchorFromTextAlign(subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]));
|
|
185
|
-
subtitle.setAttribute('font-size', "".concat(subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE], "px"));
|
|
186
|
-
subtitle.setAttribute('font-weight', subtitleFontStyle[FONT_STYLE_OPTION_BOLD] ? FONT_STYLE_OPTION_BOLD : 'normal');
|
|
187
|
-
subtitle.setAttribute('font-style', subtitleFontStyle[FONT_STYLE_OPTION_ITALIC] ? FONT_STYLE_OPTION_ITALIC : 'normal');
|
|
188
|
-
|
|
189
|
-
if (titleColor && subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] === defaultFontStyle[FONT_STYLE_VISUALIZATION_SUBTITLE][FONT_STYLE_OPTION_TEXT_COLOR]) {
|
|
190
|
-
subtitle.setAttribute('fill', titleColor);
|
|
191
|
-
} else {
|
|
192
|
-
subtitle.setAttribute('fill', subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR]);
|
|
193
|
-
}
|
|
194
254
|
|
|
195
|
-
subtitle.
|
|
255
|
+
const subtitle = document.createElementNS(svgNS, 'text');
|
|
256
|
+
const subtitleFontStyle = mergeFontStyleWithDefault(fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_SUBTITLE], FONT_STYLE_VISUALIZATION_SUBTITLE);
|
|
257
|
+
const subtitleAttributes = {
|
|
258
|
+
x: getXFromTextAlign(subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]),
|
|
259
|
+
y: titleYPosition,
|
|
260
|
+
dy: "".concat(subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE] + 10),
|
|
261
|
+
'text-anchor': getTextAnchorFromTextAlign(subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]),
|
|
262
|
+
'font-size': "".concat(subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE], "px"),
|
|
263
|
+
'font-weight': subtitleFontStyle[FONT_STYLE_OPTION_BOLD] ? FONT_STYLE_OPTION_BOLD : 'normal',
|
|
264
|
+
'font-style': subtitleFontStyle[FONT_STYLE_OPTION_ITALIC] ? FONT_STYLE_OPTION_ITALIC : 'normal',
|
|
265
|
+
fill: titleColor && subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] === defaultFontStyle[FONT_STYLE_VISUALIZATION_SUBTITLE][FONT_STYLE_OPTION_TEXT_COLOR] ? titleColor : subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR],
|
|
266
|
+
'data-test': 'visualization-subtitle'
|
|
267
|
+
};
|
|
268
|
+
Object.entries(subtitleAttributes).forEach(_ref5 => {
|
|
269
|
+
let [key, value] = _ref5;
|
|
270
|
+
return subtitle.setAttribute(key, value);
|
|
271
|
+
});
|
|
196
272
|
|
|
197
273
|
if (config.subtitle) {
|
|
198
274
|
subtitle.appendChild(document.createTextNode(config.subtitle));
|
|
199
|
-
|
|
275
|
+
svgWrapper.appendChild(subtitle);
|
|
200
276
|
}
|
|
201
277
|
|
|
202
|
-
|
|
278
|
+
svgContainer.appendChild(svgWrapper);
|
|
279
|
+
svgContainer.appendChild(generateValueSVG({
|
|
203
280
|
formattedValue: config.formattedValue,
|
|
204
281
|
subText: config.subText,
|
|
205
282
|
valueColor,
|
|
283
|
+
textColor: titleColor,
|
|
206
284
|
noData,
|
|
207
|
-
|
|
285
|
+
icon,
|
|
286
|
+
containerWidth: width,
|
|
287
|
+
containerHeight: height,
|
|
288
|
+
topMargin: TOP_MARGIN_FIXED + ((config.title ? parseInt(title.getAttribute('font-size')) : 0) + (config.subtitle ? parseInt(subtitle.getAttribute('font-size')) : 0)) * 2.5
|
|
208
289
|
}));
|
|
209
|
-
return
|
|
290
|
+
return svgContainer;
|
|
210
291
|
};
|
|
211
292
|
|
|
212
|
-
const shouldUseContrastColor =
|
|
293
|
+
const shouldUseContrastColor = function () {
|
|
294
|
+
let inputColor = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
213
295
|
// based on https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color
|
|
214
296
|
var color = inputColor.charAt(0) === '#' ? inputColor.substring(1, 7) : inputColor;
|
|
215
297
|
var r = parseInt(color.substring(0, 2), 16); // hexToR
|
|
@@ -230,14 +312,15 @@ const shouldUseContrastColor = inputColor => {
|
|
|
230
312
|
return L <= 0.179;
|
|
231
313
|
};
|
|
232
314
|
|
|
233
|
-
export default function (config, parentEl,
|
|
315
|
+
export default function (config, parentEl, _ref6) {
|
|
234
316
|
let {
|
|
235
317
|
dashboard,
|
|
236
318
|
legendSets,
|
|
237
319
|
fontStyle,
|
|
238
320
|
noData,
|
|
239
|
-
legendOptions
|
|
240
|
-
|
|
321
|
+
legendOptions,
|
|
322
|
+
icon
|
|
323
|
+
} = _ref6;
|
|
241
324
|
const legendSet = legendOptions && legendSets[0];
|
|
242
325
|
const legendColor = legendSet && getColorByValueFromLegendSet(legendSet, config.value);
|
|
243
326
|
let valueColor, titleColor, backgroundColor;
|
|
@@ -254,26 +337,42 @@ export default function (config, parentEl, _ref4) {
|
|
|
254
337
|
parentEl.style.overflow = 'hidden';
|
|
255
338
|
parentEl.style.display = 'flex';
|
|
256
339
|
parentEl.style.justifyContent = 'center';
|
|
340
|
+
const parentElBBox = parentEl.getBoundingClientRect();
|
|
341
|
+
const width = parentElBBox.width;
|
|
342
|
+
const height = parentElBBox.height;
|
|
343
|
+
const svgContainer = document.createElementNS(svgNS, 'svg');
|
|
344
|
+
svgContainer.setAttribute('xmlns', svgNS);
|
|
345
|
+
svgContainer.setAttribute('viewBox', "0 0 ".concat(width, " ").concat(height));
|
|
346
|
+
svgContainer.setAttribute('width', dashboard ? '100%' : width);
|
|
347
|
+
svgContainer.setAttribute('height', dashboard ? '100%' : height);
|
|
348
|
+
svgContainer.setAttribute('data-test', 'visualization-container');
|
|
257
349
|
|
|
258
350
|
if (dashboard) {
|
|
259
|
-
parentEl.style.borderRadius =
|
|
351
|
+
parentEl.style.borderRadius = '3px';
|
|
260
352
|
return generateDashboardItem(config, {
|
|
353
|
+
svgContainer,
|
|
354
|
+
width,
|
|
355
|
+
height,
|
|
261
356
|
valueColor,
|
|
262
357
|
backgroundColor,
|
|
263
358
|
noData,
|
|
264
|
-
|
|
359
|
+
icon,
|
|
360
|
+
...(legendOptions.style === LEGEND_DISPLAY_STYLE_FILL && legendColor && shouldUseContrastColor(legendColor) ? {
|
|
265
361
|
titleColor: colors.white
|
|
266
362
|
} : {})
|
|
267
363
|
});
|
|
268
364
|
} else {
|
|
269
365
|
parentEl.style.height = "100%";
|
|
270
366
|
return generateDVItem(config, {
|
|
367
|
+
svgContainer,
|
|
368
|
+
width,
|
|
369
|
+
height,
|
|
271
370
|
valueColor,
|
|
272
371
|
backgroundColor,
|
|
273
372
|
titleColor,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
373
|
+
noData,
|
|
374
|
+
icon,
|
|
375
|
+
fontStyle
|
|
277
376
|
});
|
|
278
377
|
}
|
|
279
378
|
}
|