@oliasoft-open-source/charts-library 0.0.2-beta-1
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/LICENSE +21 -0
- package/README.md +3 -0
- package/index.js +13 -0
- package/package.json +100 -0
- package/release-notes.md +178 -0
- package/src/assets/icons/line-and-point.svg +1 -0
- package/src/assets/icons/line-only.svg +1 -0
- package/src/assets/icons/list-hide.svg +1 -0
- package/src/assets/icons/point-only.svg +1 -0
- package/src/components/bar-chart/bar-chart-prop-types.js +188 -0
- package/src/components/bar-chart/bar-chart.interface.ts +84 -0
- package/src/components/bar-chart/bar-chart.jsx +243 -0
- package/src/components/bar-chart/bar-chart.module.less +61 -0
- package/src/components/bar-chart/get-bar-chart-data-labels.js +42 -0
- package/src/components/bar-chart/get-bar-chart-scales.js +123 -0
- package/src/components/bar-chart/get-bar-chart-tooltips.js +100 -0
- package/src/components/controls/axes-options/axes-options-form-state.js +95 -0
- package/src/components/controls/axes-options/axes-options.jsx +166 -0
- package/src/components/controls/controls.jsx +104 -0
- package/src/components/controls/controls.module.less +12 -0
- package/src/components/controls/drag-options.jsx +77 -0
- package/src/components/controls/legend-options.jsx +25 -0
- package/src/components/controls/line-options.jsx +54 -0
- package/src/components/line-chart/axis-scales/axis-scales.js +165 -0
- package/src/components/line-chart/datalabels-alignment/get-alignment-condition.js +13 -0
- package/src/components/line-chart/datalabels-alignment/get-alignment-data.js +20 -0
- package/src/components/line-chart/datalabels-alignment/get-datalabels-position.js +25 -0
- package/src/components/line-chart/get-axes-ranges-from-chart.js +10 -0
- package/src/components/line-chart/get-line-chart-data-labels.js +21 -0
- package/src/components/line-chart/get-line-chart-scales.js +120 -0
- package/src/components/line-chart/get-line-chart-tooltips.js +91 -0
- package/src/components/line-chart/line-chart-consts.js +7 -0
- package/src/components/line-chart/line-chart-prop-types.js +212 -0
- package/src/components/line-chart/line-chart-utils.js +192 -0
- package/src/components/line-chart/line-chart.interface.ts +107 -0
- package/src/components/line-chart/line-chart.jsx +531 -0
- package/src/components/line-chart/line-chart.minor-gridlines-plugin.js +88 -0
- package/src/components/line-chart/line-chart.module.less +77 -0
- package/src/components/line-chart/state/action-types.js +11 -0
- package/src/components/line-chart/state/initial-state.js +69 -0
- package/src/components/line-chart/state/line-chart-reducer.js +101 -0
- package/src/components/pie-chart/pie-chart-prop-types.js +111 -0
- package/src/components/pie-chart/pie-chart-utils.js +32 -0
- package/src/components/pie-chart/pie-chart.interface.ts +61 -0
- package/src/components/pie-chart/pie-chart.jsx +450 -0
- package/src/components/pie-chart/pie-chart.module.less +61 -0
- package/src/components/scatter-chart/scatter-chart.intefrace.ts +33 -0
- package/src/components/scatter-chart/scatter-chart.jsx +21 -0
- package/src/components/scatter-chart/scatter-chart.module.less +4 -0
- package/src/helpers/chart-border-plugin.js +19 -0
- package/src/helpers/chart-consts.js +62 -0
- package/src/helpers/chart-interface.ts +76 -0
- package/src/helpers/chart-utils.js +183 -0
- package/src/helpers/container.jsx +60 -0
- package/src/helpers/disabled-context.js +8 -0
- package/src/helpers/enums.js +87 -0
- package/src/helpers/get-chart-annotation.js +143 -0
- package/src/helpers/get-custom-legend-plugin-example.js +80 -0
- package/src/helpers/numbers/numbers.js +44 -0
- package/src/helpers/range/estimate-data-series-have-close-values.js +54 -0
- package/src/helpers/range/range.js +95 -0
- package/src/helpers/styles.js +68 -0
- package/src/helpers/text.js +6 -0
- package/src/style/external.less +4 -0
- package/src/style/fonts/lato/Lato-Bold.woff2 +0 -0
- package/src/style/fonts/lato/Lato-BoldItalic.woff2 +0 -0
- package/src/style/fonts/lato/Lato-Italic.woff2 +0 -0
- package/src/style/fonts/lato/Lato-Regular.woff2 +0 -0
- package/src/style/fonts.less +27 -0
- package/src/style/global.less +43 -0
- package/src/style/reset/reset.less +28 -0
- package/src/style/shared.less +24 -0
- package/src/style/variables.less +91 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import React, { useReducer } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Field,
|
|
5
|
+
Flex,
|
|
6
|
+
Input,
|
|
7
|
+
InputGroup,
|
|
8
|
+
InputGroupAddon,
|
|
9
|
+
Popover,
|
|
10
|
+
Text,
|
|
11
|
+
Tooltip,
|
|
12
|
+
} from '@oliasoft-open-source/react-ui-library';
|
|
13
|
+
import { RiRuler2Line } from 'react-icons/ri';
|
|
14
|
+
import { toNum } from '../../line-chart/line-chart-utils';
|
|
15
|
+
import {
|
|
16
|
+
actionTypes,
|
|
17
|
+
reducer,
|
|
18
|
+
initializeFormState,
|
|
19
|
+
validateAxes,
|
|
20
|
+
} from './axes-options-form-state';
|
|
21
|
+
|
|
22
|
+
const AxesOptionsPopover = ({
|
|
23
|
+
initialAxesRanges,
|
|
24
|
+
axes,
|
|
25
|
+
controlsAxesLabels,
|
|
26
|
+
onUpdateAxes,
|
|
27
|
+
onResetAxes,
|
|
28
|
+
close,
|
|
29
|
+
}) => {
|
|
30
|
+
const [formState, dispatch] = useReducer(
|
|
31
|
+
reducer,
|
|
32
|
+
{
|
|
33
|
+
axes,
|
|
34
|
+
initialAxesRanges,
|
|
35
|
+
},
|
|
36
|
+
initializeFormState,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const { errors, valid } = validateAxes(formState);
|
|
40
|
+
|
|
41
|
+
const onEditValue = ({ name, value, id }) => {
|
|
42
|
+
dispatch({
|
|
43
|
+
type: actionTypes.valueUpdated,
|
|
44
|
+
payload: { name, value, id },
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const onDone = () => {
|
|
49
|
+
if (valid) {
|
|
50
|
+
const sanitizedFormState = formState.map((axis) => ({
|
|
51
|
+
...axis,
|
|
52
|
+
min: toNum(axis.min),
|
|
53
|
+
max: toNum(axis.max),
|
|
54
|
+
}));
|
|
55
|
+
onUpdateAxes({
|
|
56
|
+
axes: sanitizedFormState,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
close();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const onReset = () => {
|
|
63
|
+
//reset local form
|
|
64
|
+
dispatch({
|
|
65
|
+
type: actionTypes.reset,
|
|
66
|
+
payload: { axes, initialAxesRanges },
|
|
67
|
+
});
|
|
68
|
+
//reset parent state
|
|
69
|
+
onResetAxes();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const isCustomValue = axes.filter((axis) => axis.max || axis.min).length > 0;
|
|
73
|
+
const handleInputFocus = (e) => e.target.select();
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
{axes.map((axis, i) => {
|
|
77
|
+
const axisLabel = controlsAxesLabels.find(
|
|
78
|
+
(el) => el.id === axis.id,
|
|
79
|
+
)?.label;
|
|
80
|
+
|
|
81
|
+
const min = formState.find((a) => a.id === axis.id)?.min;
|
|
82
|
+
const max = formState.find((a) => a.id === axis.id)?.max;
|
|
83
|
+
const minError = errors?.find((a) => a.id === axis.id)?.min?.[0];
|
|
84
|
+
const maxError = errors?.find((a) => a.id === axis.id)?.max?.[0];
|
|
85
|
+
return (
|
|
86
|
+
<Field key={i} label={axisLabel || axis.id || ''}>
|
|
87
|
+
<InputGroup small>
|
|
88
|
+
<Input
|
|
89
|
+
name="min"
|
|
90
|
+
value={min}
|
|
91
|
+
error={minError}
|
|
92
|
+
size={5}
|
|
93
|
+
width="100%"
|
|
94
|
+
onChange={(evt) =>
|
|
95
|
+
onEditValue({
|
|
96
|
+
name: evt.target.name,
|
|
97
|
+
value: evt.target.value,
|
|
98
|
+
id: axis.id,
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
onFocus={handleInputFocus}
|
|
102
|
+
/>
|
|
103
|
+
<InputGroupAddon>to</InputGroupAddon>
|
|
104
|
+
<Input
|
|
105
|
+
name="max"
|
|
106
|
+
value={max}
|
|
107
|
+
error={maxError}
|
|
108
|
+
size={5}
|
|
109
|
+
width="100%"
|
|
110
|
+
onChange={(evt) =>
|
|
111
|
+
onEditValue({
|
|
112
|
+
name: evt.target.name,
|
|
113
|
+
value: evt.target.value,
|
|
114
|
+
id: axis.id,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
onFocus={handleInputFocus}
|
|
118
|
+
/>
|
|
119
|
+
</InputGroup>
|
|
120
|
+
</Field>
|
|
121
|
+
);
|
|
122
|
+
})}
|
|
123
|
+
<Flex gap="8px" alignItems="center">
|
|
124
|
+
<Button small colored label="Done" onClick={onDone} disabled={!valid} />
|
|
125
|
+
<Button
|
|
126
|
+
small
|
|
127
|
+
name="resetAxes"
|
|
128
|
+
label="Reset Axes"
|
|
129
|
+
onClick={onReset}
|
|
130
|
+
disabled={!isCustomValue}
|
|
131
|
+
/>
|
|
132
|
+
<Text small muted>
|
|
133
|
+
or click on canvas
|
|
134
|
+
</Text>
|
|
135
|
+
</Flex>
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const AxesOptions = ({
|
|
141
|
+
initialAxesRanges,
|
|
142
|
+
axes,
|
|
143
|
+
controlsAxesLabels,
|
|
144
|
+
onUpdateAxes,
|
|
145
|
+
onResetAxes,
|
|
146
|
+
}) => {
|
|
147
|
+
return (
|
|
148
|
+
<Popover
|
|
149
|
+
placement="bottom"
|
|
150
|
+
overflowContainer
|
|
151
|
+
content={
|
|
152
|
+
<AxesOptionsPopover
|
|
153
|
+
initialAxesRanges={initialAxesRanges}
|
|
154
|
+
axes={axes}
|
|
155
|
+
controlsAxesLabels={controlsAxesLabels}
|
|
156
|
+
onUpdateAxes={onUpdateAxes}
|
|
157
|
+
onResetAxes={onResetAxes}
|
|
158
|
+
/>
|
|
159
|
+
}
|
|
160
|
+
>
|
|
161
|
+
<Tooltip text="Axes options" placement="bottom" display="flex">
|
|
162
|
+
<Button small basic colored="muted" round icon={<RiRuler2Line />} />
|
|
163
|
+
</Tooltip>
|
|
164
|
+
</Popover>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, Text, Tooltip } from '@oliasoft-open-source/react-ui-library';
|
|
3
|
+
import {
|
|
4
|
+
RiFileDownloadLine,
|
|
5
|
+
RiLineChartLine,
|
|
6
|
+
RiTableLine,
|
|
7
|
+
} from 'react-icons/ri';
|
|
8
|
+
import { LineOptions } from './line-options';
|
|
9
|
+
import { DragOptions } from './drag-options';
|
|
10
|
+
import { AxesOptions } from './axes-options/axes-options';
|
|
11
|
+
import { LegendOptions } from './legend-options';
|
|
12
|
+
import styles from './controls.module.less';
|
|
13
|
+
|
|
14
|
+
const Controls = ({
|
|
15
|
+
axes,
|
|
16
|
+
controlsAxesLabels,
|
|
17
|
+
chart,
|
|
18
|
+
headerComponent,
|
|
19
|
+
legendEnabled,
|
|
20
|
+
lineEnabled,
|
|
21
|
+
onDownload,
|
|
22
|
+
onResetAxes,
|
|
23
|
+
onUpdateAxes,
|
|
24
|
+
onToggleLegend,
|
|
25
|
+
onToggleLine,
|
|
26
|
+
onTogglePan,
|
|
27
|
+
onTogglePoints,
|
|
28
|
+
onToggleTable,
|
|
29
|
+
onToggleZoom,
|
|
30
|
+
panEnabled,
|
|
31
|
+
pointsEnabled,
|
|
32
|
+
initialAxesRanges,
|
|
33
|
+
showTable,
|
|
34
|
+
subheaderComponent,
|
|
35
|
+
table,
|
|
36
|
+
zoomEnabled,
|
|
37
|
+
}) => {
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
<div className={styles.controls}>
|
|
41
|
+
{!!chart.options.title && <Text bold>{chart.options.title}</Text>}
|
|
42
|
+
{headerComponent}
|
|
43
|
+
<div className={styles.buttons}>
|
|
44
|
+
{!showTable && (
|
|
45
|
+
<>
|
|
46
|
+
<AxesOptions
|
|
47
|
+
initialAxesRanges={initialAxesRanges}
|
|
48
|
+
axes={axes}
|
|
49
|
+
controlsAxesLabels={controlsAxesLabels}
|
|
50
|
+
onUpdateAxes={onUpdateAxes}
|
|
51
|
+
onResetAxes={onResetAxes}
|
|
52
|
+
/>
|
|
53
|
+
<LineOptions
|
|
54
|
+
lineEnabled={lineEnabled}
|
|
55
|
+
pointsEnabled={pointsEnabled}
|
|
56
|
+
onToggleLine={onToggleLine}
|
|
57
|
+
onTogglePoints={onTogglePoints}
|
|
58
|
+
/>
|
|
59
|
+
<LegendOptions
|
|
60
|
+
legendEnabled={legendEnabled}
|
|
61
|
+
onToggleLegend={onToggleLegend}
|
|
62
|
+
/>
|
|
63
|
+
<DragOptions
|
|
64
|
+
panEnabled={panEnabled}
|
|
65
|
+
zoomEnabled={zoomEnabled}
|
|
66
|
+
onTogglePan={onTogglePan}
|
|
67
|
+
onToggleZoom={onToggleZoom}
|
|
68
|
+
/>
|
|
69
|
+
<Tooltip text="Download as PNG" placement="bottom-end">
|
|
70
|
+
<Button
|
|
71
|
+
small
|
|
72
|
+
basic
|
|
73
|
+
colored="muted"
|
|
74
|
+
round
|
|
75
|
+
icon={<RiFileDownloadLine />}
|
|
76
|
+
onClick={onDownload}
|
|
77
|
+
/>
|
|
78
|
+
</Tooltip>
|
|
79
|
+
</>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
{table ? (
|
|
83
|
+
<Tooltip
|
|
84
|
+
text={showTable ? 'Show chart' : 'Show table'}
|
|
85
|
+
placement="bottom-end"
|
|
86
|
+
>
|
|
87
|
+
<Button
|
|
88
|
+
small
|
|
89
|
+
basic
|
|
90
|
+
colored="muted"
|
|
91
|
+
round
|
|
92
|
+
icon={showTable ? <RiLineChartLine /> : <RiTableLine />}
|
|
93
|
+
onClick={onToggleTable}
|
|
94
|
+
/>
|
|
95
|
+
</Tooltip>
|
|
96
|
+
) : null}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
{subheaderComponent}
|
|
100
|
+
</>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default Controls;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Flex,
|
|
5
|
+
Text,
|
|
6
|
+
Tooltip,
|
|
7
|
+
} from '@oliasoft-open-source/react-ui-library';
|
|
8
|
+
import { RiDragMove2Line, RiForbidLine, RiZoomInLine } from 'react-icons/ri';
|
|
9
|
+
|
|
10
|
+
export const DragOptions = ({
|
|
11
|
+
onTogglePan,
|
|
12
|
+
onToggleZoom,
|
|
13
|
+
panEnabled,
|
|
14
|
+
zoomEnabled,
|
|
15
|
+
}) => {
|
|
16
|
+
// TODO: Translate strings
|
|
17
|
+
const options = [
|
|
18
|
+
{
|
|
19
|
+
label: (
|
|
20
|
+
<Flex direction="column">
|
|
21
|
+
<Text>Drag to zoom</Text>
|
|
22
|
+
{/* <Text small muted>
|
|
23
|
+
Hold shift to change axis
|
|
24
|
+
</Text> */}
|
|
25
|
+
<Text small muted>
|
|
26
|
+
Click on canvas to reset
|
|
27
|
+
</Text>
|
|
28
|
+
</Flex>
|
|
29
|
+
),
|
|
30
|
+
icon: <RiZoomInLine />,
|
|
31
|
+
selected: zoomEnabled,
|
|
32
|
+
onClick: () => {
|
|
33
|
+
onToggleZoom();
|
|
34
|
+
onTogglePan();
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: (
|
|
39
|
+
<Flex direction="column">
|
|
40
|
+
<Text>Drag to pan</Text>
|
|
41
|
+
{/* <Text small muted>
|
|
42
|
+
Hold shift to change axis
|
|
43
|
+
</Text> */}
|
|
44
|
+
<Text small muted>
|
|
45
|
+
Click on canvas to reset
|
|
46
|
+
</Text>
|
|
47
|
+
</Flex>
|
|
48
|
+
),
|
|
49
|
+
icon: <RiDragMove2Line />,
|
|
50
|
+
selected: panEnabled,
|
|
51
|
+
onClick: () => {
|
|
52
|
+
onTogglePan();
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: 'Drag disabled',
|
|
57
|
+
icon: <RiForbidLine />,
|
|
58
|
+
selected: !zoomEnabled && !panEnabled,
|
|
59
|
+
onClick: () => {
|
|
60
|
+
onToggleZoom();
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
const selectedOption = options.filter((option) => option.selected)[0];
|
|
65
|
+
return (
|
|
66
|
+
<Tooltip text={selectedOption.label} placement="bottom-end">
|
|
67
|
+
<Button
|
|
68
|
+
small
|
|
69
|
+
basic
|
|
70
|
+
colored="muted"
|
|
71
|
+
round
|
|
72
|
+
icon={selectedOption.icon}
|
|
73
|
+
onClick={selectedOption.onClick}
|
|
74
|
+
/>
|
|
75
|
+
</Tooltip>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, Icon, Tooltip } from '@oliasoft-open-source/react-ui-library';
|
|
3
|
+
import { RiListUnordered } from 'react-icons/ri';
|
|
4
|
+
import listHideIcon from '../../assets/icons/list-hide.svg';
|
|
5
|
+
|
|
6
|
+
export const LegendOptions = ({ legendEnabled, onToggleLegend }) => {
|
|
7
|
+
// TODO: Translate strings
|
|
8
|
+
return (
|
|
9
|
+
<Tooltip
|
|
10
|
+
text={legendEnabled ? 'Legend shown' : 'Legend hidden'}
|
|
11
|
+
placement="bottom"
|
|
12
|
+
>
|
|
13
|
+
<Button
|
|
14
|
+
small
|
|
15
|
+
basic
|
|
16
|
+
colored="muted"
|
|
17
|
+
round
|
|
18
|
+
icon={
|
|
19
|
+
legendEnabled ? <RiListUnordered /> : <Icon icon={listHideIcon} />
|
|
20
|
+
}
|
|
21
|
+
onClick={onToggleLegend}
|
|
22
|
+
/>
|
|
23
|
+
</Tooltip>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, Icon, Tooltip } from '@oliasoft-open-source/react-ui-library';
|
|
3
|
+
import lineOnlyIcon from '../../assets/icons/line-only.svg';
|
|
4
|
+
import pointOnlyIcon from '../../assets/icons/point-only.svg';
|
|
5
|
+
import lineAndPointIcon from '../../assets/icons/line-and-point.svg';
|
|
6
|
+
|
|
7
|
+
export const LineOptions = ({
|
|
8
|
+
lineEnabled,
|
|
9
|
+
onToggleLine,
|
|
10
|
+
onTogglePoints,
|
|
11
|
+
pointsEnabled,
|
|
12
|
+
}) => {
|
|
13
|
+
// TODO: Translate strings
|
|
14
|
+
const options = [
|
|
15
|
+
{
|
|
16
|
+
label: 'Points & lines',
|
|
17
|
+
icon: <Icon icon={lineAndPointIcon} />,
|
|
18
|
+
selected: pointsEnabled && lineEnabled,
|
|
19
|
+
onClick: () => {
|
|
20
|
+
onTogglePoints();
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: 'Lines only',
|
|
25
|
+
icon: <Icon icon={lineOnlyIcon} />,
|
|
26
|
+
selected: !pointsEnabled && lineEnabled,
|
|
27
|
+
onClick: () => {
|
|
28
|
+
onTogglePoints();
|
|
29
|
+
onToggleLine();
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: 'Points only',
|
|
34
|
+
icon: <Icon icon={pointOnlyIcon} />,
|
|
35
|
+
selected: pointsEnabled && !lineEnabled,
|
|
36
|
+
onClick: () => {
|
|
37
|
+
onToggleLine();
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
const selectedOption = options.filter((option) => option.selected)[0];
|
|
42
|
+
return (
|
|
43
|
+
<Tooltip text={selectedOption.label} placement="bottom">
|
|
44
|
+
<Button
|
|
45
|
+
small
|
|
46
|
+
basic
|
|
47
|
+
colored="muted"
|
|
48
|
+
round
|
|
49
|
+
icon={selectedOption.icon}
|
|
50
|
+
onClick={selectedOption.onClick}
|
|
51
|
+
/>
|
|
52
|
+
</Tooltip>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import getLineChartScales from '../get-line-chart-scales';
|
|
2
|
+
import { AxisType } from '../../../helpers/enums';
|
|
3
|
+
import { getAxisTypeFromKey } from '../line-chart-utils';
|
|
4
|
+
import { estimateDataSeriesHaveCloseValues } from '../../../helpers/range/estimate-data-series-have-close-values';
|
|
5
|
+
import { getSuggestedAxisRange } from '../../../helpers/range/range';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
Function that counts the number of occurences of "x" and "y" in an array and returns the one with the highest count.
|
|
9
|
+
@param {Array} scalesKeys - An array of strings that may contain "x" or "y".
|
|
10
|
+
@return {string} - Returns "x" if "x" occurs more times, "y" if "y" occurs more times, or null if they occur the same number of times.
|
|
11
|
+
*/
|
|
12
|
+
const checkMultiAxis = (scalesKeys) => {
|
|
13
|
+
let counts = {
|
|
14
|
+
x: 0,
|
|
15
|
+
y: 0,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
scalesKeys.forEach((axeKey) => {
|
|
19
|
+
const key = getAxisTypeFromKey(axeKey);
|
|
20
|
+
counts[key]++;
|
|
21
|
+
});
|
|
22
|
+
const res =
|
|
23
|
+
(counts.x > counts.y && AxisType.X) ||
|
|
24
|
+
(counts.y > counts.x && AxisType.Y) ||
|
|
25
|
+
(counts.x === counts.y && null);
|
|
26
|
+
|
|
27
|
+
return res;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
Function that returns an object containing all the values for a specific key in an array of objects,
|
|
32
|
+
grouped by the unique values of another key in the same objects.
|
|
33
|
+
@param {Array} data - An array of objects to search for the keys.
|
|
34
|
+
@return {Object} - Returns an object with keys representing the unique values for the annotationAxis key in the data array,
|
|
35
|
+
and values representing an array of all values for the value key in the data array.
|
|
36
|
+
*/
|
|
37
|
+
const getAnnotationsData = (data) => {
|
|
38
|
+
return data.reduce((acc, obj) => {
|
|
39
|
+
return {
|
|
40
|
+
...acc,
|
|
41
|
+
[obj.annotationAxis]: [...(acc[obj.annotationAxis] || []), obj.value],
|
|
42
|
+
};
|
|
43
|
+
}, {});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* return data for each axes
|
|
48
|
+
* @function getAxesData
|
|
49
|
+
* @return {object}
|
|
50
|
+
*/
|
|
51
|
+
const getAxesData = (scalesKeys, datasets, annotationsData) => {
|
|
52
|
+
const allData =
|
|
53
|
+
datasets?.reduce((acc, item) => [...acc, ...item.data], []) ?? [];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* return data by key and depends on axes count
|
|
57
|
+
* @function getData
|
|
58
|
+
* @return {object}
|
|
59
|
+
*/
|
|
60
|
+
const getData = () => {
|
|
61
|
+
return scalesKeys.reduce((acc, axeKey) => {
|
|
62
|
+
const key = getAxisTypeFromKey(axeKey);
|
|
63
|
+
const data = getAnnotationsData(annotationsData);
|
|
64
|
+
const formData = allData
|
|
65
|
+
?.map((val) => val?.[key])
|
|
66
|
+
.concat(data[key] || []);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
...acc,
|
|
70
|
+
[axeKey]: [...new Set(formData)],
|
|
71
|
+
};
|
|
72
|
+
}, {});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
Function that returns an object containing the unique values for a multi axis key in an array of objects.
|
|
77
|
+
@return {Object} - Returns an object with key representing the found key in the scalesKeys array,
|
|
78
|
+
and values representing an array of unique values for that key in the datasets array. If no key is found, an empty object is returned.
|
|
79
|
+
*/
|
|
80
|
+
const getDataForMultiAxes = () => {
|
|
81
|
+
const multiAxesKey = checkMultiAxis(scalesKeys);
|
|
82
|
+
const axes = scalesKeys?.filter((key) => key?.includes(multiAxesKey)) ?? [];
|
|
83
|
+
const data = getAnnotationsData(annotationsData);
|
|
84
|
+
const [first, second] = axes;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Reduces an array of objects and returns an array of values from the specified key
|
|
88
|
+
* @param {Array} arr - The array of objects to be reduced
|
|
89
|
+
* @param {string} key - The key to extract the values from
|
|
90
|
+
* @returns {Array} - An array of values from the specified key
|
|
91
|
+
*/
|
|
92
|
+
const reduceData = (arr, key) =>
|
|
93
|
+
arr.reduce((acc, { [key]: value }) => [...acc, value], []);
|
|
94
|
+
|
|
95
|
+
return datasets.reduce(
|
|
96
|
+
(acc, obj) => {
|
|
97
|
+
const include = Object.values(obj).some((val) => axes.includes(val));
|
|
98
|
+
const key = include ? second : first;
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
...acc,
|
|
102
|
+
[key]: [
|
|
103
|
+
...new Set([
|
|
104
|
+
...acc[key],
|
|
105
|
+
...reduceData(obj?.data, multiAxesKey),
|
|
106
|
+
...((!include && data?.[multiAxesKey]) || []),
|
|
107
|
+
]),
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
{ [first]: [], [second]: [] },
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
...getData(),
|
|
117
|
+
...getDataForMultiAxes(),
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Auto scales axis ranges (mix, max) if no explicit range is set
|
|
123
|
+
* - overrides some edge cases not handled well by default chart.js
|
|
124
|
+
* - supports optional padding when `autoAxisPadding` is set
|
|
125
|
+
* - otherwise does not set min/max (falls back to chart.js default)
|
|
126
|
+
*
|
|
127
|
+
* @function autoScale
|
|
128
|
+
* @return {object} scales
|
|
129
|
+
*/
|
|
130
|
+
export const autoScale = (options, state, generatedDatasets) => {
|
|
131
|
+
const { additionalAxesOptions = {}, annotations = {} } = options || {};
|
|
132
|
+
const { annotationsData = [] } = annotations || {};
|
|
133
|
+
const scales = getLineChartScales(options, state) || {};
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
!additionalAxesOptions?.autoAxisPadding &&
|
|
137
|
+
!estimateDataSeriesHaveCloseValues(generatedDatasets)
|
|
138
|
+
) {
|
|
139
|
+
return scales;
|
|
140
|
+
}
|
|
141
|
+
const scalesKeys = Object.keys(scales) ?? [];
|
|
142
|
+
const data =
|
|
143
|
+
getAxesData(scalesKeys, generatedDatasets, annotationsData) ?? {};
|
|
144
|
+
|
|
145
|
+
const adjustedScales = scalesKeys?.reduce((acc, key) => {
|
|
146
|
+
const { min: propMin = undefined, max: propMax = undefined } = scales[key];
|
|
147
|
+
const { min, max } = getSuggestedAxisRange({
|
|
148
|
+
data: data[key],
|
|
149
|
+
beginAtZero: additionalAxesOptions?.beginAtZero,
|
|
150
|
+
autoAxisPadding: additionalAxesOptions?.autoAxisPadding,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const res = {
|
|
154
|
+
[key]: {
|
|
155
|
+
...scales[key],
|
|
156
|
+
min: propMin ?? min,
|
|
157
|
+
max: propMax ?? max,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return { ...acc, ...res };
|
|
162
|
+
}, {});
|
|
163
|
+
|
|
164
|
+
return adjustedScales ?? scales;
|
|
165
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** returning boolean condition depends on label position and chart area
|
|
2
|
+
*
|
|
3
|
+
* @return {object} - returning object with boolean props
|
|
4
|
+
*/
|
|
5
|
+
export const getCondition = (x, y, left, right, bottom) => {
|
|
6
|
+
const threshold = 100;
|
|
7
|
+
const overLeftSide = x - threshold <= left;
|
|
8
|
+
const overRightSide = x + threshold >= right;
|
|
9
|
+
const overBottomSide =
|
|
10
|
+
x + threshold >= left && x + threshold < right && y + threshold >= bottom;
|
|
11
|
+
|
|
12
|
+
return { overLeftSide, overRightSide, overBottomSide };
|
|
13
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** returning destructured data from context
|
|
2
|
+
*
|
|
3
|
+
* @return {object}
|
|
4
|
+
*/
|
|
5
|
+
export const getAlignmentData = (context) => {
|
|
6
|
+
const { chart = {}, dataIndex = 0, datasetIndex = 0 } = context || {};
|
|
7
|
+
const { chartArea = {} } = chart;
|
|
8
|
+
const { left = null, right = null, bottom = null } = chartArea;
|
|
9
|
+
|
|
10
|
+
const meta = chart.getDatasetMeta(datasetIndex);
|
|
11
|
+
const { x = null, y = null } = meta?.data?.[dataIndex] || {};
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
x,
|
|
15
|
+
y,
|
|
16
|
+
left,
|
|
17
|
+
right,
|
|
18
|
+
bottom,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getCondition } from './get-alignment-condition';
|
|
2
|
+
import { getAlignmentData } from './get-alignment-data';
|
|
3
|
+
import { AlignOptions } from '../../../helpers/enums';
|
|
4
|
+
|
|
5
|
+
/** returning position depends on condition
|
|
6
|
+
*
|
|
7
|
+
* @return {string} - position
|
|
8
|
+
*/
|
|
9
|
+
export const getPosition = () => {
|
|
10
|
+
return (context) => {
|
|
11
|
+
const { x, y, left, right, bottom } = getAlignmentData(context);
|
|
12
|
+
const {
|
|
13
|
+
overLeftSide = false,
|
|
14
|
+
overRightSide = false,
|
|
15
|
+
overBottomSide = false,
|
|
16
|
+
} = getCondition(x, y, left, right, bottom);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
(overLeftSide && AlignOptions.Right) ||
|
|
20
|
+
(overRightSide && AlignOptions.Left) ||
|
|
21
|
+
(overBottomSide && AlignOptions.End) ||
|
|
22
|
+
AlignOptions.Start
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AUTO } from '../../helpers/chart-consts';
|
|
2
|
+
import { getPosition } from './datalabels-alignment/get-datalabels-position';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* adjusts the position of the label depends on chart area
|
|
6
|
+
*
|
|
7
|
+
* @param {import('./line-chart.interface').ILineChartOptions} options - line chart options object
|
|
8
|
+
* @return {object} - returning position, if label exist in datasets item
|
|
9
|
+
*/
|
|
10
|
+
const getLineChartDataLabels = (options) => {
|
|
11
|
+
return options.graph.showDataLabels
|
|
12
|
+
? {
|
|
13
|
+
display: AUTO,
|
|
14
|
+
align: getPosition(),
|
|
15
|
+
formatter: (value, context) =>
|
|
16
|
+
context.dataset.data[context.dataIndex].label || '',
|
|
17
|
+
}
|
|
18
|
+
: { display: false };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default getLineChartDataLabels;
|