Visx Streamgraph Custom Typing

27 Views Asked by At

I'm using visx to build a streamgraph for my application, but am very new to visx/d3. I'm able to generate the graph using a simple 2-d number array like in this example, but run into issues when modifying the input structure. In the example below, the goal is to generate a streamgraph that runs vertically downward as t increases, where t is a time in milliseconds and value is a number [0,1]. You can see it becomes necessary to modify the input data type with the inclusion of t, which may cause the vertical distance between points to vary. I'm fairly sure there's something I'm missing with the accessors, as logging shows all paths are NaN by the time they are passed into the <Stack> child method. Hoping someone can point out how I should be structuring and accessing the data here. Thanks!

// t represents time in milliseconds, value represents value at that time
type Datum = {t: number, value: number}[];

export type StreamGraphProps = {
  data: Datum[]
  width: number;
  height: number;
  animate?: boolean;
};

export default function Streamgraph({data, width, height, animate = true}: StreamGraphProps) {
  if (data.length === 0) return null

  // Should represent the different series
  const keys = _.range(data[0].length);

  // Maximum value of t
  const tMax = data.reduce((max, cur) => Math.max(max, ...cur.map(v => v.t)), 0)

  // Scales
  const xScale = scaleLinear<number>({
    domain: [0, 1],
    range: [0, width],
  });
  const yScale = scaleLinear<number>({
    domain: [tMax, 0],
    range: [height, 0]
  });
  const colorScale = scaleOrdinal<number, string>({
    domain: keys,
    range: ['#ffc409', '#f14702', '#262d97', 'white', '#036ecd', '#9ecadd'],
  });
  const patternScale = scaleOrdinal<number, string>({
    domain: keys,
    range: ['gor', 'gpo', 'gpb', 'gpr', 'gpur', 'gsp', 'gtb'],
  });

  // x getter
  const getX = (d: SeriesPoint<Datum>, i: number) => xScale(d.data[i].value)
  // y0/y1 getters
  const getY0 = (d: SeriesPoint<Datum>) => yScale(d.data[0].t)
  const getY1 = (d: SeriesPoint<Datum>) => yScale(d.data[1].t)

  if (width < 10) return null

  return (
    <svg width={width} height={height}>
      <GradientPinkBlue id={"gpb"}/>
      <GradientPurpleOrange id={"gpo"}/>
      <GradientOrangeRed id={"gor"}/>
      <GradientPinkRed id={"gpr"}/>
      <GradientPurpleRed id={"gpur"}/>
      <GradientSteelPurple id={"gsp"}/>
      <GradientTealBlue id={"gtb"}/>

      <Group>
        <Stack<Datum, number>
          data={data}
          keys={keys}
          offset="silhouette"
          order='insideout'
          color={colorScale}
          x={getX}
          y0={getY0}
          y1={getY1}
          curve={curveCatmullRom}
        >
          {({stacks, path}) => {
            return stacks.map((stack) => {
              const pathString = path(stack) || '';
              const tweened = animate ? useSpring({pathString}) : {pathString};
              const color = colorScale(stack.key);
              const pattern = patternScale(stack.key);
              return (
                <g key={`series-${stack.key}`}>
                  <animated.path d={tweened.pathString} fill={color}/>
                  <animated.path d={tweened.pathString} fill={`url(#${pattern})`}/>
                </g>
              );
            })
          }}
        </Stack>
      </Group>
    </svg>
  );
}
0

There are 0 best solutions below