WIP Stacked Bar Graph

This commit is contained in:
e26chiu 2022-12-15 21:27:04 -05:00
parent d77ebe0f8d
commit 803457c083
5 changed files with 149 additions and 166 deletions

View File

@ -8,10 +8,17 @@
.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,17 +43,14 @@ 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: { top: number; left: number }; margin: {
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. */
@ -62,9 +59,6 @@ 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;
@ -82,14 +76,9 @@ 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,
@ -126,18 +115,15 @@ export const StackedBarGraphVertical = withTooltip<
}); });
// bounds // bounds
const xMax = width; const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - axisBottomOffset; const yMax = height - margin.top - margin.bottom;
categoryScale.rangeRound([0, xMax - axisLeftOffset]); categoryScale.rangeRound([0, xMax]);
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 <div className={styles.legend}>
className={styles.legend}
style={{ left: width + legendLeftOffset, top: legendTopOffset }}
>
<LegendOrdinal <LegendOrdinal
scale={colorScale} scale={colorScale}
direction="row" direction="row"
@ -152,7 +138,6 @@ 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}
@ -161,94 +146,89 @@ 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}
/> />
<Group left={axisLeftOffset}> <BarStack<StackedBarData, string>
<BarStack<StackedBarData, string> data={data}
data={data} keys={keys}
keys={keys} x={getCategory}
x={getCategory} xScale={categoryScale}
xScale={categoryScale} yScale={valueScale}
yScale={valueScale} color={colorScale}
color={colorScale} >
> {(barStacks) =>
{(barStacks) => barStacks.map((barStack) =>
barStacks.map((barStack) => barStack.bars.map((bar) => (
barStack.bars.map((bar) => ( <rect
<rect className={styles.barStack}
className={styles.barStack} key={`bar-stack-${barStack.index}-${bar.index}`}
key={`bar-stack-${barStack.index}-${bar.index}`} x={bar.x}
x={bar.x} y={bar.y}
y={bar.y} height={bar.height}
height={bar.height} width={bar.width}
width={bar.width / 2} fill={bar.color}
fill={bar.color} onMouseLeave={() => {
onMouseLeave={() => { tooltipTimeout = window.setTimeout(() => {
tooltipTimeout = window.setTimeout(() => { hideTooltip();
hideTooltip(); }, 300);
}, 300); }}
}} onMouseMove={(event) => {
onMouseMove={(event) => { if (tooltipTimeout) clearTimeout(tooltipTimeout);
if (tooltipTimeout) clearTimeout(tooltipTimeout); const eventSvgCoords = localPoint(event);
const eventSvgCoords = localPoint(event); const left = bar.x + bar.width / 2;
const left = bar.x + bar.width / 2; showTooltip({
showTooltip({ tooltipData: bar,
tooltipData: bar, tooltipTop: eventSvgCoords?.y,
tooltipTop: eventSvgCoords?.y, tooltipLeft: left,
tooltipLeft: left, });
}); }}
}} />
/> ))
)) )
) }
} </BarStack>
</BarStack>
</Group>
<Line <Line
fill={Color.tertiaryBackground} fill={Color.tertiaryBackground}
to={new Point({ x: axisLeftOffset, y: 0 })} to={new Point({ x: 0, y: 0 })}
from={new Point({ x: axisLeftOffset, y: yMax })} from={new Point({ x: 0, y: yMax })}
stroke={Color.tertiaryBackground} stroke={Color.tertiaryBackground}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
strokeDasharray={strokeDashArray} strokeDasharray={strokeDashArray}
/> />
<AxisBottom </Group>
top={yMax} <AxisBottom
scale={categoryScale} top={yMax}
left={ scale={categoryScale}
((categoryScale.bandwidth() * 100) / width) * hideTicks
categoryAxisLeftFactor hideAxisLine
} labelProps={{
hideTicks fontSize: `${10 / 16}rem`,
hideAxisLine }}
labelProps={{ tickLabelProps={() => ({
fontSize: `${10 / 16}rem`, className: styles.tickLabel,
}} fill: Color.label,
tickLabelProps={() => ({ fontWeight: TICK_LABEL_FONT_WEIGHT,
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 ? (
@ -279,14 +259,9 @@ 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,
@ -323,18 +298,15 @@ export const StackedBarGraphHorizontal = withTooltip<
}); });
// bounds // bounds
const xMax = width; const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - axisBottomOffset; const yMax = height - margin.top - margin.bottom;
categoryScale.rangeRound([yMax, 0]); categoryScale.rangeRound([yMax, 0]);
valueScale.range([0, xMax - axisLeftOffset]); valueScale.range([0, xMax]);
return width < 10 ? null : ( return width < 10 ? null : (
<div className={styles.container}> <div className={styles.container}>
<div <div className={styles.legend}>
className={styles.legend}
style={{ left: width + legendLeftOffset, top: legendTopOffset }}
>
<LegendOrdinal <LegendOrdinal
scale={colorScale} scale={colorScale}
direction="row" direction="row"
@ -349,7 +321,6 @@ 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}
@ -358,56 +329,52 @@ 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}
/> />
<Group left={axisLeftOffset}> <BarStackHorizontal<StackedBarData, string>
<BarStackHorizontal<StackedBarData, string> data={data}
data={data} keys={keys}
keys={keys} y={getCategory}
y={getCategory} xScale={valueScale}
xScale={valueScale} yScale={categoryScale}
yScale={categoryScale} color={colorScale}
color={colorScale} >
> {(barStacks) =>
{(barStacks) => barStacks.map((barStack) =>
barStacks.map((barStack) => barStack.bars.map((bar) => (
barStack.bars.map((bar) => ( <rect
<rect className={styles.barStack}
className={styles.barStack} key={`bar-stack-${barStack.index}-${bar.index}`}
key={`bar-stack-${barStack.index}-${bar.index}`} x={bar.x}
x={bar.x} y={bar.y}
y={bar.y} height={bar.height / 2}
height={bar.height / 2} width={bar.width}
width={bar.width} fill={bar.color}
fill={bar.color} onMouseLeave={() => {
onMouseLeave={() => { tooltipTimeout = window.setTimeout(() => {
tooltipTimeout = window.setTimeout(() => { hideTooltip();
hideTooltip(); }, 300);
}, 300); }}
}} onMouseMove={(event) => {
onMouseMove={(event) => { if (tooltipTimeout) clearTimeout(tooltipTimeout);
if (tooltipTimeout) clearTimeout(tooltipTimeout); const eventSvgCoords = localPoint(event);
const eventSvgCoords = localPoint(event); const left = bar.x + bar.width / 2;
const left = bar.x + bar.width / 2; showTooltip({
showTooltip({ tooltipData: bar,
tooltipData: bar, tooltipTop: eventSvgCoords?.y,
tooltipTop: eventSvgCoords?.y, tooltipLeft: left,
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
@ -420,10 +387,6 @@ export const StackedBarGraphHorizontal = withTooltip<
})} })}
/> />
<AxisLeft <AxisLeft
top={
-((categoryScale.bandwidth() * 100) / width) *
categoryAxisLeftFactor
}
scale={categoryScale} scale={categoryScale}
hideAxisLine hideAxisLine
hideTicks hideTicks

View File

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

View File

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

View File

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