Skip to content

Commit

Permalink
start streamchart transition
Browse files Browse the repository at this point in the history
  • Loading branch information
holtzy committed Jun 2, 2023
1 parent a4dee6f commit a2080e0
Show file tree
Hide file tree
Showing 10 changed files with 384 additions and 3 deletions.
16 changes: 15 additions & 1 deletion pages/streamchart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { ResponsiveExplanationSection } from "../component/ResponsiveExplanation
import { StreamGraphPageViewsDemo } from "viz/StreamGraphPageViews/StreamGraphPageViewsDemo";
import { Accordion } from "component/UI/Accordion";
import { LinkAsButton } from "component/LinkAsButton";
import { Pill } from "component/UI/Pill";
import { StreamGraphHoverEffectDemo } from "viz/StreamGraphHoverEffect/StreamGraphHoverEffectDemo";
import { StreamGraphShapeTransitionDemo } from "viz/StreamGraphShapeTransition/StreamGraphShapeTransitionDemo";

const graphDescription = (
<>
Expand Down Expand Up @@ -247,6 +247,20 @@ export default function Home() {
<DatavizInspirationParallaxLink chartId="stream" />
{/*
//
// Transition
//
*/}
<h2 id="transition">Streamgraph algorithm with transition</h2>
<p>Transition with react-spring</p>
<ChartOrSandbox
vizName={"StreamGraphShapeTransition"}
VizComponent={StreamGraphShapeTransitionDemo}
height={400}
maxWidth={600}
caption="StreamGraph with hover effect that highlights a specific series"
/>
{/*
//
// Application
//
*/}
Expand Down
4 changes: 2 additions & 2 deletions viz/StreamGraphHoverEffect/StreamGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ export const StreamGraph = ({ width, height, data }: StreamGraphProps) => {

return (
<div>
<svg width={width} height={height} className={styles.container}>
<svg width={width} height={height}>
<g
width={boundsWidth}
height={boundsHeight}
transform={`translate(${[MARGIN.left, MARGIN.top].join(",")})`}
>
{grid}
{allPath}
<g className={styles.container}>{allPath}</g>
</g>
</svg>
</div>
Expand Down
31 changes: 31 additions & 0 deletions viz/StreamGraphShapeTransition/AnimatedPath.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useSpring, animated } from "@react-spring/web";
import styles from "./streamgraph.module.css";

type AnimatedPathItemProps = {
path: string;
color: string;
};

export const AnimatedPathItem = ({ path, color }: AnimatedPathItemProps) => {
const springProps = useSpring({
to: {
path,
},
config: {
friction: 100,
},
immediate: true,
});

return (
<animated.path
className={styles.shape}
d={springProps.path}
opacity={1}
stroke="grey"
fill={color}
fillOpacity={0.8}
cursor="pointer"
/>
);
};
138 changes: 138 additions & 0 deletions viz/StreamGraphShapeTransition/StreamGraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import * as d3 from "d3";
import styles from "./streamgraph.module.css";
import { AnimatedPathItem } from "./AnimatedPath";

const MARGIN = { top: 30, right: 30, bottom: 50, left: 50 };

const OFFSET_TYPES = {
silouhette: d3.stackOffsetSilhouette,
wiggle: d3.stackOffsetWiggle,
none: d3.stackOffsetNone,
diverging: d3.stackOffsetDiverging,
expand: d3.stackOffsetExpand,
};

const CURVE_TYPES = {
curveBasis: d3.curveBasis,
bumpX: d3.curveBumpX,
bumpY: d3.curveBumpY,
curveCardinal: d3.curveCardinal,
catMullRom: d3.curveCatmullRom,
curveLinear: d3.curveLinear,
curveNatural: d3.curveNatural,
curveStep: d3.curveStep,
};

type StreamGraphProps = {
width: number;
height: number;
data: { [key: string]: number }[];
offsetType: string;
curveType: string;
};

export const StreamGraph = ({
width,
height,
data,
offsetType,
curveType,
}: StreamGraphProps) => {
// bounds = area inside the graph axis = calculated by substracting the margins
const boundsWidth = width - MARGIN.right - MARGIN.left;
const boundsHeight = height - MARGIN.top - MARGIN.bottom;

const groups = ["groupA", "groupB", "groupC", "groupD"];

// Data Wrangling: stack the data
const stackSeries = d3
.stack()
.keys(groups)
.order(d3.stackOrderNone)
.offset(OFFSET_TYPES[offsetType]);
const series = stackSeries(data);

// Y axis
const topYValues = series.flatMap((s) => s.map((d) => d[1])); // Extract the upper values of each data point in the stacked series
const yMax = Math.max(...topYValues);

const bottomYValues = series.flatMap((s) => s.map((d) => d[0])); // Extract the upper values of each data point in the stacked series
const yMin = Math.min(...bottomYValues);

const yScale = d3.scaleLinear().domain([yMin, yMax]).range([boundsHeight, 0]);

// X axis
const [xMin, xMax] = d3.extent(data, (d) => d.x);
const xScale = d3
.scaleLinear()
.domain([xMin || 0, xMax || 0])
.range([0, boundsWidth]);

// Color
const colorScale = d3
.scaleOrdinal<string>()
.domain(groups)
.range(["#e0ac2b", "#e85252", "#6689c6", "#9a6fb0", "#a53253"]);

// Build the shapes
const areaBuilder = d3
.area<any>()
.x((d) => {
return xScale(d.data.x);
})
.y1((d) => yScale(d[1]))
.y0((d) => yScale(d[0]))
.curve(CURVE_TYPES[curveType]);

const allPath = series.map((serie, i) => {
const path = areaBuilder(serie);
if (!path) {
console.log("null");
return null;
}
return (
<AnimatedPathItem key={i} path={path} color={colorScale(serie.key)} />
);
});

console.log({ allPath });

const grid = xScale.ticks(5).map((value, i) => (
<g key={i}>
<line
x1={xScale(value)}
x2={xScale(value)}
y1={0}
y2={boundsHeight}
stroke="#808080"
opacity={0.2}
/>
<text
x={xScale(value)}
y={boundsHeight + 10}
textAnchor="middle"
alignmentBaseline="central"
fontSize={9}
stroke="#808080"
opacity={0.8}
>
{value}
</text>
</g>
));

return (
<div>
<svg width={width} height={height}>
<g
width={boundsWidth}
height={boundsHeight}
transform={`translate(${[MARGIN.left, MARGIN.top].join(",")})`}
>
{grid}
<g className={styles.container}>{allPath}</g>
</g>
</svg>
</div>
);
};
64 changes: 64 additions & 0 deletions viz/StreamGraphShapeTransition/StreamGraphShapeTransition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useState } from "react";
import { StreamGraph } from "./StreamGraph";
import { data } from "./data";

const BUTTON_HEIGHT = 50;

export const StreamGraphShapeTransition = ({ width = 700, height = 400 }) => {
const [offsetType, setOffsetType] = useState("silhouette");
const [curveType, setCurveType] = useState("curveLinear");

return (
<div>
<div
style={{
height: BUTTON_HEIGHT,
display: "flex",
marginTop: 20,
alignItems: "center",
}}
>
<span style={{ fontSize: 14, marginRight: 5 }}>Offset type</span>
<select
onChange={(e) => {
setOffsetType(e.target.value);
}}
value={offsetType}
style={{ fontSize: 14 }}
>
<option value="silouhette">Silouhette</option>
<option value="none">None</option>
<option value="wiggle">Wiggle</option>
<option value="diverging">Diverging</option>
<option value="expand">Expand</option>
</select>
<span style={{ fontSize: 14, marginRight: 5, marginLeft: 35 }}>
Curve type
</span>
<select
onChange={(e) => {
setCurveType(e.target.value);
}}
value={curveType}
style={{ fontSize: 14 }}
>
<option value="curveBasis">Cubic basis spline</option>
<option value="bumpX">Bezier Curve Horizontal</option>
<option value="bumpY">Bezier Curve Vertical</option>
<option value="curveCardinal">Cubic cardinal spline </option>
<option value="catMullRom">Catmull–Rom spline</option>
<option value="curveLinear">Polyline</option>
<option value="curveNatural">Natural cubic spline</option>
<option value="curveStep">Step function</option>
</select>
</div>
<StreamGraph
data={data}
width={width}
height={height - BUTTON_HEIGHT}
offsetType={offsetType}
curveType={curveType}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { StreamGraphShapeTransition } from "./StreamGraphShapeTransition";

export const StreamGraphShapeTransitionDemo = ({
width = 700,
height = 400,
}) => <StreamGraphShapeTransition width={width} height={height} />;
72 changes: 72 additions & 0 deletions viz/StreamGraphShapeTransition/data.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
export const data = [
{
x: 1,
groupA: 38,
groupB: 19,
groupC: 9,
groupD: 4,
},
{
x: 2,
groupA: 16,
groupB: 14,
groupC: 96,
groupD: 40,
},
{
x: 3,
groupA: 164,
groupB: 96,
groupC: 64,
groupD: 40,
},
{
x: 4,
groupA: 32,
groupB: 65,
groupC: 64,
groupD: 40,
},
{
x: 5,
groupA: 12,
groupB: 80,
groupC: 14,
groupD: 10,
},
{
x: 6,
groupA: 12,
groupB: 180,
groupC: 14,
groupD: 10,
},
{
x: 7,
groupA: 12,
groupB: 70,
groupC: 14,
groupD: 10,
},
{
x: 8,
groupA: 12,
groupB: 30,
groupC: 24,
groupD: 10,
},
{
x: 9,
groupA: 190,
groupB: 18,
groupC: 34,
groupD: 10,
},
{
x: 10,
groupA: 10,
groupB: 18,
groupC: 4,
groupD: 10,
},
];
9 changes: 9 additions & 0 deletions viz/StreamGraphShapeTransition/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import ReactDOM from "react-dom";
import { data } from "./data";
import { StreamGraph } from "./StreamGraph";

const rootElement = document.getElementById("root");
ReactDOM.render(
<StreamGraph data={data} width={400} height={400} />,
rootElement
);
Loading

0 comments on commit a2080e0

Please sign in to comment.