diff --git a/src/Globals.d.ts b/src/Globals.d.ts
deleted file mode 100644
index a38a891..0000000
--- a/src/Globals.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-declare module '*.module.css';
-declare module '*.module.scss';
diff --git a/src/chart-types.ts b/src/chart-types.ts
index b0155f4..71b7648 100644
--- a/src/chart-types.ts
+++ b/src/chart-types.ts
@@ -13,6 +13,15 @@ export interface GeoChartData> = ChartDataset;
+/**
+ * Indicates an action to be performed by the Chart.
+ * Special type that allows the child component a accept a 'todo action' via props and reset the prop value without the parent being notified.
+ * This is essentially to simplify the setTimeout handling to be managed inside the Chart component instead of higher in the application.
+ */
+export type GeoChartAction = {
+ shouldRedraw?: boolean;
+};
+
/**
* Extends the ChartOptions used by Chart.js with more 'GeoChart' options
*/
diff --git a/src/chart-validator.ts b/src/chart-validator.ts
index cb7d0d2..45247e1 100644
--- a/src/chart-validator.ts
+++ b/src/chart-validator.ts
@@ -16,13 +16,86 @@ export class ChartValidator {
private ajv: Ajv.Ajv;
public SCHEMA_DATA = {
+ $schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
properties: {
- labels: { type: 'array' },
- datasets: { type: 'array' },
+ labels: {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
+ datasets: {
+ type: 'array',
+ items: {
+ type: 'object',
+ properties: {
+ label: {
+ type: 'string',
+ },
+ data: {
+ oneOf: [
+ {
+ type: 'array',
+ items: {
+ type: 'number',
+ },
+ },
+ {
+ type: 'array',
+ items: {
+ type: 'object',
+ properties: {
+ x: {
+ type: 'number',
+ },
+ y: {
+ type: 'number',
+ },
+ },
+ required: ['x', 'y'],
+ },
+ },
+ {
+ type: 'object',
+ },
+ ],
+ },
+ backgroundColor: {
+ oneOf: [
+ {
+ type: 'string',
+ },
+ {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
+ ],
+ },
+ borderColor: {
+ oneOf: [
+ {
+ type: 'string',
+ },
+ {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
+ ],
+ },
+ borderWidth: {
+ type: 'integer',
+ },
+ },
+ required: ['data'],
+ },
+ },
},
- required: ['labels', 'datasets'],
- // additionalProperties: false
+ required: ['datasets'],
};
public SCHEMA_OPTIONS = {
@@ -43,12 +116,15 @@ export class ChartValidator {
geochart: {
type: 'object',
properties: {
- chart: { type: 'string' },
+ chart: {
+ enum: ['line', 'bar', 'pie', 'doughnut'],
+ default: 'line',
+ description: 'Supported types of chart.',
+ },
},
},
},
required: ['geochart'],
- // additionalProperties: false
};
/**
diff --git a/src/chart.module.css b/src/chart.module.css
deleted file mode 100644
index 17dfd9d..0000000
--- a/src/chart.module.css
+++ /dev/null
@@ -1,25 +0,0 @@
-.chartContainer {
- display: grid;
- width: 100%;
-}
-
-.chartContainerGrid1 {
- grid-column: 1;
- grid-row: 1;
- height: 100%;
-}
-
-.chartContainerGrid2 {
- grid-column: 2;
- grid-row: 1;
-}
-
-.chartContainerGrid3 {
- grid-column: 1;
- grid-row: 2;
-}
-
-.chartContainerGrid4 {
- grid-column: 2;
- grid-row: 2;
-}
diff --git a/src/chart.tsx b/src/chart.tsx
index 0e25c2b..6a074ab 100644
--- a/src/chart.tsx
+++ b/src/chart.tsx
@@ -1,27 +1,50 @@
+/* eslint-disable no-console */
+// TODO: Remove the disable above
import { Box } from '@mui/material';
-import { Chart as ChartJS, ChartData, ChartOptions, DefaultDataPoint } from 'chart.js';
-import { GeoChartOptions, GeoChartType, GeoChartData, GeoChartDefaultColors } from './chart-types';
+import {
+ Chart as ChartJS,
+ ChartDataset,
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ BarElement,
+ Title,
+ Tooltip,
+ ArcElement,
+} from 'chart.js';
+import { Chart as ChartReact } from 'react-chartjs-2';
+import { GeoChartOptions, GeoChartType, GeoChartData, GeoChartAction, GeoChartDefaultColors } from './chart-types';
import { ChartValidator, ValidatorResult } from './chart-validator';
-import { ChartDoughnut } from './charts/chart-doughnut';
-import { ChartBarsVertical } from './charts/chart-bars-vertical';
-import { ChartPie } from './charts/chart-pie';
-import { ChartLine } from './charts/chart-line';
-import styles from './chart.module.css';
/**
* Main props for the Chart
*/
export interface TypeChartChartProps {
- style?: unknown;
+ style?: unknown; // Will be casted as CSSProperties later via the imported cgpv react
defaultColors?: GeoChartDefaultColors;
data?: GeoChartData;
options?: GeoChartOptions;
- redraw?: boolean;
+ action?: GeoChartAction;
handleSliderXChanged?: (value: number | number[]) => void;
handleSliderYChanged?: (value: number | number[]) => void;
handleError?: (dataErrors: ValidatorResult, optionsErrors: ValidatorResult) => void;
}
+/**
+ * SX Classes for the Chart
+ */
+const sxClasses = {
+ checkDatasetWrapper: {
+ display: 'inline-block',
+ },
+ checkDataset: {
+ display: 'inline-flex',
+ verticalAlign: 'middle',
+ marginRight: '20px !important',
+ },
+};
+
/**
* Create a customized Chart UI
*
@@ -33,14 +56,15 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const w = window as any;
const { cgpv } = w;
- const { CSSProperties } = cgpv.react;
- const { Slider } = cgpv.ui.elements;
- const { style: elStyle, data, options: elOptions, redraw } = props;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { useEffect, useState, useRef, CSSProperties } = cgpv.react;
+ const { Grid, Checkbox, Slider, Typography } = cgpv.ui.elements;
+ const { style: elStyle, data, options: elOptions, action: elAction } = props;
// Cast the style
const style = elStyle as typeof CSSProperties;
- // Attribute the default colors
+ // Attribute the ChartJS default colors
if (props.defaultColors?.backgroundColor) ChartJS.defaults.backgroundColor = props.defaultColors?.backgroundColor;
if (props.defaultColors?.borderColor) ChartJS.defaults.borderColor = props.defaultColors?.borderColor;
if (props.defaultColors?.color) ChartJS.defaults.color = props.defaultColors?.color;
@@ -63,6 +87,25 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
}
}
+ // STATE / REF SECTION *******
+ const [redraw, setRedraw] = useState(elAction?.shouldRedraw);
+ const chartRef = useRef(null);
+ // const [selectedDatasets, setSelectedDatasets] = useState();
+
+ // If redraw is true, reset the property, set the redraw property to true for the chart, then prep a timer to reset it to false after the redraw has happened.
+ // A bit funky, but as documented online.
+ if (elAction?.shouldRedraw) {
+ elAction!.shouldRedraw = false;
+ setRedraw(true);
+ setTimeout(() => {
+ setRedraw(false);
+ }, 200);
+ }
+
+ /**
+ * Handles when the X Slider changes
+ * @param value number | number[] Indicates the slider value
+ */
const handleSliderXChange = (value: number | number[]) => {
// If callback set
if (props.handleSliderXChanged) {
@@ -70,6 +113,10 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
}
};
+ /**
+ * Handles when the Y Slider changes
+ * @param value number | number[] Indicates the slider value
+ */
const handleSliderYChange = (value: number | number[]) => {
// If callback set
if (props.handleSliderYChanged) {
@@ -77,56 +124,37 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
}
};
+ /**
+ * Handles when a dataset was checked/unchecked (via the legend)
+ * @param datasetIndex number Indicates the dataset index that was checked/unchecked
+ * @param checked boolean Indicates the checked state
+ */
+ const handleDatasetChecked = (datasetIndex: number, checked: boolean) => {
+ // Toggle visibility of the dataset
+ chartRef.current.setDatasetVisibility(datasetIndex, checked);
+ chartRef.current.update();
+ };
+
/**
* Renders the Chart JSX.Element itself using Line as default
* @returns The Chart JSX.Element itself using Line as default
*/
const renderChart = (): JSX.Element => {
// Depending on the type of chart
- switch (options!.geochart.chart) {
+ switch (options.geochart.chart) {
case 'bar':
- // Vertical Bars Chart
- return (
- , string>}
- options={options as ChartOptions<'bar'>}
- redraw={redraw}
- />
- );
+ return ;
case 'pie':
- // Pie Chart
- return (
- , string>}
- options={options as ChartOptions<'pie'>}
- redraw={redraw}
- />
- );
+ return ;
case 'doughnut':
// Doughnut Chart
- return (
- }
- options={options as ChartOptions<'doughnut'>}
- redraw={redraw}
- />
- );
+ return ;
default:
// Line Chart is default
- return (
- , string>}
- options={options as ChartOptions<'line'>}
- redraw={redraw}
- />
- );
+ return ;
}
};
@@ -135,7 +163,7 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
* @returns The X Chart Slider JSX.Element or an empty div
*/
const renderXSlider = (): JSX.Element => {
- const { xSlider } = options!.geochart;
+ const { xSlider } = options.geochart;
if (xSlider?.display) {
return (
@@ -159,7 +187,7 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
* @returns The Y Chart Slider JSX.Element or an empty div
*/
const renderYSlider = (): JSX.Element => {
- const { ySlider } = options!.geochart;
+ const { ySlider } = options.geochart;
if (ySlider?.display) {
return (
@@ -175,6 +203,46 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
);
}
+ // None
+ return ;
+ };
+
+ /**
+ * Renders the Dataset selector, aka the legend
+ * @returns The Dataset selector Element
+ */
+ const renderDatasetSelector = (): JSX.Element => {
+ const { datasets } = data!;
+ if (datasets.length > 1) {
+ return (
+
+ {datasets.map((ds: ChartDataset, idx: number) => {
+ // Find a color for the legend based on the dataset info
+ let { color } = ChartJS.defaults;
+ if (ds.borderColor) color = ds.borderColor! as string;
+ else if (ds.backgroundColor) color = ds.backgroundColor! as string;
+
+ // Return the Legend item
+ return (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ) => {
+ handleDatasetChecked(idx, e.target?.checked);
+ }}
+ defaultChecked
+ />
+
+ {ds.label}
+
+
+ );
+ })}
+
+ );
+ }
+ // None
return ;
};
@@ -183,20 +251,34 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
* @returns The whole Chart container JSX.Element or an empty div
*/
const renderChartContainer = (): JSX.Element => {
- if (data && options && options.geochart) {
+ if (options.geochart && data?.datasets) {
return (
-
-
{renderChart()}
-
{renderYSlider()}
-
{renderXSlider()}
-
-
+
+
+ {renderDatasetSelector()}
+
+
+ {renderChart()}
+
+
+ {renderYSlider()}
+
+
+ {renderXSlider()}
+
+
);
}
return ;
};
+ // Effect hook to add and remove event listeners
+ useEffect(() => {
+ // Prep ChartJS
+ ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, BarElement, Title, Tooltip, ArcElement);
+ }, []);
+
return renderChartContainer();
}
diff --git a/src/charts/chart-bars-vertical.tsx b/src/charts/chart-bars-vertical.tsx
deleted file mode 100644
index e862c21..0000000
--- a/src/charts/chart-bars-vertical.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Chart as ChartJS, DefaultDataPoint, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';
-import { Bar, ChartProps } from 'react-chartjs-2';
-
-/**
- * Create a customized Chart Vertical Bars UI
- *
- * @param {TypeChartVerticalProps} props the properties passed to the Chart element
- * @returns {JSX.Element} the created Chart element
- */
-export function ChartBarsVertical(props: ChartProps<'bar', DefaultDataPoint<'bar'>>): JSX.Element {
- const { data, options, redraw, style } = props;
-
- ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
-
- return ;
-}
diff --git a/src/charts/chart-doughnut.tsx b/src/charts/chart-doughnut.tsx
deleted file mode 100644
index 4c122a7..0000000
--- a/src/charts/chart-doughnut.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Chart as ChartJS, DefaultDataPoint, ArcElement, Tooltip, Legend } from 'chart.js';
-import { Doughnut, ChartProps } from 'react-chartjs-2';
-
-/**
- * Create a customized Chart Doughnut UI
- *
- * @param {TypeChartDoughnutProps} props the properties passed to the Chart element
- * @returns {JSX.Element} the created Chart element
- */
-export function ChartDoughnut(props: ChartProps<'doughnut', DefaultDataPoint<'doughnut'>>): JSX.Element {
- const { data, options, redraw, style } = props;
-
- ChartJS.register(ArcElement, Tooltip, Legend);
-
- return ;
-}
diff --git a/src/charts/chart-line.tsx b/src/charts/chart-line.tsx
deleted file mode 100644
index f9734b5..0000000
--- a/src/charts/chart-line.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import {
- Chart as ChartJS,
- DefaultDataPoint,
- CategoryScale,
- LinearScale,
- PointElement,
- LineElement,
- Title,
- Tooltip,
- Legend,
-} from 'chart.js';
-import { Line, ChartProps } from 'react-chartjs-2';
-
-/**
- * Create a customized Chart Line UI
- *
- * @param {TypeChartLineProps} props the properties passed to the Chart element
- * @returns {JSX.Element} the created Chart element
- */
-export function ChartLine(props: ChartProps<'line', DefaultDataPoint<'line'>>): JSX.Element {
- const { data, options, redraw, style } = props;
-
- ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
-
- return ;
-}
diff --git a/src/charts/chart-pie.tsx b/src/charts/chart-pie.tsx
deleted file mode 100644
index b6e333b..0000000
--- a/src/charts/chart-pie.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Chart as ChartJS, DefaultDataPoint, ArcElement, Tooltip, Legend } from 'chart.js';
-import { Pie, ChartProps } from 'react-chartjs-2';
-
-/**
- * Create a customized Chart Pie UI
- *
- * @param {TypeChartPieProps} props the properties passed to the Chart element
- * @returns {JSX.Element} the created Chart element
- */
-export function ChartPie(props: ChartProps<'pie', DefaultDataPoint<'pie'>>): JSX.Element {
- const { data, options, redraw, style } = props;
-
- ChartJS.register(ArcElement, Tooltip, Legend);
-
- return ;
-}