@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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/index.js +13 -0
  4. package/package.json +100 -0
  5. package/release-notes.md +178 -0
  6. package/src/assets/icons/line-and-point.svg +1 -0
  7. package/src/assets/icons/line-only.svg +1 -0
  8. package/src/assets/icons/list-hide.svg +1 -0
  9. package/src/assets/icons/point-only.svg +1 -0
  10. package/src/components/bar-chart/bar-chart-prop-types.js +188 -0
  11. package/src/components/bar-chart/bar-chart.interface.ts +84 -0
  12. package/src/components/bar-chart/bar-chart.jsx +243 -0
  13. package/src/components/bar-chart/bar-chart.module.less +61 -0
  14. package/src/components/bar-chart/get-bar-chart-data-labels.js +42 -0
  15. package/src/components/bar-chart/get-bar-chart-scales.js +123 -0
  16. package/src/components/bar-chart/get-bar-chart-tooltips.js +100 -0
  17. package/src/components/controls/axes-options/axes-options-form-state.js +95 -0
  18. package/src/components/controls/axes-options/axes-options.jsx +166 -0
  19. package/src/components/controls/controls.jsx +104 -0
  20. package/src/components/controls/controls.module.less +12 -0
  21. package/src/components/controls/drag-options.jsx +77 -0
  22. package/src/components/controls/legend-options.jsx +25 -0
  23. package/src/components/controls/line-options.jsx +54 -0
  24. package/src/components/line-chart/axis-scales/axis-scales.js +165 -0
  25. package/src/components/line-chart/datalabels-alignment/get-alignment-condition.js +13 -0
  26. package/src/components/line-chart/datalabels-alignment/get-alignment-data.js +20 -0
  27. package/src/components/line-chart/datalabels-alignment/get-datalabels-position.js +25 -0
  28. package/src/components/line-chart/get-axes-ranges-from-chart.js +10 -0
  29. package/src/components/line-chart/get-line-chart-data-labels.js +21 -0
  30. package/src/components/line-chart/get-line-chart-scales.js +120 -0
  31. package/src/components/line-chart/get-line-chart-tooltips.js +91 -0
  32. package/src/components/line-chart/line-chart-consts.js +7 -0
  33. package/src/components/line-chart/line-chart-prop-types.js +212 -0
  34. package/src/components/line-chart/line-chart-utils.js +192 -0
  35. package/src/components/line-chart/line-chart.interface.ts +107 -0
  36. package/src/components/line-chart/line-chart.jsx +531 -0
  37. package/src/components/line-chart/line-chart.minor-gridlines-plugin.js +88 -0
  38. package/src/components/line-chart/line-chart.module.less +77 -0
  39. package/src/components/line-chart/state/action-types.js +11 -0
  40. package/src/components/line-chart/state/initial-state.js +69 -0
  41. package/src/components/line-chart/state/line-chart-reducer.js +101 -0
  42. package/src/components/pie-chart/pie-chart-prop-types.js +111 -0
  43. package/src/components/pie-chart/pie-chart-utils.js +32 -0
  44. package/src/components/pie-chart/pie-chart.interface.ts +61 -0
  45. package/src/components/pie-chart/pie-chart.jsx +450 -0
  46. package/src/components/pie-chart/pie-chart.module.less +61 -0
  47. package/src/components/scatter-chart/scatter-chart.intefrace.ts +33 -0
  48. package/src/components/scatter-chart/scatter-chart.jsx +21 -0
  49. package/src/components/scatter-chart/scatter-chart.module.less +4 -0
  50. package/src/helpers/chart-border-plugin.js +19 -0
  51. package/src/helpers/chart-consts.js +62 -0
  52. package/src/helpers/chart-interface.ts +76 -0
  53. package/src/helpers/chart-utils.js +183 -0
  54. package/src/helpers/container.jsx +60 -0
  55. package/src/helpers/disabled-context.js +8 -0
  56. package/src/helpers/enums.js +87 -0
  57. package/src/helpers/get-chart-annotation.js +143 -0
  58. package/src/helpers/get-custom-legend-plugin-example.js +80 -0
  59. package/src/helpers/numbers/numbers.js +44 -0
  60. package/src/helpers/range/estimate-data-series-have-close-values.js +54 -0
  61. package/src/helpers/range/range.js +95 -0
  62. package/src/helpers/styles.js +68 -0
  63. package/src/helpers/text.js +6 -0
  64. package/src/style/external.less +4 -0
  65. package/src/style/fonts/lato/Lato-Bold.woff2 +0 -0
  66. package/src/style/fonts/lato/Lato-BoldItalic.woff2 +0 -0
  67. package/src/style/fonts/lato/Lato-Italic.woff2 +0 -0
  68. package/src/style/fonts/lato/Lato-Regular.woff2 +0 -0
  69. package/src/style/fonts.less +27 -0
  70. package/src/style/global.less +43 -0
  71. package/src/style/reset/reset.less +28 -0
  72. package/src/style/shared.less +24 -0
  73. package/src/style/variables.less +91 -0
@@ -0,0 +1,76 @@
1
+ import { Plugin } from "chart.js";
2
+
3
+ export interface IChartAnnotationsData {
4
+ adjustScaleRange: boolean;
5
+ annotationAxis: 'x' | 'y';
6
+ color: string;
7
+ endValue: number;
8
+ label: string;
9
+ type: string;
10
+ value: number;
11
+ xMin: number,
12
+ xMax: number,
13
+ yMin: number,
14
+ yMax: number,
15
+ };
16
+
17
+ export interface ILabelAnnotation {
18
+ content: string[];
19
+ fontSize: number;
20
+ xAdjust: number;
21
+ yAdjust: number;
22
+ }
23
+
24
+ export interface IChartAnnotations {
25
+ showAnnotations: boolean;
26
+ controlAnnotation: boolean;
27
+ annotationsData: IChartAnnotationsData[];
28
+ labelAnnotation: ILabelAnnotation
29
+ }
30
+
31
+ export interface IChartStyling {
32
+ width: number | string;
33
+ height: number | string;
34
+ maintainAspectRatio: boolean;
35
+ squareAspectRatio?: boolean;
36
+ staticChartHeight: boolean;
37
+ performanceMode: boolean;
38
+ }
39
+
40
+ export interface ICustomLegend {
41
+ customLegendPlugin: Plugin;
42
+ customLegendContainerID: string,
43
+ }
44
+
45
+ export interface IChartLegend {
46
+ display: boolean;
47
+ position: 'top' | 'bottom' | 'right';
48
+ align: 'start' | 'center' | 'end';
49
+ customLegend?: ICustomLegend;
50
+ }
51
+
52
+ export interface IChartInteractions {
53
+ onLegendClick: () => any;
54
+ onPointHover: () => any;
55
+ onPointUnhover: () => any;
56
+ }
57
+
58
+ export interface IInitialState {
59
+ zoomEnabled?: boolean;
60
+ panEnabled?: boolean;
61
+ pointsEnabled?: boolean;
62
+ lineEnabled?: boolean;
63
+ legendEnabled?: boolean;
64
+ axes?: {id: string, label: string | any, min?: {}, max?: {}}[];
65
+ showAnnotationLineIndex: [];
66
+ }
67
+
68
+ export interface IChartPlugins {
69
+ legend: {
70
+ position: 'top' | 'right' | 'bottom' | 'left';
71
+ }
72
+ title: {
73
+ display: boolean;
74
+ text: string;
75
+ }
76
+ }
@@ -0,0 +1,183 @@
1
+ import { defaults } from 'chart.js';
2
+ import cx from 'classnames';
3
+ import { chartMinorGridlinesPlugin } from '../components/line-chart/line-chart.minor-gridlines-plugin';
4
+ import {
5
+ CUSTOM_LEGEND_PLUGIN_NAME,
6
+ BORDER_COLOR,
7
+ DEFAULT_CHART_NAME,
8
+ DEFAULT_COLOR,
9
+ DEFAULT_FONT_FAMILY,
10
+ DEFAULT_FONT_SIZE,
11
+ LEGEND_LABEL_BOX_SIZE,
12
+ WHITE_COLOR_AS_DECIMAL,
13
+ } from './chart-consts';
14
+ import { AxisType, ChartDirection, Position } from './enums';
15
+ import { chartAreaBorderPlugin } from './chart-border-plugin';
16
+
17
+ /**
18
+ * @param {import('../components/bar-chart/bar-chart.interface').IBarChartGraph |
19
+ * import('../components/line-chart/line-chart.interface').ILineChartGraph } graph - graph data from chart options
20
+ * @param {import('../helpers/chart-interface').IChartLegend} legend
21
+ * @param {import('./chart-interface').IInitialState} state - chart state object controlled by useReducer or similar
22
+ * @return {[]}
23
+ */
24
+ export const getPlugins = (graph, legend, state = null) => {
25
+ let plugins = [];
26
+ if (graph.showMinorGridlines) {
27
+ plugins.push(chartMinorGridlinesPlugin);
28
+ }
29
+ const customLegend = legend?.customLegend;
30
+ if (
31
+ customLegend?.customLegendPlugin &&
32
+ customLegend?.customLegendContainerID
33
+ ) {
34
+ plugins.push({
35
+ id: CUSTOM_LEGEND_PLUGIN_NAME,
36
+ ...legend.customLegend.customLegendPlugin,
37
+ });
38
+ }
39
+ plugins.push(chartAreaBorderPlugin);
40
+ return plugins;
41
+ };
42
+
43
+ /**
44
+ * @param {string[]} colors - color schema
45
+ * @return {string} - random color
46
+ */
47
+ export const generateRandomColor = (colors) => {
48
+ const color = `#${Math.floor(Math.random() * WHITE_COLOR_AS_DECIMAL).toString(
49
+ 16,
50
+ )}`;
51
+ if (colors.includes(color)) {
52
+ return generateRandomColor(colors);
53
+ } else {
54
+ colors.push(color);
55
+ return color;
56
+ }
57
+ };
58
+
59
+ /**
60
+ * @param {import('./chart-interface').IChartAnnotationsData[]} annotationsData
61
+ * @return {number[]|*[]}
62
+ */
63
+ export const setAnnotations = (annotationsData) => {
64
+ return annotationsData?.length ? annotationsData.map((v, i) => i) : [];
65
+ };
66
+
67
+ /**
68
+ * @param {import('../components/bar-chart/bar-chart.interface').IBarChartOptions |
69
+ * import('../components/line-chart/line-chart.interface').ILineChartOptions} options - chart options object
70
+ * @return {{color: (string|undefined), display: boolean, text}|{}}
71
+ */
72
+ export const getTitle = (options) => {
73
+ return options.title !== ''
74
+ ? {
75
+ display: true,
76
+ text: options.title,
77
+ }
78
+ : {};
79
+ };
80
+
81
+ /**
82
+ * @function isVertical
83
+ * @param {'vertical'|'horizontal'|string} direction
84
+ * @return {boolean} returns true if chart direction is vertical
85
+ */
86
+ export const isVertical = (direction) => {
87
+ return direction === ChartDirection.Vertical;
88
+ };
89
+
90
+ /**
91
+ * @param {'x'|'y'} axisType
92
+ * @param {number} i - index
93
+ * @return {'top'|'bottom'|'left'|'right'|*}
94
+ */
95
+ export const getAxisPosition = (axisType, i) => {
96
+ const [positionA, positionB] =
97
+ axisType === AxisType.Y
98
+ ? [Position.Left, Position.Right]
99
+ : [Position.Top, Position.Bottom];
100
+ return i % 2 === 0 ? positionA : positionB;
101
+ };
102
+
103
+ /**
104
+ * @param {import('../helpers/chart-interface').IChartStyling} chartStyling
105
+ * @param {Object} styles - styles imported form .less file
106
+ * @return {string} - class name
107
+ */
108
+ export const getClassName = (chartStyling, styles) => {
109
+ const { width, height, staticChartHeight, squareAspectRatio } = chartStyling;
110
+ const squareAspectRatioStyle = squareAspectRatio
111
+ ? styles.squareAspectRatio
112
+ : '';
113
+ let heightStyles = '';
114
+ if (width || height) {
115
+ heightStyles = '';
116
+ } else {
117
+ heightStyles = staticChartHeight
118
+ ? styles?.fixedHeight
119
+ : styles?.stretchHeight;
120
+ }
121
+ return cx(styles.chart, heightStyles, squareAspectRatioStyle);
122
+ };
123
+
124
+ /**
125
+ *
126
+ * @param {import('../components/bar-chart/bar-chart.interface').IBarChartOptions |
127
+ * import('../components/line-chart/line-chart.interface').ILineChartOptions} options - chart options object
128
+ * @param {function} clickHandler - on click callback
129
+ * @param {import('./chart-interface').IInitialState} state - chart state object controlled by useReducer or similar
130
+ * @returns {*}
131
+ */
132
+ export const getLegend = (options, clickHandler, state = null) => {
133
+ const { legend, chartStyling } = options;
134
+ return {
135
+ position: legend.position || Position.Top,
136
+ display: !legend?.customLegend?.customLegendPlugin
137
+ ? state
138
+ ? state.legendEnabled
139
+ : legend.display
140
+ : false,
141
+ align: legend.align,
142
+ labels: {
143
+ boxHeight: LEGEND_LABEL_BOX_SIZE,
144
+ boxWidth: LEGEND_LABEL_BOX_SIZE,
145
+ usePointStyle: true,
146
+ filter: (item, data) => !data.datasets[item.datasetIndex].hideLegend,
147
+ },
148
+ onClick: clickHandler,
149
+ };
150
+ };
151
+
152
+ export const afterLabelCallback = (tooltipItem) => {
153
+ const { error } = tooltipItem.dataset.data[tooltipItem?.dataIndex];
154
+ return error ? `Error: ${Math.round(error * 10000) / 10000}` : '';
155
+ };
156
+
157
+ export const getTooltipLabel = (tooltipItem, showLabelsInTooltips) => {
158
+ const datasetDataLabel =
159
+ tooltipItem.dataset.data[tooltipItem.dataIndex]?.label;
160
+ const dataLabel = Array.isArray(datasetDataLabel)
161
+ ? datasetDataLabel.join(' , ')
162
+ : datasetDataLabel;
163
+ return showLabelsInTooltips && dataLabel?.length ? ` (${dataLabel})` : '';
164
+ };
165
+
166
+ //TODO: consider returning chart name instead of axis names
167
+ export const getChartFileName = (axes) => {
168
+ if (!axes) {
169
+ return DEFAULT_CHART_NAME;
170
+ }
171
+ const axesLabels = axes.reduce((acc, cur, index) => {
172
+ const labelWithNoSpace = cur.label?.replace(/\s/g, '_') || index;
173
+ return [...acc, labelWithNoSpace];
174
+ }, []);
175
+ return axesLabels.join('_');
176
+ };
177
+
178
+ export const setDefaultTheme = () => {
179
+ defaults.font.size = DEFAULT_FONT_SIZE;
180
+ defaults.font.family = DEFAULT_FONT_FAMILY;
181
+ defaults.color = DEFAULT_COLOR;
182
+ defaults.borderColor = BORDER_COLOR;
183
+ };
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import '../style/reset/reset.less';
5
+ import '../style/global.less';
6
+
7
+ const outerContainerStyle = {
8
+ margin: '50px',
9
+ };
10
+
11
+ export const Container = ({
12
+ style,
13
+ children,
14
+ margin,
15
+ deprecatedMessage,
16
+ warningMessage,
17
+ }) => {
18
+ return (
19
+ <div style={margin ? outerContainerStyle : {}}>
20
+ {deprecatedMessage ? (
21
+ <div
22
+ style={{
23
+ border: '1px solid red',
24
+ color: 'red',
25
+ padding: '10px',
26
+ marginBottom: '20px',
27
+ }}
28
+ >
29
+ {deprecatedMessage}
30
+ </div>
31
+ ) : null}
32
+ {warningMessage ? (
33
+ <div
34
+ style={{
35
+ border: '1px solid orange',
36
+ color: 'orange',
37
+ padding: '10px',
38
+ marginBottom: '20px',
39
+ }}
40
+ >
41
+ {warningMessage}
42
+ </div>
43
+ ) : null}
44
+ <div style={style}>{children}</div>
45
+ </div>
46
+ );
47
+ };
48
+
49
+ Container.defaultProps = {
50
+ style: {},
51
+ margin: true,
52
+ deprecatedMessage: null,
53
+ };
54
+
55
+ Container.propTypes = {
56
+ style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
57
+ children: PropTypes.node.isRequired,
58
+ margin: PropTypes.bool,
59
+ deprecatedMessage: PropTypes.string,
60
+ };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ export const DisabledContext = React.createContext(false);
5
+
6
+ DisabledContext.Provider.propTypes = {
7
+ value: PropTypes.bool,
8
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @enum {string}
3
+ */
4
+ export const AxisType = Object.freeze({
5
+ X: 'x',
6
+ Y: 'y',
7
+ });
8
+
9
+ /**
10
+ * @enum {string}
11
+ */
12
+ export const Position = Object.freeze({
13
+ Bottom: 'bottom',
14
+ Top: 'top',
15
+ Left: 'left',
16
+ Right: 'right',
17
+ });
18
+
19
+ /**
20
+ * @enum {string}
21
+ */
22
+ export const ChartType = Object.freeze({
23
+ Line: 'line',
24
+ Bar: 'bar',
25
+ });
26
+
27
+ /**
28
+ * @enum {string}
29
+ */
30
+ export const CursorStyle = Object.freeze({
31
+ Pointer: 'pointer',
32
+ Initial: 'initial',
33
+ });
34
+
35
+ /**
36
+ * @enum {string}
37
+ */
38
+ export const ScaleType = Object.freeze({
39
+ Category: 'category', //TODO: verify
40
+ Linear: 'linear',
41
+ Logarithmic: 'logarithmic',
42
+ });
43
+
44
+ /**
45
+ * @enum {string}
46
+ */
47
+ export const ChartDirection = Object.freeze({
48
+ Vertical: 'vertical',
49
+ });
50
+
51
+ /**
52
+ * @enum {string}
53
+ */
54
+ export const TooltipLabel = Object.freeze({
55
+ Y: 'yLabel',
56
+ X: 'xLabel',
57
+ });
58
+
59
+ export const AlignOptions = Object.freeze({
60
+ End: 'end',
61
+ Start: 'start',
62
+ Center: 'center',
63
+ Right: 'right',
64
+ Left: 'left',
65
+ });
66
+
67
+ export const PointType = Object.freeze({
68
+ Casing: 'casing',
69
+ });
70
+
71
+ export const PointStyle = Object.freeze({
72
+ Circle: 'circle',
73
+ });
74
+
75
+ export const ChartHoverMode = Object.freeze({
76
+ Nearest: 'nearest',
77
+ });
78
+
79
+ export const PanZoomMode = Object.freeze({
80
+ X: 'x',
81
+ Y: 'y',
82
+ XY: 'xy',
83
+ });
84
+
85
+ export const Key = Object.freeze({
86
+ Shift: 'Shift',
87
+ });
@@ -0,0 +1,143 @@
1
+ import { AxisType, ChartType, CursorStyle, Position } from './enums';
2
+ import { ANNOTATION_DASH, BORDER_WIDTH, COLORS } from './chart-consts';
3
+
4
+ const annotationEnter = ({ element }, { chart }) => {
5
+ if (element.options.scaleID?.includes(AxisType.X)) {
6
+ element.options.label.xAdjust = chart.chartArea.left;
7
+ }
8
+ element.options.borderWidth = BORDER_WIDTH.HOVERED;
9
+ if (element.options.label) element.options.label.enabled = true;
10
+ chart.draw();
11
+ chart.canvas.style.cursor = CursorStyle.Pointer;
12
+ };
13
+
14
+ const annotationLeave = ({ element }, { chart }) => {
15
+ element.options.borderWidth = BORDER_WIDTH.INITIAL;
16
+ if (element.options.label) element.options.label.enabled = false;
17
+ chart.draw();
18
+ chart.canvas.style.cursor = CursorStyle.Initial;
19
+ };
20
+
21
+ /**
22
+ * @param {import('../components/bar-chart/bar-chart.interface').IBarChartOptions |
23
+ * import('../components/line-chart/line-chart.interface').ILineChartOptions} options - chart options object
24
+ * @param {import('./chart-interface').IInitialState} state - chart state object controlled by useReducer or similar
25
+ */
26
+ const generateAnnotations = (options, state) => {
27
+ const { annotationsData } = options.annotations;
28
+
29
+ const annotations = annotationsData?.reduce((acc, curr, i) => {
30
+ if (!state?.showAnnotationLineIndex?.includes(i)) {
31
+ return { ...acc };
32
+ }
33
+
34
+ const getScaleId = () => {
35
+ const axisType = curr.annotationAxis;
36
+ return options.axes[axisType]?.length > 1 ? `${axisType}1` : axisType;
37
+ };
38
+
39
+ const color = curr?.color || COLORS[i];
40
+ const type = curr?.type || 'line';
41
+ const adjustScaleRange = curr?.adjustScaleRange;
42
+ const borderColor = type === 'line' ? color : 'transparent';
43
+ const borderWidth = type === 'line' ? BORDER_WIDTH.INITIAL : 0;
44
+ const borderDash = ANNOTATION_DASH;
45
+
46
+ const label =
47
+ type === 'line'
48
+ ? {
49
+ backgroundColor: color,
50
+ content: curr?.label,
51
+ enabled: false,
52
+ position: Position.Top,
53
+ }
54
+ : {
55
+ content: curr?.label,
56
+ enabled: true,
57
+ font: { weight: 'normal' },
58
+ };
59
+
60
+ const enter = (context, event) => {
61
+ if (type !== 'line') return;
62
+ annotationEnter(context, event);
63
+ };
64
+
65
+ const leave = (context, event) => {
66
+ if (type !== 'line') return;
67
+ annotationLeave(context, event);
68
+ };
69
+
70
+ const annotation = {
71
+ ...curr,
72
+ id: `${curr?.label}-${curr?.value}-${i}`,
73
+ scaleID: getScaleId(),
74
+ label,
75
+ backgroundColor: color,
76
+ borderColor,
77
+ borderWidth,
78
+ borderDash,
79
+ type,
80
+ adjustScaleRange,
81
+ enter,
82
+ leave,
83
+ };
84
+ return { ...acc, [`annotation${i + 1}`]: annotation };
85
+ }, {});
86
+ return annotations;
87
+ };
88
+
89
+ /**
90
+ * @param {import('../components/bar-chart/bar-chart.interface').IBarChartOptions |
91
+ * import('../components/line-chart/line-chart.interface').ILineChartOptions} options - chart options object
92
+ * @return {{label1: {}}}
93
+ */
94
+ const getLabelAnnotation = (options) => {
95
+ const { annotations = {} } = options;
96
+ const { labelAnnotation = {} } = annotations;
97
+ const isDarkModeOn = options.chartStyling.darkMode || false;
98
+ const {
99
+ content = [],
100
+ xAdjust = -200,
101
+ yAdjust = 120,
102
+ fontSize = 12,
103
+ } = labelAnnotation;
104
+
105
+ return {
106
+ label1: {
107
+ type: 'label',
108
+ xAdjust,
109
+ yAdjust,
110
+ backgroundColor: 'rgba(0, 0, 0, 0)',
111
+ color: isDarkModeOn && 'rgba(255, 255, 255, 1)',
112
+ content,
113
+ font: {
114
+ size: fontSize,
115
+ },
116
+ },
117
+ };
118
+ };
119
+
120
+ /**
121
+ * @param {import('../components/bar-chart/bar-chart.interface').IBarChartOptions |
122
+ * import('../components/line-chart/line-chart.interface').ILineChartOptions} options - chart options object
123
+ * @param {import('./chart-interface').IInitialState} state - chart state object controlled by useReducer or similar
124
+ * @return {{annotations: []}}
125
+ */
126
+ const getAnnotation = (options, state) => {
127
+ const { annotations } = options;
128
+ const isAnnotationShown = annotations?.showAnnotations;
129
+ const isAnnotationDataProvided = annotations?.annotationsData?.length;
130
+
131
+ const formAnnotation =
132
+ isAnnotationShown && isAnnotationDataProvided
133
+ ? {
134
+ ...getLabelAnnotation(options),
135
+ ...generateAnnotations(options, state),
136
+ }
137
+ : null;
138
+
139
+ return {
140
+ annotations: formAnnotation,
141
+ };
142
+ };
143
+ export default getAnnotation;
@@ -0,0 +1,80 @@
1
+ const getOrCreateLegendList = (chart, id) => {
2
+ const legendContainer = document.getElementById(id);
3
+ let listContainer = legendContainer.querySelector('ul');
4
+
5
+ if (!listContainer) {
6
+ listContainer = document.createElement('ul');
7
+ listContainer.style.display = 'flex';
8
+ listContainer.style.flexDirection = 'row';
9
+ listContainer.style.margin = 0;
10
+ listContainer.style.padding = 0;
11
+
12
+ legendContainer.appendChild(listContainer);
13
+ }
14
+
15
+ return listContainer;
16
+ };
17
+
18
+ /**
19
+ * Gets an example custom legend plugin for use in storybook.
20
+ * @param {string} customLegendContainerID - the id of the div container to put the generated legend in
21
+ */
22
+ export const getCustomLegendPlugin = (customLegendContainerID) => ({
23
+ afterUpdate(chart, _args, _options) {
24
+ const ul = getOrCreateLegendList(chart, customLegendContainerID);
25
+
26
+ // Remove old legend items
27
+ while (ul.firstChild) {
28
+ ul.firstChild.remove();
29
+ }
30
+
31
+ // Reuse the built-in legendItems generator
32
+ const items = chart.options.plugins.legend.labels.generateLabels(chart);
33
+
34
+ items.forEach((item) => {
35
+ const li = document.createElement('li');
36
+ li.style.alignItems = 'center';
37
+ li.style.cursor = 'pointer';
38
+ li.style.display = 'flex';
39
+ li.style.flexDirection = 'row';
40
+ li.style.marginLeft = '10px';
41
+
42
+ li.onclick = () => {
43
+ const { type } = chart.config;
44
+ if (type === 'pie' || type === 'doughnut') {
45
+ // Pie and doughnut charts only have a single dataset and visibility is per item
46
+ chart.toggleDataVisibility(item.index);
47
+ } else {
48
+ chart.setDatasetVisibility(
49
+ item.datasetIndex,
50
+ !chart.isDatasetVisible(item.datasetIndex),
51
+ );
52
+ }
53
+ chart.update();
54
+ };
55
+
56
+ // Color box
57
+ const boxSpan = document.createElement('span');
58
+ boxSpan.style.background = item.fillStyle;
59
+ boxSpan.style.borderColor = item.strokeStyle;
60
+ boxSpan.style.borderWidth = item.lineWidth + 'px';
61
+ boxSpan.style.display = 'inline-block';
62
+ boxSpan.style.height = '20px';
63
+ boxSpan.style.marginRight = '10px';
64
+ boxSpan.style.width = '20px';
65
+
66
+ // Text
67
+ const textContainer = document.createElement('p');
68
+ textContainer.style.margin = 0;
69
+ textContainer.style.padding = 0;
70
+ textContainer.style.textDecoration = item.hidden ? 'line-through' : '';
71
+
72
+ const text = document.createTextNode(item.text);
73
+ textContainer.appendChild(text);
74
+
75
+ li.appendChild(boxSpan);
76
+ li.appendChild(textContainer);
77
+ ul.appendChild(li);
78
+ });
79
+ },
80
+ });
@@ -0,0 +1,44 @@
1
+ import { DECIMAL_POINT_TOLERANCE } from '../chart-consts';
2
+
3
+ /**
4
+ * Rounds a number to N decimal places
5
+ *
6
+ * @param {number} v value
7
+ * @param {number} n decimal count
8
+ * @returns {number}
9
+ */
10
+ export const roundN = (v, n) => {
11
+ const factor = 10 ** n;
12
+ return Math.round(v * factor) / factor;
13
+ };
14
+
15
+ /**
16
+ * Rounds a number to DECIMAL_POINT_TOLERANCE decimal places
17
+ *
18
+ * @param {number} v value
19
+ * @returns {number}
20
+ */
21
+ export const round = (v) => roundN(v, DECIMAL_POINT_TOLERANCE);
22
+
23
+ /**
24
+ * Determines whether two numbers are close in value with a tolerance
25
+ * (mitigates excess JavaScript floating point precision quirks)
26
+ *
27
+ * Inspired by WellDesign implementations:
28
+ * - isCloseTo in rounding.js
29
+ * - isEqual in TDHYDutils.js (recommended by Truls, but quirks comparing values close to 0 need testing)
30
+ * - TODO: replace this with a universal Oliasoft implementation when ready
31
+ *
32
+ * @param {number} a
33
+ * @param {number} b
34
+ * @returns {boolean}
35
+ */
36
+ export const isEqualWithTolerance = (a, b) => {
37
+ if (typeof a == 'number' && typeof b === typeof a) {
38
+ const tolerance = 10 ** -DECIMAL_POINT_TOLERANCE;
39
+ const difference = Math.abs(b - a);
40
+ const roundedDifference = roundN(difference, DECIMAL_POINT_TOLERANCE);
41
+ return roundedDifference <= tolerance;
42
+ }
43
+ return false;
44
+ };