如何将D3堆叠数据集函数改造为支持泛型TypeScript类型?
解决方案
1. 定义泛型约束类型
先定义一个类型约束,确保传入的数据集至少包含date、category、value三个核心字段,同时允许携带其他额外字段:
type StackableDatum<DateType = string | number, CategoryType = string | number, ValueType = number> = { date: DateType; category: CategoryType; value: ValueType; } & Record<string, any>;
这里用泛型参数兼容不同类型的日期、分类和数值,& Record<string, any>用来支持额外字段的扩展。
2. 改造getStackedSeries函数
替换掉原有的特定类型,改用泛型实现,同时支持传入可选的列名映射(适配字段名非默认值的场景):
import { stack, StackSeries } from 'd3'; function getStackedSeries< T extends StackableDatum<DateType, CategoryType, ValueType>, DateType = string | number, CategoryType = string | number, ValueType = number >( data: T[], columnNames: { date?: keyof T; category?: keyof T; value?: keyof T } = {} ): StackSeries<T, ValueType>[] { // 提取列名,默认使用date、category、value const dateKey = (columnNames.date || 'date') as keyof T; const categoryKey = (columnNames.category || 'category') as keyof T; const valueKey = (columnNames.value || 'value') as keyof T; // 按日期分组,整理成D3 stack要求的结构:每个日期对应各分类的数值 const groupedData = Array.from( d3.group(data, d => d[dateKey]), ([date, items]) => { const entry: Record<string, ValueType> = { date }; items.forEach(item => { const category = item[categoryKey] as string; entry[category] = item[valueKey] as ValueType; }); return entry; } ); // 获取所有唯一分类 const categories = Array.from(new Set(data.map(d => d[categoryKey] as string))); // 生成堆叠数据 return stack<T, ValueType>().keys(categories)(groupedData); }
3. 改造flatDataset函数
同样用泛型处理堆叠后的数据,方便后续绑定到DOM元素:
type FlatStackedDatum<DateType = string | number, CategoryType = string, ValueType = number> = { date: DateType; category: CategoryType; value: ValueType; start: ValueType; end: ValueType; }; function flatDataset< T extends StackableDatum<DateType, CategoryType, ValueType>, DateType = string | number, CategoryType = string | number, ValueType = number >( stackedSeries: StackSeries<T, ValueType>[], dateKey: keyof T = 'date' as keyof T ): FlatStackedDatum<DateType, CategoryType, ValueType>[] { return stackedSeries.flatMap(series => { const category = series.key as CategoryType; return series.map(d => ({ date: d.data[dateKey] as DateType, category, value: d[1] - d[0], start: d[0], end: d[1] })); }); }
4. 使用示例
假设你有带额外字段的自定义数据集:
interface SalesData extends StackableDatum<number, string, number> { region: string; } const rawData: SalesData[] = [ { date: 2023, category: 'A', value: 100, region: 'North' }, { date: 2023, category: 'B', value: 150, region: 'North' }, { date: 2024, category: 'A', value: 120, region: 'South' }, { date: 2024, category: 'B', value: 180, region: 'South' } ]; // 生成堆叠数据 const stacked = getStackedSeries(rawData); // 扁平化数据用于可视化 const flatData = flatDataset(stacked);
关键说明
- 泛型约束保证了只要数据集包含核心字段,就能被函数处理,同时兼容任意额外字段。
- 可选列名参数支持字段名非默认值的场景,比如你的日期字段叫
timestamp,只需传入{ date: 'timestamp' }即可适配。 - D3的
stack()要求数据按日期分组、每个分组下包含各分类数值,函数内的分组逻辑是适配的核心步骤。
内容的提问来源于stack exchange,提问作者marielle
相关产品推荐
相关产品推荐

