fix visual nits + tooltip
This commit is contained in:
parent
e346069421
commit
d53468a0eb
|
@ -9,7 +9,7 @@
|
||||||
.legend {
|
.legend {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 16px;
|
font-size: calc(16rem / 16);
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolTip p {
|
.toolTip p {
|
||||||
margin: 0 5px;
|
margin: 0 calc(5rem / 16);
|
||||||
font-size: calc(16rem / 16);
|
font-size: calc(16rem / 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,9 @@ import { Point } from "@visx/point";
|
||||||
import { scaleBand, scaleLinear, scaleOrdinal } from "@visx/scale";
|
import { scaleBand, scaleLinear, scaleOrdinal } from "@visx/scale";
|
||||||
import { BarStack, Line } from "@visx/shape";
|
import { BarStack, Line } from "@visx/shape";
|
||||||
import { SeriesPoint } from "@visx/shape/lib/types";
|
import { SeriesPoint } from "@visx/shape/lib/types";
|
||||||
import { useTooltip, useTooltipInPortal, defaultStyles } from "@visx/tooltip";
|
//import { useTooltip, useTooltipInPortal, withTooltip } from "@visx/tooltip";
|
||||||
|
import { withTooltip, Tooltip } from "@visx/tooltip";
|
||||||
|
import { WithTooltipProvidedProps } from "@visx/tooltip/lib/enhancers/withTooltip";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Color } from "utils/Color";
|
import { Color } from "utils/Color";
|
||||||
|
|
||||||
|
@ -45,6 +47,8 @@ export type StackedBarProps = {
|
||||||
numTicksLeftAxis?: number;
|
numTicksLeftAxis?: number;
|
||||||
/** Distance between the left axis labels and the start of the lines of the graph, in px. */
|
/** Distance between the left axis labels and the start of the lines of the graph, in px. */
|
||||||
valueAxisLeftOffset?: number;
|
valueAxisLeftOffset?: number;
|
||||||
|
/** Distance between the bottom axis and the bottom of the container of the graph, in px. */
|
||||||
|
categoryAxisBottomOffset?: number;
|
||||||
/** Distance between the right side of the graph and the legend, in px. */
|
/** Distance between the right side of the graph and the legend, in px. */
|
||||||
legendLeftOffset?: number;
|
legendLeftOffset?: number;
|
||||||
/** Distance between the top of the graph and the legend, in px. */
|
/** Distance between the top of the graph and the legend, in px. */
|
||||||
|
@ -57,204 +61,201 @@ export type StackedBarProps = {
|
||||||
scalePadding?: number;
|
scalePadding?: number;
|
||||||
/** Margin for each item in the legend */
|
/** Margin for each item in the legend */
|
||||||
itemMargin?: string;
|
itemMargin?: string;
|
||||||
|
/** Factor multiplied to the left offset to center the labels in the x-axis. >1 for width <600 and <1 for width >600 */
|
||||||
|
categoryAxisLeftFactor?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
let tooltipTimeout: number;
|
let tooltipTimeout: number;
|
||||||
|
|
||||||
export function StackedBarGraph({
|
export const StackedBarGraph = withTooltip<StackedBarProps, TooltipData>(
|
||||||
data,
|
({
|
||||||
width,
|
data,
|
||||||
height,
|
width,
|
||||||
keys,
|
height,
|
||||||
colorRange,
|
keys,
|
||||||
margin,
|
colorRange,
|
||||||
scalePadding = 0.3,
|
margin,
|
||||||
numTicksLeftAxis = 6,
|
scalePadding = 0.3,
|
||||||
valueAxisLeftOffset = 40,
|
numTicksLeftAxis = 6,
|
||||||
strokeWidth = 2.5,
|
valueAxisLeftOffset = 40,
|
||||||
strokeDashArray = "10,4",
|
categoryAxisBottomOffset = 40,
|
||||||
legendLeftOffset = 40,
|
strokeWidth = 2.5,
|
||||||
legendTopOffset = 40,
|
strokeDashArray = "10,4",
|
||||||
itemMargin = "15px 0 0 0",
|
legendLeftOffset = 40,
|
||||||
}: StackedBarProps) {
|
legendTopOffset = 40,
|
||||||
const yTotals = data.reduce((allTotals, currCategory) => {
|
itemMargin = "15px 0 0 0",
|
||||||
const yTotal = keys.reduce((categoryTotal, k) => {
|
categoryAxisLeftFactor = 1,
|
||||||
categoryTotal += currCategory[k] as number;
|
|
||||||
return categoryTotal;
|
|
||||||
}, 0);
|
|
||||||
allTotals.push(yTotal);
|
|
||||||
return allTotals;
|
|
||||||
}, [] as number[]);
|
|
||||||
|
|
||||||
const TICK_LABEL_FONT_WEIGHT = 800;
|
|
||||||
|
|
||||||
// accessors
|
|
||||||
const getCategory = (d: StackedBarData) => d.category;
|
|
||||||
|
|
||||||
// scales
|
|
||||||
const xScale = scaleBand<string>({
|
|
||||||
domain: data.map(getCategory),
|
|
||||||
padding: scalePadding,
|
|
||||||
});
|
|
||||||
const yScale = scaleLinear<number>({
|
|
||||||
domain: [0, Math.max(...yTotals)],
|
|
||||||
nice: true,
|
|
||||||
});
|
|
||||||
const colorScale = scaleOrdinal<string, string>({
|
|
||||||
domain: keys,
|
|
||||||
range: colorRange,
|
|
||||||
});
|
|
||||||
const {
|
|
||||||
tooltipOpen,
|
tooltipOpen,
|
||||||
tooltipLeft,
|
tooltipLeft,
|
||||||
tooltipTop,
|
tooltipTop,
|
||||||
tooltipData,
|
tooltipData,
|
||||||
hideTooltip,
|
hideTooltip,
|
||||||
showTooltip,
|
showTooltip,
|
||||||
} = useTooltip<TooltipData>();
|
}: StackedBarProps & WithTooltipProvidedProps<TooltipData>) => {
|
||||||
|
const yTotals = data.reduce((allTotals, currCategory) => {
|
||||||
|
const yTotal = keys.reduce((categoryTotal, k) => {
|
||||||
|
categoryTotal += currCategory[k] as number;
|
||||||
|
return categoryTotal;
|
||||||
|
}, 0);
|
||||||
|
allTotals.push(yTotal);
|
||||||
|
return allTotals;
|
||||||
|
}, [] as number[]);
|
||||||
|
|
||||||
const { containerRef, TooltipInPortal } = useTooltipInPortal({
|
const TICK_LABEL_FONT_WEIGHT = 800;
|
||||||
// TooltipInPortal is rendered in a separate child of <body /> and positioned
|
|
||||||
// with page coordinates which should be updated on scroll.
|
|
||||||
scroll: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (width < 10) return null;
|
// accessors
|
||||||
// bounds
|
const getCategory = (d: StackedBarData) => d.category;
|
||||||
const xMax = width;
|
|
||||||
const yMax = height - margin.top - 50;
|
|
||||||
|
|
||||||
xScale.rangeRound([0, xMax - valueAxisLeftOffset]);
|
// scales
|
||||||
yScale.range([yMax, 0]);
|
const xScale = scaleBand<string>({
|
||||||
|
domain: data.map(getCategory),
|
||||||
|
padding: scalePadding,
|
||||||
|
});
|
||||||
|
const yScale = scaleLinear<number>({
|
||||||
|
domain: [0, Math.max(...yTotals)],
|
||||||
|
nice: true,
|
||||||
|
});
|
||||||
|
const colorScale = scaleOrdinal<string, string>({
|
||||||
|
domain: keys,
|
||||||
|
range: colorRange,
|
||||||
|
});
|
||||||
|
|
||||||
return width < 10 ? null : (
|
// bounds
|
||||||
<div className={styles.container}>
|
const xMax = width;
|
||||||
<svg ref={containerRef} width={width} height={height}>
|
const yMax = height - margin.top - categoryAxisBottomOffset;
|
||||||
<Group top={margin.top} left={margin.left}>
|
|
||||||
<GridRows
|
xScale.rangeRound([0, xMax - valueAxisLeftOffset]);
|
||||||
scale={yScale}
|
yScale.range([yMax, 0]);
|
||||||
width={xMax}
|
|
||||||
height={yMax}
|
return width < 10 ? null : (
|
||||||
left={valueAxisLeftOffset}
|
<div className={styles.container}>
|
||||||
numTicks={numTicksLeftAxis}
|
<svg width={width} height={height}>
|
||||||
stroke={Color.tertiaryBackground}
|
<Group top={margin.top} left={margin.left}>
|
||||||
strokeWidth={strokeWidth}
|
<GridRows
|
||||||
strokeDasharray={strokeDashArray}
|
scale={yScale}
|
||||||
/>
|
width={xMax}
|
||||||
<GridColumns
|
height={yMax}
|
||||||
scale={xScale}
|
left={valueAxisLeftOffset}
|
||||||
height={yMax}
|
numTicks={numTicksLeftAxis}
|
||||||
left={xScale.bandwidth() / 2}
|
stroke={Color.tertiaryBackground}
|
||||||
offset={xScale.bandwidth() / 2}
|
strokeWidth={strokeWidth}
|
||||||
stroke={Color.tertiaryBackground}
|
strokeDasharray={strokeDashArray}
|
||||||
strokeWidth={strokeWidth}
|
/>
|
||||||
strokeDasharray={strokeDashArray}
|
<GridColumns
|
||||||
/>
|
scale={xScale}
|
||||||
<Group left={valueAxisLeftOffset}>
|
height={yMax}
|
||||||
<BarStack<StackedBarData, string>
|
left={valueAxisLeftOffset}
|
||||||
data={data}
|
offset={xScale.bandwidth() / 2}
|
||||||
keys={keys}
|
stroke={Color.tertiaryBackground}
|
||||||
x={getCategory}
|
strokeWidth={strokeWidth}
|
||||||
xScale={xScale}
|
strokeDasharray={strokeDashArray}
|
||||||
yScale={yScale}
|
/>
|
||||||
color={colorScale}
|
<Group left={valueAxisLeftOffset}>
|
||||||
>
|
<BarStack<StackedBarData, string>
|
||||||
{(barStacks) =>
|
data={data}
|
||||||
barStacks.map((barStack) =>
|
keys={keys}
|
||||||
barStack.bars.map((bar) => (
|
x={getCategory}
|
||||||
<rect
|
xScale={xScale}
|
||||||
className={styles.barStack}
|
yScale={yScale}
|
||||||
key={`bar-stack-${barStack.index}-${bar.index}`}
|
color={colorScale}
|
||||||
x={bar.x}
|
>
|
||||||
y={bar.y}
|
{(barStacks) =>
|
||||||
height={bar.height}
|
barStacks.map((barStack) =>
|
||||||
width={bar.width / 2}
|
barStack.bars.map((bar) => (
|
||||||
fill={bar.color}
|
<rect
|
||||||
onMouseLeave={() => {
|
className={styles.barStack}
|
||||||
tooltipTimeout = window.setTimeout(() => {
|
key={`bar-stack-${barStack.index}-${bar.index}`}
|
||||||
hideTooltip();
|
x={bar.x}
|
||||||
}, 300);
|
y={bar.y}
|
||||||
}}
|
height={bar.height}
|
||||||
onMouseMove={(event) => {
|
width={bar.width / 2}
|
||||||
if (tooltipTimeout) clearTimeout(tooltipTimeout);
|
fill={bar.color}
|
||||||
// TooltipInPortal expects coordinates to be relative to containerRef
|
onMouseLeave={() => {
|
||||||
// localPoint returns coordinates relative to the nearest SVG
|
tooltipTimeout = window.setTimeout(() => {
|
||||||
const eventSvgCoords = localPoint(event);
|
hideTooltip();
|
||||||
const left = bar.x + bar.width / 2;
|
}, 300);
|
||||||
showTooltip({
|
}}
|
||||||
tooltipData: bar,
|
onMouseMove={(event) => {
|
||||||
tooltipTop: eventSvgCoords?.y,
|
if (tooltipTimeout) clearTimeout(tooltipTimeout);
|
||||||
tooltipLeft: left,
|
const eventSvgCoords = localPoint(event);
|
||||||
});
|
const left = bar.x + bar.width / 2;
|
||||||
}}
|
showTooltip({
|
||||||
/>
|
tooltipData: bar,
|
||||||
))
|
tooltipTop: eventSvgCoords?.y,
|
||||||
)
|
tooltipLeft: left,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</BarStack>
|
||||||
|
</Group>
|
||||||
|
<Line
|
||||||
|
fill={Color.tertiaryBackground}
|
||||||
|
to={new Point({ x: valueAxisLeftOffset, y: 0 })}
|
||||||
|
from={new Point({ x: valueAxisLeftOffset, y: yMax })}
|
||||||
|
stroke={Color.tertiaryBackground}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
strokeDasharray={strokeDashArray}
|
||||||
|
/>
|
||||||
|
<AxisBottom
|
||||||
|
top={yMax}
|
||||||
|
scale={xScale}
|
||||||
|
left={
|
||||||
|
((xScale.bandwidth() * 100) / width) * categoryAxisLeftFactor
|
||||||
}
|
}
|
||||||
</BarStack>
|
hideTicks
|
||||||
</Group>
|
hideAxisLine
|
||||||
<Line
|
labelProps={{
|
||||||
fill={Color.tertiaryBackground}
|
fontSize: `${10 / 16}rem`,
|
||||||
to={new Point({ x: valueAxisLeftOffset, y: 0 })}
|
}}
|
||||||
from={new Point({ x: valueAxisLeftOffset, y: yMax })}
|
tickLabelProps={() => ({
|
||||||
stroke={Color.tertiaryBackground}
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
strokeDasharray={strokeDashArray}
|
|
||||||
/>
|
|
||||||
<AxisBottom
|
|
||||||
top={yMax}
|
|
||||||
scale={xScale}
|
|
||||||
left={xScale.bandwidth() / 6}
|
|
||||||
hideTicks
|
|
||||||
hideAxisLine
|
|
||||||
labelProps={{
|
|
||||||
fontSize: `${10 / 16}rem`,
|
|
||||||
}}
|
|
||||||
tickLabelProps={() => ({
|
|
||||||
fill: Color.label,
|
|
||||||
fontWeight: TICK_LABEL_FONT_WEIGHT,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<AxisLeft
|
|
||||||
scale={yScale}
|
|
||||||
top={5}
|
|
||||||
numTicks={numTicksLeftAxis}
|
|
||||||
hideAxisLine
|
|
||||||
labelProps={{
|
|
||||||
fontSize: `${10 / 16}rem`,
|
|
||||||
}}
|
|
||||||
tickLabelProps={() => {
|
|
||||||
return {
|
|
||||||
fill: Color.label,
|
fill: Color.label,
|
||||||
fontWeight: TICK_LABEL_FONT_WEIGHT,
|
fontWeight: TICK_LABEL_FONT_WEIGHT,
|
||||||
};
|
})}
|
||||||
}}
|
/>
|
||||||
/>
|
<AxisLeft
|
||||||
</Group>
|
scale={yScale}
|
||||||
</svg>
|
top={5}
|
||||||
<div
|
numTicks={numTicksLeftAxis}
|
||||||
className={styles.legend}
|
hideAxisLine
|
||||||
style={{ left: width + legendLeftOffset, top: legendTopOffset }}
|
labelProps={{
|
||||||
>
|
fontSize: `${10 / 16}rem`,
|
||||||
<LegendOrdinal
|
}}
|
||||||
scale={colorScale}
|
tickLabelProps={() => {
|
||||||
direction="column"
|
return {
|
||||||
itemMargin={itemMargin}
|
fill: Color.label,
|
||||||
/>
|
fontWeight: TICK_LABEL_FONT_WEIGHT,
|
||||||
</div>
|
};
|
||||||
|
}}
|
||||||
{tooltipOpen && tooltipData ? (
|
/>
|
||||||
<TooltipInPortal
|
</Group>
|
||||||
className={styles.toolTip}
|
</svg>
|
||||||
top={tooltipTop}
|
<div
|
||||||
left={tooltipLeft}
|
className={styles.legend}
|
||||||
unstyled
|
style={{ left: width + legendLeftOffset, top: legendTopOffset }}
|
||||||
>
|
>
|
||||||
<p className={styles.key}>{tooltipData.key}</p>
|
<LegendOrdinal
|
||||||
<p>{tooltipData.bar.data[tooltipData.key]}</p>
|
scale={colorScale}
|
||||||
<p>{getCategory(tooltipData.bar.data)}</p>
|
direction="column"
|
||||||
</TooltipInPortal>
|
itemMargin={itemMargin}
|
||||||
) : null}
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
}
|
{tooltipOpen && tooltipData ? (
|
||||||
|
<Tooltip
|
||||||
|
className={styles.toolTip}
|
||||||
|
top={tooltipTop}
|
||||||
|
left={tooltipLeft}
|
||||||
|
unstyled
|
||||||
|
>
|
||||||
|
<p className={styles.key}>{tooltipData.key}</p>
|
||||||
|
<p>{tooltipData.bar.data[tooltipData.key]}</p>
|
||||||
|
<p>{getCategory(tooltipData.bar.data)}</p>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -65,36 +65,6 @@ export const moreMockCategoricalData = [
|
||||||
{ key: "Dart", value: 2.21 },
|
{ key: "Dart", value: 2.21 },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const mockBoxPlotData = [
|
|
||||||
{
|
|
||||||
x: "1A",
|
|
||||||
min: 20,
|
|
||||||
firstQuartile: 25,
|
|
||||||
median: 30,
|
|
||||||
thirdQuartile: 80,
|
|
||||||
max: 100,
|
|
||||||
outliers: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: "1B",
|
|
||||||
min: 0,
|
|
||||||
firstQuartile: 20,
|
|
||||||
median: 30,
|
|
||||||
thirdQuartile: 50,
|
|
||||||
max: 100,
|
|
||||||
outliers: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: "2A",
|
|
||||||
min: 25,
|
|
||||||
firstQuartile: 35,
|
|
||||||
median: 50,
|
|
||||||
thirdQuartile: 90,
|
|
||||||
max: 100,
|
|
||||||
outliers: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const mockStackedBarGraphData = [
|
export const mockStackedBarGraphData = [
|
||||||
{
|
{
|
||||||
category: "1A",
|
category: "1A",
|
||||||
|
|
Loading…
Reference in New Issue