@oliasoft-open-source/charts-library 2.16.0-beta-1 → 2.16.0-beta-3
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/package.json +1 -1
- package/release-notes.md +6 -0
- package/src/components/controls/axes-options/axes-options.jsx +19 -6
- package/src/components/line-chart/constants/default-translations.js +24 -0
- package/src/components/line-chart/controls/axes-options/axes-options-form-state.js +4 -4
- package/src/components/line-chart/controls/controls.jsx +7 -2
- package/src/components/line-chart/controls/drag-options.jsx +14 -6
- package/src/components/line-chart/controls/legend-options.jsx +6 -2
- package/src/components/line-chart/controls/line-options.jsx +4 -3
- package/src/components/line-chart/hooks/use-chart-options.js +2 -0
- package/src/components/line-chart/line-chart-prop-types.js +7 -2
- package/src/components/line-chart/line-chart.jsx +13 -3
- package/src/components/line-chart/plugins/chart-area-text-plugin.js +182 -32
- package/src/components/line-chart/plugins/plugin-constants.js +11 -0
- package/src/components/line-chart/state/use-chart-state.js +23 -9
- package/src/components/line-chart/utils/generate-line-chart-datasets.js +8 -2
- package/src/components/line-chart/utils/get-translations/get-translations.js +17 -0
- package/src/helpers/chart-interface.ts +5 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oliasoft-open-source/charts-library",
|
|
3
|
-
"version": "2.16.0-beta-
|
|
3
|
+
"version": "2.16.0-beta-3",
|
|
4
4
|
"description": "React Chart Library (based on Chart.js and react-chart-js-2)",
|
|
5
5
|
"homepage": "https://gitlab.com/oliasoft-open-source/charts-library",
|
|
6
6
|
"bugs": {
|
package/release-notes.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
## 2.16.0
|
|
4
4
|
- Added common chart area text plugin
|
|
5
5
|
|
|
6
|
+
## 2.15.0
|
|
7
|
+
- Added translation, by provider handled([OW-11237](https://oliasoft.atlassian.net/browse/OW-11237))
|
|
8
|
+
|
|
9
|
+
## 2.14.1
|
|
10
|
+
- Fix save initAxesRange when dataset changed by parent component([OW-11332](https://oliasoft.atlassian.net/browse/OW-11332))
|
|
11
|
+
|
|
6
12
|
## 2.14.0
|
|
7
13
|
- Refactored line chart, for better performance, readable and reduce not needed re-renders
|
|
8
14
|
|
|
@@ -31,6 +31,7 @@ const AxesOptionsPopover = ({
|
|
|
31
31
|
onResetAxes,
|
|
32
32
|
close,
|
|
33
33
|
depthType,
|
|
34
|
+
translations,
|
|
34
35
|
}) => {
|
|
35
36
|
const [depthTypeState, setDepthTypeState] = useState(
|
|
36
37
|
depthType?.selectedDepthType,
|
|
@@ -124,7 +125,7 @@ const AxesOptionsPopover = ({
|
|
|
124
125
|
<Input
|
|
125
126
|
name="min"
|
|
126
127
|
value={min}
|
|
127
|
-
error={minError}
|
|
128
|
+
error={translations[minError]}
|
|
128
129
|
size={5}
|
|
129
130
|
width="100%"
|
|
130
131
|
onChange={(evt) =>
|
|
@@ -140,7 +141,7 @@ const AxesOptionsPopover = ({
|
|
|
140
141
|
<Input
|
|
141
142
|
name="max"
|
|
142
143
|
value={max}
|
|
143
|
-
error={maxError}
|
|
144
|
+
error={translations[maxError]}
|
|
144
145
|
size={5}
|
|
145
146
|
width="100%"
|
|
146
147
|
onChange={(evt) =>
|
|
@@ -190,16 +191,22 @@ const AxesOptionsPopover = ({
|
|
|
190
191
|
) : null}
|
|
191
192
|
|
|
192
193
|
<Flex gap="8px" alignItems="center">
|
|
193
|
-
<Button
|
|
194
|
+
<Button
|
|
195
|
+
type="submit"
|
|
196
|
+
small
|
|
197
|
+
colored
|
|
198
|
+
label={translations.done}
|
|
199
|
+
disabled={!valid}
|
|
200
|
+
/>
|
|
194
201
|
<Button
|
|
195
202
|
small
|
|
196
203
|
name="resetAxes"
|
|
197
|
-
label=
|
|
204
|
+
label={translations.resetAxes}
|
|
198
205
|
onClick={onReset}
|
|
199
206
|
disabled={!isCustomValue}
|
|
200
207
|
/>
|
|
201
208
|
<Text small muted>
|
|
202
|
-
|
|
209
|
+
{translations.orDoubleClickToCanvas}
|
|
203
210
|
</Text>
|
|
204
211
|
</Flex>
|
|
205
212
|
</form>
|
|
@@ -213,6 +220,7 @@ export const AxesOptions = ({
|
|
|
213
220
|
onUpdateAxes,
|
|
214
221
|
onResetAxes,
|
|
215
222
|
depthType,
|
|
223
|
+
translations,
|
|
216
224
|
}) => {
|
|
217
225
|
return (
|
|
218
226
|
<Popover
|
|
@@ -226,10 +234,15 @@ export const AxesOptions = ({
|
|
|
226
234
|
onUpdateAxes={onUpdateAxes}
|
|
227
235
|
onResetAxes={onResetAxes}
|
|
228
236
|
depthType={depthType}
|
|
237
|
+
translations={translations}
|
|
229
238
|
/>
|
|
230
239
|
}
|
|
231
240
|
>
|
|
232
|
-
<Tooltip
|
|
241
|
+
<Tooltip
|
|
242
|
+
text={translations.axesOptions}
|
|
243
|
+
placement="bottom"
|
|
244
|
+
display="flex"
|
|
245
|
+
>
|
|
233
246
|
<Button small basic colored="muted" round icon={<RiRuler2Line />} />
|
|
234
247
|
</Tooltip>
|
|
235
248
|
</Popover>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const defaultTranslations = Object.freeze({
|
|
2
|
+
label: 'Label',
|
|
3
|
+
pointsLines: 'Points & lines',
|
|
4
|
+
linesOnly: 'Lines only',
|
|
5
|
+
pointsOnly: 'Points only',
|
|
6
|
+
axesOptions: 'Axes options',
|
|
7
|
+
resetAxes: 'Reset Axes',
|
|
8
|
+
done: 'Done',
|
|
9
|
+
downloadAsPNG: 'Download as PNG',
|
|
10
|
+
showChart: 'Show chart',
|
|
11
|
+
showTable: 'Show table',
|
|
12
|
+
dragToZoom: 'Drag to zoom',
|
|
13
|
+
dragToPan: 'Drag to pan',
|
|
14
|
+
dragToMovePoints: 'Drag to move points',
|
|
15
|
+
dragDisabled: 'Drag disabled',
|
|
16
|
+
hideLegend: 'Hide Legend',
|
|
17
|
+
showLegend: 'Show Legend',
|
|
18
|
+
mustHaveAValue: 'Must have a value',
|
|
19
|
+
mustBeANumber: 'Must be a number',
|
|
20
|
+
mustBeLessThanMax: 'Must be less than max',
|
|
21
|
+
mustBeGreaterThanMin: 'Must be greater than min',
|
|
22
|
+
doubleClickToReset: 'Double click on canvas to reset',
|
|
23
|
+
orDoubleClickToCanvas: 'or double click on canvas',
|
|
24
|
+
});
|
|
@@ -36,13 +36,13 @@ const isEmptyString = (value) => value === '';
|
|
|
36
36
|
|
|
37
37
|
const createErrorMessages = (value, compareTo, type) => {
|
|
38
38
|
const errors = [];
|
|
39
|
-
if (isEmptyString(value)) errors.push('
|
|
40
|
-
if (!validNumber(value)) errors.push('
|
|
39
|
+
if (isEmptyString(value)) errors.push('mustHaveAValue');
|
|
40
|
+
if (!validNumber(value)) errors.push('mustBeANumber');
|
|
41
41
|
|
|
42
42
|
if (type === 'min' && !isLessThanMax(value, compareTo)) {
|
|
43
|
-
errors.push('
|
|
43
|
+
errors.push('mustBeLessThanMax');
|
|
44
44
|
} else if (type === 'max' && !isGreaterThanMin(value, compareTo)) {
|
|
45
|
-
errors.push('
|
|
45
|
+
errors.push('mustBeGreaterThanMin');
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
return errors;
|
|
@@ -21,6 +21,7 @@ const Controls = ({
|
|
|
21
21
|
options,
|
|
22
22
|
dispatch,
|
|
23
23
|
generatedDatasets,
|
|
24
|
+
translations,
|
|
24
25
|
}) => {
|
|
25
26
|
const {
|
|
26
27
|
enableDragPoints,
|
|
@@ -72,18 +73,21 @@ const Controls = ({
|
|
|
72
73
|
onUpdateAxes={onUpdateAxes}
|
|
73
74
|
onResetAxes={onResetAxes}
|
|
74
75
|
depthType={options.depthType}
|
|
76
|
+
translations={translations}
|
|
75
77
|
/>
|
|
76
78
|
<LineOptions
|
|
77
79
|
lineEnabled={lineEnabled}
|
|
78
80
|
pointsEnabled={pointsEnabled}
|
|
79
81
|
onToggleLine={onToggleLine}
|
|
80
82
|
onTogglePoints={onTogglePoints}
|
|
83
|
+
translations={translations}
|
|
81
84
|
/>
|
|
82
85
|
<LegendOptions
|
|
83
86
|
legendEnabled={legendEnabled}
|
|
84
87
|
onToggleLegend={onToggleLegend}
|
|
88
|
+
translations={translations}
|
|
85
89
|
/>
|
|
86
|
-
<Tooltip text=
|
|
90
|
+
<Tooltip text={translations.downloadAsPNG} placement="bottom-end">
|
|
87
91
|
<Button
|
|
88
92
|
small
|
|
89
93
|
basic
|
|
@@ -102,13 +106,14 @@ const Controls = ({
|
|
|
102
106
|
isDragDataAllowed={dragData.enableDragData}
|
|
103
107
|
onToggleDragPoints={onToggleDragPoints}
|
|
104
108
|
onDisableDragOptions={onDisableDragOptions}
|
|
109
|
+
translations={translations}
|
|
105
110
|
/>
|
|
106
111
|
</>
|
|
107
112
|
)}
|
|
108
113
|
|
|
109
114
|
{table ? (
|
|
110
115
|
<Tooltip
|
|
111
|
-
text={showTable ?
|
|
116
|
+
text={showTable ? translations.showChart : translations.showTable}
|
|
112
117
|
placement="bottom-end"
|
|
113
118
|
>
|
|
114
119
|
<Button
|
|
@@ -13,16 +13,24 @@ export const DragOptions = ({
|
|
|
13
13
|
isDragDataAllowed,
|
|
14
14
|
onToggleDragPoints,
|
|
15
15
|
onDisableDragOptions,
|
|
16
|
+
translations: {
|
|
17
|
+
dragToZoom,
|
|
18
|
+
doubleClickToReset,
|
|
19
|
+
dragToPan,
|
|
20
|
+
orDoubleClickToCanvas,
|
|
21
|
+
dragToMovePoints,
|
|
22
|
+
dragDisabled,
|
|
23
|
+
},
|
|
16
24
|
}) => {
|
|
17
25
|
const options = useMemo(
|
|
18
26
|
() => [
|
|
19
27
|
{
|
|
20
|
-
buttonLabel:
|
|
28
|
+
buttonLabel: dragToZoom,
|
|
21
29
|
label: (
|
|
22
30
|
<Flex direction="column">
|
|
23
31
|
<Text>Drag to zoom</Text>
|
|
24
32
|
<Text small muted>
|
|
25
|
-
|
|
33
|
+
{doubleClickToReset}
|
|
26
34
|
</Text>
|
|
27
35
|
</Flex>
|
|
28
36
|
),
|
|
@@ -31,12 +39,12 @@ export const DragOptions = ({
|
|
|
31
39
|
onClick: onToggleZoom,
|
|
32
40
|
},
|
|
33
41
|
{
|
|
34
|
-
buttonLabel:
|
|
42
|
+
buttonLabel: dragToPan,
|
|
35
43
|
label: (
|
|
36
44
|
<Flex direction="column">
|
|
37
45
|
<Text>Drag to pan</Text>
|
|
38
46
|
<Text small muted>
|
|
39
|
-
|
|
47
|
+
{orDoubleClickToCanvas}
|
|
40
48
|
</Text>
|
|
41
49
|
</Flex>
|
|
42
50
|
),
|
|
@@ -47,7 +55,7 @@ export const DragOptions = ({
|
|
|
47
55
|
...(isDragDataAllowed
|
|
48
56
|
? [
|
|
49
57
|
{
|
|
50
|
-
label:
|
|
58
|
+
label: dragToMovePoints,
|
|
51
59
|
icon: <TbHandStop />,
|
|
52
60
|
selected: enableDragPoints,
|
|
53
61
|
type: 'Option',
|
|
@@ -56,7 +64,7 @@ export const DragOptions = ({
|
|
|
56
64
|
]
|
|
57
65
|
: []),
|
|
58
66
|
{
|
|
59
|
-
label:
|
|
67
|
+
label: dragDisabled,
|
|
60
68
|
icon: <RiForbidLine />,
|
|
61
69
|
selected: !zoomEnabled && !panEnabled && !enableDragPoints,
|
|
62
70
|
onClick: onDisableDragOptions,
|
|
@@ -4,8 +4,12 @@ import { Button, Icon, Tooltip } from '@oliasoft-open-source/react-ui-library';
|
|
|
4
4
|
import { RiListUnordered } from 'react-icons/ri';
|
|
5
5
|
import listHideIcon from '../../../assets/icons/list-hide.svg';
|
|
6
6
|
|
|
7
|
-
export const LegendOptions = ({
|
|
8
|
-
|
|
7
|
+
export const LegendOptions = ({
|
|
8
|
+
legendEnabled,
|
|
9
|
+
onToggleLegend,
|
|
10
|
+
translations: { hideLegend, showLegend },
|
|
11
|
+
}) => {
|
|
12
|
+
const tooltipText = legendEnabled ? hideLegend : showLegend;
|
|
9
13
|
const icon = legendEnabled ? (
|
|
10
14
|
<RiListUnordered />
|
|
11
15
|
) : (
|
|
@@ -10,10 +10,11 @@ export const LineOptions = ({
|
|
|
10
10
|
onToggleLine,
|
|
11
11
|
onTogglePoints,
|
|
12
12
|
pointsEnabled,
|
|
13
|
+
translations,
|
|
13
14
|
}) => {
|
|
14
15
|
const options = [
|
|
15
16
|
{
|
|
16
|
-
label:
|
|
17
|
+
label: translations.pointsLines,
|
|
17
18
|
icon: <Icon icon={lineAndPointIcon} />,
|
|
18
19
|
selected: pointsEnabled && lineEnabled,
|
|
19
20
|
onClick: () => {
|
|
@@ -21,7 +22,7 @@ export const LineOptions = ({
|
|
|
21
22
|
},
|
|
22
23
|
},
|
|
23
24
|
{
|
|
24
|
-
label:
|
|
25
|
+
label: translations.linesOnly,
|
|
25
26
|
icon: <Icon icon={lineOnlyIcon} />,
|
|
26
27
|
selected: !pointsEnabled && lineEnabled,
|
|
27
28
|
onClick: () => {
|
|
@@ -30,7 +31,7 @@ export const LineOptions = ({
|
|
|
30
31
|
},
|
|
31
32
|
},
|
|
32
33
|
{
|
|
33
|
-
label:
|
|
34
|
+
label: translations.pointsOnly,
|
|
34
35
|
icon: <Icon icon={pointOnlyIcon} />,
|
|
35
36
|
selected: pointsEnabled && !lineEnabled,
|
|
36
37
|
onClick: () => {
|
|
@@ -42,6 +42,7 @@ export const useChartOptions = ({
|
|
|
42
42
|
labelAnnotation: {
|
|
43
43
|
showLabel,
|
|
44
44
|
text,
|
|
45
|
+
position,
|
|
45
46
|
fontSize,
|
|
46
47
|
xOffset,
|
|
47
48
|
yOffset,
|
|
@@ -136,6 +137,7 @@ export const useChartOptions = ({
|
|
|
136
137
|
chartAreaText: {
|
|
137
138
|
showLabel,
|
|
138
139
|
text,
|
|
140
|
+
position,
|
|
139
141
|
fontSize,
|
|
140
142
|
xOffset,
|
|
141
143
|
yOffset,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
|
+
import { TextLabelPosition } from './plugins/plugin-constants';
|
|
2
3
|
|
|
3
4
|
export const LineChartPropTypes = {
|
|
4
5
|
table: PropTypes.node,
|
|
@@ -78,6 +79,7 @@ export const LineChartPropTypes = {
|
|
|
78
79
|
PropTypes.string,
|
|
79
80
|
PropTypes.arrayOf(PropTypes.string),
|
|
80
81
|
]),
|
|
82
|
+
position: PropTypes.string,
|
|
81
83
|
fontSize: PropTypes.number,
|
|
82
84
|
xOffset: PropTypes.number,
|
|
83
85
|
yOffset: PropTypes.number,
|
|
@@ -104,7 +106,7 @@ export const LineChartPropTypes = {
|
|
|
104
106
|
}),
|
|
105
107
|
legend: PropTypes.shape({
|
|
106
108
|
display: PropTypes.bool,
|
|
107
|
-
position: PropTypes.oneOf(['top', 'bottom', 'right']),
|
|
109
|
+
position: PropTypes.oneOf(['top', 'bottom', 'right', 'left']),
|
|
108
110
|
align: PropTypes.oneOf(['start', 'center', 'end']),
|
|
109
111
|
customLegend: PropTypes.shape({
|
|
110
112
|
customLegendPlugin: PropTypes.object,
|
|
@@ -216,12 +218,15 @@ export const getDefaultProps = (props) => {
|
|
|
216
218
|
showLabel:
|
|
217
219
|
props.chart.options.annotations.labelAnnotation?.showLabel ?? false,
|
|
218
220
|
text: props.chart.options.annotations.labelAnnotation?.text ?? '',
|
|
221
|
+
position:
|
|
222
|
+
props.chart.options.annotations.labelAnnotation?.position ??
|
|
223
|
+
TextLabelPosition.BOTTOM_RIGHT,
|
|
219
224
|
fontSize:
|
|
220
225
|
props.chart.options.annotations.labelAnnotation?.fontSize ?? 12,
|
|
221
226
|
xOffset:
|
|
222
227
|
props.chart.options.annotations.labelAnnotation?.xOffset ?? 5,
|
|
223
228
|
yOffset:
|
|
224
|
-
props.chart.options.annotations.labelAnnotation?.yOffset ??
|
|
229
|
+
props.chart.options.annotations.labelAnnotation?.yOffset ?? 5,
|
|
225
230
|
maxWidth:
|
|
226
231
|
props.chart.options.annotations.labelAnnotation?.maxWidth ?? 300,
|
|
227
232
|
lineHeight:
|
|
@@ -29,6 +29,7 @@ import { useChartOptions } from './hooks/use-chart-options';
|
|
|
29
29
|
import { useChartPlugins } from './hooks/use-chart-plugins';
|
|
30
30
|
import { generateKey } from './utils/line-chart-utils';
|
|
31
31
|
import { useChartState } from './state/use-chart-state';
|
|
32
|
+
import { getTranslations } from './utils/get-translations/get-translations';
|
|
32
33
|
import { chartAreaTextPlugin } from './plugins/chart-area-text-plugin';
|
|
33
34
|
|
|
34
35
|
ChartJS.register(
|
|
@@ -55,7 +56,8 @@ ChartJS.register(
|
|
|
55
56
|
const LineChart = (props) => {
|
|
56
57
|
setDefaultTheme();
|
|
57
58
|
const chartRef = useRef(null);
|
|
58
|
-
const { table } = props;
|
|
59
|
+
const { table, translations: translationsRaw } = props;
|
|
60
|
+
const translations = getTranslations(translationsRaw);
|
|
59
61
|
const chart = getDefaultProps(props);
|
|
60
62
|
const {
|
|
61
63
|
data: { datasets },
|
|
@@ -78,11 +80,18 @@ const LineChart = (props) => {
|
|
|
78
80
|
);
|
|
79
81
|
|
|
80
82
|
const generatedDatasets = useMemo(() => {
|
|
81
|
-
return generateLineChartDatasets(datasets, state, options);
|
|
83
|
+
return generateLineChartDatasets(datasets, state, options, translations);
|
|
82
84
|
}, [state.lineEnabled, state.pointsEnabled, axes, annotations, graph]);
|
|
83
85
|
|
|
84
86
|
// Call the custom hooks.
|
|
85
|
-
useChartState({
|
|
87
|
+
useChartState({
|
|
88
|
+
chartRef,
|
|
89
|
+
options,
|
|
90
|
+
state,
|
|
91
|
+
generatedDatasets,
|
|
92
|
+
dispatch,
|
|
93
|
+
persistenceId,
|
|
94
|
+
});
|
|
86
95
|
|
|
87
96
|
const { resetZoom, handleDownload, handleKeyDown, handleKeyUp } =
|
|
88
97
|
useChartFunctions({
|
|
@@ -127,6 +136,7 @@ const LineChart = (props) => {
|
|
|
127
136
|
options={options}
|
|
128
137
|
dispatch={dispatch}
|
|
129
138
|
generatedDatasets={generatedDatasets}
|
|
139
|
+
translations={translations}
|
|
130
140
|
/>
|
|
131
141
|
{table && state.showTable ? (
|
|
132
142
|
<div className={styles.table}>{table}</div>
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { TextLabelPosition } from './plugin-constants';
|
|
2
|
+
import { AlignOptions } from '../../../helpers/enums';
|
|
3
|
+
|
|
1
4
|
const WORD_SEPARATOR = ' ';
|
|
2
5
|
const TRANSPARENT = 'rgba(0, 0, 0, 0.5)';
|
|
3
6
|
|
|
@@ -9,9 +12,9 @@ const TRANSPARENT = 'rgba(0, 0, 0, 0.5)';
|
|
|
9
12
|
* @returns {string[]} An array of words.
|
|
10
13
|
*/
|
|
11
14
|
const getWords = (text) => {
|
|
12
|
-
return Array.isArray(text)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
return (Array.isArray(text) ? text.join(WORD_SEPARATOR) : text).split(
|
|
16
|
+
WORD_SEPARATOR,
|
|
17
|
+
);
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
/**
|
|
@@ -24,30 +27,169 @@ const getWords = (text) => {
|
|
|
24
27
|
* @returns {number} The number of lines required.
|
|
25
28
|
*/
|
|
26
29
|
const countLines = (words, maxWidth, ctx) => {
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
const lineCount = words.reduce(
|
|
31
|
+
(lines, word) => {
|
|
32
|
+
const testLine = `${lines.currentLine}${word}${WORD_SEPARATOR}`;
|
|
33
|
+
const { width: testWidth } = ctx.measureText(testLine);
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
if (testWidth > maxWidth) {
|
|
36
|
+
return {
|
|
37
|
+
lineCount: lines.lineCount + 1,
|
|
38
|
+
currentLine: `${word}${WORD_SEPARATOR}`,
|
|
39
|
+
};
|
|
40
|
+
} else {
|
|
41
|
+
return { lineCount: lines.lineCount, currentLine: testLine };
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{ lineCount: 0, currentLine: '' },
|
|
45
|
+
);
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
line = `${word}${WORD_SEPARATOR}`;
|
|
36
|
-
lines++;
|
|
37
|
-
} else {
|
|
38
|
-
line = testLine;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
lines++; // Add the last line
|
|
42
|
-
return lines;
|
|
47
|
+
return lineCount.lineCount + 1; // Add the last line
|
|
43
48
|
};
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Renders the lines of text on the canvas context.
|
|
52
|
+
* It iterates over the array of lines and renders each line with the provided styling.
|
|
53
|
+
*
|
|
54
|
+
* @param {CanvasRenderingContext2D} ctx - The canvas rendering context to draw on.
|
|
55
|
+
* @param {string[]} lines - The array of lines to be rendered.
|
|
56
|
+
* @param {number} lineHeight - The height of each line.
|
|
57
|
+
* @param {number} x - The x-coordinate of the starting position.
|
|
58
|
+
* @param {number} y - The y-coordinate of the starting position.
|
|
59
|
+
* @param {string} position - The position for the text (one of the values from the TextLabelPosition enum).
|
|
60
|
+
*/
|
|
61
|
+
const drawText = (ctx, lines, lineHeight, x, y, position) => {
|
|
46
62
|
lines.forEach((line, index) => {
|
|
47
|
-
|
|
63
|
+
const lineY = position.includes('top')
|
|
64
|
+
? y + lineHeight * (index + 1) - 5
|
|
65
|
+
: y - (lines.length - 1 - index) * lineHeight;
|
|
66
|
+
ctx.fillText(line, x, lineY);
|
|
48
67
|
});
|
|
49
68
|
};
|
|
50
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Calculates the maximum width for the text based on the initial maximum width
|
|
72
|
+
* and the chart area width.
|
|
73
|
+
* @param {number} initialMaxWidth - The initial maximum width.
|
|
74
|
+
* @param {number} chartAreaWidth - The width of the chart area.
|
|
75
|
+
* @returns {number} The updated maximum width.
|
|
76
|
+
*/
|
|
77
|
+
const calculateMaxWidth = (initialMaxWidth, chartAreaWidth) => {
|
|
78
|
+
const factorMiddle = 0.5;
|
|
79
|
+
const factorSmall = 0.7;
|
|
80
|
+
const maxWidthFactor =
|
|
81
|
+
chartAreaWidth < 500
|
|
82
|
+
? factorMiddle
|
|
83
|
+
: chartAreaWidth < 700
|
|
84
|
+
? factorSmall
|
|
85
|
+
: 1;
|
|
86
|
+
return initialMaxWidth * maxWidthFactor;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Determines the legend dimensions (width and height) based on its position.
|
|
91
|
+
*
|
|
92
|
+
* @param {object} chart - The Chart.js chart object.
|
|
93
|
+
* @returns {object} An object containing the legend width and height.
|
|
94
|
+
*/
|
|
95
|
+
const getLegendDimensions = (chart) => {
|
|
96
|
+
const { legend } = chart;
|
|
97
|
+
|
|
98
|
+
const legendWidth =
|
|
99
|
+
legend && legend.display && legend.options.position === 'left'
|
|
100
|
+
? legend.width + legend.options.labels.padding * 2
|
|
101
|
+
: 0;
|
|
102
|
+
|
|
103
|
+
const legendHeight =
|
|
104
|
+
legend &&
|
|
105
|
+
legend.display &&
|
|
106
|
+
(legend.options.position === 'top' || legend.options.position === 'bottom')
|
|
107
|
+
? legend.height + legend.options.labels.padding * 2
|
|
108
|
+
: 0;
|
|
109
|
+
|
|
110
|
+
return { legendWidth, legendHeight };
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Determines the X and Y coordinates for the provided position.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} position - The position for the text (one of the values from the TextLabelPosition enum).
|
|
117
|
+
* @param {object} chart - The Chart.js chart object.
|
|
118
|
+
* @param {number} xOffset - The horizontal offset from the specified position.
|
|
119
|
+
* @param {number} yOffset - The vertical offset from the specified position.
|
|
120
|
+
* @returns {number[]} An array with the X and Y coordinates.
|
|
121
|
+
*/
|
|
122
|
+
const getPositionCoordinates = (position, chart, xOffset, yOffset) => {
|
|
123
|
+
const { chartArea } = chart;
|
|
124
|
+
|
|
125
|
+
const { legendWidth, legendHeight } = getLegendDimensions(chart);
|
|
126
|
+
|
|
127
|
+
switch (position) {
|
|
128
|
+
case TextLabelPosition.TOP_LEFT:
|
|
129
|
+
return [
|
|
130
|
+
chartArea.left + xOffset + legendWidth,
|
|
131
|
+
chartArea.top + yOffset + legendHeight,
|
|
132
|
+
];
|
|
133
|
+
case TextLabelPosition.TOP_CENTER:
|
|
134
|
+
return [
|
|
135
|
+
chartArea.left + chartArea.width / 2,
|
|
136
|
+
chartArea.top + yOffset + legendHeight,
|
|
137
|
+
];
|
|
138
|
+
case TextLabelPosition.TOP_RIGHT:
|
|
139
|
+
return [
|
|
140
|
+
chartArea.right - xOffset,
|
|
141
|
+
chartArea.top + yOffset + legendHeight,
|
|
142
|
+
];
|
|
143
|
+
case TextLabelPosition.MIDDLE_LEFT:
|
|
144
|
+
return [
|
|
145
|
+
chartArea.left + xOffset + legendWidth,
|
|
146
|
+
chartArea.top + chartArea.height / 2,
|
|
147
|
+
];
|
|
148
|
+
case TextLabelPosition.MIDDLE_CENTER:
|
|
149
|
+
return [
|
|
150
|
+
chartArea.left + chartArea.width / 2,
|
|
151
|
+
chartArea.top + chartArea.height / 2,
|
|
152
|
+
];
|
|
153
|
+
case TextLabelPosition.MIDDLE_RIGHT:
|
|
154
|
+
return [
|
|
155
|
+
chartArea.right - xOffset - legendWidth,
|
|
156
|
+
chartArea.top + chartArea.height / 2,
|
|
157
|
+
];
|
|
158
|
+
case TextLabelPosition.BOTTOM_LEFT:
|
|
159
|
+
return [
|
|
160
|
+
chartArea.left + xOffset + legendWidth,
|
|
161
|
+
chartArea.bottom - yOffset - legendHeight,
|
|
162
|
+
];
|
|
163
|
+
case TextLabelPosition.BOTTOM_CENTER:
|
|
164
|
+
return [
|
|
165
|
+
chartArea.left + chartArea.width / 2,
|
|
166
|
+
chartArea.bottom - yOffset - legendHeight,
|
|
167
|
+
];
|
|
168
|
+
case TextLabelPosition.BOTTOM_RIGHT:
|
|
169
|
+
default:
|
|
170
|
+
return [
|
|
171
|
+
chartArea.right - xOffset - legendWidth,
|
|
172
|
+
chartArea.bottom - yOffset - legendHeight,
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Determines the appropriate text alignment based on the position provided.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} position - The position for the text (one of the values from the TextLabelPosition enum).
|
|
181
|
+
* @returns {string} The text alignment ('left', 'center', or 'right').
|
|
182
|
+
*/
|
|
183
|
+
const getTextAlign = (position) => {
|
|
184
|
+
if (position.includes(AlignOptions.Center)) {
|
|
185
|
+
return AlignOptions.Center;
|
|
186
|
+
} else if (position.includes(AlignOptions.Left)) {
|
|
187
|
+
return AlignOptions.Left;
|
|
188
|
+
} else {
|
|
189
|
+
return AlignOptions.Right;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
51
193
|
export const chartAreaTextPlugin = {
|
|
52
194
|
id: 'chartAreaText',
|
|
53
195
|
|
|
@@ -64,26 +206,34 @@ export const chartAreaTextPlugin = {
|
|
|
64
206
|
const words = getWords(text);
|
|
65
207
|
const lines = countLines(words, maxWidth, ctx);
|
|
66
208
|
|
|
67
|
-
// Calculate and set the padding needed at the bottom of the chart
|
|
209
|
+
// Calculate and set the padding needed at the top or bottom of the chart
|
|
68
210
|
const paddingNeeded = lines * lineHeight + lineHeight;
|
|
69
|
-
|
|
211
|
+
if (options.position.includes('top')) {
|
|
212
|
+
chart.options.layout.padding.top = paddingNeeded;
|
|
213
|
+
} else {
|
|
214
|
+
chart.options.layout.padding.bottom = paddingNeeded;
|
|
215
|
+
}
|
|
70
216
|
|
|
71
217
|
ctx.restore();
|
|
72
218
|
},
|
|
73
219
|
|
|
74
220
|
beforeDraw: (chart, args, options) => {
|
|
75
|
-
const {
|
|
221
|
+
const {
|
|
222
|
+
showLabel,
|
|
223
|
+
text,
|
|
224
|
+
fontSize,
|
|
225
|
+
xOffset,
|
|
226
|
+
yOffset,
|
|
227
|
+
lineHeight,
|
|
228
|
+
maxWidth: initialMaxWidth,
|
|
229
|
+
position,
|
|
230
|
+
} = options;
|
|
76
231
|
const { ctx, chartArea } = chart;
|
|
77
232
|
|
|
78
233
|
if (!showLabel || !text) return;
|
|
79
234
|
|
|
80
235
|
// Determine the maxWidth based on chartArea width
|
|
81
|
-
const maxWidth =
|
|
82
|
-
chartArea.width < 500
|
|
83
|
-
? 100
|
|
84
|
-
: chartArea.width < 700
|
|
85
|
-
? 200
|
|
86
|
-
: options.maxWidth;
|
|
236
|
+
const maxWidth = calculateMaxWidth(initialMaxWidth, chartArea.width);
|
|
87
237
|
|
|
88
238
|
// Split the text into words and calculate the number of lines
|
|
89
239
|
const words = getWords(text);
|
|
@@ -105,12 +255,12 @@ export const chartAreaTextPlugin = {
|
|
|
105
255
|
ctx.save();
|
|
106
256
|
ctx.font = `${fontSize}px Arial`;
|
|
107
257
|
ctx.fillStyle = TRANSPARENT;
|
|
108
|
-
ctx.textAlign =
|
|
258
|
+
ctx.textAlign = AlignOptions.Right;
|
|
109
259
|
|
|
110
|
-
const y =
|
|
111
|
-
|
|
260
|
+
const [x, y] = getPositionCoordinates(position, chart, xOffset, yOffset);
|
|
261
|
+
ctx.textAlign = getTextAlign(position);
|
|
112
262
|
|
|
113
|
-
drawText(ctx, lines, lineHeight, x, y);
|
|
263
|
+
drawText(ctx, lines, lineHeight, x, y, position);
|
|
114
264
|
|
|
115
265
|
ctx.restore();
|
|
116
266
|
},
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const TextLabelPosition = {
|
|
2
|
+
TOP_LEFT: 'top-left',
|
|
3
|
+
TOP_CENTER: 'top-center',
|
|
4
|
+
TOP_RIGHT: 'top-right',
|
|
5
|
+
MIDDLE_LEFT: 'middle-left',
|
|
6
|
+
MIDDLE_CENTER: 'middle-center',
|
|
7
|
+
MIDDLE_RIGHT: 'middle-right',
|
|
8
|
+
BOTTOM_LEFT: 'bottom-left',
|
|
9
|
+
BOTTOM_CENTER: 'bottom-center',
|
|
10
|
+
BOTTOM_RIGHT: 'bottom-right',
|
|
11
|
+
};
|
|
@@ -75,26 +75,38 @@ const useUpdateAxesRanges = (range, dispatch) => {
|
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
78
|
+
* A custom hook to save the initial axes ranges of a chart when the generated datasets change.
|
|
79
79
|
*
|
|
80
|
-
* @param chartRef -
|
|
81
|
-
* @param axes -
|
|
82
|
-
* @param
|
|
80
|
+
* @param {Object} chartRef - A reference to the chart object.
|
|
81
|
+
* @param {Array} axes - An array of axes configurations.
|
|
82
|
+
* @param {Array} generatedDatasets - An array of generated datasets for the chart.
|
|
83
|
+
* @param {Function} dispatch - A dispatch function from the useContext hook for managing the state.
|
|
83
84
|
*/
|
|
84
|
-
const useSaveInitialAxesRanges = (
|
|
85
|
+
const useSaveInitialAxesRanges = (
|
|
86
|
+
chartRef,
|
|
87
|
+
axes,
|
|
88
|
+
generatedDatasets,
|
|
89
|
+
dispatch,
|
|
90
|
+
) => {
|
|
91
|
+
const prevGeneratedDatasets = useRef();
|
|
92
|
+
|
|
85
93
|
const saveInitialAxesRanges = useCallback(() => {
|
|
86
|
-
if (
|
|
94
|
+
if (
|
|
95
|
+
chartRef &&
|
|
96
|
+
!isEqual(generatedDatasets, prevGeneratedDatasets.current)
|
|
97
|
+
) {
|
|
87
98
|
const initialAxesRanges = getAxesRangesFromChart(chartRef, axes);
|
|
88
99
|
dispatch({
|
|
89
100
|
type: SAVE_INITIAL_AXES_RANGES,
|
|
90
101
|
payload: { initialAxesRanges },
|
|
91
102
|
});
|
|
103
|
+
prevGeneratedDatasets.current = generatedDatasets;
|
|
92
104
|
}
|
|
93
|
-
}, [chartRef]);
|
|
105
|
+
}, [chartRef, generatedDatasets]);
|
|
94
106
|
|
|
95
107
|
useEffect(() => {
|
|
96
108
|
saveInitialAxesRanges();
|
|
97
|
-
}, [saveInitialAxesRanges]);
|
|
109
|
+
}, [saveInitialAxesRanges, generatedDatasets]);
|
|
98
110
|
};
|
|
99
111
|
|
|
100
112
|
/**
|
|
@@ -103,6 +115,7 @@ const useSaveInitialAxesRanges = (chartRef, axes, dispatch) => {
|
|
|
103
115
|
* @param chartRef - The reference to the chart.
|
|
104
116
|
* @param options - The chart options.
|
|
105
117
|
* @param state - The chart state.
|
|
118
|
+
* @param {Array} generatedDatasets - An array of generated datasets for the chart.
|
|
106
119
|
* @param dispatch - The dispatch function for state management.
|
|
107
120
|
* @param persistenceId - The chart persistenceId.
|
|
108
121
|
*/
|
|
@@ -110,6 +123,7 @@ export const useChartState = ({
|
|
|
110
123
|
chartRef,
|
|
111
124
|
options,
|
|
112
125
|
state,
|
|
126
|
+
generatedDatasets,
|
|
113
127
|
dispatch,
|
|
114
128
|
persistenceId,
|
|
115
129
|
}) => {
|
|
@@ -123,5 +137,5 @@ export const useChartState = ({
|
|
|
123
137
|
useStoreChartStateInStorage(memoState, persistenceId);
|
|
124
138
|
useToggleCustomLegendVisibility(memoState, memoOptions);
|
|
125
139
|
useUpdateAxesRanges(range, dispatch);
|
|
126
|
-
useSaveInitialAxesRanges(chartRef, axes, dispatch);
|
|
140
|
+
useSaveInitialAxesRanges(chartRef, axes, generatedDatasets, dispatch);
|
|
127
141
|
};
|
|
@@ -15,9 +15,15 @@ import { generateRandomColor } from '../../../helpers/chart-utils';
|
|
|
15
15
|
* @param {Array} datasets - The initial datasets for the line chart.
|
|
16
16
|
* @param {Object} state - The state object containing chart settings (e.g., lineEnabled, pointsEnabled, axes).
|
|
17
17
|
* @param {Object} options - The options object containing additional settings (e.g., annotations, graph).
|
|
18
|
+
* @param {Object} translations - The translations object with the label property
|
|
18
19
|
* @returns {Array} - The generated line chart datasets with applied settings and configurations.
|
|
19
20
|
*/
|
|
20
|
-
export const generateLineChartDatasets = (
|
|
21
|
+
export const generateLineChartDatasets = (
|
|
22
|
+
datasets,
|
|
23
|
+
state,
|
|
24
|
+
options,
|
|
25
|
+
{ label },
|
|
26
|
+
) => {
|
|
21
27
|
const copyDataset = [...datasets];
|
|
22
28
|
const { annotations, graph } = options;
|
|
23
29
|
const {
|
|
@@ -88,7 +94,7 @@ export const generateLineChartDatasets = (datasets, state, options) => {
|
|
|
88
94
|
// Return the dataset with applied settings and configurations
|
|
89
95
|
return {
|
|
90
96
|
...line,
|
|
91
|
-
label: line.label ||
|
|
97
|
+
label: line.label || `${label} ${i + 1}`,
|
|
92
98
|
data: filteredData,
|
|
93
99
|
showLine: lineEnabled,
|
|
94
100
|
lineTension,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defaultTranslations } from '../../constants/default-translations';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Merges custom translations with the default translations.
|
|
5
|
+
* If a custom translation is provided for a key, it will override the default one.
|
|
6
|
+
* @param {object} translations - Custom translations.
|
|
7
|
+
* @returns {object} - The resulting translations object, containing both default and custom translations.
|
|
8
|
+
*/
|
|
9
|
+
export const getTranslations = (translations = {}) => {
|
|
10
|
+
return Object.keys(defaultTranslations).reduce(
|
|
11
|
+
(acc, key) => ({
|
|
12
|
+
...acc,
|
|
13
|
+
[key]: translations[key] || defaultTranslations[key],
|
|
14
|
+
}),
|
|
15
|
+
{},
|
|
16
|
+
);
|
|
17
|
+
};
|
|
@@ -28,11 +28,12 @@ export interface IChartAnnotationsData {
|
|
|
28
28
|
export interface ILabelAnnotation {
|
|
29
29
|
showLabel: boolean;
|
|
30
30
|
text: string | string[];
|
|
31
|
+
position?: string
|
|
31
32
|
fontSize?: number;
|
|
32
|
-
xOffset
|
|
33
|
-
yOffset
|
|
34
|
-
maxWidth
|
|
35
|
-
lineHeight
|
|
33
|
+
xOffset?: number
|
|
34
|
+
yOffset?: number
|
|
35
|
+
maxWidth?: number
|
|
36
|
+
lineHeight?: number
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
|