@dhis2/analytics 25.1.9 → 25.1.11
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
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [25.1.11](https://github.com/dhis2/analytics/compare/v25.1.10...v25.1.11) (2023-06-05)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add data-test to SV icon ([#1479](https://github.com/dhis2/analytics/issues/1479)) ([df1c7ef](https://github.com/dhis2/analytics/commit/df1c7efb3136be99b5026b252a499ba0bb57ecd9))
|
|
7
|
+
|
|
8
|
+
## [25.1.10](https://github.com/dhis2/analytics/compare/v25.1.9...v25.1.10) (2023-05-30)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* single value size and position issues (DHIS2-15344) ([#1470](https://github.com/dhis2/analytics/issues/1470)) ([d94fe02](https://github.com/dhis2/analytics/commit/d94fe02c7cc85a6b4aca41c85ba60ed37871b645))
|
|
14
|
+
|
|
1
15
|
## [25.1.9](https://github.com/dhis2/analytics/compare/v25.1.8...v25.1.9) (2023-05-30)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -11,14 +11,59 @@ 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
|
|
15
44
|
// Not exactly precise but close enough
|
|
16
45
|
|
|
17
46
|
const getTextWidth = (text, font) => {
|
|
18
47
|
const canvas = document.createElement('canvas');
|
|
19
48
|
const context = canvas.getContext('2d');
|
|
20
49
|
context.font = font;
|
|
21
|
-
return context.measureText(text).width;
|
|
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;
|
|
22
67
|
};
|
|
23
68
|
|
|
24
69
|
const generateValueSVG = _ref => {
|
|
@@ -30,26 +75,18 @@ const generateValueSVG = _ref => {
|
|
|
30
75
|
icon,
|
|
31
76
|
noData,
|
|
32
77
|
containerWidth,
|
|
33
|
-
containerHeight
|
|
78
|
+
containerHeight,
|
|
79
|
+
topMargin = 0
|
|
34
80
|
} = _ref;
|
|
35
|
-
const ratio = containerHeight / containerWidth;
|
|
36
|
-
const iconSize = 300;
|
|
37
|
-
const iconPadding = 50;
|
|
38
|
-
const textSize = iconSize * 0.85;
|
|
39
|
-
const textWidth = getTextWidth(formattedValue, "".concat(textSize, "px Roboto"));
|
|
40
|
-
const subTextSize = 40;
|
|
41
81
|
const showIcon = icon && formattedValue !== noData.text;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const viewBoxHeight = viewBoxWidth * ratio;
|
|
82
|
+
const textSize = getTextSize(formattedValue, containerWidth, containerHeight, showIcon);
|
|
83
|
+
const textWidth = getTextWidth(formattedValue, "".concat(textSize, "px Roboto"));
|
|
84
|
+
const iconSize = textSize;
|
|
85
|
+
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;
|
|
49
86
|
const svgValue = document.createElementNS(svgNS, 'svg');
|
|
50
|
-
svgValue.setAttribute('viewBox', "0 0 ".concat(
|
|
51
|
-
svgValue.setAttribute('width', '
|
|
52
|
-
svgValue.setAttribute('height', '
|
|
87
|
+
svgValue.setAttribute('viewBox', "0 0 ".concat(containerWidth, " ").concat(containerHeight));
|
|
88
|
+
svgValue.setAttribute('width', '50%');
|
|
89
|
+
svgValue.setAttribute('height', '50%');
|
|
53
90
|
svgValue.setAttribute('x', '50%');
|
|
54
91
|
svgValue.setAttribute('y', '50%');
|
|
55
92
|
svgValue.setAttribute('style', 'overflow: visible');
|
|
@@ -66,26 +103,27 @@ const generateValueSVG = _ref => {
|
|
|
66
103
|
// embed icon to allow changing color
|
|
67
104
|
// (elements with fill need to use "currentColor" for this to work)
|
|
68
105
|
const iconSvgNode = document.createElementNS(svgNS, 'svg');
|
|
106
|
+
iconSvgNode.setAttribute('viewBox', '0 0 48 48');
|
|
69
107
|
iconSvgNode.setAttribute('width', iconSize);
|
|
70
108
|
iconSvgNode.setAttribute('height', iconSize);
|
|
71
|
-
iconSvgNode.setAttribute('
|
|
72
|
-
iconSvgNode.setAttribute('
|
|
73
|
-
iconSvgNode.setAttribute('x', "-".concat((iconSize + iconPadding + textWidth) / 2));
|
|
109
|
+
iconSvgNode.setAttribute('y', (iconSize / 2 - topMargin / 2) * -1);
|
|
110
|
+
iconSvgNode.setAttribute('x', "-".concat((iconSize + getIconPadding(textSize) + textWidth) / 2));
|
|
74
111
|
iconSvgNode.setAttribute('style', "color: ".concat(fillColor));
|
|
112
|
+
iconSvgNode.setAttribute('data-test', 'visualization-icon');
|
|
75
113
|
const parser = new DOMParser();
|
|
76
114
|
const svgIconDocument = parser.parseFromString(icon, 'image/svg+xml');
|
|
77
115
|
Array.from(svgIconDocument.documentElement.children).forEach(node => iconSvgNode.appendChild(node));
|
|
78
116
|
svgValue.appendChild(iconSvgNode);
|
|
79
117
|
}
|
|
80
118
|
|
|
119
|
+
const letterSpacing = Math.round(textSize * LETTER_SPACING_TEXT_SIZE_FACTOR);
|
|
81
120
|
const textNode = document.createElementNS(svgNS, 'text');
|
|
82
121
|
textNode.setAttribute('font-size', textSize);
|
|
83
122
|
textNode.setAttribute('font-weight', '300');
|
|
84
|
-
textNode.setAttribute('letter-spacing',
|
|
123
|
+
textNode.setAttribute('letter-spacing', letterSpacing < LETTER_SPACING_MIN_THRESHOLD ? LETTER_SPACING_MIN_THRESHOLD : letterSpacing > LETTER_SPACING_MAX_THRESHOLD ? LETTER_SPACING_MAX_THRESHOLD : letterSpacing);
|
|
85
124
|
textNode.setAttribute('text-anchor', 'middle');
|
|
86
|
-
textNode.setAttribute('x', showIcon ? "".concat((iconSize +
|
|
87
|
-
|
|
88
|
-
textNode.setAttribute('y', '.35em');
|
|
125
|
+
textNode.setAttribute('x', showIcon ? "".concat((iconSize + getIconPadding(textSize)) / 2) : 0);
|
|
126
|
+
textNode.setAttribute('y', topMargin / 2 + getTextHeightForNumbers(textSize) / 2);
|
|
89
127
|
textNode.setAttribute('fill', fillColor);
|
|
90
128
|
textNode.setAttribute('data-test', 'visualization-primary-value');
|
|
91
129
|
textNode.appendChild(document.createTextNode(formattedValue));
|
|
@@ -95,8 +133,8 @@ const generateValueSVG = _ref => {
|
|
|
95
133
|
const subTextNode = document.createElementNS(svgNS, 'text');
|
|
96
134
|
subTextNode.setAttribute('text-anchor', 'middle');
|
|
97
135
|
subTextNode.setAttribute('font-size', subTextSize);
|
|
98
|
-
subTextNode.setAttribute('y', iconSize / 2);
|
|
99
|
-
subTextNode.setAttribute('dy', subTextSize);
|
|
136
|
+
subTextNode.setAttribute('y', iconSize / 2 + topMargin / 2);
|
|
137
|
+
subTextNode.setAttribute('dy', subTextSize * 1.7);
|
|
100
138
|
subTextNode.setAttribute('fill', textColor);
|
|
101
139
|
subTextNode.appendChild(document.createTextNode(subText));
|
|
102
140
|
svgValue.appendChild(subTextNode);
|
|
@@ -198,47 +236,49 @@ const generateDVItem = (config, _ref3) => {
|
|
|
198
236
|
svgContainer.appendChild(background);
|
|
199
237
|
}
|
|
200
238
|
|
|
201
|
-
const svgWrapper = document.createElementNS(svgNS, 'svg');
|
|
239
|
+
const svgWrapper = document.createElementNS(svgNS, 'svg'); // title
|
|
240
|
+
|
|
202
241
|
const title = document.createElementNS(svgNS, 'text');
|
|
203
242
|
const titleFontStyle = (0, _fontStyle.mergeFontStyleWithDefault)(fontStyle && fontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_TITLE], _fontStyle.FONT_STYLE_VISUALIZATION_TITLE);
|
|
204
|
-
const titleYPosition = titleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE];
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
+
});
|
|
219
258
|
|
|
220
259
|
if (config.title) {
|
|
221
260
|
title.appendChild(document.createTextNode(config.title));
|
|
222
261
|
svgWrapper.appendChild(title);
|
|
223
|
-
}
|
|
262
|
+
} // subtitle
|
|
224
263
|
|
|
225
|
-
const subtitleFontStyle = (0, _fontStyle.mergeFontStyleWithDefault)(fontStyle && fontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE], _fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE);
|
|
226
|
-
const subtitle = document.createElementNS(svgNS, 'text');
|
|
227
|
-
subtitle.setAttribute('x', getXFromTextAlign(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]));
|
|
228
|
-
subtitle.setAttribute('y', titleYPosition);
|
|
229
|
-
subtitle.setAttribute('dy', "".concat(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE] + 10));
|
|
230
|
-
subtitle.setAttribute('text-anchor', getTextAnchorFromTextAlign(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]));
|
|
231
|
-
subtitle.setAttribute('font-size', "".concat(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE], "px"));
|
|
232
|
-
subtitle.setAttribute('font-weight', subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_BOLD] ? _fontStyle.FONT_STYLE_OPTION_BOLD : 'normal');
|
|
233
|
-
subtitle.setAttribute('font-style', subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_ITALIC] ? _fontStyle.FONT_STYLE_OPTION_ITALIC : 'normal');
|
|
234
|
-
|
|
235
|
-
if (titleColor && subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR] === _fontStyle.defaultFontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE][_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR]) {
|
|
236
|
-
subtitle.setAttribute('fill', titleColor);
|
|
237
|
-
} else {
|
|
238
|
-
subtitle.setAttribute('fill', subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR]);
|
|
239
|
-
}
|
|
240
264
|
|
|
241
|
-
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
|
+
});
|
|
242
282
|
|
|
243
283
|
if (config.subtitle) {
|
|
244
284
|
subtitle.appendChild(document.createTextNode(config.subtitle));
|
|
@@ -254,7 +294,8 @@ const generateDVItem = (config, _ref3) => {
|
|
|
254
294
|
noData,
|
|
255
295
|
icon,
|
|
256
296
|
containerWidth: width,
|
|
257
|
-
containerHeight: height
|
|
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
|
|
258
299
|
}));
|
|
259
300
|
return svgContainer;
|
|
260
301
|
};
|
|
@@ -281,7 +322,7 @@ const shouldUseContrastColor = function () {
|
|
|
281
322
|
return L <= 0.179;
|
|
282
323
|
};
|
|
283
324
|
|
|
284
|
-
function _default(config, parentEl,
|
|
325
|
+
function _default(config, parentEl, _ref6) {
|
|
285
326
|
let {
|
|
286
327
|
dashboard,
|
|
287
328
|
legendSets,
|
|
@@ -289,7 +330,7 @@ function _default(config, parentEl, _ref4) {
|
|
|
289
330
|
noData,
|
|
290
331
|
legendOptions,
|
|
291
332
|
icon
|
|
292
|
-
} =
|
|
333
|
+
} = _ref6;
|
|
293
334
|
const legendSet = legendOptions && legendSets[0];
|
|
294
335
|
const legendColor = legendSet && (0, _legends.getColorByValueFromLegendSet)(legendSet, config.value);
|
|
295
336
|
let valueColor, titleColor, backgroundColor;
|
|
@@ -1,14 +1,59 @@
|
|
|
1
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
|
|
5
34
|
// Not exactly precise but close enough
|
|
6
35
|
|
|
7
36
|
const getTextWidth = (text, font) => {
|
|
8
37
|
const canvas = document.createElement('canvas');
|
|
9
38
|
const context = canvas.getContext('2d');
|
|
10
39
|
context.font = font;
|
|
11
|
-
return context.measureText(text).width;
|
|
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;
|
|
12
57
|
};
|
|
13
58
|
|
|
14
59
|
const generateValueSVG = _ref => {
|
|
@@ -20,26 +65,18 @@ const generateValueSVG = _ref => {
|
|
|
20
65
|
icon,
|
|
21
66
|
noData,
|
|
22
67
|
containerWidth,
|
|
23
|
-
containerHeight
|
|
68
|
+
containerHeight,
|
|
69
|
+
topMargin = 0
|
|
24
70
|
} = _ref;
|
|
25
|
-
const ratio = containerHeight / containerWidth;
|
|
26
|
-
const iconSize = 300;
|
|
27
|
-
const iconPadding = 50;
|
|
28
|
-
const textSize = iconSize * 0.85;
|
|
29
|
-
const textWidth = getTextWidth(formattedValue, "".concat(textSize, "px Roboto"));
|
|
30
|
-
const subTextSize = 40;
|
|
31
71
|
const showIcon = icon && formattedValue !== noData.text;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const viewBoxHeight = viewBoxWidth * ratio;
|
|
72
|
+
const textSize = getTextSize(formattedValue, containerWidth, containerHeight, showIcon);
|
|
73
|
+
const textWidth = getTextWidth(formattedValue, "".concat(textSize, "px Roboto"));
|
|
74
|
+
const iconSize = textSize;
|
|
75
|
+
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;
|
|
39
76
|
const svgValue = document.createElementNS(svgNS, 'svg');
|
|
40
|
-
svgValue.setAttribute('viewBox', "0 0 ".concat(
|
|
41
|
-
svgValue.setAttribute('width', '
|
|
42
|
-
svgValue.setAttribute('height', '
|
|
77
|
+
svgValue.setAttribute('viewBox', "0 0 ".concat(containerWidth, " ").concat(containerHeight));
|
|
78
|
+
svgValue.setAttribute('width', '50%');
|
|
79
|
+
svgValue.setAttribute('height', '50%');
|
|
43
80
|
svgValue.setAttribute('x', '50%');
|
|
44
81
|
svgValue.setAttribute('y', '50%');
|
|
45
82
|
svgValue.setAttribute('style', 'overflow: visible');
|
|
@@ -56,26 +93,27 @@ const generateValueSVG = _ref => {
|
|
|
56
93
|
// embed icon to allow changing color
|
|
57
94
|
// (elements with fill need to use "currentColor" for this to work)
|
|
58
95
|
const iconSvgNode = document.createElementNS(svgNS, 'svg');
|
|
96
|
+
iconSvgNode.setAttribute('viewBox', '0 0 48 48');
|
|
59
97
|
iconSvgNode.setAttribute('width', iconSize);
|
|
60
98
|
iconSvgNode.setAttribute('height', iconSize);
|
|
61
|
-
iconSvgNode.setAttribute('
|
|
62
|
-
iconSvgNode.setAttribute('
|
|
63
|
-
iconSvgNode.setAttribute('x', "-".concat((iconSize + iconPadding + textWidth) / 2));
|
|
99
|
+
iconSvgNode.setAttribute('y', (iconSize / 2 - topMargin / 2) * -1);
|
|
100
|
+
iconSvgNode.setAttribute('x', "-".concat((iconSize + getIconPadding(textSize) + textWidth) / 2));
|
|
64
101
|
iconSvgNode.setAttribute('style', "color: ".concat(fillColor));
|
|
102
|
+
iconSvgNode.setAttribute('data-test', 'visualization-icon');
|
|
65
103
|
const parser = new DOMParser();
|
|
66
104
|
const svgIconDocument = parser.parseFromString(icon, 'image/svg+xml');
|
|
67
105
|
Array.from(svgIconDocument.documentElement.children).forEach(node => iconSvgNode.appendChild(node));
|
|
68
106
|
svgValue.appendChild(iconSvgNode);
|
|
69
107
|
}
|
|
70
108
|
|
|
109
|
+
const letterSpacing = Math.round(textSize * LETTER_SPACING_TEXT_SIZE_FACTOR);
|
|
71
110
|
const textNode = document.createElementNS(svgNS, 'text');
|
|
72
111
|
textNode.setAttribute('font-size', textSize);
|
|
73
112
|
textNode.setAttribute('font-weight', '300');
|
|
74
|
-
textNode.setAttribute('letter-spacing',
|
|
113
|
+
textNode.setAttribute('letter-spacing', letterSpacing < LETTER_SPACING_MIN_THRESHOLD ? LETTER_SPACING_MIN_THRESHOLD : letterSpacing > LETTER_SPACING_MAX_THRESHOLD ? LETTER_SPACING_MAX_THRESHOLD : letterSpacing);
|
|
75
114
|
textNode.setAttribute('text-anchor', 'middle');
|
|
76
|
-
textNode.setAttribute('x', showIcon ? "".concat((iconSize +
|
|
77
|
-
|
|
78
|
-
textNode.setAttribute('y', '.35em');
|
|
115
|
+
textNode.setAttribute('x', showIcon ? "".concat((iconSize + getIconPadding(textSize)) / 2) : 0);
|
|
116
|
+
textNode.setAttribute('y', topMargin / 2 + getTextHeightForNumbers(textSize) / 2);
|
|
79
117
|
textNode.setAttribute('fill', fillColor);
|
|
80
118
|
textNode.setAttribute('data-test', 'visualization-primary-value');
|
|
81
119
|
textNode.appendChild(document.createTextNode(formattedValue));
|
|
@@ -85,8 +123,8 @@ const generateValueSVG = _ref => {
|
|
|
85
123
|
const subTextNode = document.createElementNS(svgNS, 'text');
|
|
86
124
|
subTextNode.setAttribute('text-anchor', 'middle');
|
|
87
125
|
subTextNode.setAttribute('font-size', subTextSize);
|
|
88
|
-
subTextNode.setAttribute('y', iconSize / 2);
|
|
89
|
-
subTextNode.setAttribute('dy', subTextSize);
|
|
126
|
+
subTextNode.setAttribute('y', iconSize / 2 + topMargin / 2);
|
|
127
|
+
subTextNode.setAttribute('dy', subTextSize * 1.7);
|
|
90
128
|
subTextNode.setAttribute('fill', textColor);
|
|
91
129
|
subTextNode.appendChild(document.createTextNode(subText));
|
|
92
130
|
svgValue.appendChild(subTextNode);
|
|
@@ -188,47 +226,49 @@ const generateDVItem = (config, _ref3) => {
|
|
|
188
226
|
svgContainer.appendChild(background);
|
|
189
227
|
}
|
|
190
228
|
|
|
191
|
-
const svgWrapper = document.createElementNS(svgNS, 'svg');
|
|
229
|
+
const svgWrapper = document.createElementNS(svgNS, 'svg'); // title
|
|
230
|
+
|
|
192
231
|
const title = document.createElementNS(svgNS, 'text');
|
|
193
232
|
const titleFontStyle = mergeFontStyleWithDefault(fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_TITLE], FONT_STYLE_VISUALIZATION_TITLE);
|
|
194
|
-
const titleYPosition = titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE];
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
+
});
|
|
209
248
|
|
|
210
249
|
if (config.title) {
|
|
211
250
|
title.appendChild(document.createTextNode(config.title));
|
|
212
251
|
svgWrapper.appendChild(title);
|
|
213
|
-
}
|
|
252
|
+
} // subtitle
|
|
214
253
|
|
|
215
|
-
const subtitleFontStyle = mergeFontStyleWithDefault(fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_SUBTITLE], FONT_STYLE_VISUALIZATION_SUBTITLE);
|
|
216
|
-
const subtitle = document.createElementNS(svgNS, 'text');
|
|
217
|
-
subtitle.setAttribute('x', getXFromTextAlign(subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]));
|
|
218
|
-
subtitle.setAttribute('y', titleYPosition);
|
|
219
|
-
subtitle.setAttribute('dy', "".concat(subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE] + 10));
|
|
220
|
-
subtitle.setAttribute('text-anchor', getTextAnchorFromTextAlign(subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]));
|
|
221
|
-
subtitle.setAttribute('font-size', "".concat(subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE], "px"));
|
|
222
|
-
subtitle.setAttribute('font-weight', subtitleFontStyle[FONT_STYLE_OPTION_BOLD] ? FONT_STYLE_OPTION_BOLD : 'normal');
|
|
223
|
-
subtitle.setAttribute('font-style', subtitleFontStyle[FONT_STYLE_OPTION_ITALIC] ? FONT_STYLE_OPTION_ITALIC : 'normal');
|
|
224
|
-
|
|
225
|
-
if (titleColor && subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] === defaultFontStyle[FONT_STYLE_VISUALIZATION_SUBTITLE][FONT_STYLE_OPTION_TEXT_COLOR]) {
|
|
226
|
-
subtitle.setAttribute('fill', titleColor);
|
|
227
|
-
} else {
|
|
228
|
-
subtitle.setAttribute('fill', subtitleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR]);
|
|
229
|
-
}
|
|
230
254
|
|
|
231
|
-
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
|
+
});
|
|
232
272
|
|
|
233
273
|
if (config.subtitle) {
|
|
234
274
|
subtitle.appendChild(document.createTextNode(config.subtitle));
|
|
@@ -244,7 +284,8 @@ const generateDVItem = (config, _ref3) => {
|
|
|
244
284
|
noData,
|
|
245
285
|
icon,
|
|
246
286
|
containerWidth: width,
|
|
247
|
-
containerHeight: height
|
|
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
|
|
248
289
|
}));
|
|
249
290
|
return svgContainer;
|
|
250
291
|
};
|
|
@@ -271,7 +312,7 @@ const shouldUseContrastColor = function () {
|
|
|
271
312
|
return L <= 0.179;
|
|
272
313
|
};
|
|
273
314
|
|
|
274
|
-
export default function (config, parentEl,
|
|
315
|
+
export default function (config, parentEl, _ref6) {
|
|
275
316
|
let {
|
|
276
317
|
dashboard,
|
|
277
318
|
legendSets,
|
|
@@ -279,7 +320,7 @@ export default function (config, parentEl, _ref4) {
|
|
|
279
320
|
noData,
|
|
280
321
|
legendOptions,
|
|
281
322
|
icon
|
|
282
|
-
} =
|
|
323
|
+
} = _ref6;
|
|
283
324
|
const legendSet = legendOptions && legendSets[0];
|
|
284
325
|
const legendColor = legendSet && getColorByValueFromLegendSet(legendSet, config.value);
|
|
285
326
|
let valueColor, titleColor, backgroundColor;
|