Skip to content

Commit

Permalink
feat: adding componentPropsWithRef to balanced block chart (#26)
Browse files Browse the repository at this point in the history
* style: adding basic styles

* feat: add forwardRef and ComponentPropsWithRef for better compatibility and flexibility
  • Loading branch information
motinados authored Apr 13, 2024
1 parent 5c46777 commit 9851b92
Showing 1 changed file with 69 additions and 63 deletions.
132 changes: 69 additions & 63 deletions src/stacked-block-chart.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from "react";
import { ComponentPropsWithRef, forwardRef, useMemo } from "react";
import Block from "./block";
import BlockLabels from "./block-labels";
import Legend from "./legend";
Expand All @@ -18,75 +18,81 @@ export type Datum = {
color?: string;
};

type BalancedBlockChartProps = {
type BalancedBlockChartProps = ComponentPropsWithRef<"svg"> & {
type: "stable-balanced" | "unstable-inverted" | "shuffled";
data: Datum[];
showDataLabels?: boolean;
};

export default function StackedBlockChart({
type,
data,
showDataLabels = true,
}: BalancedBlockChartProps) {
const svgWidth = 400;
const svgHeight = 300;
const blocksOffsetX = 40;
const legendWidth = 100;
const legendItemHeight = 16;
const legendPaddingTop = 10;
const legendPaddingRight = 10;
const StackedBlockChart = forwardRef<SVGSVGElement, BalancedBlockChartProps>(
(
{ type, data, showDataLabels = true, ...rest }: BalancedBlockChartProps,
ref
) => {
const svgWidth = 400;
const svgHeight = 300;
const blocksOffsetX = 40;
const legendWidth = 100;
const legendItemHeight = 16;
const legendPaddingTop = 10;
const legendPaddingRight = 10;

const { blocks, legendItems } = useMemo(() => {
const initialBlocks = data.map(createInitialBlockDatum);
const blocksWithColor = initialBlocks.map(ensureBlockHasColor);
const total = blocksWithColor.reduce((acc, d) => acc + d.value, 0);
const blocksWithPercentage = blocksWithColor.map((d) =>
calcPercentage(d, total)
);
blocksWithPercentage.sort((a, b) => a.percentage - b.percentage);
const { blocks, legendItems } = useMemo(() => {
const initialBlocks = data.map(createInitialBlockDatum);
const blocksWithColor = initialBlocks.map(ensureBlockHasColor);
const total = blocksWithColor.reduce((acc, d) => acc + d.value, 0);
const blocksWithPercentage = blocksWithColor.map((d) =>
calcPercentage(d, total)
);
blocksWithPercentage.sort((a, b) => a.percentage - b.percentage);

let blocksWithWidthHeight = calcWidthsAndHeights(blocksWithPercentage, {
multiple: 100,
});
if (type === "unstable-inverted") {
blocksWithWidthHeight.reverse();
} else if (type === "shuffled") {
blocksWithWidthHeight = shuffleArray(blocksWithWidthHeight);
}
let blocksWithWidthHeight = calcWidthsAndHeights(blocksWithPercentage, {
multiple: 100,
});
if (type === "unstable-inverted") {
blocksWithWidthHeight.reverse();
} else if (type === "shuffled") {
blocksWithWidthHeight = shuffleArray(blocksWithWidthHeight);
}

const svgCenterX = (svgWidth - legendWidth) / 2 - blocksOffsetX;
const blocksWithPosition = calcBlocksPosition(
blocksWithWidthHeight,
svgCenterX
);
const blocks = alignToBottom(blocksWithPosition, svgHeight);
const legendItems = blocksWithWidthHeight.map((d) => {
return { name: d.name, color: d.fill };
});
const svgCenterX = (svgWidth - legendWidth) / 2 - blocksOffsetX;
const blocksWithPosition = calcBlocksPosition(
blocksWithWidthHeight,
svgCenterX
);
const blocks = alignToBottom(blocksWithPosition, svgHeight);
const legendItems = blocksWithWidthHeight.map((d) => {
return { name: d.name, color: d.fill };
});

return { blocks, legendItems };
}, [type, data]);
return { blocks, legendItems };
}, [type, data]);

return (
<>
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
style={{ width: "100%", height: "auto" }}
{...rest}
>
{blocks.map((block, index) => (
<Block key={index} {...block} />
))}
{showDataLabels && <BlockLabels blocks={blocks} />}
<Legend
items={legendItems}
svgWidth={svgWidth}
width={legendWidth}
paddingRight={legendPaddingRight}
paddingTop={legendPaddingTop}
itemHeight={legendItemHeight}
/>
</svg>
</>
);
}
);

return (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
>
{blocks.map((block, index) => (
<Block key={index} {...block} />
))}
{showDataLabels && <BlockLabels blocks={blocks} />}
<Legend
items={legendItems}
svgWidth={svgWidth}
width={legendWidth}
paddingRight={legendPaddingRight}
paddingTop={legendPaddingTop}
itemHeight={legendItemHeight}
/>
</svg>
</>
);
}
export default StackedBlockChart;

0 comments on commit 9851b92

Please sign in to comment.