@oliasoft-open-source/charts-library 2.16.0-beta-2 → 2.16.0-beta-4
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 +3 -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 +5 -2
- package/src/components/line-chart/plugins/chart-area-text-plugin.js +144 -25
- package/src/components/line-chart/plugins/plugin-constants.js +11 -0
- 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-4",
|
|
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,9 @@
|
|
|
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
|
+
|
|
6
9
|
## 2.14.1
|
|
7
10
|
- Fix save initAxesRange when dataset changed by parent component([OW-11332](https://oliasoft.atlassian.net/browse/OW-11332))
|
|
8
11
|
|
|
@@ -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,7 +80,7 @@ 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.
|
|
@@ -134,6 +136,7 @@ const LineChart = (props) => {
|
|
|
134
136
|
options={options}
|
|
135
137
|
dispatch={dispatch}
|
|
136
138
|
generatedDatasets={generatedDatasets}
|
|
139
|
+
translations={translations}
|
|
137
140
|
/>
|
|
138
141
|
{table && state.showTable ? (
|
|
139
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,22 +27,24 @@ 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,10 +56,14 @@ const countLines = (words, maxWidth, ctx) => {
|
|
|
51
56
|
* @param {number} lineHeight - The height of each line.
|
|
52
57
|
* @param {number} x - The x-coordinate of the starting position.
|
|
53
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).
|
|
54
60
|
*/
|
|
55
|
-
const drawText = (ctx, lines, lineHeight, x, y) => {
|
|
61
|
+
const drawText = (ctx, lines, lineHeight, x, y, position) => {
|
|
56
62
|
lines.forEach((line, index) => {
|
|
57
|
-
|
|
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);
|
|
58
67
|
});
|
|
59
68
|
};
|
|
60
69
|
|
|
@@ -77,6 +86,111 @@ const calculateMaxWidth = (initialMaxWidth, chartAreaWidth) => {
|
|
|
77
86
|
return initialMaxWidth * maxWidthFactor;
|
|
78
87
|
};
|
|
79
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, width } = chart;
|
|
124
|
+
const temporaryChartAreaRight = width - 8;
|
|
125
|
+
|
|
126
|
+
const { legendWidth, legendHeight } = getLegendDimensions(chart);
|
|
127
|
+
|
|
128
|
+
switch (position) {
|
|
129
|
+
case TextLabelPosition.TOP_LEFT:
|
|
130
|
+
return [
|
|
131
|
+
chartArea.left + xOffset + legendWidth,
|
|
132
|
+
chartArea.top + yOffset + legendHeight,
|
|
133
|
+
];
|
|
134
|
+
case TextLabelPosition.TOP_CENTER:
|
|
135
|
+
return [
|
|
136
|
+
chartArea.left + chartArea.width / 2,
|
|
137
|
+
chartArea.top + yOffset + legendHeight,
|
|
138
|
+
];
|
|
139
|
+
case TextLabelPosition.TOP_RIGHT:
|
|
140
|
+
return [
|
|
141
|
+
temporaryChartAreaRight - xOffset, //replace within chartArea.right when resize bug will be fixed
|
|
142
|
+
chartArea.top + yOffset + legendHeight,
|
|
143
|
+
];
|
|
144
|
+
case TextLabelPosition.MIDDLE_LEFT:
|
|
145
|
+
return [
|
|
146
|
+
chartArea.left + xOffset + legendWidth,
|
|
147
|
+
chartArea.top + chartArea.height / 2,
|
|
148
|
+
];
|
|
149
|
+
case TextLabelPosition.MIDDLE_CENTER:
|
|
150
|
+
return [
|
|
151
|
+
chartArea.left + chartArea.width / 2,
|
|
152
|
+
chartArea.top + chartArea.height / 2,
|
|
153
|
+
];
|
|
154
|
+
case TextLabelPosition.MIDDLE_RIGHT:
|
|
155
|
+
return [
|
|
156
|
+
temporaryChartAreaRight - xOffset - legendWidth, //replace within chartArea.right when resize bug will be fixed
|
|
157
|
+
chartArea.top + chartArea.height / 2,
|
|
158
|
+
];
|
|
159
|
+
case TextLabelPosition.BOTTOM_LEFT:
|
|
160
|
+
return [
|
|
161
|
+
chartArea.left + xOffset + legendWidth,
|
|
162
|
+
chartArea.bottom - yOffset - legendHeight,
|
|
163
|
+
];
|
|
164
|
+
case TextLabelPosition.BOTTOM_CENTER:
|
|
165
|
+
return [
|
|
166
|
+
chartArea.left + chartArea.width / 2,
|
|
167
|
+
chartArea.bottom - yOffset - legendHeight,
|
|
168
|
+
];
|
|
169
|
+
case TextLabelPosition.BOTTOM_RIGHT:
|
|
170
|
+
default:
|
|
171
|
+
return [
|
|
172
|
+
temporaryChartAreaRight - xOffset - legendWidth, //replace within chartArea.right when resize bug will be fixed
|
|
173
|
+
chartArea.bottom - yOffset - legendHeight,
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Determines the appropriate text alignment based on the position provided.
|
|
180
|
+
*
|
|
181
|
+
* @param {string} position - The position for the text (one of the values from the TextLabelPosition enum).
|
|
182
|
+
* @returns {string} The text alignment ('left', 'center', or 'right').
|
|
183
|
+
*/
|
|
184
|
+
const getTextAlign = (position) => {
|
|
185
|
+
if (position.includes(AlignOptions.Center)) {
|
|
186
|
+
return AlignOptions.Center;
|
|
187
|
+
} else if (position.includes(AlignOptions.Left)) {
|
|
188
|
+
return AlignOptions.Left;
|
|
189
|
+
} else {
|
|
190
|
+
return AlignOptions.Right;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
80
194
|
export const chartAreaTextPlugin = {
|
|
81
195
|
id: 'chartAreaText',
|
|
82
196
|
|
|
@@ -93,9 +207,13 @@ export const chartAreaTextPlugin = {
|
|
|
93
207
|
const words = getWords(text);
|
|
94
208
|
const lines = countLines(words, maxWidth, ctx);
|
|
95
209
|
|
|
96
|
-
// Calculate and set the padding needed at the bottom of the chart
|
|
210
|
+
// Calculate and set the padding needed at the top or bottom of the chart
|
|
97
211
|
const paddingNeeded = lines * lineHeight + lineHeight;
|
|
98
|
-
|
|
212
|
+
if (options.position.includes('top')) {
|
|
213
|
+
chart.options.layout.padding.top = paddingNeeded;
|
|
214
|
+
} else {
|
|
215
|
+
chart.options.layout.padding.bottom = paddingNeeded;
|
|
216
|
+
}
|
|
99
217
|
|
|
100
218
|
ctx.restore();
|
|
101
219
|
},
|
|
@@ -109,6 +227,7 @@ export const chartAreaTextPlugin = {
|
|
|
109
227
|
yOffset,
|
|
110
228
|
lineHeight,
|
|
111
229
|
maxWidth: initialMaxWidth,
|
|
230
|
+
position,
|
|
112
231
|
} = options;
|
|
113
232
|
const { ctx, chartArea } = chart;
|
|
114
233
|
|
|
@@ -137,12 +256,12 @@ export const chartAreaTextPlugin = {
|
|
|
137
256
|
ctx.save();
|
|
138
257
|
ctx.font = `${fontSize}px Arial`;
|
|
139
258
|
ctx.fillStyle = TRANSPARENT;
|
|
140
|
-
ctx.textAlign =
|
|
259
|
+
ctx.textAlign = AlignOptions.Right;
|
|
141
260
|
|
|
142
|
-
const y =
|
|
143
|
-
|
|
261
|
+
const [x, y] = getPositionCoordinates(position, chart, xOffset, yOffset);
|
|
262
|
+
ctx.textAlign = getTextAlign(position);
|
|
144
263
|
|
|
145
|
-
drawText(ctx, lines, lineHeight, x, y);
|
|
264
|
+
drawText(ctx, lines, lineHeight, x, y, position);
|
|
146
265
|
|
|
147
266
|
ctx.restore();
|
|
148
267
|
},
|
|
@@ -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
|
+
};
|
|
@@ -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
|
|