我正在使用 visx 为我的应用程序构建流图,但对 visx/d3 非常陌生。我可以使用简单的二维数字数组(如this示例中所示)生成图形,但在修改输入结构时遇到问题。在下面的示例中,目标是生成一个随着
t
增加而垂直向下运行的流图,其中 t
是以毫秒为单位的时间,value
是数字 [0,1]
。您可以看到有必要修改输入数据类型以包含 t
,这可能会导致点之间的垂直距离发生变化。我相当确定访问器缺少一些东西,因为日志记录显示所有路径在传递到 NaN
子方法时都是 <Stack>
的。希望有人能指出我应该如何构建和访问这里的数据。谢谢!
// 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>
);
}
好吧,我发现我需要设置
value
访问器。这就是让 visx
从任意输入类型检索 y0,y1 值的原因。
// t represents time in milliseconds, value represents value at that time
type Datum = {t_start: number, t_end: number, features: Record<Features, 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 = _.uniq(data.flatMap(v => Object.keys(v.features))) as Features[]
// Maximum value of t
const tMax = Object.values(data).reduce((acc,v) => {
return Math.max(v.t_end, acc)
}, 0)
// Scales
const xScale = scaleLinear<number>({
domain: [0, tMax],
range: [0, width],
});
const yScale = scaleLinear<number>({
domain: [-3.5, 3.5],
range: [height, 0]
});
const colorScale = scaleOrdinal<Features, string>({
domain: keys,
range: ['#ffc409', '#f14702', '#262d97', 'white', '#036ecd', '#9ecadd', 'green'],
});
const patternScale = scaleOrdinal<Features, string>({
domain: keys,
range: ['gor', 'gpo', 'gpb', 'gpr', 'gpur', 'gsp', 'gtb'],
});
// x getter
const getX = (d: SeriesPoint<Datum>) => d.data.t_start === 0 ? 0 :
d.data.t_end === tMax ? tMax :
xScale((d.data.t_end + d.data.t_start)/2)
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 width={width} height={height}>
<Stack<Datum, Features>
data={Object.values(data)}
keys={keys}
offset="silhouette"
order='insideout'
color={colorScale}
value={(d,k) => d.features[k]}
x={getX}
curve={curveCatmullRom}
y0={v => yScale(v[0])}
y1={v => yScale(v[1])}
>
{({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>
);
}