diff --git a/.eslintrc b/.eslintrc
index bd8bbc7..aba3e1f 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -74,5 +74,14 @@
"endOfLine": "auto"
}
]
- }
+ },
+ "overrides": [
+ {
+ // enable the rule specifically for TypeScript files
+ "files": ["*.ts", "*.mts", "*.cts", "*.tsx"],
+ "rules": {
+ "@typescript-eslint/explicit-function-return-type": "error"
+ }
+ }
+ ]
}
diff --git a/index.html b/index.html
index e487a57..be11895 100644
--- a/index.html
+++ b/index.html
@@ -59,6 +59,26 @@
};
const DATA_NATIVE_3 = {
+ labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
+ datasets: [
+ {
+ label: 'Dataset 1',
+ data: [12, 19, 3, 5, 2, 3],
+ borderColor: "rgba(255, 99, 132, 0.5)",
+ backgroundColor: 'rgba(255, 99, 132, 0.5)',
+ stepped: 'middle',
+ },
+ {
+ label: 'Dataset 2',
+ data: [22, 29, 13, 15, 12, 13],
+ borderColor: "rgba(53, 162, 235, 0.5)",
+ backgroundColor: 'rgba(53, 162, 235, 0.5)',
+ stepped: 'middle',
+ },
+ ],
+ };
+
+ const DATA_NATIVE_4 = {
datasets: [{
data: {
January: 10,
@@ -68,7 +88,7 @@
}]
};
- const DATA_NATIVE_4 = {
+ const DATA_NATIVE_5 = {
labels: ['Red', 'Blue', 'Yellow'],
datasets: [{
data: [{"x": 10, "y": 15}, {"x": 15, "y": 25}, {"x": 20, "y": 10}]
@@ -106,15 +126,13 @@
display: true,
min: 0,
max: 100,
- value: 50,
- track: 'normal',
+ value: [0, 100],
},
ySlider: {
display: true,
min: 0,
max: 100,
- value: 100,
- track: 'normal',
+ value: [0, 100],
}
}
};
@@ -135,15 +153,13 @@
display: true,
min: 0,
max: 100,
- value: 50,
- track: 'normal',
+ value: [0, 100],
},
ySlider: {
display: true,
min: 0,
max: 100,
- value: 100,
- track: 'normal',
+ value: [0, 100],
},
xAxis: {
type: 'time'
@@ -167,8 +183,9 @@
-
-
+
+
+
diff --git a/src/app.tsx b/src/app.tsx
index 28e9861..dafe712 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -1,5 +1,5 @@
import { Chart } from './chart';
-import { ChartValidator, ValidatorResult } from './chart-validator';
+import { SchemaValidator, ValidatorResult } from './schema-validator';
/**
* Create a container to visualize a GeoChart in a standalone manner.
@@ -21,7 +21,7 @@ export function App(): JSX.Element {
/**
* Handles when the Chart has to be loaded with data or options.
*/
- const handleChartLoad = (e: Event) => {
+ const handleChartLoad = (e: Event): void => {
const ev = e as CustomEvent;
if (ev.detail.data) {
setData(ev.detail.data);
@@ -36,11 +36,12 @@ export function App(): JSX.Element {
* @param dataErrors The data errors that happened (if any)
* @param optionsErrors The options errors that happened (if any)
*/
- const handleError = (dataErrors: ValidatorResult, optionsErrors: ValidatorResult) => {
+ const handleError = (dataErrors: ValidatorResult, optionsErrors: ValidatorResult): void => {
// Gather all error messages
- const msgAll = ChartValidator.parseValidatorResultsMessages([dataErrors, optionsErrors]);
+ const msgAll = SchemaValidator.parseValidatorResultsMessages([dataErrors, optionsErrors]);
- // Show the error (actually, can't because the snackbar is linked to a map at the moment and geochart is standalone)
+ // Show the error (actually, can't because the snackbar is linked to a map at the moment
+ // and geochart is standalone without a cgpv.init() at all)
// TODO: Decide if we want the snackbar outside of a map or not and use showError or not
cgpv.api.utilities.showError('', msgAll);
alert(`There was an error parsing the Chart inputs.\n\n${msgAll}\n\nView console for details.`);
diff --git a/src/chart-validator.ts b/src/chart-validator.ts
deleted file mode 100644
index a548b12..0000000
--- a/src/chart-validator.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-import Ajv from 'ajv';
-
-/**
- * Represents the result of a Chart data or options inputs validations.
- */
-export type ValidatorResult = {
- param: string;
- valid: boolean;
- errors?: string[];
-};
-
-/**
- * The Char Validator class to validate data and options inputs.
- */
-export class ChartValidator {
- // The JSON validator used by ChartValidate
- private ajv: Ajv.Ajv;
-
- public SCHEMA_DATA = {
- $schema: 'http://json-schema.org/draft-07/schema#',
- type: 'object',
- properties: {
- 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: ['datasets'],
- };
-
- public SCHEMA_OPTIONS = {
- type: 'object',
- properties: {
- responsive: { type: 'boolean' },
- plugins: {
- type: 'object',
- properties: {
- legend: {
- type: 'object',
- properties: {
- display: { type: 'boolean' },
- },
- },
- },
- },
- geochart: {
- type: 'object',
- properties: {
- chart: {
- enum: ['line', 'bar', 'pie', 'doughnut'],
- default: 'line',
- description: 'Supported types of chart.',
- },
- },
- },
- },
- required: ['geochart'],
- };
-
- /**
- * Constructs a Chart Validate object to validate schemas.
- */
- constructor() {
- // The embedded JSON validator
- this.ajv = new Ajv();
- }
-
- /**
- * Validates the data input parameters.
- */
- validateData = (data: unknown): ValidatorResult => {
- // Compile
- const validate = this.ajv.compile(this.SCHEMA_DATA);
-
- // Validate
- const valid = validate(data) as boolean;
- return {
- param: 'data',
- valid,
- errors: validate.errors?.map((e: Ajv.ErrorObject) => {
- const m = e.message || 'generic schema error';
- return `${e.schemaPath} | ${e.keyword} | ${m}`;
- }),
- };
- };
-
- /**
- * Validates the options input parameters.
- */
- validateOptions = (options: unknown): ValidatorResult => {
- // Compile
- const validate = this.ajv.compile(this.SCHEMA_OPTIONS);
-
- // Validate
- const valid = validate(options) as boolean;
- return {
- param: 'options',
- valid,
- errors: validate.errors?.map((e: Ajv.ErrorObject) => {
- const m = e.message || 'generic schema error';
- return `${e.schemaPath} | ${e.keyword} | ${m}`;
- }),
- };
- };
-
- public static parseValidatorResultsMessages(valRes: ValidatorResult[]) {
- // Gather all error messages for data input
- let msg = '';
- valRes.forEach((v) => {
- // Redirect
- msg += ChartValidator.parseValidatorResultMessage(v);
- });
- return msg.replace(/^\n+|\n+$/gm, '');
- }
-
- public static parseValidatorResultMessage(valRes: ValidatorResult) {
- // Gather all error messages for data input
- let msg = '';
- valRes.errors?.forEach((m: string) => {
- msg += `${m}\n`;
- });
- return msg.replace(/^\n+|\n+$/gm, '');
- }
-}
diff --git a/src/chart.tsx b/src/chart.tsx
index f5047f4..08b2d65 100644
--- a/src/chart.tsx
+++ b/src/chart.tsx
@@ -4,7 +4,7 @@ import { Box } from '@mui/material';
import { Chart as ChartJS, ChartDataset, registerables } 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 { SchemaValidator, ValidatorResult } from './schema-validator';
/**
* Main props for the Chart
@@ -24,6 +24,10 @@ export interface TypeChartChartProps
{
* SX Classes for the Chart
*/
const sxClasses = {
+ chartError: {
+ fontStyle: 'italic',
+ color: 'red',
+ },
checkDatasetWrapper: {
display: 'inline-block',
},
@@ -84,7 +88,7 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
* Handles when the X Slider changes
* @param value number | number[] Indicates the slider value
*/
- const handleSliderXChange = (value: number | number[]) => {
+ const handleSliderXChange = (value: number | number[]): void => {
// Callback
handleSliderXChanged?.(value);
};
@@ -93,7 +97,7 @@ 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[]) => {
+ const handleSliderYChange = (value: number | number[]): void => {
// Callback
handleSliderYChanged?.(value);
};
@@ -103,7 +107,7 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
* @param datasetIndex number Indicates the dataset index that was checked/unchecked
* @param checked boolean Indicates the checked state
*/
- const handleDatasetChecked = (datasetIndex: number, checked: boolean) => {
+ const handleDatasetChecked = (datasetIndex: number, checked: boolean): void => {
// Toggle visibility of the dataset
chartRef.current.setDatasetVisibility(datasetIndex, checked);
chartRef.current.update();
@@ -119,8 +123,8 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
};
/**
- * Renders the X Chart Slider JSX.Element or an empty div
- * @returns The X Chart Slider JSX.Element or an empty div
+ * Renders the X Chart Slider JSX.Element or an empty box
+ * @returns The X Chart Slider JSX.Element or an empty box
*/
const renderXSlider = (): JSX.Element => {
const { xSlider } = options.geochart;
@@ -132,19 +136,18 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
min={xSlider.min || 0}
max={xSlider.max || 100}
value={xSlider.value || 0}
- track={xSlider.track || false}
customOnChange={handleSliderXChange}
/>
);
}
// None
- return ;
+ return ;
};
/**
- * Renders the Y Chart Slider JSX.Element or an empty div
- * @returns The Y Chart Slider JSX.Element or an empty div
+ * Renders the Y Chart Slider JSX.Element or an empty box
+ * @returns The Y Chart Slider JSX.Element or an empty box
*/
const renderYSlider = (): JSX.Element => {
const { ySlider } = options.geochart;
@@ -156,7 +159,6 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
min={ySlider.min || 0}
max={ySlider.max || 100}
value={ySlider.value || 0}
- track={ySlider.track || false}
orientation="vertical"
customOnChange={handleSliderYChange}
/>
@@ -164,7 +166,7 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
);
}
// None
- return ;
+ return ;
};
/**
@@ -175,7 +177,7 @@ export function Chart(props: TypeChartChartProps): 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;
@@ -188,7 +190,7 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
) => {
+ onChange={(e: React.ChangeEvent): void => {
handleDatasetChecked(idx, e.target?.checked);
}}
defaultChecked
@@ -199,16 +201,16 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
);
})}
-
+
);
}
// None
- return ;
+ return ;
};
/**
- * Renders the whole Chart container JSX.Element or an empty div
- * @returns The whole Chart container JSX.Element or an empty div
+ * Renders the whole Chart container JSX.Element or an empty box
+ * @returns The whole Chart container JSX.Element or an empty box
*/
const renderChartContainer = (): JSX.Element => {
if (options.geochart && data?.datasets) {
@@ -230,15 +232,15 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
);
}
- return ;
+ return ;
};
/**
- * Renders the whole Chart container JSX.Element or an empty div
- * @returns The whole Chart container JSX.Element or an empty div
+ * Renders the whole Chart container JSX.Element or an empty box
+ * @returns The whole Chart container JSX.Element or an empty box
*/
const renderChartContainerFailed = (): JSX.Element => {
- return Error rendering the Chart. Check console for details.
;
+ return Error rendering the Chart. Check console for details.;
};
//
@@ -250,7 +252,7 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
let resData: ValidatorResult | undefined;
if (options && data) {
// Validate the data and options as received
- const validator = new ChartValidator();
+ const validator = new SchemaValidator();
resOptions = validator.validateOptions(options) || undefined;
resData = validator.validateData(data);
}
@@ -290,7 +292,7 @@ export function Chart(props: TypeChartChartProps): JSX.Element {
}
// Nothing to render, no errors either
- return ;
+ return ;
}
/**
diff --git a/src/index.tsx b/src/index.tsx
index 71a1af0..cd9e549 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,7 +1,8 @@
import App from './app';
+// Export the types from the package
export * from './chart-types';
-export * from './chart-validator';
+export * from './schema-validator';
export * from './chart';
// Search for a special root in case we are loading the geochart standalone
diff --git a/src/schema-validator-data.json b/src/schema-validator-data.json
new file mode 100644
index 0000000..8f2176d
--- /dev/null
+++ b/src/schema-validator-data.json
@@ -0,0 +1,86 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "GeoChart Data Schema",
+ "description": "This Schema validator validates the GeoChart data.",
+ "type": "object",
+ "properties": {
+ "labels": {
+ "description": "The labels to use for the X axis.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "datasets": {
+ "description": "The mandatory datasets information to use to build the chart.",
+ "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": ["datasets"]
+}
\ No newline at end of file
diff --git a/src/schema-validator-options.json b/src/schema-validator-options.json
new file mode 100644
index 0000000..c48036c
--- /dev/null
+++ b/src/schema-validator-options.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "GeoChart Options Schema",
+ "description": "This Schema validator validates the GeoChart options.",
+ "type": "object",
+ "properties": {
+ "responsive": { "type": "boolean" },
+ "plugins": {
+ "type": "object",
+ "properties": {
+ "legend": {
+ "type": "object",
+ "properties": {
+ "display": { "type": "boolean" }
+ }
+ }
+ }
+ },
+ "geochart": {
+ "type": "object",
+ "properties": {
+ "chart": {
+ "enum": ["line", "bar", "pie", "doughnut"],
+ "default": "line",
+ "description": "Supported types of chart."
+ }
+ }
+ }
+ },
+ "required": ["geochart"]
+}
\ No newline at end of file
diff --git a/src/schema-validator.ts b/src/schema-validator.ts
new file mode 100644
index 0000000..aeaae7e
--- /dev/null
+++ b/src/schema-validator.ts
@@ -0,0 +1,94 @@
+import Ajv from 'ajv';
+import SCHEMA_DATA from './schema-validator-data.json';
+import SCHEMA_OPTIONS from './schema-validator-options.json';
+
+/**
+ * Represents the result of a Chart data or options inputs validations.
+ */
+export type ValidatorResult = {
+ valid: boolean;
+ errors?: string[];
+};
+
+/**
+ * The Schema Validator class to validate json objects.
+ */
+export class SchemaValidator {
+ // The embedded JSON validator
+ private ajv: Ajv.Ajv;
+
+ /**
+ * Constructs a Chart Validate object to validate schemas.
+ */
+ constructor() {
+ // The embedded JSON validator
+ this.ajv = new Ajv();
+ }
+
+ /**
+ * Validates the data input parameters.
+ * @param data object the data json object to validate
+ */
+ validateData = (data: object): ValidatorResult => {
+ // Redirect
+ return this.validateJsonSchema(SCHEMA_DATA, data);
+ };
+
+ /**
+ * Validates the options input parameters.
+ * @param options object the options json object to validate
+ */
+ validateOptions = (options: object): ValidatorResult => {
+ // Redirect
+ return this.validateJsonSchema(SCHEMA_OPTIONS, options);
+ };
+
+ /**
+ * Validates the a jsonObj using a schema validator.
+ * @param schema object the schema validator json to validate the jsonObj with
+ * @param jsonObj object the json object to validate
+ */
+ validateJsonSchema = (schema: object, jsonObj: object): ValidatorResult => {
+ // Compile
+ const validate = this.ajv.compile(schema);
+
+ // Validate
+ const valid = validate(jsonObj) as boolean;
+
+ // Return a ValidatorResult
+ return {
+ valid,
+ errors: validate.errors?.map((e: Ajv.ErrorObject) => {
+ const m = e.message || 'generic schema error';
+ return `${e.schemaPath} | ${e.keyword} | ${m}`;
+ }),
+ };
+ };
+
+ /**
+ * Returns a string representation of the errors of all ValidatorResult objects.
+ * @param valRes ValidatorResult[] the list of validation results to read and put to string
+ */
+ public static parseValidatorResultsMessages = (valRes: ValidatorResult[]): string => {
+ // Gather all error messages for data input
+ let msg = '';
+ valRes.forEach((v) => {
+ // Redirect
+ msg += SchemaValidator.parseValidatorResultMessage(v);
+ });
+ return msg.replace(/^\n+|\n+$/gm, '');
+ };
+
+ /**
+ * Returns a string representation of the error in the ValidatorResult object.
+ * @param valRes ValidatorResult the validation result to read and put to string
+ */
+ public static parseValidatorResultMessage = (valRes: ValidatorResult): string => {
+ // Gather all error messages for data input
+ let msg = '';
+ valRes.errors?.forEach((m: string) => {
+ msg += `${m}\n`;
+ });
+ return msg.replace(/^\n+|\n+$/gm, '');
+ };
+}