WIP Fix Stacked Bar Graph

This commit is contained in:
e26chiu 2022-12-15 21:33:14 -05:00
parent 803457c083
commit 047889162e
5 changed files with 166 additions and 149 deletions

View File

@ -8,17 +8,10 @@
.legend { .legend {
display: flex; display: flex;
justify-content: center;
font-size: calc(16rem / 16); font-size: calc(16rem / 16);
top: 0; top: 0;
} }
.tickLabel {
font-family: "Inconsolata", monospace;
font-weight: 800;
fill: var(--label);
}
.key { .key {
font-weight: bold; font-weight: bold;
} }

View File

@ -43,14 +43,17 @@ export type StackedBarProps = {
/** Colours for each key */ /** Colours for each key */
colorRange: string[]; colorRange: string[];
/** Distance between the edge of the graph and the area where the bars are drawn, in pixels. */ /** Distance between the edge of the graph and the area where the bars are drawn, in pixels. */
margin: { margin: { top: number; left: number };
top: number;
left: number;
bottom: number;
right: number;
};
/** Number of ticks for the value axis */ /** Number of ticks for the value axis */
numTicksValueAxis?: number; numTicksValueAxis?: number;
/** Distance between the left axis labels and the start of the lines of the graph, in px. */
axisLeftOffset?: number;
/** Distance between the bottom axis and the bottom of the container of the graph, in px. */
axisBottomOffset?: number;
/** Distance between the right side of the graph and the legend, in px. */
legendLeftOffset?: number;
/** Distance between the top of the graph and the legend, in px. */
legendTopOffset?: number;
/** Width of the lines in the graph, in px. */ /** Width of the lines in the graph, in px. */
strokeWidth?: number; strokeWidth?: number;
/** Length of the dashes and the gaps in the graph, in px. */ /** Length of the dashes and the gaps in the graph, in px. */
@ -59,6 +62,9 @@ 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 with an offset to center the labels of the category-axis depending on the width/height of the graph.
* >1 for width/height <600 and <1 for width/height >600 (vertical=width/horizontal=height) */
categoryAxisLeftFactor?: number;
}; };
let tooltipTimeout: number; let tooltipTimeout: number;
@ -76,9 +82,14 @@ export const StackedBarGraphVertical = withTooltip<
margin, margin,
scalePadding = 0.3, scalePadding = 0.3,
numTicksValueAxis = 6, numTicksValueAxis = 6,
axisLeftOffset = 40,
axisBottomOffset = 40,
strokeWidth = 2.5, strokeWidth = 2.5,
strokeDashArray = "10,4", strokeDashArray = "10,4",
legendLeftOffset = 40,
legendTopOffset = 40,
itemMargin = "0 0 0 15px", itemMargin = "0 0 0 15px",
categoryAxisLeftFactor = 1,
tooltipOpen, tooltipOpen,
tooltipLeft, tooltipLeft,
tooltipTop, tooltipTop,
@ -115,15 +126,18 @@ export const StackedBarGraphVertical = withTooltip<
}); });
// bounds // bounds
const xMax = width - margin.left - margin.right; const xMax = width;
const yMax = height - margin.top - margin.bottom; const yMax = height - margin.top - axisBottomOffset;
categoryScale.rangeRound([0, xMax]); categoryScale.rangeRound([0, xMax - axisLeftOffset]);
valueScale.range([yMax, 0]); valueScale.range([yMax, 0]);
return width < 10 ? null : ( return width < 10 ? null : (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.legend}> <div
className={styles.legend}
style={{ left: width + legendLeftOffset, top: legendTopOffset }}
>
<LegendOrdinal <LegendOrdinal
scale={colorScale} scale={colorScale}
direction="row" direction="row"
@ -138,6 +152,7 @@ export const StackedBarGraphVertical = withTooltip<
scale={valueScale} scale={valueScale}
width={xMax} width={xMax}
height={yMax} height={yMax}
left={axisLeftOffset}
numTicks={numTicksValueAxis} numTicks={numTicksValueAxis}
stroke={Color.tertiaryBackground} stroke={Color.tertiaryBackground}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
@ -146,89 +161,94 @@ export const StackedBarGraphVertical = withTooltip<
<GridColumns <GridColumns
scale={categoryScale} scale={categoryScale}
height={yMax} height={yMax}
left={axisLeftOffset}
offset={categoryScale.bandwidth() / 2}
stroke={Color.tertiaryBackground} stroke={Color.tertiaryBackground}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
strokeDasharray={strokeDashArray} strokeDasharray={strokeDashArray}
/> />
<BarStack<StackedBarData, string> <Group left={axisLeftOffset}>
data={data} <BarStack<StackedBarData, string>
keys={keys} data={data}
x={getCategory} keys={keys}
xScale={categoryScale} x={getCategory}
yScale={valueScale} xScale={categoryScale}
color={colorScale} yScale={valueScale}
> color={colorScale}
{(barStacks) => >
barStacks.map((barStack) => {(barStacks) =>
barStack.bars.map((bar) => ( barStacks.map((barStack) =>
<rect barStack.bars.map((bar) => (
className={styles.barStack} <rect
key={`bar-stack-${barStack.index}-${bar.index}`} className={styles.barStack}
x={bar.x} key={`bar-stack-${barStack.index}-${bar.index}`}
y={bar.y} x={bar.x}
height={bar.height} y={bar.y}
width={bar.width} height={bar.height}
fill={bar.color} width={bar.width / 2}
onMouseLeave={() => { fill={bar.color}
tooltipTimeout = window.setTimeout(() => { onMouseLeave={() => {
hideTooltip(); tooltipTimeout = window.setTimeout(() => {
}, 300); hideTooltip();
}} }, 300);
onMouseMove={(event) => { }}
if (tooltipTimeout) clearTimeout(tooltipTimeout); onMouseMove={(event) => {
const eventSvgCoords = localPoint(event); if (tooltipTimeout) clearTimeout(tooltipTimeout);
const left = bar.x + bar.width / 2; const eventSvgCoords = localPoint(event);
showTooltip({ const left = bar.x + bar.width / 2;
tooltipData: bar, showTooltip({
tooltipTop: eventSvgCoords?.y, tooltipData: bar,
tooltipLeft: left, tooltipTop: eventSvgCoords?.y,
}); tooltipLeft: left,
}} });
/> }}
)) />
) ))
} )
</BarStack> }
</BarStack>
</Group>
<Line <Line
fill={Color.tertiaryBackground} fill={Color.tertiaryBackground}
to={new Point({ x: 0, y: 0 })} to={new Point({ x: axisLeftOffset, y: 0 })}
from={new Point({ x: 0, y: yMax })} from={new Point({ x: axisLeftOffset, y: yMax })}
stroke={Color.tertiaryBackground} stroke={Color.tertiaryBackground}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
strokeDasharray={strokeDashArray} strokeDasharray={strokeDashArray}
/> />
</Group> <AxisBottom
<AxisBottom top={yMax}
top={yMax} scale={categoryScale}
scale={categoryScale} left={
hideTicks ((categoryScale.bandwidth() * 100) / width) *
hideAxisLine categoryAxisLeftFactor
labelProps={{ }
fontSize: `${10 / 16}rem`, hideTicks
}} hideAxisLine
tickLabelProps={() => ({ labelProps={{
className: styles.tickLabel, fontSize: `${10 / 16}rem`,
fill: Color.label, }}
fontWeight: TICK_LABEL_FONT_WEIGHT, tickLabelProps={() => ({
width: categoryScale.bandwidth(),
verticalAnchor: "start",
})}
/>
<AxisLeft
scale={valueScale}
top={5}
numTicks={numTicksValueAxis}
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
scale={valueScale}
top={5}
numTicks={numTicksValueAxis}
hideAxisLine
labelProps={{
fontSize: `${10 / 16}rem`,
}}
tickLabelProps={() => {
return {
fill: Color.label,
fontWeight: TICK_LABEL_FONT_WEIGHT,
};
}}
/>
</Group>
</svg> </svg>
{tooltipOpen && tooltipData ? ( {tooltipOpen && tooltipData ? (
@ -259,9 +279,14 @@ export const StackedBarGraphHorizontal = withTooltip<
margin, margin,
scalePadding = 0.3, scalePadding = 0.3,
numTicksValueAxis = 6, numTicksValueAxis = 6,
axisLeftOffset = 40,
axisBottomOffset = 40,
strokeWidth = 2.5, strokeWidth = 2.5,
strokeDashArray = "10,4", strokeDashArray = "10,4",
legendLeftOffset = 40,
legendTopOffset = 40,
itemMargin = "0 0 0 15px", itemMargin = "0 0 0 15px",
categoryAxisLeftFactor = 1,
tooltipOpen, tooltipOpen,
tooltipLeft, tooltipLeft,
tooltipTop, tooltipTop,
@ -298,15 +323,18 @@ export const StackedBarGraphHorizontal = withTooltip<
}); });
// bounds // bounds
const xMax = width - margin.left - margin.right; const xMax = width;
const yMax = height - margin.top - margin.bottom; const yMax = height - margin.top - axisBottomOffset;
categoryScale.rangeRound([yMax, 0]); categoryScale.rangeRound([yMax, 0]);
valueScale.range([0, xMax]); valueScale.range([0, xMax - axisLeftOffset]);
return width < 10 ? null : ( return width < 10 ? null : (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.legend}> <div
className={styles.legend}
style={{ left: width + legendLeftOffset, top: legendTopOffset }}
>
<LegendOrdinal <LegendOrdinal
scale={colorScale} scale={colorScale}
direction="row" direction="row"
@ -321,6 +349,7 @@ export const StackedBarGraphHorizontal = withTooltip<
width={xMax} width={xMax}
height={yMax} height={yMax}
offset={categoryScale.bandwidth() / 2} offset={categoryScale.bandwidth() / 2}
left={axisLeftOffset}
stroke={Color.tertiaryBackground} stroke={Color.tertiaryBackground}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
strokeDasharray={strokeDashArray} strokeDasharray={strokeDashArray}
@ -329,52 +358,56 @@ export const StackedBarGraphHorizontal = withTooltip<
scale={valueScale} scale={valueScale}
height={yMax} height={yMax}
numTicks={numTicksValueAxis} numTicks={numTicksValueAxis}
left={axisLeftOffset}
stroke={Color.tertiaryBackground} stroke={Color.tertiaryBackground}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
strokeDasharray={strokeDashArray} strokeDasharray={strokeDashArray}
/> />
<BarStackHorizontal<StackedBarData, string> <Group left={axisLeftOffset}>
data={data} <BarStackHorizontal<StackedBarData, string>
keys={keys} data={data}
y={getCategory} keys={keys}
xScale={valueScale} y={getCategory}
yScale={categoryScale} xScale={valueScale}
color={colorScale} yScale={categoryScale}
> color={colorScale}
{(barStacks) => >
barStacks.map((barStack) => {(barStacks) =>
barStack.bars.map((bar) => ( barStacks.map((barStack) =>
<rect barStack.bars.map((bar) => (
className={styles.barStack} <rect
key={`bar-stack-${barStack.index}-${bar.index}`} className={styles.barStack}
x={bar.x} key={`bar-stack-${barStack.index}-${bar.index}`}
y={bar.y} x={bar.x}
height={bar.height / 2} y={bar.y}
width={bar.width} height={bar.height / 2}
fill={bar.color} width={bar.width}
onMouseLeave={() => { fill={bar.color}
tooltipTimeout = window.setTimeout(() => { onMouseLeave={() => {
hideTooltip(); tooltipTimeout = window.setTimeout(() => {
}, 300); hideTooltip();
}} }, 300);
onMouseMove={(event) => { }}
if (tooltipTimeout) clearTimeout(tooltipTimeout); onMouseMove={(event) => {
const eventSvgCoords = localPoint(event); if (tooltipTimeout) clearTimeout(tooltipTimeout);
const left = bar.x + bar.width / 2; const eventSvgCoords = localPoint(event);
showTooltip({ const left = bar.x + bar.width / 2;
tooltipData: bar, showTooltip({
tooltipTop: eventSvgCoords?.y, tooltipData: bar,
tooltipLeft: left, tooltipTop: eventSvgCoords?.y,
}); tooltipLeft: left,
}} });
/> }}
)) />
) ))
} )
</BarStackHorizontal> }
</BarStackHorizontal>
</Group>
<AxisBottom <AxisBottom
top={yMax} top={yMax}
scale={valueScale} scale={valueScale}
left={axisLeftOffset}
numTicks={numTicksValueAxis} numTicks={numTicksValueAxis}
hideAxisLine hideAxisLine
hideTicks hideTicks
@ -387,6 +420,10 @@ export const StackedBarGraphHorizontal = withTooltip<
})} })}
/> />
<AxisLeft <AxisLeft
top={
-((categoryScale.bandwidth() * 100) / width) *
categoryAxisLeftFactor
}
scale={categoryScale} scale={categoryScale}
hideAxisLine hideAxisLine
hideTicks hideTicks

View File

@ -251,11 +251,10 @@ export default function Academics() {
Color.primaryAccentLighter, Color.primaryAccentLighter,
]} ]}
data={A12i} data={A12i}
axisLeftOffset={80}
margin={{ margin={{
top: 20, top: 20,
left: 80, left: 20,
bottom: 0,
right: 0,
}} }}
/> />
</ComponentWrapper> </ComponentWrapper>
@ -274,8 +273,8 @@ export default function Academics() {
noBackground noBackground
> >
<StackedBarGraphVertical <StackedBarGraphVertical
width={800} width={600}
height={500} height={400}
keys={A16Keys} keys={A16Keys}
colorRange={[ colorRange={[
Color.primaryAccent, Color.primaryAccent,
@ -285,9 +284,7 @@ export default function Academics() {
data={A16} data={A16}
margin={{ margin={{
top: 20, top: 20,
left: 80, left: 20,
bottom: 70,
right: 0,
}} }}
/> />
</ComponentWrapper> </ComponentWrapper>

View File

@ -226,8 +226,6 @@ export default function CoopPage() {
margin={{ margin={{
top: 20, top: 20,
left: 20, left: 20,
bottom: 0,
right: 0,
}} }}
/> />
</div> </div>
@ -256,8 +254,6 @@ export default function CoopPage() {
margin={{ margin={{
top: 20, top: 20,
left: 20, left: 20,
bottom: 0,
right: 0,
}} }}
/> />
</div> </div>
@ -301,8 +297,6 @@ export default function CoopPage() {
margin={{ margin={{
top: 20, top: 20,
left: 20, left: 20,
bottom: 0,
right: 0,
}} }}
/> />
</div> </div>

View File

@ -126,8 +126,6 @@ export default function Home() {
margin={{ margin={{
top: 20, top: 20,
left: 20, left: 20,
bottom: 0,
right: 0,
}} }}
/> />
@ -151,8 +149,6 @@ export default function Home() {
margin={{ margin={{
top: 20, top: 20,
left: 20, left: 20,
bottom: 0,
right: 0,
}} }}
/> />