@dhis2/analytics 25.1.9 → 25.1.10

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,10 @@
1
+ ## [25.1.10](https://github.com/dhis2/analytics/compare/v25.1.9...v25.1.10) (2023-05-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * single value size and position issues (DHIS2-15344) ([#1470](https://github.com/dhis2/analytics/issues/1470)) ([d94fe02](https://github.com/dhis2/analytics/commit/d94fe02c7cc85a6b4aca41c85ba60ed37871b645))
7
+
1
8
  ## [25.1.9](https://github.com/dhis2/analytics/compare/v25.1.8...v25.1.9) (2023-05-30)
2
9
 
3
10
 
@@ -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'; // Compute text width before rendering
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
- let viewBoxWidth = textWidth;
43
-
44
- if (showIcon) {
45
- viewBoxWidth += iconSize + iconPadding;
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(viewBoxWidth, " ").concat(viewBoxHeight));
51
- svgValue.setAttribute('width', '95%');
52
- svgValue.setAttribute('height', '95%');
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,11 +103,11 @@ 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('viewBox', '0 0 48 48');
72
- iconSvgNode.setAttribute('y', "-".concat(iconSize / 2));
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));
75
112
  const parser = new DOMParser();
76
113
  const svgIconDocument = parser.parseFromString(icon, 'image/svg+xml');
@@ -78,14 +115,14 @@ const generateValueSVG = _ref => {
78
115
  svgValue.appendChild(iconSvgNode);
79
116
  }
80
117
 
118
+ const letterSpacing = Math.round(textSize * LETTER_SPACING_TEXT_SIZE_FACTOR);
81
119
  const textNode = document.createElementNS(svgNS, 'text');
82
120
  textNode.setAttribute('font-size', textSize);
83
121
  textNode.setAttribute('font-weight', '300');
84
- textNode.setAttribute('letter-spacing', '-5');
122
+ textNode.setAttribute('letter-spacing', letterSpacing < LETTER_SPACING_MIN_THRESHOLD ? LETTER_SPACING_MIN_THRESHOLD : letterSpacing > LETTER_SPACING_MAX_THRESHOLD ? LETTER_SPACING_MAX_THRESHOLD : letterSpacing);
85
123
  textNode.setAttribute('text-anchor', 'middle');
86
- textNode.setAttribute('x', showIcon ? "".concat((iconSize + iconPadding) / 2) : 0); // vertical align, "alignment-baseline: central" is not supported by Batik
87
-
88
- textNode.setAttribute('y', '.35em');
124
+ textNode.setAttribute('x', showIcon ? "".concat((iconSize + getIconPadding(textSize)) / 2) : 0);
125
+ textNode.setAttribute('y', topMargin / 2 + getTextHeightForNumbers(textSize) / 2);
89
126
  textNode.setAttribute('fill', fillColor);
90
127
  textNode.setAttribute('data-test', 'visualization-primary-value');
91
128
  textNode.appendChild(document.createTextNode(formattedValue));
@@ -95,8 +132,8 @@ const generateValueSVG = _ref => {
95
132
  const subTextNode = document.createElementNS(svgNS, 'text');
96
133
  subTextNode.setAttribute('text-anchor', 'middle');
97
134
  subTextNode.setAttribute('font-size', subTextSize);
98
- subTextNode.setAttribute('y', iconSize / 2);
99
- subTextNode.setAttribute('dy', subTextSize);
135
+ subTextNode.setAttribute('y', iconSize / 2 + topMargin / 2);
136
+ subTextNode.setAttribute('dy', subTextSize * 1.7);
100
137
  subTextNode.setAttribute('fill', textColor);
101
138
  subTextNode.appendChild(document.createTextNode(subText));
102
139
  svgValue.appendChild(subTextNode);
@@ -198,47 +235,49 @@ const generateDVItem = (config, _ref3) => {
198
235
  svgContainer.appendChild(background);
199
236
  }
200
237
 
201
- const svgWrapper = document.createElementNS(svgNS, 'svg');
238
+ const svgWrapper = document.createElementNS(svgNS, 'svg'); // title
239
+
202
240
  const title = document.createElementNS(svgNS, 'text');
203
241
  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
- title.setAttribute('x', getXFromTextAlign(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]));
206
- title.setAttribute('y', titleYPosition);
207
- title.setAttribute('text-anchor', getTextAnchorFromTextAlign(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]));
208
- title.setAttribute('font-size', "".concat(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE], "px"));
209
- title.setAttribute('font-weight', titleFontStyle[_fontStyle.FONT_STYLE_OPTION_BOLD] ? _fontStyle.FONT_STYLE_OPTION_BOLD : 'normal');
210
- title.setAttribute('font-style', titleFontStyle[_fontStyle.FONT_STYLE_OPTION_ITALIC] ? _fontStyle.FONT_STYLE_OPTION_ITALIC : 'normal');
211
-
212
- if (titleColor && titleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR] === _fontStyle.defaultFontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_TITLE][_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR]) {
213
- title.setAttribute('fill', titleColor);
214
- } else {
215
- title.setAttribute('fill', titleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_COLOR]);
216
- }
217
-
218
- title.setAttribute('data-test', 'visualization-title');
242
+ const titleYPosition = TOP_MARGIN_FIXED + parseInt(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE]) + 'px';
243
+ const titleAttributes = {
244
+ x: getXFromTextAlign(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]),
245
+ y: titleYPosition,
246
+ 'text-anchor': getTextAnchorFromTextAlign(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]),
247
+ 'font-size': "".concat(titleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE], "px"),
248
+ 'font-weight': titleFontStyle[_fontStyle.FONT_STYLE_OPTION_BOLD] ? _fontStyle.FONT_STYLE_OPTION_BOLD : 'normal',
249
+ 'font-style': titleFontStyle[_fontStyle.FONT_STYLE_OPTION_ITALIC] ? _fontStyle.FONT_STYLE_OPTION_ITALIC : 'normal',
250
+ 'data-test': 'visualization-title',
251
+ 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]
252
+ };
253
+ Object.entries(titleAttributes).forEach(_ref4 => {
254
+ let [key, value] = _ref4;
255
+ return title.setAttribute(key, value);
256
+ });
219
257
 
220
258
  if (config.title) {
221
259
  title.appendChild(document.createTextNode(config.title));
222
260
  svgWrapper.appendChild(title);
223
- }
261
+ } // subtitle
224
262
 
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
263
 
241
- subtitle.setAttribute('data-test', 'visualization-subtitle');
264
+ const subtitle = document.createElementNS(svgNS, 'text');
265
+ const subtitleFontStyle = (0, _fontStyle.mergeFontStyleWithDefault)(fontStyle && fontStyle[_fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE], _fontStyle.FONT_STYLE_VISUALIZATION_SUBTITLE);
266
+ const subtitleAttributes = {
267
+ x: getXFromTextAlign(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]),
268
+ y: titleYPosition,
269
+ dy: "".concat(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE] + 10),
270
+ 'text-anchor': getTextAnchorFromTextAlign(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_TEXT_ALIGN]),
271
+ 'font-size': "".concat(subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_FONT_SIZE], "px"),
272
+ 'font-weight': subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_BOLD] ? _fontStyle.FONT_STYLE_OPTION_BOLD : 'normal',
273
+ 'font-style': subtitleFontStyle[_fontStyle.FONT_STYLE_OPTION_ITALIC] ? _fontStyle.FONT_STYLE_OPTION_ITALIC : 'normal',
274
+ 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],
275
+ 'data-test': 'visualization-subtitle'
276
+ };
277
+ Object.entries(subtitleAttributes).forEach(_ref5 => {
278
+ let [key, value] = _ref5;
279
+ return subtitle.setAttribute(key, value);
280
+ });
242
281
 
243
282
  if (config.subtitle) {
244
283
  subtitle.appendChild(document.createTextNode(config.subtitle));
@@ -254,7 +293,8 @@ const generateDVItem = (config, _ref3) => {
254
293
  noData,
255
294
  icon,
256
295
  containerWidth: width,
257
- containerHeight: height
296
+ containerHeight: height,
297
+ topMargin: TOP_MARGIN_FIXED + ((config.title ? parseInt(title.getAttribute('font-size')) : 0) + (config.subtitle ? parseInt(subtitle.getAttribute('font-size')) : 0)) * 2.5
258
298
  }));
259
299
  return svgContainer;
260
300
  };
@@ -281,7 +321,7 @@ const shouldUseContrastColor = function () {
281
321
  return L <= 0.179;
282
322
  };
283
323
 
284
- function _default(config, parentEl, _ref4) {
324
+ function _default(config, parentEl, _ref6) {
285
325
  let {
286
326
  dashboard,
287
327
  legendSets,
@@ -289,7 +329,7 @@ function _default(config, parentEl, _ref4) {
289
329
  noData,
290
330
  legendOptions,
291
331
  icon
292
- } = _ref4;
332
+ } = _ref6;
293
333
  const legendSet = legendOptions && legendSets[0];
294
334
  const legendColor = legendSet && (0, _legends.getColorByValueFromLegendSet)(legendSet, config.value);
295
335
  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'; // Compute text width before rendering
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
- let viewBoxWidth = textWidth;
33
-
34
- if (showIcon) {
35
- viewBoxWidth += iconSize + iconPadding;
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(viewBoxWidth, " ").concat(viewBoxHeight));
41
- svgValue.setAttribute('width', '95%');
42
- svgValue.setAttribute('height', '95%');
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,11 +93,11 @@ 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('viewBox', '0 0 48 48');
62
- iconSvgNode.setAttribute('y', "-".concat(iconSize / 2));
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));
65
102
  const parser = new DOMParser();
66
103
  const svgIconDocument = parser.parseFromString(icon, 'image/svg+xml');
@@ -68,14 +105,14 @@ const generateValueSVG = _ref => {
68
105
  svgValue.appendChild(iconSvgNode);
69
106
  }
70
107
 
108
+ const letterSpacing = Math.round(textSize * LETTER_SPACING_TEXT_SIZE_FACTOR);
71
109
  const textNode = document.createElementNS(svgNS, 'text');
72
110
  textNode.setAttribute('font-size', textSize);
73
111
  textNode.setAttribute('font-weight', '300');
74
- textNode.setAttribute('letter-spacing', '-5');
112
+ textNode.setAttribute('letter-spacing', letterSpacing < LETTER_SPACING_MIN_THRESHOLD ? LETTER_SPACING_MIN_THRESHOLD : letterSpacing > LETTER_SPACING_MAX_THRESHOLD ? LETTER_SPACING_MAX_THRESHOLD : letterSpacing);
75
113
  textNode.setAttribute('text-anchor', 'middle');
76
- textNode.setAttribute('x', showIcon ? "".concat((iconSize + iconPadding) / 2) : 0); // vertical align, "alignment-baseline: central" is not supported by Batik
77
-
78
- textNode.setAttribute('y', '.35em');
114
+ textNode.setAttribute('x', showIcon ? "".concat((iconSize + getIconPadding(textSize)) / 2) : 0);
115
+ textNode.setAttribute('y', topMargin / 2 + getTextHeightForNumbers(textSize) / 2);
79
116
  textNode.setAttribute('fill', fillColor);
80
117
  textNode.setAttribute('data-test', 'visualization-primary-value');
81
118
  textNode.appendChild(document.createTextNode(formattedValue));
@@ -85,8 +122,8 @@ const generateValueSVG = _ref => {
85
122
  const subTextNode = document.createElementNS(svgNS, 'text');
86
123
  subTextNode.setAttribute('text-anchor', 'middle');
87
124
  subTextNode.setAttribute('font-size', subTextSize);
88
- subTextNode.setAttribute('y', iconSize / 2);
89
- subTextNode.setAttribute('dy', subTextSize);
125
+ subTextNode.setAttribute('y', iconSize / 2 + topMargin / 2);
126
+ subTextNode.setAttribute('dy', subTextSize * 1.7);
90
127
  subTextNode.setAttribute('fill', textColor);
91
128
  subTextNode.appendChild(document.createTextNode(subText));
92
129
  svgValue.appendChild(subTextNode);
@@ -188,47 +225,49 @@ const generateDVItem = (config, _ref3) => {
188
225
  svgContainer.appendChild(background);
189
226
  }
190
227
 
191
- const svgWrapper = document.createElementNS(svgNS, 'svg');
228
+ const svgWrapper = document.createElementNS(svgNS, 'svg'); // title
229
+
192
230
  const title = document.createElementNS(svgNS, 'text');
193
231
  const titleFontStyle = mergeFontStyleWithDefault(fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_TITLE], FONT_STYLE_VISUALIZATION_TITLE);
194
- const titleYPosition = titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE];
195
- title.setAttribute('x', getXFromTextAlign(titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]));
196
- title.setAttribute('y', titleYPosition);
197
- title.setAttribute('text-anchor', getTextAnchorFromTextAlign(titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]));
198
- title.setAttribute('font-size', "".concat(titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE], "px"));
199
- title.setAttribute('font-weight', titleFontStyle[FONT_STYLE_OPTION_BOLD] ? FONT_STYLE_OPTION_BOLD : 'normal');
200
- title.setAttribute('font-style', titleFontStyle[FONT_STYLE_OPTION_ITALIC] ? FONT_STYLE_OPTION_ITALIC : 'normal');
201
-
202
- if (titleColor && titleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR] === defaultFontStyle[FONT_STYLE_VISUALIZATION_TITLE][FONT_STYLE_OPTION_TEXT_COLOR]) {
203
- title.setAttribute('fill', titleColor);
204
- } else {
205
- title.setAttribute('fill', titleFontStyle[FONT_STYLE_OPTION_TEXT_COLOR]);
206
- }
207
-
208
- title.setAttribute('data-test', 'visualization-title');
232
+ const titleYPosition = TOP_MARGIN_FIXED + parseInt(titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE]) + 'px';
233
+ const titleAttributes = {
234
+ x: getXFromTextAlign(titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]),
235
+ y: titleYPosition,
236
+ 'text-anchor': getTextAnchorFromTextAlign(titleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]),
237
+ 'font-size': "".concat(titleFontStyle[FONT_STYLE_OPTION_FONT_SIZE], "px"),
238
+ 'font-weight': titleFontStyle[FONT_STYLE_OPTION_BOLD] ? FONT_STYLE_OPTION_BOLD : 'normal',
239
+ 'font-style': titleFontStyle[FONT_STYLE_OPTION_ITALIC] ? FONT_STYLE_OPTION_ITALIC : 'normal',
240
+ 'data-test': 'visualization-title',
241
+ 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]
242
+ };
243
+ Object.entries(titleAttributes).forEach(_ref4 => {
244
+ let [key, value] = _ref4;
245
+ return title.setAttribute(key, value);
246
+ });
209
247
 
210
248
  if (config.title) {
211
249
  title.appendChild(document.createTextNode(config.title));
212
250
  svgWrapper.appendChild(title);
213
- }
251
+ } // subtitle
214
252
 
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
253
 
231
- subtitle.setAttribute('data-test', 'visualization-subtitle');
254
+ const subtitle = document.createElementNS(svgNS, 'text');
255
+ const subtitleFontStyle = mergeFontStyleWithDefault(fontStyle && fontStyle[FONT_STYLE_VISUALIZATION_SUBTITLE], FONT_STYLE_VISUALIZATION_SUBTITLE);
256
+ const subtitleAttributes = {
257
+ x: getXFromTextAlign(subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]),
258
+ y: titleYPosition,
259
+ dy: "".concat(subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE] + 10),
260
+ 'text-anchor': getTextAnchorFromTextAlign(subtitleFontStyle[FONT_STYLE_OPTION_TEXT_ALIGN]),
261
+ 'font-size': "".concat(subtitleFontStyle[FONT_STYLE_OPTION_FONT_SIZE], "px"),
262
+ 'font-weight': subtitleFontStyle[FONT_STYLE_OPTION_BOLD] ? FONT_STYLE_OPTION_BOLD : 'normal',
263
+ 'font-style': subtitleFontStyle[FONT_STYLE_OPTION_ITALIC] ? FONT_STYLE_OPTION_ITALIC : 'normal',
264
+ 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],
265
+ 'data-test': 'visualization-subtitle'
266
+ };
267
+ Object.entries(subtitleAttributes).forEach(_ref5 => {
268
+ let [key, value] = _ref5;
269
+ return subtitle.setAttribute(key, value);
270
+ });
232
271
 
233
272
  if (config.subtitle) {
234
273
  subtitle.appendChild(document.createTextNode(config.subtitle));
@@ -244,7 +283,8 @@ const generateDVItem = (config, _ref3) => {
244
283
  noData,
245
284
  icon,
246
285
  containerWidth: width,
247
- containerHeight: height
286
+ containerHeight: height,
287
+ topMargin: TOP_MARGIN_FIXED + ((config.title ? parseInt(title.getAttribute('font-size')) : 0) + (config.subtitle ? parseInt(subtitle.getAttribute('font-size')) : 0)) * 2.5
248
288
  }));
249
289
  return svgContainer;
250
290
  };
@@ -271,7 +311,7 @@ const shouldUseContrastColor = function () {
271
311
  return L <= 0.179;
272
312
  };
273
313
 
274
- export default function (config, parentEl, _ref4) {
314
+ export default function (config, parentEl, _ref6) {
275
315
  let {
276
316
  dashboard,
277
317
  legendSets,
@@ -279,7 +319,7 @@ export default function (config, parentEl, _ref4) {
279
319
  noData,
280
320
  legendOptions,
281
321
  icon
282
- } = _ref4;
322
+ } = _ref6;
283
323
  const legendSet = legendOptions && legendSets[0];
284
324
  const legendColor = legendSet && getColorByValueFromLegendSet(legendSet, config.value);
285
325
  let valueColor, titleColor, backgroundColor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhis2/analytics",
3
- "version": "25.1.9",
3
+ "version": "25.1.10",
4
4
  "main": "./build/cjs/index.js",
5
5
  "module": "./build/es/index.js",
6
6
  "exports": {