@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'; // 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,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('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));
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', '-5');
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 + iconPadding) / 2) : 0); // vertical align, "alignment-baseline: central" is not supported by Batik
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
- 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');
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.setAttribute('data-test', 'visualization-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, _ref4) {
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
- } = _ref4;
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'; // 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,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('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));
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', '-5');
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 + iconPadding) / 2) : 0); // vertical align, "alignment-baseline: central" is not supported by Batik
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
- 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');
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.setAttribute('data-test', 'visualization-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, _ref4) {
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
- } = _ref4;
323
+ } = _ref6;
283
324
  const legendSet = legendOptions && legendSets[0];
284
325
  const legendColor = legendSet && getColorByValueFromLegendSet(legendSet, config.value);
285
326
  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.11",
4
4
  "main": "./build/cjs/index.js",
5
5
  "module": "./build/es/index.js",
6
6
  "exports": {