diff --git a/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx b/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx index 16ebcd33988da..e9de24c6f9f1b 100644 --- a/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx +++ b/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx @@ -8,19 +8,18 @@ import { transformPlotlyJsonToColumnProps, transformPlotlyJsonToScatterChartProps, transformPlotlyJsonToHorizontalBarWithAxisProps, + isDateArray, + isNumberArray, transformPlotlyJsonToHeatmapProps, transformPlotlyJsonToSankeyProps, + transformPlotlyJsonToGaugeProps, } from './PlotlySchemaAdapter'; import { LineChart } from '../LineChart/index'; import { HorizontalBarChartWithAxis } from '../HorizontalBarChartWithAxis/index'; import { AreaChart } from '../AreaChart/index'; import { HeatMapChart } from '../HeatMapChart/index'; - -const isDate = (value: any): boolean => !isNaN(Date.parse(value)); -const isNumber = (value: any): boolean => !isNaN(parseFloat(value)) && isFinite(value); -export const isDateArray = (array: any[]): boolean => Array.isArray(array) && array.every(isDate); -export const isNumberArray = (array: any[]): boolean => Array.isArray(array) && array.every(isNumber); import { SankeyChart } from '../SankeyChart/SankeyChart'; +import { GaugeChart } from '../GaugeChart/index'; /** * DeclarativeChart props. @@ -68,6 +67,11 @@ export const DeclarativeChart: React.FunctionComponent = return ; case 'sankey': return ; + case 'indicator': + if (props.chartSchema?.data?.[0]?.mode?.includes('gauge')) { + return ; + } + return
Unsupported Schema
; default: return
Unsupported Schema
; } diff --git a/packages/charts/react-charting/src/components/DeclarativeChart/PlotlySchemaAdapter.ts b/packages/charts/react-charting/src/components/DeclarativeChart/PlotlySchemaAdapter.ts index 735bbb6a8ff1f..427a18bb17862 100644 --- a/packages/charts/react-charting/src/components/DeclarativeChart/PlotlySchemaAdapter.ts +++ b/packages/charts/react-charting/src/components/DeclarativeChart/PlotlySchemaAdapter.ts @@ -14,12 +14,13 @@ import { IHeatMapChartDataPoint, } from '../../types/IDataPoint'; import { ISankeyChartProps } from '../SankeyChart/index'; -import { getNextColor, DataVizPalette } from '../../utilities/colors'; +import { getNextColor, DataVizPalette, getColorFromToken } from '../../utilities/colors'; import { IVerticalStackedBarChartProps } from '../VerticalStackedBarChart/index'; import { IHorizontalBarChartWithAxisProps } from '../HorizontalBarChartWithAxis/index'; import { ILineChartProps } from '../LineChart/index'; import { IAreaChartProps } from '../AreaChart/index'; import { IHeatMapChartProps } from '../HeatMapChart/index'; +import { IGaugeChartProps, IGaugeChartSegment } from '../GaugeChart/index'; const isDate = (value: any): boolean => !isNaN(Date.parse(value)); const isNumber = (value: any): boolean => !isNaN(parseFloat(value)) && isFinite(value); @@ -157,15 +158,31 @@ export const transformPlotlyJsonToHorizontalBarWithAxisProps = (jsonObj: any): I return series.y.map((yValue: string, i: number) => ({ x: series.x[i], y: yValue, - legend: series.name, + legend: yValue, color: series.marker?.color || getNextColor(index), })); }) .flat(); + const chartHeight = layout.height || 350; + const margin = layout.margin?.l || 0; + const padding = layout.margin?.pad || 0; + const availableHeight = chartHeight - margin - padding; + const numberOfBars = data[0].y.length; + const gapFactor = 0.5; + const barHeight = availableHeight / (numberOfBars * (1 + gapFactor)); + return { data: chartData, chartTitle: layout.title || '', + barHeight, + showYAxisLables: true, + styles: { + root: { + height: chartHeight, + width: layout.width || 600, + }, + }, }; }; @@ -254,6 +271,53 @@ export const transformPlotlyJsonToSankeyProps = (jsonObj: any): ISankeyChartProp }; }; +export const transformPlotlyJsonToGaugeProps = (jsonObj: any): IGaugeChartProps => { + const { data, layout } = jsonObj; + const firstData = data[0]; + + const segments = firstData.gauge?.steps?.map((step: any, index: number): IGaugeChartSegment => { + return { + legend: step.name || `Segment ${index + 1}`, + size: step.range?.[1] - step.range?.[0], + color: step.color || getNextColor(index), + }; + }); + + let sublabel: string | undefined; + let sublabelColor: string | undefined; + if (typeof firstData.delta?.reference === 'number') { + const diff = firstData.value - firstData.delta.reference; + if (diff >= 0) { + sublabel = `\u25B2 ${diff}`; + sublabelColor = firstData.delta.increasing?.color || getColorFromToken(DataVizPalette.success); + } else { + sublabel = `\u25BC ${Math.abs(diff)}`; + sublabelColor = firstData.delta.decreasing?.color || getColorFromToken(DataVizPalette.error); + } + } + + const styles: IGaugeChartProps['styles'] = { + sublabel: { + fill: sublabelColor, + }, + }; + + return { + segments, + chartValue: firstData.value, + chartTitle: firstData.title?.text, + sublabel, + // range values can be null + minValue: firstData.gauge?.axis?.range?.[0] ?? undefined, + maxValue: firstData.gauge?.axis?.range?.[1] ?? undefined, + chartValueFormat: () => firstData.value, + width: layout?.width, + height: layout?.height, + hideLegend: true, + styles, + }; +}; + function isTypedArray(a: any) { return ArrayBuffer.isView(a) && !(a instanceof DataView); } diff --git a/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_gauge.json b/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_gauge.json index 9e372fdd511a9..c92d0d6c0c9ee 100644 --- a/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_gauge.json +++ b/packages/react-examples/src/react-charting/DeclarativeChart/schema/fluent_gauge.json @@ -2,71 +2,35 @@ "visualizer": "plotly", "data": [ { - "name": "speed", - "text": 2468, - "type": "scatter", - "x": [0], - "y": [0], - "marker": { - "size": 28, - "color": "850000" - }, - "hoverinfo": "text+name", - "showlegend": false - }, - { - "hole": 0.5, - "type": "pie", - "marker": { - "colors": [ - "rgba(14, 127, 0, .5)", - "rgba(110, 154, 22, .5)", - "rgba(170, 202, 42, .5)", - "rgba(202, 209, 95, .5)", - "rgba(210, 206, 145, .5)", - "rgba(232, 226, 202, .5)", - "rgba(255, 255, 255, 0)" - ] - }, - "text": ["60% +", "50%", "40%", "30%", "20%", "10%", ""], - "rotation": 90, - "textinfo": "text", - "hoverinfo": "label", - "labels": ["151-180", "121-150", "91-120", "61-90", "31-60", "0-30", ""], - "values": [ - 8.333333333333334, 8.333333333333334, 8.333333333333334, 8.333333333333334, 8.333333333333334, - 8.333333333333334, 50 - ], - "showlegend": false, - "textposition": "inside" + "type": "indicator", + "mode": "gauge+number+delta", + "value": 420, + "title": { "text": "Speed", "font": { "size": 24 } }, + "delta": { "reference": 400, "increasing": { "color": "RebeccaPurple" } }, + "gauge": { + "axis": { "range": [null, 500], "tickwidth": 1, "tickcolor": "darkblue" }, + "bar": { "color": "darkblue" }, + "bgcolor": "white", + "borderwidth": 2, + "bordercolor": "gray", + "steps": [ + { "range": [0, 250], "color": "cyan" }, + { "range": [250, 400], "color": "royalblue" } + ], + "threshold": { + "line": { "color": "red", "width": 4 }, + "thickness": 0.75, + "value": 490 + } + } } ], "layout": { - "title": "Gauge", - "width": 1000, - "xaxis": { - "range": [-1, 1], - "showgrid": false, - "zeroline": false, - "showticklabels": false - }, - "yaxis": { - "range": [-1, 1], - "showgrid": false, - "zeroline": false, - "showticklabels": false - }, - "height": 1000, - "shapes": [ - { - "line": { - "color": "850000" - }, - "path": "M -.0 -0.025 L .0 0.025 L -0.659197688992 0.751969684779 Z", - "type": "path", - "fillcolor": "850000" - } - ] + "width": 500, + "height": 400, + "margin": { "t": 25, "r": 25, "l": 25, "b": 25 }, + "paper_bgcolor": "lavender", + "font": { "color": "darkblue", "family": "Arial" } }, "frames": [] }