diff --git a/src/__demo__/SingleValue.stories.js b/src/__demo__/SingleValue.stories.js
index ac71e1e1b..0b3bda6fa 100644
--- a/src/__demo__/SingleValue.stories.js
+++ b/src/__demo__/SingleValue.stories.js
@@ -1,5 +1,5 @@
import { storiesOf } from '@storybook/react'
-import React, { useCallback, useState, useMemo, useRef, useEffect } from 'react'
+import React, { useState, useMemo, useRef, useEffect, useCallback } from 'react'
import { createVisualization } from '../index.js'
const constainerStyleBase = {
width: 400,
@@ -7,6 +7,13 @@ const constainerStyleBase = {
border: '1px solid magenta',
marginBottom: 14,
}
+const innerContainerStyle = {
+ overflow: 'hidden',
+ display: 'flex',
+ justifyContent: 'center',
+ height: '100%',
+}
+
const data = [
{
response: {
@@ -601,43 +608,23 @@ const layout = {
},
axes: [],
}
-// const icon =
-// ''
+const icon =
+ ''
const extraOptions = {
dashboard: false,
animation: 200,
legendSets: [],
- // icon,
+ icon,
}
storiesOf('SingleValue', module).add('default', () => {
const newChartRef = useRef(null)
+ const oldContainerRef = useRef(null)
+ const newContainerRef = useRef(null)
+ const [transpose, setTranspose] = useState(false)
const [width, setWidth] = useState(constainerStyleBase.width)
const [height, setHeight] = useState(constainerStyleBase.height)
- const onOldContainerMounted = useCallback((el) => {
- createVisualization(
- data,
- layout,
- el,
- extraOptions,
- undefined,
- undefined,
- 'dhis'
- )
- }, [])
- const onNewContainerMounted = useCallback((el) => {
- const obj = createVisualization(
- data,
- layout,
- el,
- extraOptions,
- undefined,
- undefined,
- 'singleValue'
- )
- newChartRef.current = obj.visualization
- }, [])
const containerStyle = useMemo(
() => ({
...constainerStyleBase,
@@ -647,17 +634,47 @@ storiesOf('SingleValue', module).add('default', () => {
[width, height]
)
useEffect(() => {
- if (newChartRef.current) {
- console.log('calling reflow')
- newChartRef.current.redraw()
+ if (oldContainerRef.current && newContainerRef.current) {
+ requestAnimationFrame(() => {
+ createVisualization(
+ data,
+ layout,
+ oldContainerRef.current,
+ extraOptions,
+ undefined,
+ undefined,
+ 'dhis'
+ )
+ const newVisualization = createVisualization(
+ data,
+ layout,
+ newContainerRef.current,
+ extraOptions,
+ undefined,
+ undefined,
+ 'singleValue'
+ )
+ newChartRef.current = newVisualization.visualization
+ })
}
}, [containerStyle])
+ const downloadOffline = useCallback(() => {
+ if (newChartRef.current) {
+ newChartRef.current.exportChartLocal({
+ sourceHeight: 768,
+ sourceWidth: 1024,
+ scale: 1,
+ fallbackToExportServer: false,
+ filename: 'testOfflineDownload',
+ showExportInProgress: true,
+ type: 'image/png',
+ })
+ }
+ }, [])
return (
<>
-
+
{
value={height.toString()}
/>
+
+
+
+
>
)
diff --git a/src/visualizations/config/generators/dhis/singleValue.js b/src/visualizations/config/generators/dhis/singleValue.js
index 25ec5bab9..934e14fdb 100644
--- a/src/visualizations/config/generators/dhis/singleValue.js
+++ b/src/visualizations/config/generators/dhis/singleValue.js
@@ -151,6 +151,7 @@ const generateValueSVG = ({
// embed icon to allow changing color
// (elements with fill need to use "currentColor" for this to work)
const iconSvgNode = document.createElementNS(svgNS, 'svg')
+ console.log('old', iconSize)
iconSvgNode.setAttribute('viewBox', '0 0 48 48')
iconSvgNode.setAttribute('width', iconSize)
iconSvgNode.setAttribute('height', iconSize)
diff --git a/src/visualizations/config/generators/highcharts/index.js b/src/visualizations/config/generators/highcharts/index.js
index ba3daf380..5356cc27f 100644
--- a/src/visualizations/config/generators/highcharts/index.js
+++ b/src/visualizations/config/generators/highcharts/index.js
@@ -3,6 +3,7 @@ import HM from 'highcharts/highcharts-more'
import HB from 'highcharts/modules/boost'
import HE from 'highcharts/modules/exporting'
import HNDTD from 'highcharts/modules/no-data-to-display'
+import HOE from 'highcharts/modules/offline-exporting'
import HPF from 'highcharts/modules/pattern-fill'
import HSG from 'highcharts/modules/solid-gauge'
import renderSingleValueSvg from './renderSingleValueSvg/index.js'
@@ -12,6 +13,7 @@ HM(H)
HSG(H)
HNDTD(H)
HE(H)
+HOE(H)
HPF(H)
HB(H)
@@ -90,33 +92,30 @@ export function highcharts(config, el) {
}
export function singleValue(config, el, extraOptions) {
- console.log('el', el)
- let elClientHeight, elClientWidth
return H.chart(el, {
accessibility: { enabled: false },
chart: {
backgroundColor: 'transparent',
events: {
- redraw: function () {
- if (
- el.clientHeight !== elClientHeight ||
- el.clientWidth !== elClientWidth
- ) {
- console.log('resize!!!', el)
- elClientHeight = el.clientHeight
- elClientWidth = el.clientWidth
- renderSingleValueSvg(config, el, extraOptions, this)
- } else {
- console.log('No action needed')
- }
+ load: function () {
+ renderSingleValueSvg(config, el, extraOptions, this)
},
},
animation: false,
},
credits: { enabled: false },
- // exporting: {
- // enabled: false,
- // },
+ exporting: {
+ enabled: true,
+ error: (options, error) => {
+ console.log('options', options)
+ console.log(error)
+ },
+ chartOptions: {
+ title: {
+ text: null,
+ },
+ },
+ },
lang: {
noData: null,
},
diff --git a/src/visualizations/config/generators/highcharts/renderSingleValueSvg/generateValueSVG.js b/src/visualizations/config/generators/highcharts/renderSingleValueSvg/generateValueSVG.js
index 99f2247f3..dd7efd382 100644
--- a/src/visualizations/config/generators/highcharts/renderSingleValueSvg/generateValueSVG.js
+++ b/src/visualizations/config/generators/highcharts/renderSingleValueSvg/generateValueSVG.js
@@ -1,12 +1,12 @@
import { colors } from '@dhis2/ui'
import {
+ svgNS,
LETTER_SPACING_MAX_THRESHOLD,
LETTER_SPACING_MIN_THRESHOLD,
LETTER_SPACING_TEXT_SIZE_FACTOR,
SUB_TEXT_SIZE_FACTOR,
SUB_TEXT_SIZE_MAX_THRESHOLD,
SUB_TEXT_SIZE_MIN_THRESHOLD,
- svgNS,
} from './constants.js'
import {
getIconPadding,
@@ -15,6 +15,8 @@ import {
getTextWidth,
} from './textSize.js'
+const parser = new DOMParser()
+
export const generateValueSVG = ({
renderer,
formattedValue,
@@ -27,8 +29,13 @@ export const generateValueSVG = ({
containerHeight,
topMargin = 0,
}) => {
- console.log('show value', renderer)
const showIcon = icon && formattedValue !== noData.text
+ const group = renderer
+ .g('value')
+ .css({
+ transform: 'scale(0.5) translate(100%, 100%)',
+ })
+ .add()
const textSize = getTextSize(
formattedValue,
@@ -48,28 +55,6 @@ export const generateValueSVG = ({
? SUB_TEXT_SIZE_MIN_THRESHOLD
: textSize * SUB_TEXT_SIZE_FACTOR
- const svgValue = document.createElementNS(svgNS, 'svg')
- svgValue.setAttribute('viewBox', `0 0 ${containerWidth} ${containerHeight}`)
- svgValue.setAttribute('width', '50%')
- svgValue.setAttribute('height', '50%')
- svgValue.setAttribute('x', '50%')
- svgValue.setAttribute('y', '50%')
- svgValue.setAttribute('style', 'overflow: visible')
-
- const box = renderer
- .rect(0, 0, containerWidth, containerHeight)
- .attr({
- with: '50%',
- height: '50%',
- x: '50%',
- y: '50%',
- })
- .css({
- overflow: 'visible',
- backgroundColor: 'green',
- })
- .add()
-
let fillColor = colors.grey900
if (valueColor) {
@@ -78,72 +63,73 @@ export const generateValueSVG = ({
fillColor = colors.grey600
}
+ const letterSpacing = Math.round(textSize * LETTER_SPACING_TEXT_SIZE_FACTOR)
+
+ const formattedValueText = renderer
+ .text(formattedValue)
+ .attr({
+ 'font-size': textSize,
+ 'font-weight': '300',
+ 'letter-spacing':
+ letterSpacing < LETTER_SPACING_MIN_THRESHOLD
+ ? LETTER_SPACING_MIN_THRESHOLD
+ : letterSpacing > LETTER_SPACING_MAX_THRESHOLD
+ ? LETTER_SPACING_MAX_THRESHOLD
+ : letterSpacing,
+ 'text-anchor': 'middle',
+ width: '100%',
+ x: showIcon ? `${iconSize / 2 + getIconPadding(textSize / 2)}` : 0,
+ y: topMargin / 2 + getTextHeightForNumbers(textSize) / 2,
+ fill: fillColor,
+ 'data-test': 'visualization-primary-value',
+ })
+ .add(group)
+
// show icon if configured in maintenance app
if (showIcon) {
- // embed icon to allow changing color
- // (elements with fill need to use "currentColor" for this to work)
- const iconSvgNode = document.createElementNS(svgNS, 'svg')
- iconSvgNode.setAttribute('viewBox', '0 0 48 48')
- iconSvgNode.setAttribute('width', iconSize)
- iconSvgNode.setAttribute('height', iconSize)
- iconSvgNode.setAttribute('y', (iconSize / 2 - topMargin / 2) * -1)
- iconSvgNode.setAttribute(
- 'x',
- `-${(iconSize + getIconPadding(textSize) + textWidth) / 2}`
- )
- iconSvgNode.setAttribute('style', `color: ${fillColor}`)
- iconSvgNode.setAttribute('data-test', 'visualization-icon')
-
- const parser = new DOMParser()
const svgIconDocument = parser.parseFromString(icon, 'image/svg+xml')
+ const iconElHeight =
+ svgIconDocument.documentElement.getAttribute('height')
+ const iconElWidth =
+ svgIconDocument.documentElement.getAttribute('width')
+ const x = ((iconSize + getIconPadding(textSize) + textWidth) / 2) * -1
+ const y = (iconSize / 2 - topMargin / 2) * -1
+ const iconGroup = renderer
+ .g('icon')
+ .attr('data-test', 'visualization-icon')
+ .css({
+ color: 'green',
+ // color: fillColor,
+ })
+ /* Force the group element to have the same dimensions as the original
+ * SVG image by adding this rect. This ensures the icon has the intended
+ * whitespace around it and makes scaling and translating easier. */
+ renderer.rect(0, 0, iconElWidth, iconElHeight).add(iconGroup)
Array.from(svgIconDocument.documentElement.children).forEach((node) =>
- iconSvgNode.appendChild(node)
+ iconGroup.element.appendChild(node)
)
-
- svgValue.appendChild(iconSvgNode)
+ iconGroup.add()
+ const formattedValueBox = formattedValueText.getBBox()
+ const targetHeight = textSize / 2
+ const scaleFactor = targetHeight / iconElHeight
+
+ console.log(formattedValueBox)
+ iconGroup.css({
+ transform: `scale(${scaleFactor}) translate(16px, 104px)`,
+ })
}
- const letterSpacing = Math.round(textSize * LETTER_SPACING_TEXT_SIZE_FACTOR)
-
- const textNode = document.createElementNS(svgNS, 'text')
- textNode.setAttribute('font-size', textSize)
- textNode.setAttribute('font-weight', '300')
- textNode.setAttribute(
- 'letter-spacing',
- letterSpacing < LETTER_SPACING_MIN_THRESHOLD
- ? LETTER_SPACING_MIN_THRESHOLD
- : letterSpacing > LETTER_SPACING_MAX_THRESHOLD
- ? LETTER_SPACING_MAX_THRESHOLD
- : letterSpacing
- )
- textNode.setAttribute('text-anchor', 'middle')
- textNode.setAttribute(
- 'x',
- showIcon ? `${(iconSize + getIconPadding(textSize)) / 2}` : 0
- )
- textNode.setAttribute(
- 'y',
- topMargin / 2 + getTextHeightForNumbers(textSize) / 2
- )
- textNode.setAttribute('fill', fillColor)
- textNode.setAttribute('data-test', 'visualization-primary-value')
-
- textNode.appendChild(document.createTextNode(formattedValue))
-
- svgValue.appendChild(textNode)
-
if (subText) {
- const subTextNode = document.createElementNS(svgNS, 'text')
- subTextNode.setAttribute('text-anchor', 'middle')
- subTextNode.setAttribute('font-size', subTextSize)
- subTextNode.setAttribute('y', iconSize / 2 + topMargin / 2)
- subTextNode.setAttribute('dy', subTextSize * 1.7)
- subTextNode.setAttribute('fill', textColor)
- subTextNode.appendChild(document.createTextNode(subText))
-
- svgValue.appendChild(subTextNode)
+ renderer
+ .text(subText)
+ .attr({
+ 'text-anchor': 'middle',
+ 'font-size': subTextSize,
+ y: iconSize / 2 + topMargin / 2,
+ dy: subTextSize * 1.7,
+ fill: textColor,
+ })
+ .add(group)
}
-
- return svgValue
}
diff --git a/src/visualizations/config/generators/highcharts/renderSingleValueSvg/generateValueSVGOLD.js b/src/visualizations/config/generators/highcharts/renderSingleValueSvg/generateValueSVGOLD.js
new file mode 100644
index 000000000..1a36f7eda
--- /dev/null
+++ b/src/visualizations/config/generators/highcharts/renderSingleValueSvg/generateValueSVGOLD.js
@@ -0,0 +1,135 @@
+import { colors } from '@dhis2/ui'
+import {
+ svgNS,
+ LETTER_SPACING_MAX_THRESHOLD,
+ LETTER_SPACING_MIN_THRESHOLD,
+ LETTER_SPACING_TEXT_SIZE_FACTOR,
+ SUB_TEXT_SIZE_FACTOR,
+ SUB_TEXT_SIZE_MAX_THRESHOLD,
+ SUB_TEXT_SIZE_MIN_THRESHOLD,
+} from './constants.js'
+import {
+ getIconPadding,
+ getTextHeightForNumbers,
+ getTextSize,
+ getTextWidth,
+} from './textSize.js'
+
+export const generateValueSVG = ({
+ renderer,
+ formattedValue,
+ subText,
+ valueColor,
+ textColor,
+ icon,
+ noData,
+ containerWidth,
+ containerHeight,
+ topMargin = 0,
+}) => {
+ const showIcon = icon && formattedValue !== noData.text
+
+ const textSize = getTextSize(
+ formattedValue,
+ containerWidth,
+ containerHeight,
+ showIcon
+ )
+
+ const textWidth = getTextWidth(formattedValue, `${textSize}px Roboto`)
+
+ const iconSize = textSize
+
+ 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
+
+ const svgValue = document.createElementNS(svgNS, 'svg')
+ svgValue.setAttribute('viewBox', `0 0 ${containerWidth} ${containerHeight}`)
+ svgValue.setAttribute('width', '50%')
+ svgValue.setAttribute('height', '50%')
+ svgValue.setAttribute('x', '50%')
+ svgValue.setAttribute('y', '50%')
+ svgValue.setAttribute('style', 'overflow: visible')
+
+ let fillColor = colors.grey900
+
+ if (valueColor) {
+ fillColor = valueColor
+ } else if (formattedValue === noData.text) {
+ fillColor = colors.grey600
+ }
+
+ // show icon if configured in maintenance app
+ if (showIcon) {
+ // embed icon to allow changing color
+ // (elements with fill need to use "currentColor" for this to work)
+ const iconSvgNode = document.createElementNS(svgNS, 'svg')
+ console.log('old', iconSize)
+ iconSvgNode.setAttribute('viewBox', '0 0 48 48')
+ iconSvgNode.setAttribute('width', iconSize)
+ iconSvgNode.setAttribute('height', iconSize)
+ iconSvgNode.setAttribute('y', (iconSize / 2 - topMargin / 2) * -1)
+ iconSvgNode.setAttribute(
+ 'x',
+ `-${(iconSize + getIconPadding(textSize) + textWidth) / 2}`
+ )
+ iconSvgNode.setAttribute('style', `color: ${fillColor}`)
+ iconSvgNode.setAttribute('data-test', 'visualization-icon')
+
+ const parser = new DOMParser()
+ const svgIconDocument = parser.parseFromString(icon, 'image/svg+xml')
+
+ Array.from(svgIconDocument.documentElement.children).forEach((node) =>
+ iconSvgNode.appendChild(node)
+ )
+
+ svgValue.appendChild(iconSvgNode)
+ }
+
+ const letterSpacing = Math.round(textSize * LETTER_SPACING_TEXT_SIZE_FACTOR)
+
+ const textNode = document.createElementNS(svgNS, 'text')
+ textNode.setAttribute('font-size', textSize)
+ textNode.setAttribute('font-weight', '300')
+ textNode.setAttribute(
+ 'letter-spacing',
+ letterSpacing < LETTER_SPACING_MIN_THRESHOLD
+ ? LETTER_SPACING_MIN_THRESHOLD
+ : letterSpacing > LETTER_SPACING_MAX_THRESHOLD
+ ? LETTER_SPACING_MAX_THRESHOLD
+ : letterSpacing
+ )
+ textNode.setAttribute('text-anchor', 'middle')
+ textNode.setAttribute(
+ 'x',
+ showIcon ? `${(iconSize + getIconPadding(textSize)) / 2}` : 0
+ )
+ textNode.setAttribute(
+ 'y',
+ topMargin / 2 + getTextHeightForNumbers(textSize) / 2
+ )
+ textNode.setAttribute('fill', fillColor)
+ textNode.setAttribute('data-test', 'visualization-primary-value')
+
+ textNode.appendChild(document.createTextNode(formattedValue))
+
+ svgValue.appendChild(textNode)
+
+ if (subText) {
+ const subTextNode = document.createElementNS(svgNS, 'text')
+ subTextNode.setAttribute('text-anchor', 'middle')
+ subTextNode.setAttribute('font-size', subTextSize)
+ subTextNode.setAttribute('y', iconSize / 2 + topMargin / 2)
+ subTextNode.setAttribute('dy', subTextSize * 1.7)
+ subTextNode.setAttribute('fill', textColor)
+ subTextNode.appendChild(document.createTextNode(subText))
+
+ svgValue.appendChild(subTextNode)
+ }
+
+ renderer.box.appendChild(svgValue)
+}