Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/ts/src/components/stacked-bar/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { ColorAccessor, StringAccessor } from 'types/accessor'
import { Orientation } from 'types/position'

export interface StackedBarConfigInterface<Datum> extends XYComponentConfigInterface<Datum> {
/** Spacing in pixels between stacked items in a single bar. Default: `0` */
stackSpacing?: number;
/** Bar color accessor function. Default: `d => d.color` */
color?: ColorAccessor<Datum>;
/** Force set bar width in pixels. Default: `undefined` */
Expand Down Expand Up @@ -39,6 +41,7 @@ export const StackedBarDefaultConfig: StackedBarConfigInterface<unknown> = {
dataStep: undefined,
barPadding: 0.0,
roundedCorners: 2,
stackSpacing: 0,
cursor: null,
barMinHeight1Px: false,
barMinHeightZeroValue: null,
Expand Down
86 changes: 62 additions & 24 deletions packages/ts/src/components/stacked-bar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class StackedBar<Datum> extends XYComponentCore<Datum, StackedBarConfigIn
getAccessors = (): NumericAccessor<Datum>[] => (isArray(this.config.y) ? this.config.y : [this.config.y])
stacked = true
events = {}
private _prevNegative: boolean[] | undefined // To help guessing the bar direction when an accessor was set to null or 0
private _prevNegative: boolean[] | undefined
private _barData: Datum[] = []

constructor (config?: StackedBarConfigInterface<Datum>) {
Expand Down Expand Up @@ -124,18 +124,38 @@ export class StackedBar<Datum> extends XYComponentCore<Datum, StackedBarConfigIn
)

// Render Bars
const stackExtents = this._barData.map((_, j) => {
const values = stacked.map(s => s[j]) // Get all [y0, y1] pairs for stack j
const positiveY1 = values.filter(v => v[1] >= v[0]).map(v => v[1])
const negativeY1 = values.filter(v => v[1] < v[0]).map(v => v[1])
return {
maxPositive: positiveY1.length ? max(positiveY1) : undefined,
minNegative: negativeY1.length ? min(negativeY1) : undefined,
}
})

const bars = barGroupsMerged
.selectAll<SVGPathElement, StackedBarDataRecord<Datum>>(`.${s.bar}`)
.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]),
}))
)
.data((d, j) => {
const { maxPositive, minNegative } = stackExtents[j]
return stacked.map((s) => {
const y0 = s[j][0]
const y1 = s[j][1]
const isSegmentPositive = y1 >= y0

// A bar is "ending" if it's the outermost in its respective direction (positive or negative)
const isEnding = isSegmentPositive
? y1 === maxPositive
: y1 === minNegative

return {
...d,
_index: j,
_stacked: s[j],
_ending: isEnding,
}
})
})

const barsEnter = bars.enter().append('path')
.attr('class', s.bar)
Expand Down Expand Up @@ -220,33 +240,51 @@ export class StackedBar<Datum> extends XYComponentCore<Datum, StackedBarConfigIn
const x = -barWidth / 2
const width = barWidth

// --- Spacing between stacked items ---
const stackSpacing = config.stackSpacing || 0
let hWithSpacing = h
let yWithSpacing = y
// For negative bars, shift y position by stackSpacing so the base remains at the correct value
if (!isEntering && stackSpacing > 0) {
hWithSpacing = h - stackSpacing
if (hWithSpacing <= 0 && isFinite(value) && (value !== config.barMinHeightZeroValue)) {
hWithSpacing = 1
} else {
hWithSpacing = Math.max(0, hWithSpacing)
}
if (this.isVertical() && isNegative) {
yWithSpacing = y + stackSpacing
}
}

const cornerRadius = config.roundedCorners
? isNumber(config.roundedCorners) ? +config.roundedCorners : width / 2
: 0
const cornerRadiusClamped = clamp(cornerRadius, 0, Math.min(height, width) / 2)
const cornerRadiusClamped = clamp(cornerRadius, 0, Math.min(hWithSpacing, width) / 2)
const isNorthDirected = this.yScale.range()[0] > this.yScale.range()[1]

const allCornersRounded = stackSpacing > 0 && !!config.roundedCorners
return roundedRectPath({
x: this.isVertical() ? x : y - h,
y: this.isVertical() ? y + (isNorthDirected ? 0 : -h) : x,
w: this.isVertical() ? width : h,
h: this.isVertical() ? h : width,
tl: isEnding && (this.isVertical()
x: this.isVertical() ? x : yWithSpacing - hWithSpacing,
y: this.isVertical() ? yWithSpacing + (isNorthDirected ? 0 : -hWithSpacing) : x,
w: this.isVertical() ? width : hWithSpacing,
h: this.isVertical() ? hWithSpacing : width,
tl: allCornersRounded || (isEnding && (this.isVertical()
? (!isNegative && isNorthDirected) || (isNegative && !isNorthDirected)
: isNegative
),
tr: isEnding && (this.isVertical()
)),
tr: allCornersRounded || (isEnding && (this.isVertical()
? (!isNegative && isNorthDirected) || (isNegative && !isNorthDirected)
: !isNegative
),
br: isEnding && (this.isVertical()
)),
br: allCornersRounded || (isEnding && (this.isVertical()
? (isNegative && isNorthDirected) || (!isNegative && !isNorthDirected)
: !isNegative
),
bl: isEnding && (this.isVertical()
)),
bl: allCornersRounded || (isEnding && (this.isVertical()
? (isNegative && isNorthDirected) || (!isNegative && !isNorthDirected)
: isNegative
),
)),
r: cornerRadiusClamped,
})
}
Expand Down
4 changes: 4 additions & 0 deletions packages/website/docs/xy-charts/StackedBar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ The _Stacked Bar_ component has been designed to work together with _XY Containe
_Stacked Bar_ can accept an array of y accessors to display values as a stack of bars:
<XYWrapper {...stackedBarProps()} showContext="full" y={[d => d.y, d => d.y1, d => d.y2]}/>

### Stacked Bar with Spacing `1.6`
_Stacked Bar_ supports spacing between each stacked bars using the `stackSpacing` property.
<XYWrapper {...stackedBarProps()} showContext="full" y={[d => d.y, d => d.y1, d => d.y2]} stackSpacing={3}/>

## Orientation
_Stacked Bar_ supports horizontal and vertical orientation.
<XYWrapper {...stackedBarProps()} y={[d => d.y, d => d.y1, d => d.y2]} height={250} showAxes property="orientation" inputType="select" options={["horizontal","vertical"]}/>
Expand Down
Loading