@oliasoft-open-source/charts-library 2.4.6 → 2.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oliasoft-open-source/charts-library",
3
- "version": "2.4.6",
3
+ "version": "2.5.1",
4
4
  "description": "React Chart Library (based on Chart.js and react-chart-js-2)",
5
5
  "main": "index.js",
6
6
  "files": [
package/release-notes.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Charts Library Release Notes
2
2
 
3
+ ## 2.5.1
4
+
5
+ - Added resize delay for performance
6
+
7
+ ## 2.5.0
8
+
9
+ - Separate controls for points/lines, axes options, legend, download
10
+ - Standard control for table
11
+ - Box zoom
12
+
3
13
  ## 2.4.6
4
14
 
5
15
  - Fix Multiple X Axes example in LineChart
@@ -0,0 +1 @@
1
+ <svg fill="none" height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h12v12h-12z"/></clipPath><g clip-path="url(#a)"><path d="m7.937 6.5c-.11122.42912-.36179.80916-.71237 1.08047-.35058.2713-.78133.4185-1.22463.4185s-.87405-.1472-1.22463-.4185c-.35058-.27131-.60115-.65135-.71237-1.08047h-3.563v-1h3.563c.11122-.42912.36179-.80916.71237-1.08046s.78133-.41851 1.22463-.41851.87405.14721 1.22463.41851.60115.65134.71237 1.08046h3.563v1z" fill="currentColor"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h12v12h-12z"/></clipPath><g clip-path="url(#a)"><path d="m.5 6.5v-1h11v1c-4.16104 0-6.63549 0-11 0z" fill="currentColor"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h12v12h-12z"/></clipPath><g clip-path="url(#a)"><path clip-rule="evenodd" d="m1.85351 1.14645-.35356-.353558-.707103.707108.353553.35355.39645.39645c-.02813.07955-.0429.16401-.0429.25 0 .19891.07902.38968.21967.53033.14066.14065.33142.21967.53033.21967.08599 0 .17045-.01477.25-.04289l2.2929 2.29289h-.7929v1h1.7929l2.5 2.5h-4.2929v1h5.2929l.85355.8536.3536.3535.7071-.7071-.3536-.3536-.3535-.35351v-.79289h-.79294l-2.5-2.5h3.29294v-1h-4.29294l-3.25-3.25c-.03694-.10448-.09692-.20048-.17678-.28033-.07985-.07985-.17585-.13984-.28033-.17678zm8.64649.85355h-6.50005v1h6.50005zm-8.78038 4.53033c.14066.14065.33142.21967.53033.21967.19892 0 .38968-.07902.53033-.21967.14066-.14065.21967-.33142.21967-.53033s-.07901-.38968-.21967-.53033c-.14065-.14065-.33141-.21967-.53033-.21967-.19891 0-.38967.07902-.53033.21967-.14065.14065-.21967.33142-.21967.53033s.07902.38968.21967.53033zm0 3.45c.14066.14067.33142.21967.53033.21967.19892 0 .38968-.079.53033-.21967.14066-.14065.21967-.33142.21967-.53033s-.07901-.38968-.21967-.53033c-.14065-.14065-.33141-.21967-.53033-.21967-.19891 0-.38967.07902-.53033.21967-.14065.14065-.21967.33142-.21967.53033s.07902.38968.21967.53033z" fill="currentColor" fill-rule="evenodd"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><circle cx="6" cy="6" fill="currentColor" r="2"/></svg>
@@ -199,6 +199,7 @@ const BarChart = (props) => {
199
199
  options={{
200
200
  onClick,
201
201
  onHover,
202
+ resizeDelay: 50,
202
203
  indexAxis: isVertical(options.direction) ? AxisType.X : AxisType.Y,
203
204
  maintainAspectRatio: chartStyling.maintainAspectRatio,
204
205
  animation: {
@@ -0,0 +1,132 @@
1
+ import React 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
+
15
+ const AxesOptionsPopover = ({
16
+ onResetAxes,
17
+ axes,
18
+ onSetAxisValue,
19
+ panEnabled,
20
+ scalesMaxMin,
21
+ zoomEnabled,
22
+ close,
23
+ }) => {
24
+ const isCustomValue =
25
+ axes.filter((axis) => axis.max.displayValue || axis.min.displayValue)
26
+ .length > 0;
27
+ const handleInputFocus = (e) => e.target.select();
28
+ return (
29
+ <>
30
+ {axes.map((axis, i) => {
31
+ // TODO: Translate strings
32
+ if (!axis.min || !axis.max) return null;
33
+ return (
34
+ <Field key={i} label={axis.label || axis.id || ''}>
35
+ <InputGroup small>
36
+ <Input
37
+ name="min"
38
+ // TODO: Fix input values not updating first time when scales reset
39
+ value={axis.min.inputValue || scalesMaxMin[axis?.id]?.min}
40
+ error={
41
+ !axis.min.valid
42
+ ? 'Invalid value' //t(InputWarningType.MustBeNumericAndLessThanMax)
43
+ : undefined
44
+ }
45
+ size={5}
46
+ width="100%"
47
+ onChange={(evt) =>
48
+ onSetAxisValue({
49
+ name: evt.target.name,
50
+ value: evt.target.value,
51
+ id: axis.id,
52
+ })
53
+ }
54
+ onFocus={handleInputFocus}
55
+ disabled={zoomEnabled || panEnabled}
56
+ />
57
+ <InputGroupAddon>
58
+ to
59
+ {/*t(translations.to)*/}
60
+ </InputGroupAddon>
61
+ <Input
62
+ name="max"
63
+ // TODO: Fix input values not updating first time when scales reset
64
+ value={axis.max.inputValue || scalesMaxMin[axis?.id]?.max}
65
+ error={
66
+ !axis.max.valid
67
+ ? 'Invalid value' //t(InputWarningType.MustBeNumericAndGreaterThanMin)
68
+ : undefined
69
+ }
70
+ size={5}
71
+ width="100%"
72
+ onChange={(evt) =>
73
+ onSetAxisValue({
74
+ name: evt.target.name,
75
+ value: evt.target.value,
76
+ id: axis.id,
77
+ })
78
+ }
79
+ onFocus={handleInputFocus}
80
+ disabled={zoomEnabled || panEnabled}
81
+ />
82
+ </InputGroup>
83
+ {/* TODO: Add units */}
84
+ </Field>
85
+ );
86
+ })}
87
+ <Flex gap="8px" alignItems="center">
88
+ <Button small colored label="Done" onClick={close} />
89
+ <Button
90
+ small
91
+ name="resetAxes"
92
+ label="Reset Axes"
93
+ onClick={onResetAxes}
94
+ disabled={!isCustomValue}
95
+ />
96
+ <Text small muted>
97
+ or click on canvas
98
+ </Text>
99
+ </Flex>
100
+ </>
101
+ );
102
+ };
103
+
104
+ export const AxesOptions = ({
105
+ onResetAxes,
106
+ axes,
107
+ onSetAxisValue,
108
+ panEnabled,
109
+ scalesMaxMin,
110
+ zoomEnabled,
111
+ }) => {
112
+ return (
113
+ <Popover
114
+ placement="bottom"
115
+ overflowContainer
116
+ content={
117
+ <AxesOptionsPopover
118
+ onResetAxes={onResetAxes}
119
+ axes={axes}
120
+ onSetAxisValue={onSetAxisValue}
121
+ panEnabled={panEnabled}
122
+ scalesMaxMin={scalesMaxMin}
123
+ zoomEnabled={zoomEnabled}
124
+ />
125
+ }
126
+ >
127
+ <Tooltip text="Axes options" placement="bottom" display="flex">
128
+ <Button small basic colored="muted" round icon={<RiRuler2Line />} />
129
+ </Tooltip>
130
+ </Popover>
131
+ );
132
+ };
@@ -0,0 +1,105 @@
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';
11
+ import { LegendOptions } from './legend-options';
12
+ import styles from './controls.module.less';
13
+
14
+ const Controls = ({
15
+ axes,
16
+ chart,
17
+ headerComponent,
18
+ legendEnabled,
19
+ lineEnabled,
20
+ onDownload,
21
+ onResetAxes,
22
+ onSetAxisValue,
23
+ onToggleLegend,
24
+ onToggleLine,
25
+ onTogglePan,
26
+ onTogglePoints,
27
+ onToggleTable,
28
+ onToggleZoom,
29
+ panEnabled,
30
+ pointsEnabled,
31
+ scalesMaxMin,
32
+ showTable,
33
+ subheaderComponent,
34
+ table,
35
+ zoomEnabled,
36
+ }) => {
37
+ return (
38
+ <>
39
+ <div className={styles.controls}>
40
+ <div>{headerComponent || <Text bold>{chart.options.title}</Text>}</div>
41
+ <div className={styles.buttons}>
42
+ {!showTable && (
43
+ <>
44
+ <AxesOptions
45
+ onResetAxes={onResetAxes}
46
+ axes={axes}
47
+ onSetAxisValue={onSetAxisValue}
48
+ panEnabled={panEnabled}
49
+ scalesMaxMin={scalesMaxMin}
50
+ zoomEnabled={zoomEnabled}
51
+ />
52
+ <LineOptions
53
+ lineEnabled={lineEnabled}
54
+ pointsEnabled={pointsEnabled}
55
+ onToggleLine={onToggleLine}
56
+ onTogglePoints={onTogglePoints}
57
+ />
58
+ <LegendOptions
59
+ legendEnabled={legendEnabled}
60
+ onToggleLegend={onToggleLegend}
61
+ />
62
+ <DragOptions
63
+ panEnabled={panEnabled}
64
+ zoomEnabled={zoomEnabled}
65
+ onTogglePan={onTogglePan}
66
+ onToggleZoom={onToggleZoom}
67
+ />
68
+ {/* TODO: implement usetranslation */}
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
+ {/* TODO: implement usetranslation */}
88
+ <Button
89
+ small
90
+ basic
91
+ colored="muted"
92
+ round
93
+ icon={showTable ? <RiLineChartLine /> : <RiTableLine />}
94
+ onClick={onToggleTable}
95
+ />
96
+ </Tooltip>
97
+ ) : null}
98
+ </div>
99
+ </div>
100
+ {subheaderComponent}
101
+ </>
102
+ );
103
+ };
104
+
105
+ export default Controls;
@@ -0,0 +1,12 @@
1
+ .controls {
2
+ display: flex;
3
+ align-items: center;
4
+ flex-wrap: wrap;
5
+ gap: 8px;
6
+ }
7
+
8
+ .buttons {
9
+ display: flex;
10
+ margin-left: auto;
11
+ gap: 4px;
12
+ }
@@ -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
+ };
@@ -1,6 +1,7 @@
1
1
  import PropTypes from 'prop-types';
2
2
 
3
3
  export const LineChartPropTypes = {
4
+ table: PropTypes.node,
4
5
  chart: PropTypes.shape({
5
6
  testId: PropTypes.string,
6
7
  data: PropTypes.object.isRequired,
@@ -28,10 +28,11 @@ import {
28
28
  TOGGLE_LINE,
29
29
  TOGGLE_PAN,
30
30
  TOGGLE_POINTS,
31
+ TOGGLE_TABLE,
31
32
  TOGGLE_ZOOM,
32
33
  UNSET_AXES_VALUES,
33
34
  } from './state/action-types';
34
- import { Controls } from './Controls/Controls';
35
+ import Controls from '../controls/controls';
35
36
  import { getDefaultProps, LineChartPropTypes } from './line-chart-prop-types';
36
37
  import getLineChartScales from './get-line-chart-scales';
37
38
  import getLineChartToolTips from './get-line-chart-tooltips';
@@ -95,6 +96,7 @@ const LineChart = (props) => {
95
96
  let pointHover = false;
96
97
  const chart = getDefaultProps(props);
97
98
  const { options, testId } = chart;
99
+ const { headerComponent, subheaderComponent, table } = props;
98
100
 
99
101
  const {
100
102
  additionalAxesOptions,
@@ -106,8 +108,7 @@ const LineChart = (props) => {
106
108
  interactions,
107
109
  legend,
108
110
  } = options;
109
- const { showLine, showPoints, enableZoom, enablePan, closeOnOutsideClick } =
110
- chartOptions;
111
+ const { showLine, showPoints, enableZoom, enablePan } = chartOptions;
111
112
 
112
113
  /**
113
114
  * @type {[object, import('react').Dispatch<{type: String, payload: any}>]} useReducer
@@ -392,93 +393,106 @@ const LineChart = (props) => {
392
393
  onKeyUp={handleKeyUp}
393
394
  data-testid={testId}
394
395
  >
395
- <div className={styles.zoomForm}>
396
- <Controls
397
- scalesMaxMin={getScalesMaxMin()}
398
- axes={controlsAxes}
399
- legendEnabled={state.legendEnabled}
400
- lineEnabled={state.lineEnabled}
401
- panEnabled={state.panEnabled}
402
- pointsEnabled={state.pointsEnabled}
403
- zoomEnabled={state.zoomEnabled}
404
- onSetAxisValue={(evt) =>
405
- dispatch({ type: SET_AXIS_VALUE, payload: evt })
406
- }
407
- onResetAxes={() => {
408
- dispatch({ type: UNSET_AXES_VALUES });
409
- }}
410
- onToggleLegend={() => dispatch({ type: TOGGLE_LEGEND })}
411
- onToggleLine={() => dispatch({ type: TOGGLE_LINE })}
412
- onTogglePan={() => dispatch({ type: TOGGLE_PAN })}
413
- onTogglePoints={() => dispatch({ type: TOGGLE_POINTS })}
414
- onToggleZoom={() => dispatch({ type: TOGGLE_ZOOM })}
415
- onDownload={handleDownload}
416
- closeOnOutsideClick={closeOnOutsideClick}
417
- />
418
- </div>
419
- <Line
420
- ref={chartRef}
421
- data={{
422
- datasets: generatedDatasets,
396
+ <Controls
397
+ axes={controlsAxes}
398
+ chart={chart}
399
+ headerComponent={headerComponent}
400
+ legendEnabled={state.legendEnabled}
401
+ lineEnabled={state.lineEnabled}
402
+ onDownload={handleDownload}
403
+ onResetAxes={() => {
404
+ dispatch({ type: UNSET_AXES_VALUES });
423
405
  }}
424
- options={{
425
- onClick,
426
- onHover,
427
- maintainAspectRatio:
428
- chartStyling.squareAspectRatio || chartStyling.maintainAspectRatio,
429
- aspectRatio: chartStyling.squareAspectRatio ? 1 : null, // 1 equals square aspect ratio
430
- animation: {
431
- duration: chartStyling.performanceMode
432
- ? ANIMATION_DURATION.NO
433
- : ANIMATION_DURATION.FAST,
434
- },
435
- hover: {
436
- mode: ChartHoverMode.Nearest,
437
- intersect: true,
438
- animationDuration: chartStyling.performanceMode
439
- ? ANIMATION_DURATION.NO
440
- : ANIMATION_DURATION.SLOW,
441
- },
442
- elements: {
443
- line: {
444
- pointStyle: PointStyle.Circle,
445
- showLine: state.lineEnabled,
446
- },
447
- },
448
- scales: getLineChartScales(options, state),
449
- plugins: {
450
- title: getTitle(options),
451
- datalabels: getLineChartDataLabels(options),
452
- annotation: getAnnotation(options, state),
453
- zoom: {
454
- pan: {
455
- enabled: state.panEnabled,
456
- mode: PanZoomMode.X,
457
- onPanComplete({ chart }) {
458
- setAxisValuesInSettings(chart);
406
+ onSetAxisValue={(evt) =>
407
+ dispatch({ type: SET_AXIS_VALUE, payload: evt })
408
+ }
409
+ onToggleLegend={() => dispatch({ type: TOGGLE_LEGEND })}
410
+ onToggleLine={() => dispatch({ type: TOGGLE_LINE })}
411
+ onTogglePan={() => dispatch({ type: TOGGLE_PAN })}
412
+ onTogglePoints={() => dispatch({ type: TOGGLE_POINTS })}
413
+ onToggleTable={() => dispatch({ type: TOGGLE_TABLE })}
414
+ onToggleZoom={() => dispatch({ type: TOGGLE_ZOOM })}
415
+ panEnabled={state.panEnabled}
416
+ pointsEnabled={state.pointsEnabled}
417
+ scalesMaxMin={getScalesMaxMin()}
418
+ showTable={state.showTable}
419
+ subheaderComponent={subheaderComponent}
420
+ table={table}
421
+ zoomEnabled={state.zoomEnabled}
422
+ />
423
+ {table && state.showTable ? (
424
+ <div className={styles.table}>{table}</div>
425
+ ) : (
426
+ <div className={styles.canvas}>
427
+ <Line
428
+ ref={chartRef}
429
+ data={{
430
+ datasets: generatedDatasets,
431
+ }}
432
+ options={{
433
+ onClick,
434
+ onHover,
435
+ resizeDelay: 50,
436
+ maintainAspectRatio: chartStyling.maintainAspectRatio,
437
+ aspectRatio: chartStyling.squareAspectRatio ? 1 : null, // 1 equals square aspect ratio
438
+ animation: {
439
+ duration: chartStyling.performanceMode
440
+ ? ANIMATION_DURATION.NO
441
+ : ANIMATION_DURATION.FAST,
442
+ },
443
+ hover: {
444
+ mode: ChartHoverMode.Nearest,
445
+ intersect: true,
446
+ animationDuration: chartStyling.performanceMode
447
+ ? ANIMATION_DURATION.NO
448
+ : ANIMATION_DURATION.SLOW,
449
+ },
450
+ elements: {
451
+ line: {
452
+ pointStyle: PointStyle.Circle,
453
+ showLine: state.lineEnabled,
459
454
  },
460
455
  },
461
- zoom: {
462
- mode: PanZoomMode.X,
463
- drag: {
464
- enabled: state.zoomEnabled,
465
- threshold: 3,
456
+ scales: getLineChartScales(options, state),
457
+ plugins: {
458
+ // title: getTitle(options),
459
+ datalabels: getLineChartDataLabels(options),
460
+ annotation: getAnnotation(options, state),
461
+ zoom: {
462
+ pan: {
463
+ enabled: state.panEnabled,
464
+ mode: PanZoomMode.XY,
465
+ onPanComplete({ chart }) {
466
+ setAxisValuesInSettings(chart);
467
+ },
468
+ },
469
+ zoom: {
470
+ mode: PanZoomMode.XY,
471
+ drag: {
472
+ enabled: state.zoomEnabled,
473
+ threshold: 3,
474
+ backgroundColor: 'rgba(0,0,0,0.1)',
475
+ borderColor: 'rgba(0,0,0,0.2)',
476
+ borderWidth: 1,
477
+ },
478
+ onZoomComplete({ chart }) {
479
+ setAxisValuesInSettings(chart);
480
+ },
481
+ },
466
482
  },
467
- onZoomComplete({ chart }) {
468
- setAxisValuesInSettings(chart);
483
+ tooltip: getLineChartToolTips(options),
484
+ legend: getLegend(options, legendClick, state),
485
+ [CUSTOM_LEGEND_PLUGIN_NAME]: options.legend.customLegend
486
+ .customLegendPlugin && {
487
+ containerID:
488
+ options.legend.customLegend.customLegendContainerID,
469
489
  },
470
490
  },
471
- },
472
- tooltip: getLineChartToolTips(options),
473
- legend: getLegend(options, legendClick, state),
474
- [CUSTOM_LEGEND_PLUGIN_NAME]: options.legend.customLegend
475
- .customLegendPlugin && {
476
- containerID: options.legend.customLegend.customLegendContainerID,
477
- },
478
- },
479
- }}
480
- plugins={getPlugins(graph, legend, state)}
481
- />
491
+ }}
492
+ plugins={getPlugins(graph, legend, state)}
493
+ />
494
+ </div>
495
+ )}
482
496
  </div>
483
497
  );
484
498
  };
@@ -5,12 +5,18 @@ html[data-theme='dark'] .chart canvas {
5
5
 
6
6
  .chart {
7
7
  border: 1px solid rgba(255, 255, 255, 0);
8
- padding-top: 10px;
9
8
  position: relative;
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: 8px; // Spacing between each section
10
12
 
11
- canvas {
12
- width: 100% !important; // Fix for resizing bug
13
- height: 100% !important; // Remove if stretched when maintainAspectRatio=true
13
+ .canvas {
14
+ flex-grow: 1;
15
+ min-height: 0; // Prevents chart exceeding available space
16
+ canvas {
17
+ width: 100% !important; // Fix for resizing bug
18
+ height: 100% !important; // Remove if stretched when maintainAspectRatio=true
19
+ }
14
20
  }
15
21
 
16
22
  &.fixedHeight {
@@ -34,7 +40,7 @@ html[data-theme='dark'] .chart canvas {
34
40
  }
35
41
 
36
42
  &:focus {
37
- border: 1px solid #85b7d9;
43
+ // border: 1px solid #85b7d9;
38
44
  outline: none; // Remove dotted outline on FF
39
45
  }
40
46
 
@@ -65,3 +71,7 @@ html[data-theme='dark'] .chart canvas {
65
71
  width: auto;
66
72
  height: auto;
67
73
  }
74
+
75
+ .table {
76
+ overflow: auto;
77
+ }
@@ -1,4 +1,12 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
+ import {
3
+ Flex,
4
+ Menu,
5
+ Slider,
6
+ Table,
7
+ Text,
8
+ Toggle,
9
+ } from '@oliasoft-open-source/react-ui-library';
2
10
  import { LineChart } from './line-chart';
3
11
  import { getCustomLegendPlugin } from '../../helpers/get-custom-legend-plugin-example';
4
12
 
@@ -156,6 +164,7 @@ FillContainer.args = {
156
164
  options: {
157
165
  ...basicChart.options,
158
166
  chartStyling: {
167
+ ...basicChart.options.chartStyling,
159
168
  height: '100%',
160
169
  },
161
170
  },
@@ -169,6 +178,17 @@ FillContainer.decorators = [
169
178
  ),
170
179
  ];
171
180
 
181
+ export const NoTitle = Template.bind({});
182
+ NoTitle.args = {
183
+ chart: {
184
+ ...basicChart,
185
+ options: {
186
+ ...basicChart.options,
187
+ title: undefined,
188
+ },
189
+ },
190
+ };
191
+
172
192
  export const DataGaps = Template.bind({});
173
193
  DataGaps.args = {
174
194
  chart: {
@@ -426,19 +446,6 @@ Animation.args = {
426
446
  },
427
447
  };
428
448
 
429
- export const CloseControlsOnOutsideClick = Template.bind({});
430
- CloseControlsOnOutsideClick.args = {
431
- chart: {
432
- ...basicChart,
433
- options: {
434
- ...basicChart.options,
435
- chartOptions: {
436
- closeOnOutsideClick: true,
437
- },
438
- },
439
- },
440
- };
441
-
442
449
  export const SquareAspectRatio = Template.bind({});
443
450
  SquareAspectRatio.args = {
444
451
  chart: {
@@ -454,6 +461,116 @@ SquareAspectRatio.args = {
454
461
  },
455
462
  };
456
463
 
464
+ export const HeaderComponent = (args) => {
465
+ const options = ['Category A', 'Category B', 'Category C'];
466
+
467
+ const suboptions = ['Chart 1', 'Chart 2', 'Chart 3'];
468
+
469
+ const charts = options.map((option) => ({
470
+ label: option,
471
+ options: suboptions.map((suboption) => ({
472
+ label: suboption,
473
+ options: {
474
+ label: `${option} - ${suboption}`,
475
+ },
476
+ })),
477
+ }));
478
+
479
+ const [selectedChartTitle, setSelectedChartTitle] = useState(
480
+ `${options[0]} - ${suboptions[0]}`,
481
+ );
482
+
483
+ const chart = {
484
+ ...basicChart,
485
+ options: { ...basicChart.options, title: selectedChartTitle },
486
+ };
487
+
488
+ return (
489
+ <LineChart
490
+ chart={chart}
491
+ headerComponent={
492
+ <Flex gap="10px" alignItems="center">
493
+ <Text bold>
494
+ <Menu
495
+ menu={{
496
+ label: selectedChartTitle,
497
+ sections: charts.map((option) => ({
498
+ type: 'Menu',
499
+ trigger: 'Text',
500
+ menu: {
501
+ label: option.label,
502
+ trigger: 'Text',
503
+ sections: option.options.map((suboption) => ({
504
+ label: suboption.label,
505
+ type: 'Option',
506
+ onClick: () =>
507
+ setSelectedChartTitle(
508
+ `${option.label} - ${suboption.label}`,
509
+ ),
510
+ })),
511
+ },
512
+ })),
513
+ trigger: 'Text',
514
+ small: true,
515
+ }}
516
+ />
517
+ </Text>
518
+ <Toggle label="Toggle" onChange={() => {}} noMargin />
519
+ </Flex>
520
+ }
521
+ />
522
+ );
523
+ };
524
+
525
+ export const SubheaderComponent = Template.bind({});
526
+ SubheaderComponent.args = {
527
+ subheaderComponent: (
528
+ <Slider max={100} min={0} onChange={() => {}} showArrows value={50} />
529
+ ),
530
+ };
531
+
532
+ const table = {
533
+ headers: [
534
+ {
535
+ cells: [
536
+ { value: 'Name' },
537
+ { value: 'Weight' },
538
+ { value: 'Energy' },
539
+ { value: 'Origin' },
540
+ ],
541
+ },
542
+ ],
543
+ rows: [
544
+ {
545
+ cells: [
546
+ { value: 'Brown rice' },
547
+ { value: 100 },
548
+ { value: 361 },
549
+ { value: 'Vietnam' },
550
+ ],
551
+ },
552
+ {
553
+ cells: [
554
+ { value: 'Buckwheat' },
555
+ { value: 50 },
556
+ { value: 358 },
557
+ { value: 'Poland' },
558
+ ],
559
+ },
560
+ {
561
+ cells: [
562
+ { value: 'Couscous' },
563
+ { value: 10 },
564
+ { value: 368 },
565
+ { value: 'France' },
566
+ ],
567
+ },
568
+ ],
569
+ };
570
+
571
+ export const WithTable = (args) => {
572
+ return <LineChart chart={basicChart} table={<Table table={table} />} />;
573
+ };
457
574
  export const SquareAspectRatioFillContainer = Template.bind({});
458
575
  SquareAspectRatioFillContainer.args = {
459
576
  chart: {
@@ -7,3 +7,4 @@ export const UNSET_AXES_VALUES = 'UNSET_AXES_VALUES';
7
7
  export const SET_AXIS_VALUE = 'SET_AXIS_VALUE';
8
8
  export const SET_POINTS_ZOOM_DEFAULTS = 'SET_POINTS_ZOOM_DEFAULTS';
9
9
  export const TOGGLE_ANNOTATION = 'TOGGLE_ANNOTATION';
10
+ export const TOGGLE_TABLE = 'TOGGLE_TABLE';
@@ -45,6 +45,7 @@ const initialState = ({
45
45
  legendEnabled: legendDisplay !== false,
46
46
  axes: stateAxes,
47
47
  showAnnotationLineIndex: setAnnotations(annotationsData),
48
+ showTable: false,
48
49
  };
49
50
  };
50
51
 
@@ -15,6 +15,7 @@ import {
15
15
  SET_AXIS_VALUE,
16
16
  SET_POINTS_ZOOM_DEFAULTS,
17
17
  TOGGLE_ANNOTATION,
18
+ TOGGLE_TABLE,
18
19
  } from './action-types';
19
20
  import { getAxisValue } from '../../../helpers/chart-utils';
20
21
 
@@ -64,6 +65,12 @@ export const reducer = (state, action) => {
64
65
  legendEnabled: !newState.legendEnabled,
65
66
  };
66
67
  }
68
+ case TOGGLE_TABLE: {
69
+ return {
70
+ ...newState,
71
+ showTable: !newState.showTable,
72
+ };
73
+ }
67
74
  case UNSET_AXES_VALUES: {
68
75
  return {
69
76
  ...newState,
@@ -413,6 +413,7 @@ const PieChart = (props) => {
413
413
  animationDuration: options.chartStyling.performanceMode ? 0 : 400,
414
414
  },
415
415
  onHover,
416
+ resizeDelay: 50,
416
417
  elements: {
417
418
  pie: {
418
419
  pointStyle: 'circle',
@@ -77,6 +77,7 @@ export const ChartHoverMode = Object.freeze({
77
77
  export const PanZoomMode = Object.freeze({
78
78
  X: 'x',
79
79
  Y: 'y',
80
+ XY: 'xy',
80
81
  });
81
82
 
82
83
  export const Key = Object.freeze({
@@ -1,61 +0,0 @@
1
- import React from 'react';
2
- import { FaCog } from 'react-icons/fa';
3
- import { Popover, Button } from '@oliasoft-open-source/react-ui-library';
4
- import { Layer } from './Layer';
5
-
6
- export const Controls = ({
7
- scalesMaxMin,
8
- zoomEnabled,
9
- onToggleZoom,
10
- panEnabled,
11
- onTogglePan,
12
- legendEnabled,
13
- onToggleLegend,
14
- onResetAxes,
15
- pointsEnabled,
16
- onTogglePoints,
17
- lineEnabled,
18
- onToggleLine,
19
- axes,
20
- onSetAxisValue,
21
- onDownload,
22
- closeOnOutsideClick,
23
- }) => {
24
- return (
25
- <Popover
26
- placement="bottom-start"
27
- closeOnOutsideClick={closeOnOutsideClick}
28
- showCloseButton
29
- overflowContainer
30
- content={
31
- <Layer
32
- scalesMaxMin={scalesMaxMin}
33
- zoomEnabled={zoomEnabled}
34
- onToggleZoom={onToggleZoom}
35
- panEnabled={panEnabled}
36
- onTogglePan={onTogglePan}
37
- onResetAxes={onResetAxes}
38
- legendEnabled={legendEnabled}
39
- onToggleLegend={onToggleLegend}
40
- pointsEnabled={pointsEnabled}
41
- onTogglePoints={onTogglePoints}
42
- lineEnabled={lineEnabled}
43
- onToggleLine={onToggleLine}
44
- axes={axes}
45
- onSetAxisValue={onSetAxisValue}
46
- onDownload={onDownload}
47
- />
48
- }
49
- >
50
- <Button
51
- name="example"
52
- colored="muted"
53
- onClick={() => {}}
54
- basic
55
- round
56
- small
57
- icon={<FaCog />}
58
- />
59
- </Popover>
60
- );
61
- };
@@ -1,21 +0,0 @@
1
- .layer {
2
- width: 200px;
3
- }
4
- .help {
5
- font-size: 0.8rem;
6
- color: #666;
7
-
8
- p {
9
- margin-bottom: 5px;
10
- }
11
- }
12
-
13
- .reset {
14
- display: flex;
15
- align-items: center;
16
- span {
17
- margin-left: 5px;
18
- font-size: 10px;
19
- color: #666;
20
- }
21
- }
@@ -1,172 +0,0 @@
1
- import React from 'react';
2
- //import { useTranslation } from 'react-i18next';
3
- import { HiOutlineDocumentDownload } from 'react-icons/hi';
4
- import {
5
- Button,
6
- Toggle,
7
- Spacer,
8
- Field,
9
- Input,
10
- InputGroup,
11
- InputGroupAddon,
12
- Text,
13
- } from '@oliasoft-open-source/react-ui-library';
14
- //import translations from '~common/Internationalisation/translation-map.json';
15
- //import { InputWarningType } from '~common/const/warning-messages';
16
- import styles from './Controls.module.less';
17
-
18
- export const Layer = ({
19
- scalesMaxMin,
20
- zoomEnabled,
21
- onToggleZoom,
22
- panEnabled,
23
- onTogglePan,
24
- onResetAxes,
25
- legendEnabled,
26
- onToggleLegend,
27
- pointsEnabled,
28
- onTogglePoints,
29
- lineEnabled,
30
- onToggleLine,
31
- axes,
32
- onSetAxisValue,
33
- onDownload,
34
- }) => {
35
- // TODO: implement usetranslation
36
- //const { t } = useTranslation();
37
- return (
38
- <div className={styles.layer}>
39
- <Toggle
40
- small
41
- name="enablePoints"
42
- label="Enable Points" //{t(translations.showPoints)} temporary static value before translation is implemented
43
- checked={pointsEnabled}
44
- onChange={onTogglePoints}
45
- />
46
- <Spacer height={8} />
47
- <Toggle
48
- small
49
- name="enableLine"
50
- label="Show line"
51
- checked={lineEnabled}
52
- onChange={onToggleLine}
53
- />
54
- <Spacer height={8} />
55
- <Toggle
56
- small
57
- name="enableLegend"
58
- label="Enable Legend" //{t(translations.showLegend)} temporary static value before translation is implemented
59
- checked={legendEnabled}
60
- onChange={onToggleLegend}
61
- />
62
- <Spacer height={8} />
63
- <Toggle
64
- small
65
- name="enableZoom"
66
- label="Enable Zoom" //{t(translations.enableZoom)} temporary static value before translation is implemented
67
- checked={zoomEnabled}
68
- onChange={onToggleZoom}
69
- />
70
- <Spacer height={8} />
71
- <Toggle
72
- small
73
- name="enablePan"
74
- label="Enable Pan" //{t(translations.enablePan)} temporary static value before translation is implemented
75
- checked={panEnabled}
76
- onChange={onTogglePan}
77
- />
78
- <Spacer height={10} />
79
- {(zoomEnabled || panEnabled) && (
80
- <>
81
- <div className={styles.help}>
82
- {zoomEnabled && (
83
- <p>
84
- <strong>Zoom: </strong>
85
- <span>click and drag</span>
86
- </p>
87
- )}
88
- {panEnabled && (
89
- <p>
90
- <strong>Pan: </strong>
91
- <span>click and drag</span>
92
- </p>
93
- )}
94
- <p>
95
- <strong>Change axis: </strong>
96
- <span>hold shift key</span>
97
- </p>
98
- </div>
99
- <Spacer height={10} />
100
- </>
101
- )}
102
- {axes.map((axis, i) => {
103
- return (
104
- <Field key={i} label={axis.label || ''}>
105
- <InputGroup small>
106
- <Input
107
- name="min"
108
- value={axis.min.inputValue || scalesMaxMin[axis?.id]?.min}
109
- error={
110
- !axis.min.valid
111
- ? 'Invalid value' //t(InputWarningType.MustBeNumericAndLessThanMax)
112
- : undefined
113
- }
114
- size={5}
115
- width="100%"
116
- onChange={(evt) =>
117
- onSetAxisValue({
118
- name: evt.target.name,
119
- value: evt.target.value,
120
- id: axis.id,
121
- })
122
- }
123
- disabled={zoomEnabled || panEnabled}
124
- />
125
- <InputGroupAddon>
126
- To
127
- {/*t(translations.to)*/}
128
- </InputGroupAddon>
129
- <Input
130
- name="max"
131
- value={axis.max.inputValue || scalesMaxMin[axis?.id].max}
132
- error={
133
- !axis.max.valid
134
- ? 'Invalid value' //t(InputWarningType.MustBeNumericAndGreaterThanMin)
135
- : undefined
136
- }
137
- size={5}
138
- width="100%"
139
- onChange={(evt) =>
140
- onSetAxisValue({
141
- name: evt.target.name,
142
- value: evt.target.value,
143
- id: axis.id,
144
- })
145
- }
146
- disabled={zoomEnabled || panEnabled}
147
- />
148
- </InputGroup>
149
- </Field>
150
- );
151
- })}
152
- <Field>
153
- <Button
154
- small
155
- name="resetAxes"
156
- label="Reset Axes"
157
- onClick={onResetAxes}
158
- />{' '}
159
- <Text small muted>
160
- or click on canvas
161
- </Text>
162
- </Field>
163
- <Spacer height={8} />
164
- <Button
165
- label="Download PNG" //{t(translations.downloadPNG)}
166
- small
167
- onClick={onDownload}
168
- icon={<HiOutlineDocumentDownload />}
169
- />
170
- </div>
171
- );
172
- };