From 82a04a46199224638f96c886f53988bba7912909 Mon Sep 17 00:00:00 2001 From: adrianmroz <78143552+adrianmroz-allegro@users.noreply.github.com> Date: Wed, 9 Mar 2022 15:46:21 +0100 Subject: [PATCH] Split utils (#849) * Set limit for newly created Split based on last available limit on dimension * Utils for selecting values and formatting them based on defined Split * Replace raw property access with proper Split utils * Fix test * Split tests * Use split utils in Scatterplot --- .../splits/flattened-split-columns.tsx | 2 +- .../improved-bar-chart/bar/single-bar.tsx | 8 ++--- .../bar/single-time-shift-bar.tsx | 8 ++--- .../improved-bar-chart/bar/stacked-bar.tsx | 11 +++---- .../bar/stacked-time-shift-bar.tsx | 13 +++----- .../bars/bars-container.tsx | 6 +--- .../improved-bar-chart/bars/bars.tsx | 14 +++------ .../foreground/foreground.tsx | 14 +++------ .../foreground/highlight-modal.tsx | 16 ++++------ .../foreground/highlight-overlay.tsx | 11 +++---- .../hover-tooltip/hover-tooltip.tsx | 10 ++---- .../hover-tooltip/tooltip-content.tsx | 3 +- .../interactions/interaction-controller.tsx | 5 ++- .../utils/bar-chart-model.ts | 3 +- .../utils/transpose-dataset.ts | 23 +++++++------- .../utils/x-domain.mocha.ts | 5 +-- .../improved-bar-chart/utils/x-domain.ts | 3 +- .../improved-bar-chart/utils/x-scale.mocha.ts | 9 +----- .../improved-bar-chart/utils/x-scale.ts | 6 +--- .../charts/charts-per-series/series-chart.tsx | 4 +-- .../series-hover-content.tsx | 4 +-- .../charts/charts-per-split/label.tsx | 7 +++-- .../charts-per-split/nominal-value-key.ts | 8 ++--- .../charts/charts-per-split/split-chart.tsx | 2 +- .../interactions/find-closest-datum.ts | 18 +++++------ .../line-chart/legend/split-legend.tsx | 2 +- .../line-chart/utils/x-scale.ts | 10 +++--- .../scatterplot/scatterplot.tsx | 6 ++-- .../visualizations/scatterplot/tooltip.tsx | 8 ++--- src/common/models/split/split.mocha.ts | 31 +++++++++++++++++++ src/common/models/split/split.ts | 17 +++++++--- 31 files changed, 142 insertions(+), 145 deletions(-) diff --git a/src/client/components/tabular-scroller/splits/flattened-split-columns.tsx b/src/client/components/tabular-scroller/splits/flattened-split-columns.tsx index 9ede609eb..5f2b4fe46 100644 --- a/src/client/components/tabular-scroller/splits/flattened-split-columns.tsx +++ b/src/client/components/tabular-scroller/splits/flattened-split-columns.tsx @@ -32,7 +32,7 @@ export const FlattenedSplitColumns: React.SFC = ({ s {splits.map(split => { const { reference } = split; - const value = datum[reference]; + const value = split.selectValue(datum); return
{formatSegment(value, timezone)}
; })}
; diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/bar/single-bar.tsx b/src/client/visualizations/bar-chart/improved-bar-chart/bar/single-bar.tsx index c0541be82..630ea8923 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/bar/single-bar.tsx +++ b/src/client/visualizations/bar-chart/improved-bar-chart/bar/single-bar.tsx @@ -17,8 +17,8 @@ import { Datum } from "plywood"; import React from "react"; import { ConcreteSeries } from "../../../../../common/models/series/concrete-series"; -import { Unary } from "../../../../../common/utils/functional/functional"; import { LinearScale } from "../../../../utils/linear-scale/linear-scale"; +import { BaseBarChartModel } from "../utils/bar-chart-model"; import { DomainValue } from "../utils/x-domain"; import { XScale } from "../utils/x-scale"; import { SIDE_PADDING } from "./padding"; @@ -28,13 +28,13 @@ interface SingleBarProps { yScale: LinearScale; xScale: XScale; series: ConcreteSeries; - getX: Unary; + model: BaseBarChartModel; } export const SingleBar: React.SFC = props => { - const { datum, xScale, yScale, getX, series } = props; + const { datum, xScale, yScale, model: { continuousSplit }, series } = props; const [maxHeight] = yScale.range(); - const x = getX(datum); + const x = continuousSplit.selectValue(datum); const xPos = xScale.calculate(x) + SIDE_PADDING; const width = xScale.bandwidth() - (2 * SIDE_PADDING); const y = series.selectValue(datum); diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/bar/single-time-shift-bar.tsx b/src/client/visualizations/bar-chart/improved-bar-chart/bar/single-time-shift-bar.tsx index c2c09260d..c94a1d36a 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/bar/single-time-shift-bar.tsx +++ b/src/client/visualizations/bar-chart/improved-bar-chart/bar/single-time-shift-bar.tsx @@ -17,8 +17,8 @@ import { Datum } from "plywood"; import React from "react"; import { ConcreteSeries, SeriesDerivation } from "../../../../../common/models/series/concrete-series"; -import { Unary } from "../../../../../common/utils/functional/functional"; import { LinearScale } from "../../../../utils/linear-scale/linear-scale"; +import { BaseBarChartModel } from "../utils/bar-chart-model"; import { DomainValue } from "../utils/x-domain"; import { XScale } from "../utils/x-scale"; import { SIDE_PADDING } from "./padding"; @@ -28,13 +28,13 @@ interface SingleTimeShiftBar { yScale: LinearScale; xScale: XScale; series: ConcreteSeries; - getX: Unary; + model: BaseBarChartModel; } export const SingleTimeShiftBar: React.SFC = props => { - const { datum, xScale, yScale, getX, series } = props; + const { datum, xScale, yScale, model: { continuousSplit }, series } = props; const [maxHeight] = yScale.range(); - const x = getX(datum); + const x = continuousSplit.selectValue(datum); const xStart = xScale.calculate(x); const rangeBand = xScale.bandwidth(); const fullWidth = rangeBand - 2 * SIDE_PADDING; diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/bar/stacked-bar.tsx b/src/client/visualizations/bar-chart/improved-bar-chart/bar/stacked-bar.tsx index 8e4340d5a..2b8a11bd9 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/bar/stacked-bar.tsx +++ b/src/client/visualizations/bar-chart/improved-bar-chart/bar/stacked-bar.tsx @@ -17,7 +17,6 @@ import { Datum } from "plywood"; import React from "react"; import { ConcreteSeries } from "../../../../../common/models/series/concrete-series"; -import { Unary } from "../../../../../common/utils/functional/functional"; import { selectSplitDatums } from "../../../../utils/dataset/selectors/selectors"; import { LinearScale } from "../../../../utils/linear-scale/linear-scale"; import { StackedBarChartModel } from "../utils/bar-chart-model"; @@ -32,21 +31,19 @@ interface StackedBarProps { yScale: LinearScale; xScale: XScale; series: ConcreteSeries; - getX: Unary; } export const StackedBar: React.SFC = props => { - const { datum, xScale, yScale, getX, series, model } = props; - const ref = model.nominalSplit.reference; + const { datum, xScale, yScale, series, model: { colors, continuousSplit, nominalSplit } } = props; const datums = selectSplitDatums(datum); - const x = getX(datum); + const x = continuousSplit.selectValue(datum); const xPos = xScale.calculate(x) + SIDE_PADDING; const width = xScale.bandwidth() - (2 * SIDE_PADDING); - const color = (d: Datum) => model.colors.get(String(d[ref])); + const color = (d: Datum) => colors.get(String(nominalSplit.selectValue(d))); return {datums.map(datum => { - const key = datum[ref]; + const key = String(nominalSplit.selectValue(datum)); const y = series.selectValue(datum); const y0 = selectBase(datum, series); const yPos = yScale(y + y0); diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/bar/stacked-time-shift-bar.tsx b/src/client/visualizations/bar-chart/improved-bar-chart/bar/stacked-time-shift-bar.tsx index f9f71e351..6d8c24332 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/bar/stacked-time-shift-bar.tsx +++ b/src/client/visualizations/bar-chart/improved-bar-chart/bar/stacked-time-shift-bar.tsx @@ -18,7 +18,6 @@ import * as d3 from "d3"; import { Datum } from "plywood"; import React from "react"; import { ConcreteSeries, SeriesDerivation } from "../../../../../common/models/series/concrete-series"; -import { Unary } from "../../../../../common/utils/functional/functional"; import { selectSplitDatums } from "../../../../utils/dataset/selectors/selectors"; import { LinearScale } from "../../../../utils/linear-scale/linear-scale"; import { StackedBarChartModel } from "../utils/bar-chart-model"; @@ -33,24 +32,22 @@ interface StackedTimeShiftBarProps { yScale: LinearScale; xScale: XScale; series: ConcreteSeries; - getX: Unary; } export const StackedTimeShiftBar: React.SFC = props => { - const { datum, xScale, yScale, getX, series, model } = props; - const { reference: nominalReference } = model.nominalSplit; + const { datum, xScale, yScale, series, model: { nominalSplit, continuousSplit, colors } } = props; const datums = selectSplitDatums(datum); - const x = getX(datum); + const x = continuousSplit.selectValue(datum); const xStart = xScale.calculate(x); const rangeBand = xScale.bandwidth(); const fullWidth = rangeBand - 2 * SIDE_PADDING; const barWidth = fullWidth * 2 / 3; - const color = (d: Datum) => model.colors.get(String(d[nominalReference])); + const color = (d: Datum) => colors.get(String(nominalSplit.selectValue(d))); return {datums.map(datum => { - const key = `${datum[nominalReference]}--previous`; + const key = `${nominalSplit.selectValue(datum)}--previous`; const yPrevious = series.selectValue(datum, SeriesDerivation.PREVIOUS); const yPreviousBase = selectBase(datum, series, SeriesDerivation.PREVIOUS); const yPreviousPos = yScale(yPrevious + yPreviousBase); @@ -70,7 +67,7 @@ export const StackedTimeShiftBar: React.SFC = props => />; })} {datums.map(datum => { - const key = `${datum[nominalReference]}--current`; + const key = `${nominalSplit.selectValue(datum)}--current`; const yCurrent = series.selectValue(datum); const yCurrentBase = selectBase(datum, series); const yCurrentPos = yScale(yCurrent + yCurrentBase); diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/bars/bars-container.tsx b/src/client/visualizations/bar-chart/improved-bar-chart/bars/bars-container.tsx index 539ec942b..90060d6da 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/bars/bars-container.tsx +++ b/src/client/visualizations/bar-chart/improved-bar-chart/bars/bars-container.tsx @@ -25,7 +25,7 @@ import { Foreground } from "../foreground/foreground"; import { Interaction } from "../interactions/interaction"; import { BarChartModel } from "../utils/bar-chart-model"; import { calculateChartStage } from "../utils/layout"; -import { xGetter, XScale } from "../utils/x-scale"; +import { XScale } from "../utils/x-scale"; import { yExtent } from "../utils/y-extent"; import { Bars } from "./bars"; import "./bars.scss"; @@ -53,8 +53,6 @@ export class BarsContainer extends React.Component { const { dropHighlight, acceptHighlight, interaction, model, stage, scrollLeft, series, totals, datums, xScale } = this.props; const hasComparison = model.hasComparison; const chartStage = calculateChartStage(stage); - const { reference: continuousReference } = model.continuousSplit; - const getX = xGetter(continuousReference); const extent = yExtent(datums, series, hasComparison); const yScale = getScale(extent, chartStage.height); @@ -73,7 +71,6 @@ export class BarsContainer extends React.Component { model={model} stage={chartStage} xScale={xScale} - getX={getX} series={series} datums={datums} /> {interaction && { model={model} xScale={xScale} series={series} - getX={getX} yScale={yScale} />} ; diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/bars/bars.tsx b/src/client/visualizations/bar-chart/improved-bar-chart/bars/bars.tsx index aa09a263f..de09d2997 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/bars/bars.tsx +++ b/src/client/visualizations/bar-chart/improved-bar-chart/bars/bars.tsx @@ -18,14 +18,12 @@ import { Datum } from "plywood"; import React from "react"; import { ConcreteSeries } from "../../../../../common/models/series/concrete-series"; import { Stage } from "../../../../../common/models/stage/stage"; -import { Unary } from "../../../../../common/utils/functional/functional"; import getScale, { LinearScale } from "../../../../utils/linear-scale/linear-scale"; import { SingleBar } from "../bar/single-bar"; import { SingleTimeShiftBar } from "../bar/single-time-shift-bar"; import { StackedBar } from "../bar/stacked-bar"; import { StackedTimeShiftBar } from "../bar/stacked-time-shift-bar"; -import { BarChartModel, isStacked, StackedBarChartModel } from "../utils/bar-chart-model"; -import { DomainValue } from "../utils/x-domain"; +import { BarChartModel, isStacked } from "../utils/bar-chart-model"; import { XScale } from "../utils/x-scale"; import { yExtent } from "../utils/y-extent"; import { Background } from "./background"; @@ -36,7 +34,6 @@ interface BarProps { yScale: LinearScale; xScale: XScale; series: ConcreteSeries; - getX: Unary; } const Bar: React.SFC = props => { @@ -48,21 +45,20 @@ const Bar: React.SFC = props => { : ; } return showPrevious - ? - : ; + ? + : ; }; interface BarsProps { model: BarChartModel; stage: Stage; xScale: XScale; - getX: Unary; series: ConcreteSeries; datums: Datum[]; } export const Bars: React.SFC = props => { - const { model, stage, getX, xScale, series, datums } = props; + const { model, stage, xScale, series, datums } = props; const extent = yExtent(datums, series, model.hasComparison); const yScale = getScale(extent, stage.height); if (!yScale) return null; @@ -77,7 +73,7 @@ export const Bars: React.SFC = props => { yScale={yScale} xScale={xScale} series={series} - getX={getX} />)} + />)} ; diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/foreground/foreground.tsx b/src/client/visualizations/bar-chart/improved-bar-chart/foreground/foreground.tsx index e3b3c2ca7..2ec674d6b 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/foreground/foreground.tsx +++ b/src/client/visualizations/bar-chart/improved-bar-chart/foreground/foreground.tsx @@ -14,16 +14,14 @@ * limitations under the License. */ -import { Datum } from "plywood"; import React from "react"; import { ConcreteSeries } from "../../../../../common/models/series/concrete-series"; import { Stage } from "../../../../../common/models/stage/stage"; -import { Nullary, Unary } from "../../../../../common/utils/functional/functional"; +import { Nullary } from "../../../../../common/utils/functional/functional"; import { LinearScale } from "../../../../utils/linear-scale/linear-scale"; import { HoverTooltip } from "../hover-tooltip/hover-tooltip"; import { Interaction, isHighlight, isHover } from "../interactions/interaction"; import { BarChartModel } from "../utils/bar-chart-model"; -import { DomainValue } from "../utils/x-domain"; import { XScale } from "../utils/x-scale"; import { HighlightModal } from "./highlight-modal"; import { HighlightOverlay } from "./highlight-overlay"; @@ -36,13 +34,12 @@ interface ForegroundProps { xScale: XScale; yScale: LinearScale; series: ConcreteSeries; - getX: Unary; model: BarChartModel; stage: Stage; } export const Foreground: React.SFC = props => { - const { stage, dropHighlight, acceptHighlight, container, getX, model, series, xScale, yScale, interaction } = props; + const { stage, dropHighlight, acceptHighlight, container, model, series, xScale, yScale, interaction } = props; const rect = container.current.getBoundingClientRect(); return {isHighlight(interaction) && @@ -50,27 +47,24 @@ export const Foreground: React.SFC = props => { interaction={interaction} dropHighlight={dropHighlight} acceptHighlight={acceptHighlight} - timezone={model.timezone} xScale={xScale} yScale={yScale} - getX={getX} + model={model} series={series} rect={rect} /> + model={model} /> } {isHover(interaction) && } ; diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/foreground/highlight-modal.tsx b/src/client/visualizations/bar-chart/improved-bar-chart/foreground/highlight-modal.tsx index 2f522b7f3..6d2211bd8 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/foreground/highlight-modal.tsx +++ b/src/client/visualizations/bar-chart/improved-bar-chart/foreground/highlight-modal.tsx @@ -14,15 +14,13 @@ * limitations under the License. */ -import { Timezone } from "chronoshift"; -import { Datum } from "plywood"; import React from "react"; import { ConcreteSeries } from "../../../../../common/models/series/concrete-series"; -import { formatValue } from "../../../../../common/utils/formatter/formatter"; -import { Nullary, Unary } from "../../../../../common/utils/functional/functional"; +import { Nullary } from "../../../../../common/utils/functional/functional"; import { HighlightModal as BaseHighlightModal } from "../../../../components/highlight-modal/highlight-modal"; import { LinearScale } from "../../../../utils/linear-scale/linear-scale"; import { Highlight } from "../interactions/interaction"; +import { BarChartModel } from "../utils/bar-chart-model"; import { DomainValue } from "../utils/x-domain"; import { XScale } from "../utils/x-scale"; @@ -30,31 +28,29 @@ interface HighlightModalProps { interaction: Highlight; dropHighlight: Nullary; acceptHighlight: Nullary; - timezone: Timezone; xScale: XScale; yScale: LinearScale; series: ConcreteSeries; - getX: Unary; + model: BarChartModel; rect: ClientRect | DOMRect; } export const HighlightModal: React.SFC = props => { const { - timezone, + model: { timezone, continuousSplit }, rect: { left, top }, interaction: { datum }, dropHighlight, acceptHighlight, yScale, - getX, series, xScale } = props; - const xValue = getX(datum); + const xValue = continuousSplit.selectValue(datum); const x = xScale.calculate(xValue) + (xScale.bandwidth() / 2); const yValue = series.selectValue(datum); const y = yScale(yValue); return ; stage: Stage; - showPrevious: boolean; + model: BarChartModel; } function getYValue(datum: Datum, series: ConcreteSeries, includePrevious: boolean): number { @@ -44,11 +43,11 @@ function getYValue(datum: Datum, series: ConcreteSeries, includePrevious: boolea } export const HighlightOverlay: React.SFC = props => { - const { stage, yScale, series, xScale, showPrevious, interaction: { datum }, getX } = props; - const xValue = getX(datum); + const { stage, yScale, series, xScale, model: { hasComparison, continuousSplit }, interaction: { datum } } = props; + const xValue = continuousSplit.selectValue(datum); const left = xScale.calculate(xValue); const right = left + xScale.bandwidth(); - const yValue = getYValue(datum, series, showPrevious); + const yValue = getYValue(datum, series, hasComparison); const top = yScale(yValue) + stage.y - TOP_PADDING; return ; }; diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/hover-tooltip/hover-tooltip.tsx b/src/client/visualizations/bar-chart/improved-bar-chart/hover-tooltip/hover-tooltip.tsx index 83b6662f0..239f871f8 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/hover-tooltip/hover-tooltip.tsx +++ b/src/client/visualizations/bar-chart/improved-bar-chart/hover-tooltip/hover-tooltip.tsx @@ -14,11 +14,8 @@ * limitations under the License. */ -import { Datum } from "plywood"; import React from "react"; import { ConcreteSeries } from "../../../../../common/models/series/concrete-series"; -import { formatValue } from "../../../../../common/utils/formatter/formatter"; -import { Unary } from "../../../../../common/utils/functional/functional"; import { SegmentBubble } from "../../../../components/segment-bubble/segment-bubble"; import { LinearScale } from "../../../../utils/linear-scale/linear-scale"; import { Hover } from "../interactions/interaction"; @@ -32,7 +29,6 @@ interface HoverTooltipProps { xScale: XScale; yScale: LinearScale; series: ConcreteSeries; - getX: Unary; model: BarChartModel; rect: ClientRect | DOMRect; } @@ -42,17 +38,17 @@ export const HoverTooltip: React.SFC = props => { model, rect: { left, top }, interaction: { datum }, - getX, series, xScale, yScale } = props; + const { continuousSplit, timezone } = model; const y = yScale(series.selectValue(datum)); - const xValue = getX(datum); + const xValue = continuousSplit.selectValue(datum); const x = xScale.calculate(xValue) + (xScale.bandwidth() / 2); return } />; }; diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/hover-tooltip/tooltip-content.tsx b/src/client/visualizations/bar-chart/improved-bar-chart/hover-tooltip/tooltip-content.tsx index 7c035f0dd..c4dd2c1dc 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/hover-tooltip/tooltip-content.tsx +++ b/src/client/visualizations/bar-chart/improved-bar-chart/hover-tooltip/tooltip-content.tsx @@ -31,11 +31,10 @@ interface ContentProps { function colorEntries(datum: Datum, series: ConcreteSeries, model: StackedBarChartModel) { const { nominalSplit, colors, hasComparison } = model; - const { reference } = nominalSplit; const datums = selectSplitDatums(datum); const colorEntries = colors.entrySeq().toArray(); return colorEntries.map(([name, color]) => { - const datum = datums.find(d => String(d[reference]) === name); + const datum = datums.find(d => String(nominalSplit.selectValue(d)) === name); if (!datum) { return { color, name, value: "-" }; diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/interactions/interaction-controller.tsx b/src/client/visualizations/bar-chart/improved-bar-chart/interactions/interaction-controller.tsx index 34323308a..abae175c6 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/interactions/interaction-controller.tsx +++ b/src/client/visualizations/bar-chart/improved-bar-chart/interactions/interaction-controller.tsx @@ -110,9 +110,8 @@ export class InteractionController extends React.Component safeEquals(value, datum[reference])); + const { model: { continuousSplit }, datums } = this.props; + return datums.find(datum => safeEquals(value, continuousSplit.selectValue(datum))); } getSeriesFromEvent(y: number, part: ScrollerPart): ConcreteSeries | null { diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/utils/bar-chart-model.ts b/src/client/visualizations/bar-chart/improved-bar-chart/utils/bar-chart-model.ts index f5b3d9bbb..ba1c141cd 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/utils/bar-chart-model.ts +++ b/src/client/visualizations/bar-chart/improved-bar-chart/utils/bar-chart-model.ts @@ -71,10 +71,9 @@ function readCommons(essence: Essence): Omit { } function createColorMap(nominalSplit: Split, dataset: Dataset): ColorMap { - const { reference } = nominalSplit; const datums = selectFirstSplitDatums(dataset); return datums.reduce((map: ColorMap, datum: Datum, i: number) => { - const key = String(datum[reference]); + const key = String(nominalSplit.selectValue(datum)); const colorIndex = i % NORMAL_COLORS.length; const color = NORMAL_COLORS[colorIndex]; return map.set(key, color); diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/utils/transpose-dataset.ts b/src/client/visualizations/bar-chart/improved-bar-chart/utils/transpose-dataset.ts index 81066227f..78bbd4643 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/utils/transpose-dataset.ts +++ b/src/client/visualizations/bar-chart/improved-bar-chart/utils/transpose-dataset.ts @@ -15,23 +15,24 @@ */ import { Dataset, Datum, TimeRange } from "plywood"; +import { Split } from "../../../../../common/models/split/split"; import { Binary, cons, replaceAt, Unary } from "../../../../../common/utils/functional/functional"; import { SPLIT } from "../../../../config/constants"; import { selectFirstSplitDatums, selectSplitDatums } from "../../../../utils/dataset/selectors/selectors"; import { BarChartModel, isStacked } from "./bar-chart-model"; -function rangeComparator(continuousRef: string): Binary { +function rangeComparator(continuousSplit: Split): Binary { return (a: Datum, b: Datum) => { - const aRange = a[continuousRef] as TimeRange; - const bRange = b[continuousRef] as TimeRange; + const aRange = continuousSplit.selectValue(a); + const bRange = continuousSplit.selectValue(b); return aRange.compare(bRange); }; } -function equalBy(continuousRef: string, datum: Datum): Unary { - const value = datum[continuousRef]; +function equalBy(split: Split, datum: Datum): Unary { + const value = split.selectValue(datum); return (d: Datum) => { - const range = d[continuousRef]; + const range = split.selectValue(d); return TimeRange.isTimeRange(value) && TimeRange.isTimeRange(range) && value.equals(range); }; } @@ -57,19 +58,19 @@ function mergeDatums(continuousRef: string, datum: Datum, newDatum: Datum): Datu export function transposeDataset(dataset: Dataset, model: BarChartModel): Datum[] { if (!isStacked(model)) return selectFirstSplitDatums(dataset); - const { reference } = model.continuousSplit; + const { continuousSplit } = model; const { data } = dataset.flatten(); const result = data.reduce((coll: Datum[], currentDatum: Datum) => { - const idx = coll.findIndex(equalBy(reference, currentDatum)); + const idx = coll.findIndex(equalBy(continuousSplit, currentDatum)); const notFound = idx === -1; if (notFound) { - return cons(coll, createInnerDatum(reference, currentDatum)); + return cons(coll, createInnerDatum(continuousSplit.reference, currentDatum)); } - return replaceAt(coll, idx, mergeDatums(reference, coll[idx], currentDatum)); + return replaceAt(coll, idx, mergeDatums(continuousSplit.reference, coll[idx], currentDatum)); }, []); - return result.sort(rangeComparator(reference)); + return result.sort(rangeComparator(continuousSplit)); } diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-domain.mocha.ts b/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-domain.mocha.ts index 5b92ec0c7..c3476aa25 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-domain.mocha.ts +++ b/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-domain.mocha.ts @@ -15,11 +15,12 @@ */ import { expect } from "chai"; +import { Split } from "../../../../../common/models/split/split"; import { BarChartModel } from "./bar-chart-model"; import { getXDomain } from "./x-domain"; describe("getXDomain", () => { - const mode = { continuousSplit: { reference: "foobar" } } as any as BarChartModel; + const model = { continuousSplit: new Split({ reference: "foobar" }) } as any as BarChartModel; const datums = [ { foobar: 1, bazz: 42 }, { foobar: 65, bazz: 1, qvux: 42 }, @@ -27,7 +28,7 @@ describe("getXDomain", () => { ]; it("should pick split values from datums", () => { - const domain = getXDomain(datums, mode); + const domain = getXDomain(datums, model); expect(domain).to.be.deep.equal([1, 65, "dummy"]); }); }); diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-domain.ts b/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-domain.ts index 6d6212a51..d91e50638 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-domain.ts +++ b/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-domain.ts @@ -22,6 +22,5 @@ export type DomainValue = boolean | number | string | Date | NumberRange | TimeR export type XDomain = DomainValue[]; export function getXDomain(datums: Datum[], { continuousSplit }: BarChartModel): XDomain { - const { reference: continuousReference } = continuousSplit; - return datums.map(datum => datum[continuousReference] as DomainValue); + return datums.map(datum => continuousSplit.selectValue(datum)); } diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-scale.mocha.ts b/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-scale.mocha.ts index ffae4b2c3..6547d2f87 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-scale.mocha.ts +++ b/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-scale.mocha.ts @@ -18,7 +18,7 @@ import { expect, use } from "chai"; import { TimeRange } from "plywood"; import { january } from "../../../../utils/dataset/selectors/dataset-fixtures"; import equivalent from "../../../../utils/test-utils/equivalent"; -import { createXScale, xGetter } from "./x-scale"; +import { createXScale } from "./x-scale"; use(equivalent); @@ -28,13 +28,6 @@ const januaryDateAsRange = (date: number) => new TimeRange({ }); describe("x-scale", () => { - describe("xGetter", () => { - it("should return function that picks defined prop", () => { - const getDummy = xGetter("dummy"); - expect(getDummy({ dummy: "foo" })).to.be.equal("foo"); - }); - }); - describe("createXScale", () => { describe("TimeRange", () => { const domain = [ diff --git a/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-scale.ts b/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-scale.ts index 09bb8e95a..67f12ab1d 100644 --- a/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-scale.ts +++ b/src/client/visualizations/bar-chart/improved-bar-chart/utils/x-scale.ts @@ -15,8 +15,7 @@ */ import * as d3 from "d3"; -import { Datum, NumberRange, TimeRange } from "plywood"; -import { Unary } from "../../../../../common/utils/functional/functional"; +import { NumberRange, TimeRange } from "plywood"; import { DomainValue, XDomain } from "./x-domain"; export interface XScale { @@ -45,9 +44,6 @@ export function createXScale(domain: XDomain, width: number): XScale { }; } -export const xGetter = (reference: string): Unary => - datum => (datum[reference] as DomainValue); - function formatDomainValue(value: DomainValue): string { if (TimeRange.isTimeRange(value)) { const { start } = value; diff --git a/src/client/visualizations/line-chart/charts/charts-per-series/series-chart.tsx b/src/client/visualizations/line-chart/charts/charts-per-series/series-chart.tsx index 9f71dbb24..cbcb1689a 100644 --- a/src/client/visualizations/line-chart/charts/charts-per-series/series-chart.tsx +++ b/src/client/visualizations/line-chart/charts/charts-per-series/series-chart.tsx @@ -63,7 +63,7 @@ export const SeriesChart: React.SFC = props => { showPrevious={hasComparison} />; const continuousSplit = getContinuousSplit(essence); - const getX = (d: Datum) => d[continuousSplit.reference] as (TimeRange | NumberRange); + const getX = (d: Datum) => continuousSplit.selectValue(d); const domain = extentAcrossSplits(continuousSplitDataset, essence, series); @@ -83,7 +83,7 @@ export const SeriesChart: React.SFC = props => { yDomain={domain}> {({ yScale, lineStage }) => {continuousSplitDataset.data.map((datum, index) => { - const splitKey = datum[nominalSplit.reference]; + const splitKey = nominalSplit.selectValue(datum); const color = NORMAL_COLORS[index]; return { - const name = String(datum[nominalRef]); + const name = String(nominalSplit.selectValue(datum)); const color = NORMAL_COLORS[i]; const hoverDatum = findSplitDatumByAttribute(datum, continuousRef, range); diff --git a/src/client/visualizations/line-chart/charts/charts-per-split/label.tsx b/src/client/visualizations/line-chart/charts/charts-per-split/label.tsx index 673fbb7f1..7780b1b88 100644 --- a/src/client/visualizations/line-chart/charts/charts-per-split/label.tsx +++ b/src/client/visualizations/line-chart/charts/charts-per-split/label.tsx @@ -14,11 +14,11 @@ * limitations under the License. */ -import { Datum, PlywoodValue } from "plywood"; +import { Datum } from "plywood"; import React from "react"; import { Essence } from "../../../../../common/models/essence/essence"; import { formatSegment } from "../../../../../common/utils/formatter/formatter"; -import { getNominalDimension, hasNominalSplit } from "../../utils/splits"; +import { getNominalDimension, getNominalSplit, hasNominalSplit } from "../../utils/splits"; import "./label.scss"; interface LabelProps { @@ -29,8 +29,9 @@ interface LabelProps { export const Label: React.SFC = props => { const { essence, datum } = props; if (hasNominalSplit(essence)) { + const nominalSplit = getNominalSplit(essence); const nominalDimension = getNominalDimension(essence); - const splitValue = datum[nominalDimension.name] as PlywoodValue; + const splitValue = nominalSplit.selectValue(datum); return
{nominalDimension.title} diff --git a/src/client/visualizations/line-chart/charts/charts-per-split/nominal-value-key.ts b/src/client/visualizations/line-chart/charts/charts-per-split/nominal-value-key.ts index 40607b153..3be6597ee 100644 --- a/src/client/visualizations/line-chart/charts/charts-per-split/nominal-value-key.ts +++ b/src/client/visualizations/line-chart/charts/charts-per-split/nominal-value-key.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { Datum, PlywoodValue } from "plywood"; +import { Datum } from "plywood"; import { Essence } from "../../../../../common/models/essence/essence"; import { formatSegment } from "../../../../../common/utils/formatter/formatter"; -import { getNominalDimension, hasNominalSplit } from "../../utils/splits"; +import { getNominalSplit, hasNominalSplit } from "../../utils/splits"; export function nominalValueKey(datum: Datum, essence: Essence): string { if (!hasNominalSplit(essence)) return "no-nominal-split"; - const nominalDimension = getNominalDimension(essence); - const splitValue = datum[nominalDimension.name] as PlywoodValue; + const nominalSplit = getNominalSplit(essence); + const splitValue = nominalSplit.selectValue(datum); return formatSegment(splitValue, essence.timezone); } diff --git a/src/client/visualizations/line-chart/charts/charts-per-split/split-chart.tsx b/src/client/visualizations/line-chart/charts/charts-per-split/split-chart.tsx index 0846e3fab..1f8626f1b 100644 --- a/src/client/visualizations/line-chart/charts/charts-per-split/split-chart.tsx +++ b/src/client/visualizations/line-chart/charts/charts-per-split/split-chart.tsx @@ -61,7 +61,7 @@ export const SplitChart: React.SFC = props => { dataset={splitDataset} />; const continuousSplit = getContinuousSplit(essence); - const getX = (d: Datum) => d[continuousSplit.reference] as (TimeRange | NumberRange); + const getX = (d: Datum) => continuousSplit.selectValue(d); const domain = extentAcrossSeries(splitDataset, essence); if (series.count() === 1) { diff --git a/src/client/visualizations/line-chart/interactions/find-closest-datum.ts b/src/client/visualizations/line-chart/interactions/find-closest-datum.ts index 049a36ee5..396925b44 100644 --- a/src/client/visualizations/line-chart/interactions/find-closest-datum.ts +++ b/src/client/visualizations/line-chart/interactions/find-closest-datum.ts @@ -14,20 +14,20 @@ * limitations under the License. */ -const MAX_HOVER_DIST = 50; - import { Dataset, Datum, NumberRange, Range, TimeRange } from "plywood"; -import { Dimension } from "../../../../common/models/dimension/dimension"; import { Essence } from "../../../../common/models/essence/essence"; +import { Split } from "../../../../common/models/split/split"; import { selectFirstSplitDataset, selectFirstSplitDatums } from "../../../utils/dataset/selectors/selectors"; import { ContinuousScale, ContinuousValue } from "../utils/continuous-types"; -import { getContinuousDimension, hasNominalSplit } from "../utils/splits"; +import { getContinuousSplit, hasNominalSplit } from "../utils/splits"; + +const MAX_HOVER_DIST = 50; -function findClosest(data: Datum[], value: ContinuousValue, scaleX: ContinuousScale, continuousDimension: Dimension): Datum | null { +function findClosest(data: Datum[], value: ContinuousValue, scaleX: ContinuousScale, continuousSplit: Split): Datum | null { let closestDatum: Datum = null; let minDist = Infinity; for (const datum of data) { - const continuousSegmentValue = datum[continuousDimension.name] as (TimeRange | NumberRange); + const continuousSegmentValue = continuousSplit.selectValue(datum); if (!continuousSegmentValue || !Range.isRange(continuousSegmentValue)) continue; // !Range.isRange => temp solution for non-bucketed reaching here const mid = continuousSegmentValue.midpoint(); const dist = Math.abs(mid.valueOf() - value.valueOf()); @@ -41,10 +41,10 @@ function findClosest(data: Datum[], value: ContinuousValue, scaleX: ContinuousSc } export function findClosestDatum(value: ContinuousValue, essence: Essence, dataset: Dataset, xScale: ContinuousScale): Datum | null { - const continuousDimension = getContinuousDimension(essence); + const continuousSplit = getContinuousSplit(essence); if (hasNominalSplit(essence)) { const flattened = selectFirstSplitDataset(dataset).flatten(); - return findClosest(flattened.data, value, xScale, continuousDimension); + return findClosest(flattened.data, value, xScale, continuousSplit); } - return findClosest(selectFirstSplitDatums(dataset), value, xScale, continuousDimension); + return findClosest(selectFirstSplitDatums(dataset), value, xScale, continuousSplit); } diff --git a/src/client/visualizations/line-chart/legend/split-legend.tsx b/src/client/visualizations/line-chart/legend/split-legend.tsx index df0815c48..98aa117a4 100644 --- a/src/client/visualizations/line-chart/legend/split-legend.tsx +++ b/src/client/visualizations/line-chart/legend/split-legend.tsx @@ -34,7 +34,7 @@ export const SplitLegend: React.SFC = props => { const title = legendSplit.getTitle(legendDimension); const nestedDataset = selectFirstSplitDatums(dataset); - const values = nestedDataset.map(datum => String(datum[legendSplit.reference])); + const values = nestedDataset.map(datum => String(legendSplit.selectValue(datum))); return datum[continuousDimensionKey] as PlywoodRange) + .map(datum => continuousSplit.selectValue(datum) as PlywoodRange) .reduce(safeRangeSum, null); } export function calculateXRange(essence: Essence, timekeeper: Timekeeper, dataset: Dataset): ContinuousRange | null { - const continuousDimension = getContinuousDimension(essence); + const continuousSplit = getContinuousSplit(essence); const filterRange = getFilterRange(essence, timekeeper); - const datasetRange = getDatasetXRange(dataset, continuousDimension); + const datasetRange = getDatasetXRange(dataset, continuousSplit); return union(filterRange, datasetRange) as ContinuousRange; } diff --git a/src/client/visualizations/scatterplot/scatterplot.tsx b/src/client/visualizations/scatterplot/scatterplot.tsx index c55c753a6..bbd697a3c 100644 --- a/src/client/visualizations/scatterplot/scatterplot.tsx +++ b/src/client/visualizations/scatterplot/scatterplot.tsx @@ -62,7 +62,7 @@ export class Scatterplot extends React.Component { render() { const { data, essence, stage } = this.props; - const splitKey = essence.splits.splits.first().toKey(); + const mainSplit = essence.splits.splits.first(); const showHeatmap = (essence.visualizationSettings as ScatterplotSettings).showSummary; const { @@ -88,7 +88,7 @@ export class Scatterplot extends React.Component { xSeries={xSeries} yScale={yScale} xScale={xScale} - splitKey={splitKey} + split={mainSplit} timezone={essence.timezone} showPrevious={essence.hasComparison()}/> @@ -119,7 +119,7 @@ export class Scatterplot extends React.Component { {scatterplotData.map(datum => { return ( = ({ ySeries, xScale, yScale, - splitKey, + split, timezone, showPrevious }) => { @@ -61,7 +61,7 @@ export const Tooltip: React.SFC = ({ return {xSeries.title()}
diff --git a/src/common/models/split/split.mocha.ts b/src/common/models/split/split.mocha.ts index f9e1f0c6d..7a6e28097 100644 --- a/src/common/models/split/split.mocha.ts +++ b/src/common/models/split/split.mocha.ts @@ -15,7 +15,38 @@ * limitations under the License. */ +import { expect } from "chai"; +import { Timezone } from "chronoshift"; +import * as sinon from "sinon"; +import * as formatterModule from "../../utils/formatter/formatter"; import { Split } from "./split"; describe("Split", () => { + describe("selectValue", () => { + it("should select property under own key", () => { + const split = new Split({ reference: "foobar" }); + const datum = { foobar: 42 }; + + expect(split.selectValue(datum)).to.equal(42); + }); + }); + + describe("formatValue", () => { + let formatValueStub: sinon.SinonStub; + + beforeEach(() => { + formatValueStub = sinon.stub(formatterModule, "formatValue"); + }); + + it("should select property under own key and pass for formatValue", () => { + const timezone = "timezone-string" as unknown as Timezone; + const split = new Split({ reference: "foobar" }); + const datum = { foobar: 42 }; + + split.formatValue(datum, timezone); + + expect(formatValueStub.calledWith(42, timezone)).to.be.true; + }); + + }); }); diff --git a/src/common/models/split/split.ts b/src/common/models/split/split.ts index 1fbea1d01..464f1747a 100644 --- a/src/common/models/split/split.ts +++ b/src/common/models/split/split.ts @@ -15,9 +15,10 @@ * limitations under the License. */ -import { Duration } from "chronoshift"; +import { Duration, Timezone } from "chronoshift"; import { Record } from "immutable"; -import { Expression, NumberBucketExpression, TimeBucketExpression } from "plywood"; +import { Datum, Expression, NumberBucketExpression, PlywoodValue, TimeBucketExpression } from "plywood"; +import { formatValue } from "../../utils/formatter/formatter"; import { isTruthy } from "../../utils/general/general"; import nullableEquals from "../../utils/immutable-utils/nullable-equals"; import { Dimension, DimensionKind } from "../dimension/dimension"; @@ -84,8 +85,8 @@ export function kindToType(kind: DimensionKind): SplitType { export class Split extends Record(defaultSplit) { - static fromDimension({ name, kind }: Dimension): Split { - return new Split({ reference: name, type: kindToType(kind) }); + static fromDimension({ name, kind, limits }: Dimension): Split { + return new Split({ reference: name, type: kindToType(kind), limit: limits[limits.length - 1] }); } public toString(): string { @@ -112,6 +113,14 @@ export class Split extends Record(defaultSplit) { return (dimension ? dimension.title : "?") + this.getBucketTitle(); } + public selectValue(datum: Datum): T { + return datum[this.toKey()] as T; + } + + public formatValue(datum: Datum, timezone: Timezone): string { + return formatValue(datum[this.toKey()], timezone); + } + public getBucketTitle(): string { const { bucket } = this; if (!isTruthy(bucket)) {