From ec0250f5f4629b42fa5347e9c20fd71051f65f03 Mon Sep 17 00:00:00 2001 From: Nikita Rokotyan Date: Thu, 23 Jan 2025 12:02:34 -0800 Subject: [PATCH 1/3] Component | Stacked Bar: Better handling of stacked values --- packages/ts/src/components/area/index.ts | 2 +- .../ts/src/components/stacked-bar/index.ts | 18 ++++++++++++------ .../ts/src/components/stacked-bar/types.ts | 2 +- packages/ts/src/types/data.ts | 2 +- packages/ts/src/utils/data.ts | 16 +++------------- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/ts/src/components/area/index.ts b/packages/ts/src/components/area/index.ts index 07b4b3b96..4db21353d 100644 --- a/packages/ts/src/components/area/index.ts +++ b/packages/ts/src/components/area/index.ts @@ -65,7 +65,7 @@ export class Area extends XYComponentCore this.xScale(getNumber(d, config.x, i))) const stacked = getStackedData(data, config.baseline, yAccessors, this._prevNegative) - this._prevNegative = stacked.map(s => !!s.negative) + this._prevNegative = stacked.map(s => !!s.isMostlyNegative) const stackedData: AreaDatum[][] = stacked.map( arr => arr.map( (d, j) => ({ diff --git a/packages/ts/src/components/stacked-bar/index.ts b/packages/ts/src/components/stacked-bar/index.ts index 2615bb1a8..4879cdc65 100644 --- a/packages/ts/src/components/stacked-bar/index.ts +++ b/packages/ts/src/components/stacked-bar/index.ts @@ -86,7 +86,7 @@ export class StackedBar extends XYComponentCore !!s.negative) + this._prevNegative = stacked.map(s => !!s.isMostlyNegative) const barGroups = this.g .selectAll(`.${s.barGroup}`) @@ -126,8 +126,15 @@ export class StackedBar extends XYComponentCore>(`.${s.bar}`) - .data((d, j) => stacked.map((s) => - ({ ...d, _stacked: s[j], _negative: s.negative, _ending: s.ending })) + .data((d, j) => stacked.map((s, stackIndex) => + ({ + ...d, + _index: j, + _stacked: s[j], + // Ending bar if the next stack is not the same as the current one + _ending: (stackIndex === stacked.length - 1) || + ((stackIndex <= stacked.length - 1) && stacked[stackIndex + 1][j][0] !== s[j][1]), + })) ) const barsEnter = bars.enter().append('path') @@ -201,10 +208,9 @@ export class StackedBar extends XYComponentCore = D & { + _index: number; _stacked: [number, number]; - _negative: boolean; _ending: boolean; } diff --git a/packages/ts/src/types/data.ts b/packages/ts/src/types/data.ts index da8a7f159..e4598da3f 100644 --- a/packages/ts/src/types/data.ts +++ b/packages/ts/src/types/data.ts @@ -2,4 +2,4 @@ export type GenericDataRecord = Record /** Extension of a numbers array that carries additional information required for plotting stacked data */ -export type StackValuesRecord = Array<[number, number]> & { negative?: boolean; ending?: boolean } +export type StackValuesRecord = Array<[number, number]> & { isMostlyNegative: boolean } diff --git a/packages/ts/src/utils/data.ts b/packages/ts/src/utils/data.ts index f2ce1a0af..cf3bdb45c 100644 --- a/packages/ts/src/utils/data.ts +++ b/packages/ts/src/utils/data.ts @@ -263,13 +263,13 @@ export function getStackedData ( return (average === 0 && Array.isArray(prevNegative)) ? prevNegative[j] : average < 0 }) - const stackedData: StackValuesRecord[] = acs.map(() => []) + const stackedData = acs.map(() => [] as StackValuesRecord) data.forEach((d, i) => { let positiveStack = baselineValues[i] let negativeStack = baselineValues[i] acs.forEach((a, j) => { const value = getNumber(d, a, i) || 0 - if (!isNegativeStack[j]) { + if (value >= 0) { stackedData[j].push([positiveStack, positiveStack += value]) } else { stackedData[j].push([negativeStack, negativeStack += value]) @@ -279,19 +279,9 @@ export function getStackedData ( // Fill in additional stack information stackedData.forEach((stack, i) => { - stack.negative = isNegativeStack[i] + stack.isMostlyNegative = isNegativeStack[i] }) - stackedData.filter(s => s.negative) - .forEach((s, i, arr) => { - s.ending = i === arr.length - 1 - }) - - stackedData.filter(s => !s.negative) - .forEach((s, i, arr) => { - s.ending = i === arr.length - 1 - }) - return stackedData } From 154a4344715186bbc86f46d0831b4cfe19f98bc8 Mon Sep 17 00:00:00 2001 From: Nikita Rokotyan Date: Thu, 23 Jan 2025 12:11:00 -0800 Subject: [PATCH 2/3] Dev | Examples | Stacked Bar: Add example for handling positive and negative values --- .../positive-negative-values/index.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 packages/dev/src/examples/xy-components/stacked-bar/positive-negative-values/index.tsx diff --git a/packages/dev/src/examples/xy-components/stacked-bar/positive-negative-values/index.tsx b/packages/dev/src/examples/xy-components/stacked-bar/positive-negative-values/index.tsx new file mode 100644 index 000000000..2c050c409 --- /dev/null +++ b/packages/dev/src/examples/xy-components/stacked-bar/positive-negative-values/index.tsx @@ -0,0 +1,27 @@ +import React, { useRef } from 'react' +import { VisXYContainer, VisStackedBar, VisAxis, VisTooltip, VisCrosshair } from '@unovis/react' + +import { XYDataRecord, generateXYDataRecords } from '@src/utils/data' +import { ExampleViewerDurationProps } from '@src/components/ExampleViewer/index' + +export const title = 'Stacked Bar: Negative Values' +export const subTitle = 'Positive and Negative Stack' +export const component = (props: ExampleViewerDurationProps): JSX.Element => { + const tooltipRef = useRef(null) + const data = generateXYDataRecords(15) + const accessors = [ + (d: XYDataRecord) => (d.y1 || 0) - 3, + (d: XYDataRecord, i: number) => i % 4 ? d.y : -d.y, + (d: XYDataRecord, i: number) => i % 2 ? d.y1 : -(d.y1 as number), + ] + + return ( + data={data} margin={{ top: 5, left: 5 }}> + d.x} y={accessors} duration={props.duration}/> + `${x}ms`} duration={props.duration}/> + `${y}bps`} duration={props.duration}/> + `${d.x}`} /> + + + ) +} From 6e3f29a36c04c977dcef277d53b252e6179875c1 Mon Sep 17 00:00:00 2001 From: Nikita Rokotyan Date: Thu, 23 Jan 2025 12:12:21 -0800 Subject: [PATCH 3/3] Dev | Examples: Fixing typing errors --- .../xy-components/scatter/scatter-with-line/index.tsx | 5 +++-- packages/dev/src/utils/data.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/dev/src/examples/xy-components/scatter/scatter-with-line/index.tsx b/packages/dev/src/examples/xy-components/scatter/scatter-with-line/index.tsx index 0b8073252..2aa584f44 100644 --- a/packages/dev/src/examples/xy-components/scatter/scatter-with-line/index.tsx +++ b/packages/dev/src/examples/xy-components/scatter/scatter-with-line/index.tsx @@ -3,6 +3,7 @@ import { VisXYContainer, VisScatter, VisAxis, VisLine } from '@unovis/react' import { XYDataRecord } from '@src/utils/data' import { ExampleViewerDurationProps } from '@src/components/ExampleViewer/index' +import type { WithOptional } from '@unovis/ts/lib/types/misc' export const title = 'Scatter with Line' export const subTitle = 'And undefined segments' @@ -11,7 +12,7 @@ export const component = (props: ExampleViewerDurationProps): JSX.Element => { (d: XYDataRecord) => d.y, ] - const data: XYDataRecord[] = [ + const data: WithOptional[] = [ { x: 0, y: 9.4 }, { x: 1, y: 8.6 }, { x: 2, y: undefined }, @@ -25,7 +26,7 @@ export const component = (props: ExampleViewerDurationProps): JSX.Element => { { x: 14, y: 5.2 }, ] return ( - data={data} margin={{ top: 5, left: 5 }} xDomain={[-1, 15]}> + d.x} y={accessors} duration={props.duration}/> d.x} y={accessors} duration={props.duration}/> `${x}`} duration={props.duration}/> diff --git a/packages/dev/src/utils/data.ts b/packages/dev/src/utils/data.ts index 23c2ad4cb..38ed0e5c4 100644 --- a/packages/dev/src/utils/data.ts +++ b/packages/dev/src/utils/data.ts @@ -6,7 +6,7 @@ export const randomNumberGenerator = new Seedramdon('unovis') export type XYDataRecord = { x: number; - y: number | undefined; + y: number; y1?: number; y2?: number; }