-
Notifications
You must be signed in to change notification settings - Fork 715
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new(xychart): add BarStack series #865
Conversation
@@ -17,20 +18,19 @@ type Props = { | |||
height: number; | |||
}; | |||
|
|||
const xScaleConfig = { type: 'band', paddingInner: 0.3 } as const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved a lot of the Example
params into ExampleControls
sharedTooltip, | ||
showGridColumns, | ||
showGridRows, | ||
showHorizontalCrosshair, | ||
showTooltip, | ||
showVerticalCrosshair, | ||
snapTooltipToDatumX, | ||
snapTooltipToDatumY, | ||
snapTooltipToDatumX: renderBarOrBarStack === 'bar' && snapTooltipToDatumX, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moving stuff in here allows us to handle couple control logic like this
const entry = dataRegistry.get(barStack.key); | ||
|
||
return entry | ||
? barStack.map((bar, index) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this mirrors the logic in BarStack/BarStackHorizontal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps do horizontal
and other conditions outside the loop when possible.
const thickness = getBandwidth(horizontal ? yScale : xScale);
const halfThickness = thickness / 2;
let widthFn;
let heightFn;
let xFn;
let yFn;
if (horizontal) {
widthFn = bar => (xScale(getSecondItem(bar)) || 0) - (xScale(getFirstItem(bar)) || 0);
heightFn = () => thickness;
xFn = bar => xScale(getFirstItem(bar));
yFn = 'bandwidth' in yScale
? bar => yScale(getStackValue(bar.data))
: bar => Math.max((yScale(getStackValue(bar.data)) || 0) - halfThickness);
} else {
widthFn = () => thickness;
heightFn = bar => (yScale(getFirstItem(bar)) || 0) - (yScale(getSecondItem(bar)) || 0);
xFn = 'bandwidth' in xScale
? bar => xScale(getStackValue(bar.data))
: bar => Math.max((xScale(getStackValue(bar.data)) || 0) - halfThickness);
}
return (
<g className="visx-bar-stack">
{stackedData.map((barStack, stackIndex) => {
const entry = dataRegistry.get(barStack.key);
if (!entry) return null;
const y = yFn ?? (bar => yScale(entry.yAccessor(bar)));
return barStack.map((bar, index) => {
const barX = xFn(bar);
if (!isValidNumber(barX)) return null;
const barY = y(bar);
if (!isValidNumber(barY)) return null;
return (
<rect
key={`${stackIndex}-${barStack.key}-${index}`}
x={barX ?? 0}
y={barY ?? 0}
width={widthFn(bar)}
height={heightFn(bar)}
fill={colorScale(barStack.key)}
stroke="transparent"
/>
);
});
})}
</g>
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great suggestion, also the special yFn
handling made me realize I didn't actually need the entry
accessor function 👍
Pull Request Test Coverage Report for Build 207
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly minor perf suggestion.
: Math.max((yScale(getStackValue(bar.data)) || 0) - barThickness / 2) | ||
: yScale(entry.yAccessor(bar)); | ||
|
||
const barX: number | undefined = horizontal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this one have type defined but barY
does not?
Usually eslint doesn't like nested ternary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type was an oversight, I think we have nested ternary disabled in this repo because it's so often useful
const entry = dataRegistry.get(barStack.key); | ||
|
||
return entry | ||
? barStack.map((bar, index) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps do horizontal
and other conditions outside the loop when possible.
const thickness = getBandwidth(horizontal ? yScale : xScale);
const halfThickness = thickness / 2;
let widthFn;
let heightFn;
let xFn;
let yFn;
if (horizontal) {
widthFn = bar => (xScale(getSecondItem(bar)) || 0) - (xScale(getFirstItem(bar)) || 0);
heightFn = () => thickness;
xFn = bar => xScale(getFirstItem(bar));
yFn = 'bandwidth' in yScale
? bar => yScale(getStackValue(bar.data))
: bar => Math.max((yScale(getStackValue(bar.data)) || 0) - halfThickness);
} else {
widthFn = () => thickness;
heightFn = bar => (yScale(getFirstItem(bar)) || 0) - (yScale(getSecondItem(bar)) || 0);
xFn = 'bandwidth' in xScale
? bar => xScale(getStackValue(bar.data))
: bar => Math.max((xScale(getStackValue(bar.data)) || 0) - halfThickness);
}
return (
<g className="visx-bar-stack">
{stackedData.map((barStack, stackIndex) => {
const entry = dataRegistry.get(barStack.key);
if (!entry) return null;
const y = yFn ?? (bar => yScale(entry.yAccessor(bar)));
return barStack.map((bar, index) => {
const barX = xFn(bar);
if (!isValidNumber(barX)) return null;
const barY = y(bar);
if (!isValidNumber(barY)) return null;
return (
<rect
key={`${stackIndex}-${barStack.key}-${index}`}
x={barX ?? 0}
y={barY ?? 0}
width={widthFn(bar)}
height={heightFn(bar)}
fill={colorScale(barStack.key)}
stroke="transparent"
/>
);
});
})}
</g>
);
new(demo/xychart): add BarStack new(xychart): add horizontal support to BarStack new(demo/xychart): add negative values, refactor accessors internal(xychart/barstack): refactor to use d3-shape :/ new(xychart): clean up BarStack, add mouse events fix(demo/xychart): disable tooltip snapping when using BarStack internal(xychart/BarStack): add util comments, simplify combineBarStackData types(xychart): fix some registry types internal(xychart/BarStack): make perf improvements
c8ba503
to
2a5a8dd
Compare
* tes(xychart/BarStack): add tests * test(xychart/combineBarStackData): add tests * test(xychart/findNearestStackDatum): add tests * internal(xychart): remove console logs in tests
TODO
🚀 Enhancements
This PR adds
BarStack
to@visx/xychart
with an API that wrapsBarSeries
(this was planned in the POC, but could simplify in the future):It also updates the
/xychart
demoBarStack
(in the demo this is not compatible withLineSeries
/BarSeries
because they sharedataKey
s so controls are disabled accordingly)Tooltip
Negative values
Horizontal
Some notes on the implementation
The general flow is:
props.data
from childBarSeries
props.dataKey
)DataContext
for each childdataKey
, but updatedata
+x/yAccessor
to use the stacked data (so that scales are computed correctly)BarStack
using data + scales fromDataContext
I first wrote this using
@visx/shape
sBarStack(Horizontal)
, but it doesn't allow us to use the stacked data independently of their scaled values (i.e., it creates the stack data structure and scales the stacked values). For theDataContext
scales +findNearestDatum
functions (TooltipContext
), we need to add the stacked data itself as described in prev pointBarStack
s implementation is currently is incompatible withTooltip.snapTooltipToDatumX/Y
.Tooltip
attempts to useDataContext
fordata
+x/yAccessor
s to map theTooltipContext.tooltipData
to the scaled positions. HowevertooltipData
is typed asDatum
, and thex/yAccessor
s +x/yScale
s fromDataContext
use the stackedSeriesDatum
type. Will noodle on this more.@kristw @techniq