You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 03:05:22