@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oliasoft-open-source/charts-library",
3
- "version": "2.16.0-beta-1",
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 type="submit" small colored label="Done" disabled={!valid} />
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="Reset Axes"
204
+ label={translations.resetAxes}
198
205
  onClick={onReset}
199
206
  disabled={!isCustomValue}
200
207
  />
201
208
  <Text small muted>
202
- or double click on canvas
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 text="Axes options" placement="bottom" display="flex">
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('Must have a value');
40
- if (!validNumber(value)) errors.push('Must be a number');
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('Must be less than max');
43
+ errors.push('mustBeLessThanMax');
44
44
  } else if (type === 'max' && !isGreaterThanMin(value, compareTo)) {
45
- errors.push('Must be greater than min');
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="Download as PNG" placement="bottom-end">
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 ? 'Show chart' : 'Show table'}
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: 'Drag to zoom',
28
+ buttonLabel: dragToZoom,
21
29
  label: (
22
30
  <Flex direction="column">
23
31
  <Text>Drag to zoom</Text>
24
32
  <Text small muted>
25
- Double click on canvas to reset
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: 'Drag to pan',
42
+ buttonLabel: dragToPan,
35
43
  label: (
36
44
  <Flex direction="column">
37
45
  <Text>Drag to pan</Text>
38
46
  <Text small muted>
39
- Double click on canvas to reset
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: 'Drag to move points',
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: 'Drag disabled',
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 = ({ legendEnabled, onToggleLegend }) => {
8
- const tooltipText = legendEnabled ? 'Hide Legend' : 'Show Legend';
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: 'Points & lines',
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: 'Lines only',
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: 'Points only',
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 ?? 10,
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({ chartRef, options, state, dispatch, persistenceId });
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
- ? text.join(WORD_SEPARATOR).split(WORD_SEPARATOR)
14
- : text.split(WORD_SEPARATOR);
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
- let line = '';
28
- let lines = 0;
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
- for (const word of words) {
31
- const testLine = `${line}${word}${WORD_SEPARATOR}`;
32
- const { width: testWidth } = ctx.measureText(testLine);
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
- if (testWidth > maxWidth) {
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
- const drawText = (ctx, lines, lineHeight, x, y) => {
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
- ctx.fillText(line, x, y - (lines.length - 1 - index) * lineHeight);
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
- chart.options.layout.padding.bottom = paddingNeeded;
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 { showLabel, text, fontSize, xOffset, yOffset, lineHeight } = options;
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 = 'right';
258
+ ctx.textAlign = AlignOptions.Right;
109
259
 
110
- const y = chartArea.bottom - yOffset;
111
- const x = chartArea.right - xOffset; // Subtract margin on the right
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
- * Hook for saving the initial axes ranges in the chart state.
78
+ * A custom hook to save the initial axes ranges of a chart when the generated datasets change.
79
79
  *
80
- * @param chartRef - The reference to the chart.
81
- * @param axes - The axes data.
82
- * @param dispatch - The dispatch function for state management.
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 = (chartRef, axes, dispatch) => {
85
+ const useSaveInitialAxesRanges = (
86
+ chartRef,
87
+ axes,
88
+ generatedDatasets,
89
+ dispatch,
90
+ ) => {
91
+ const prevGeneratedDatasets = useRef();
92
+
85
93
  const saveInitialAxesRanges = useCallback(() => {
86
- if (chartRef) {
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 = (datasets, state, options) => {
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 || `Label ${i + 1}`,
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: number
33
- yOffset: number
34
- maxWidth: number
35
- lineHeight: number
33
+ xOffset?: number
34
+ yOffset?: number
35
+ maxWidth?: number
36
+ lineHeight?: number
36
37
  }
37
38
 
38
39