diff --git a/packages/visualizations-react/stories/Chart/AxisAssemblages/AxisAssemblages.stories.tsx b/packages/visualizations-react/stories/Chart/AxisAssemblages/AxisAssemblages.stories.tsx index b8261dc9..015f6625 100644 --- a/packages/visualizations-react/stories/Chart/AxisAssemblages/AxisAssemblages.stories.tsx +++ b/packages/visualizations-react/stories/Chart/AxisAssemblages/AxisAssemblages.stories.tsx @@ -6,12 +6,12 @@ import { COLORS } from '../../utils'; import ChartTemplate from '../ChartTemplate'; const meta: Meta = { + component: ChartTemplate, title: 'Chart/AxisAssemblages', }; export default meta; -export const AreaChartStacked = ChartTemplate.bind({}); const AreaChartStackedArgs: Props = { data: { loading: false, @@ -63,9 +63,8 @@ const AreaChartStackedArgs: Props = { }, }, }; -AreaChartStacked.args = AreaChartStackedArgs; +export const AreaChartStacked = {args: AreaChartStackedArgs}; -export const AreaChartPercentage = ChartTemplate.bind({}); const AreaChartPercentageArgs: Props = { data: { loading: false, @@ -117,9 +116,8 @@ const AreaChartPercentageArgs: Props = { }, }, }; -AreaChartPercentage.args = AreaChartPercentageArgs; +export const AreaChartPercentage = {args: AreaChartPercentageArgs}; -export const LineChartStacked = ChartTemplate.bind({}); const LineChartStackedArgs: Props = { data: { loading: false, @@ -173,9 +171,8 @@ const LineChartStackedArgs: Props = { }, }, }; -LineChartStacked.args = LineChartStackedArgs; +export const LineChartStacked = {args: LineChartStackedArgs}; -export const LineChartPercentage = ChartTemplate.bind({}); const LineChartPercentageArgs: Props = { data: { loading: false, @@ -229,9 +226,8 @@ const LineChartPercentageArgs: Props = { }, }, }; -LineChartPercentage.args = LineChartPercentageArgs; +export const LineChartPercentage = {args: LineChartPercentageArgs}; -export const BarChartStacked = ChartTemplate.bind({}); const BarChartStackedArgs: Props = { data: { loading: false, @@ -301,9 +297,8 @@ const BarChartStackedArgs: Props = { }, }, }; -BarChartStacked.args = BarChartStackedArgs; +export const BarChartStacked = {args: BarChartStackedArgs}; -export const BarChartPercentage = ChartTemplate.bind({}); const BarChartPercentageArgs: Props = { data: { loading: false, @@ -373,9 +368,8 @@ const BarChartPercentageArgs: Props = { }, }, }; -BarChartPercentage.args = BarChartPercentageArgs; +export const BarChartPercentage = {args: BarChartPercentageArgs}; -export const BarChartStackedGroups = ChartTemplate.bind({}); const BarChartStackedGroupsArgs: Props = { data: { loading: false, @@ -471,9 +465,8 @@ const BarChartStackedGroupsArgs: Props = { }, }, }; -BarChartStackedGroups.args = BarChartStackedGroupsArgs; +export const BarChartStackedGroups = {args: BarChartStackedGroupsArgs}; -export const ColumnChartStacked = ChartTemplate.bind({}); const ColumnChartStackedArgs: Props = { data: { loading: false, @@ -542,9 +535,8 @@ const ColumnChartStackedArgs: Props = { }, }, }; -ColumnChartStacked.args = ColumnChartStackedArgs; +export const ColumnChartStacked = {args: ColumnChartStackedArgs}; -export const ColumnChartPercentage = ChartTemplate.bind({}); const ColumnChartPercentageArgs: Props = { data: { loading: false, @@ -613,10 +605,8 @@ const ColumnChartPercentageArgs: Props = { }, }, }; -ColumnChartPercentage.args = ColumnChartPercentageArgs; +export const ColumnChartPercentage = {args: ColumnChartPercentageArgs}; - -export const ColumnChartStackedGroups = ChartTemplate.bind({}); const ColumnChartStackedGroupsArgs: Props = { data: { loading: false, @@ -709,4 +699,115 @@ const ColumnChartStackedGroupsArgs: Props = { }, }, }; -ColumnChartStackedGroups.args = ColumnChartStackedGroupsArgs; \ No newline at end of file +export const ColumnChartStackedGroups = {args: ColumnChartStackedGroupsArgs}; + +const ScatterPlotChartArgs: Props = { + data: { + loading: false, + value: [ + {label: 'id-0', x: -10, y: 20}, + {label: 'id-1', x: 20, y: -10}, + {label: 'id-2', x: 5, y: 2}, + {label: 'id-3', x: 7, y: 3} + ], + }, + options: { + labelColumn: 'label', + series: [ + { + type: ChartSeriesType.Scatter, + label: "Serie 1", + valueColumn:"x", + indexAxis:"y", + backgroundColor: COLORS.blue, + }, + ], + axis: { + x: { + display: true, + type: 'linear', + title: { + display: true, + text: "Horizontal axis" + }, + }, + y: { + display: true, + title: { + display: true, + text: "Vertical axis" + }, + type: 'linear', + }, + }, + title: { + text: 'Scatterplot Chart', + }, + }, +}; +export const ScatterplotChart = {args: ScatterPlotChartArgs}; + +function randomNormal(mean = 0, stdDev = 1) { + let u1 = 0; + let u2 = 0; + while (u1 === 0) u1 = Math.random(); + while (u2 === 0) u2 = Math.random(); + const z0 = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2); + return z0 * stdDev + mean; + } + +function generateNormalDistribution(n: number, xMean = 0, xStdDev = 1, yMean = 0, yStdDev = 1) { + const points = []; + for (let i = 0; i < n; i++) { + points.push({ + label: `id-${i}`, + x: randomNormal(xMean, xStdDev), + y: randomNormal(yMean, yStdDev), + }); + } + return points; + } + +const ScatterplotNormalDistribChartArgs: Props = { + data: { + loading: false, + value: generateNormalDistribution(1000, 5, 2, 5, 2), + }, + options: { + labelColumn: 'label', + series: [ + { + label: "Serie 1", + type: ChartSeriesType.Scatter, + valueColumn:"x", + indexAxis:"y", + backgroundColor: COLORS.blue, + }, + ], + axis: { + x: { + display: true, + title: { + display: true, + text: "Horizontal axis" + }, + beginAtZero: true + }, + y: { + display: true, + title: { + display: true, + text: "Vertical axis" + }, + beginAtZero: true + }, + }, + title: { + text: 'Scatterplot Chart - Normal distribution', + }, + }, +}; +export const ScatterplotNormalDistribChart = { + args: ScatterplotNormalDistribChartArgs, + parameters: {chromatic: { disableSnapshot: true }} +}; diff --git a/packages/visualizations/src/components/Chart/Chart.svelte b/packages/visualizations/src/components/Chart/Chart.svelte index 87bb3c3a..c5c38567 100644 --- a/packages/visualizations/src/components/Chart/Chart.svelte +++ b/packages/visualizations/src/components/Chart/Chart.svelte @@ -139,6 +139,13 @@ // charts, the label is not the series legend, it's the category. return `${dataFrame[dataIndex].x}: ${format(parsed)}`; } + if (seriesType === ChartSeriesType.Scatter) { + const formattedValues = `${format(parsed.x)}, ${format(parsed.y)}`; + // e.g. dataset 1: (4.5, 54) + if (prefix) return `${prefix}(${formattedValues})`; + // 4.5, 54 + return formattedValues; + } } return prefix + formattedValue + suffix; diff --git a/packages/visualizations/src/components/Chart/datasets.ts b/packages/visualizations/src/components/Chart/datasets.ts index 81a1162c..b611e1de 100644 --- a/packages/visualizations/src/components/Chart/datasets.ts +++ b/packages/visualizations/src/components/Chart/datasets.ts @@ -101,5 +101,18 @@ export default function toDataset(df: DataFrame, s: ChartSeries): ChartDataset { }; } + if (s.type === 'scatter') { + return { + type: 'scatter', + label: defaultValue(s.label, ''), + data: df.map((entry) => ({ x: entry[s.indexAxis], y: entry[s.valueColumn] })), + datalabels: chartJsDataLabels(s.dataLabels), + backgroundColor: singleChartJsColor(s.backgroundColor), + pointRadius: defaultValue(s.pointRadius, 5), + pointHitRadius: defaultValue(s.pointHitRadius, 5), + pointHoverRadius: defaultValue(s.pointHoverRadius, 5), + pointBorderColor: defaultValue(s.pointBorderColor, 'rgba(255,255,255, 0)'), + }; + } throw new Error('Unknown chart type'); } diff --git a/packages/visualizations/src/components/Chart/scales.ts b/packages/visualizations/src/components/Chart/scales.ts index c256d144..6e682b96 100644 --- a/packages/visualizations/src/components/Chart/scales.ts +++ b/packages/visualizations/src/components/Chart/scales.ts @@ -81,6 +81,9 @@ export default function buildScales(options: ChartOptions): ChartJsChartOptions[ // X Axis if (options.axis?.x) { scales.x = { + ...(options.axis.x.type === 'linear' && { + beginAtZero: defaultValue(options?.axis?.x?.beginAtZero, true), + }), stacked: options.axis?.assemblage?.stacked, max: options?.axis?.x?.type === 'linear' && options.axis?.assemblage?.percentaged @@ -130,6 +133,9 @@ export default function buildScales(options: ChartOptions): ChartJsChartOptions[ // Y Axis if (options.axis?.y) { scales.y = { + ...(options.axis.y.type === 'linear' && { + beginAtZero: defaultValue(options?.axis?.y?.beginAtZero, true), + }), stacked: options.axis?.assemblage?.stacked, max: options?.axis?.y?.type === 'linear' && options.axis?.assemblage?.percentaged diff --git a/packages/visualizations/src/components/Chart/types.ts b/packages/visualizations/src/components/Chart/types.ts index 3e6b7757..349a87c9 100644 --- a/packages/visualizations/src/components/Chart/types.ts +++ b/packages/visualizations/src/components/Chart/types.ts @@ -76,10 +76,19 @@ export interface CategoryCartesianAxisConfiguration extends BaseCartesianAxisCon type?: 'category'; } -export interface NumericCartesianAxisConfiguration extends BaseCartesianAxisConfiguration { - type: 'linear' | 'logarithmic'; +export interface LinearCartesianAxisConfiguration extends BaseCartesianAxisConfiguration { + type: 'linear'; + beginAtZero?: boolean; +} + +export interface LogarithmicCartesianAxisConfiguration extends BaseCartesianAxisConfiguration { + type: 'logarithmic'; } +export type NumericCartesianAxisConfiguration = + | LinearCartesianAxisConfiguration + | LogarithmicCartesianAxisConfiguration; + export interface AxisTitleConfiguration { display?: boolean; align?: 'start' | 'center' | 'end'; @@ -138,7 +147,7 @@ export interface DataLabelsConfiguration { padding?: number; } -export type ChartSeries = Line | Bar | Pie | Radar | Doughnut; +export type ChartSeries = Line | Bar | Pie | Radar | Doughnut | Scatter; export enum ChartSeriesType { Line = 'line', @@ -146,6 +155,7 @@ export enum ChartSeriesType { Pie = 'pie', Radar = 'radar', Doughnut = 'doughnut', + Scatter = 'scatter', } export interface Line { @@ -209,6 +219,20 @@ export interface Doughnut { indexAxis?: 'x' | 'y'; } +export interface Scatter { + type: ChartSeriesType.Scatter; + valueColumn: string; + label?: string; + indexAxis: string; + /** Point color */ + backgroundColor?: Color | Color[]; + pointRadius?: number; + pointHitRadius?: number; + pointHoverRadius?: number; + pointBorderColor?: string; + dataLabels?: DataLabelsConfiguration; +} + export type FillMode = false | number | string | { value: number }; export interface FillConfiguration {