From 1b848089cadc89be120d4be86be34fc18b05fde8 Mon Sep 17 00:00:00 2001 From: Cameron Yick Date: Sat, 3 Jul 2021 21:40:52 -0400 Subject: [PATCH] fix(bar,summary,xyplot): safely stringify null and undefined cells when finding unique values Previously, there would be runtime errors if users switched to columns with cell values that lacked a toString() method BREAKING CHANGE: App does not crash when columns contain cell values that do not have toString() inside, such as "undefined" or "null" --- src/charts/bar.tsx | 12 ++---------- src/charts/shared.tsx | 21 ++++++++++++++++++++- src/charts/summary.tsx | 10 ++-------- src/charts/xyplot.tsx | 11 ++--------- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/charts/bar.tsx b/src/charts/bar.tsx index 02c0c9b..8b5602f 100644 --- a/src/charts/bar.tsx +++ b/src/charts/bar.tsx @@ -5,7 +5,7 @@ import TooltipContent, { safeDisplayValue } from "../utilities/tooltip-content"; import { numeralFormatting } from "../utilities/utilities"; import * as Dx from "../utilities/types"; -import { sortByOrdinalRange } from "./shared"; +import { sortByOrdinalRange, getUniqueValues } from "./shared"; interface BarOptions { selectedDimensions: string[]; @@ -112,15 +112,7 @@ export const semioticBarChart = ( }; } - const uniqueValues = sortedData.reduce( - (uniques, datapoint) => - !uniques.find( - (uniqueDimName: string) => uniqueDimName === datapoint[dim1].toString(), - ) - ? [...uniques, datapoint[dim1].toString()] - : uniques, - [], - ); + const uniqueValues = dim1 === "none" ? [] : getUniqueValues(sortedData, dim1); if (!colorHashOverride && dim1 && dim1 !== "none") { uniqueValues.forEach((value: string, index: number) => { diff --git a/src/charts/shared.tsx b/src/charts/shared.tsx index 9f3c821..8ba4428 100644 --- a/src/charts/shared.tsx +++ b/src/charts/shared.tsx @@ -20,7 +20,7 @@ export const sortByOrdinalRange = ( rAccessor: string | (() => void), secondarySort: string, data: Dx.DataProps["data"], -): any[] => { +): Dx.DataProps["data"] => { const subsortData: { [index: string]: SubsortObject } = {}; let subsortArrays: SubsortObject[] = []; data.forEach((datapoint) => { @@ -65,3 +65,22 @@ export const sortByOrdinalRange = ( [], ); }; + +/* + Returns uniques values in a column as strings + Safely stringifies different data types, including null/undefined. +*/ +export const getUniqueValues = ( + points: Dx.DataProps["data"], + accessor: string, +): string[] => { + return [ + ...new Set( + points.map((d) => { + const value = d[accessor]; + // Don't call stringify on a string, as it will add "quote" marks around your value. + return typeof value === "string" ? value : JSON.stringify(value); + }), + ), + ]; +}; diff --git a/src/charts/summary.tsx b/src/charts/summary.tsx index c486fb7..97e9228 100644 --- a/src/charts/summary.tsx +++ b/src/charts/summary.tsx @@ -5,6 +5,7 @@ import HTMLLegend from "../components/HTMLLegend"; import TooltipContent, { safeDisplayValue } from "../utilities/tooltip-content"; import * as Dx from "../utilities/types"; import { numeralFormatting } from "../utilities/utilities"; +import { getUniqueValues } from "./shared"; interface SummaryOptions { chart: Dx.Chart; @@ -37,14 +38,7 @@ export const semioticSummaryChart = ( const rAccessor = metric1; - const uniqueValues = data.reduce( - (uniqueArray: string[], datapoint) => - (!uniqueArray.find( - (dimValue: string) => dimValue === datapoint[dim1].toString(), - ) && [...uniqueArray, datapoint[dim1].toString()]) || - uniqueArray, - [], - ); + const uniqueValues = dim1 === "none" ? [] : getUniqueValues(data, dim1); if (!colorHashOverride && dim1 && dim1 !== "none") { uniqueValues.sort().forEach((dimValue, index) => { diff --git a/src/charts/xyplot.tsx b/src/charts/xyplot.tsx index e5fe54a..05bc0ae 100644 --- a/src/charts/xyplot.tsx +++ b/src/charts/xyplot.tsx @@ -7,7 +7,7 @@ import TooltipContent from "../utilities/tooltip-content"; import { numeralFormatting } from "../utilities/utilities"; import * as Dx from "../utilities/types"; -import { sortByOrdinalRange } from "./shared"; +import { sortByOrdinalRange, getUniqueValues } from "./shared"; import styled from "styled-components"; @@ -253,14 +253,7 @@ export const semioticXYPlot = ( dim1 && dim1 !== "none" ) { - const uniqueValues = sortedData.reduce( - (uniqueArray, datapoint) => - (!uniqueArray.find( - (uniqueDim: string) => uniqueDim === datapoint[dim1].toString(), - ) && [...uniqueArray, datapoint[dim1].toString()]) || - uniqueArray, - [], - ); + const uniqueValues = getUniqueValues(sortedData, dim1); if (!colorHashOverride) { uniqueValues.sort().forEach((dimValue: string, index: number) => {