From cc3b194a09b896a275105310a9b06b5f8cd0fdcb Mon Sep 17 00:00:00 2001 From: deecay Date: Sun, 8 Nov 2020 05:15:16 +0900 Subject: [PATCH] Histogram --- .../chart/Editor/AxisSettings.tsx | 8 +-- .../chart/Editor/ChartTypeSelect.tsx | 1 + .../chart/Editor/GeneralSettings.tsx | 4 +- .../chart/Editor/XAxisSettings.tsx | 31 +++++++++++- .../src/visualizations/chart/getChartData.ts | 40 ++++++++------- .../chart/plotly/prepareDefaultData.ts | 20 ++++++++ .../visualizations/chart/plotly/updateData.ts | 50 +++++++++++-------- .../src/visualizations/chart/plotly/utils.ts | 7 ++- 8 files changed, 111 insertions(+), 50 deletions(-) diff --git a/viz-lib/src/visualizations/chart/Editor/AxisSettings.tsx b/viz-lib/src/visualizations/chart/Editor/AxisSettings.tsx index 7db3a1a50f..5370b27d73 100644 --- a/viz-lib/src/visualizations/chart/Editor/AxisSettings.tsx +++ b/viz-lib/src/visualizations/chart/Editor/AxisSettings.tsx @@ -1,13 +1,9 @@ -import { isString, isObject, isFinite, isNumber, merge } from "lodash"; +import { isString, isObject, merge } from "lodash"; import React from "react"; import { useDebouncedCallback } from "use-debounce"; import * as Grid from "antd/lib/grid"; import { Section, Select, Input, InputNumber, ContextHelp } from "@/components/visualizations/editor"; - -function toNumber(value: any) { - value = isNumber(value) ? value : parseFloat(value); - return isFinite(value) ? value : null; -} +import { toNumber } from "../plotly/utils"; type OwnProps = { id: string; diff --git a/viz-lib/src/visualizations/chart/Editor/ChartTypeSelect.tsx b/viz-lib/src/visualizations/chart/Editor/ChartTypeSelect.tsx index e54f229644..35cf040b0f 100644 --- a/viz-lib/src/visualizations/chart/Editor/ChartTypeSelect.tsx +++ b/viz-lib/src/visualizations/chart/Editor/ChartTypeSelect.tsx @@ -6,6 +6,7 @@ import { visualizationsSettings } from "@/visualizations/visualizationsSettings" const allChartTypes = [ { type: "line", name: "Line", icon: "line-chart" }, { type: "column", name: "Bar", icon: "bar-chart" }, + { type: "histogram", name: "Histogram", icon: "bar-chart" }, { type: "area", name: "Area", icon: "area-chart" }, { type: "pie", name: "Pie", icon: "pie-chart" }, { type: "scatter", name: "Scatter", icon: "circle-o" }, diff --git a/viz-lib/src/visualizations/chart/Editor/GeneralSettings.tsx b/viz-lib/src/visualizations/chart/Editor/GeneralSettings.tsx index 03e12d5a66..95aa74ec84 100644 --- a/viz-lib/src/visualizations/chart/Editor/GeneralSettings.tsx +++ b/viz-lib/src/visualizations/chart/Editor/GeneralSettings.tsx @@ -293,7 +293,7 @@ export default function GeneralSettings({ options, data, onOptionsChange }: any) label="Stacking" data-test="Chart.Stacking" defaultValue={options.series.stacking} - disabled={!includes(["line", "area", "column"], options.globalSeriesType)} + disabled={!includes(["line", "area", "column", "histogram"], options.globalSeriesType)} onChange={(stacking: any) => onOptionsChange({ series: { stacking } })}> {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */} @@ -309,7 +309,7 @@ export default function GeneralSettings({ options, data, onOptionsChange }: any) )} - {includes(["line", "area", "column"], options.globalSeriesType) && ( + {includes(["line", "area", "column", "histogram"], options.globalSeriesType) && ( // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message
onOptionsChange({ xAxis })} /> - {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */} + {includes(["histogram"], options.globalSeriesType) && ( + +
+ onOptionsChange({ binSize: toNumber(binSize) })} + /> +
+ +
+ onOptionsChange({ binStart: toNumber(binStart) })} + /> +
+
+ )} +
{/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */} { + if (options.globalSeriesType === "histogram" && isEmpty(yValues)) { // @ts-expect-error ts-migrate(2322) FIXME: Type '{ x: number; y: never; $raw: any; }' is not ... Remove this comment to see the full error message - point = { x: xValue, y: yValue, $raw: point.$raw }; - if (eValue !== null) { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'yError' does not exist on type '{ $raw: ... Remove this comment to see the full error message - point.yError = eValue; - } + point = { x: xValue, y: null, $raw: point.$raw }; + addPointToSeries(point, series, "Count"); + } else { + each(yValues, (yValue, ySeriesName) => { + // @ts-expect-error ts-migrate(2322) FIXME: Type '{ x: number; y: never; $raw: any; }' is not ... Remove this comment to see the full error message + point = { x: xValue, y: yValue, $raw: point.$raw }; + if (eValue !== null) { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'yError' does not exist on type '{ $raw: ... Remove this comment to see the full error message + point.yError = eValue; + } - if (sizeValue !== null) { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'size' does not exist on type '{ $raw: an... Remove this comment to see the full error message - point.size = sizeValue; - } + if (sizeValue !== null) { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'zVal' does not exist on type '{ $raw: an... Remove this comment to see the full error message + point.size = sizeValue; + } - if (zValue !== null) { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'zVal' does not exist on type '{ $raw: an... Remove this comment to see the full error message - point.zVal = zValue; - } - addPointToSeries(point, series, ySeriesName); - }); + if (zValue !== null) { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'zVal' does not exist on type '{ $raw: an... Remove this comment to see the full error message + point.zVal = zValue; + } + addPointToSeries(point, series, ySeriesName); + }); + } } else { addPointToSeries(point, series, seriesName); } diff --git a/viz-lib/src/visualizations/chart/plotly/prepareDefaultData.ts b/viz-lib/src/visualizations/chart/plotly/prepareDefaultData.ts index c5102309f4..e53f3aab25 100644 --- a/viz-lib/src/visualizations/chart/plotly/prepareDefaultData.ts +++ b/viz-lib/src/visualizations/chart/plotly/prepareDefaultData.ts @@ -71,6 +71,23 @@ function prepareBoxSeries(series: any, options: any, { seriesColor }: any) { return series; } +function prepareHistogramSeries(series: any, options: any) { + series.type = 'histogram'; + series.hoverinfo = 'x+y+name'; + + if (!isNil(options.binSize)) { + series.autobinx = false; + series.xbins = series.xbins || {}; + series.xbins.size = options.binSize; + } + if (!isNil(options.binStart)) { + series.autobinx = false; + series.xbins = series.xbins || {}; + series.xbins.start = options.binStart; + } + return series; +} + function prepareSeries(series: any, options: any, additionalOptions: any) { const { hoverInfoPattern, index } = additionalOptions; @@ -148,6 +165,9 @@ function prepareSeries(series: any, options: any, additionalOptions: any) { return prepareBubbleSeries(plotlySeries, options, additionalOptions); case "box": return prepareBoxSeries(plotlySeries, options, additionalOptions); + case 'histogram': + // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 3. + return prepareHistogramSeries(plotlySeries, options, additionalOptions); default: return plotlySeries; } diff --git a/viz-lib/src/visualizations/chart/plotly/updateData.ts b/viz-lib/src/visualizations/chart/plotly/updateData.ts index 03557683dc..6b4f0fafcd 100644 --- a/viz-lib/src/visualizations/chart/plotly/updateData.ts +++ b/viz-lib/src/visualizations/chart/plotly/updateData.ts @@ -101,31 +101,37 @@ function updateSeriesText(seriesList: any, options: any) { function updatePercentValues(seriesList: any, options: any) { if (options.series.percentValues) { - // Some series may not have corresponding x-values; - // do calculations for each x only for series that do have that x - const sumOfCorrespondingPoints = new Map(); - each(seriesList, series => { - series.sourceData.forEach((item: any) => { - const sum = sumOfCorrespondingPoints.get(item.x) || 0; - sumOfCorrespondingPoints.set(item.x, sum + Math.abs(item.y || 0.0)); + if (options.globalSeriesType === "histogram") { + each(seriesList, (series) => { + series.histnorm = "probability"; }); - }); - - each(seriesList, series => { - const yValues: any = []; - - series.sourceData.forEach((item: any) => { - if (isNil(item.y) && !options.missingValuesAsZero) { - item.yPercent = null; - } else { - const sum = sumOfCorrespondingPoints.get(item.x); - item.yPercent = (item.y / sum); - } - yValues.push(item.yPercent); + } else { + // Some series may not have corresponding x-values; + // do calculations for each x only for series that do have that x + const sumOfCorrespondingPoints = new Map(); + each(seriesList, series => { + series.sourceData.forEach((item: any) => { + const sum = sumOfCorrespondingPoints.get(item.x) || 0; + sumOfCorrespondingPoints.set(item.x, sum + Math.abs(item.y || 0.0)); + }); }); - series.y = yValues; - }); + each(seriesList, series => { + const yValues: any = []; + + series.sourceData.forEach((item: any) => { + if (isNil(item.y) && !options.missingValuesAsZero) { + item.yPercent = null; + } else { + const sum = sumOfCorrespondingPoints.get(item.x); + item.yPercent = (item.y / sum); + } + yValues.push(item.yPercent); + }); + + series.y = yValues; + }); + } } } diff --git a/viz-lib/src/visualizations/chart/plotly/utils.ts b/viz-lib/src/visualizations/chart/plotly/utils.ts index b1551fd5b6..4e6af32207 100644 --- a/viz-lib/src/visualizations/chart/plotly/utils.ts +++ b/viz-lib/src/visualizations/chart/plotly/utils.ts @@ -1,4 +1,4 @@ -import { isUndefined } from "lodash"; +import { isUndefined, isNumber, isFinite } from "lodash"; import moment from "moment"; // @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module 'plot... Remove this comment to see the full error message import plotlyCleanNumber from "plotly.js/src/lib/clean_number"; @@ -7,6 +7,11 @@ export function cleanNumber(value: any) { return isUndefined(value) ? value : plotlyCleanNumber(value); } +export function toNumber(value: any) { + value = isNumber(value) ? value : parseFloat(value); + return isFinite(value) ? value : null; +} + export function getSeriesAxis(series: any, options: any) { const seriesOptions = options.seriesOptions[series.name] || { type: options.globalSeriesType }; if (seriesOptions.yAxis === 1 && (!options.series.stacking || seriesOptions.type === "line")) {