typescript 创建一个通用函数,该函数使用d3创建堆叠数据集

kx7yvsdv  于 2022-11-26  发布在  TypeScript
关注(0)|答案(2)|浏览(99)

我有这个数据集:

const dataset = [
  { date: "2022-01-01", category: "red", value: 10 },
  { date: "2022-01-01", category: "blue", value: 20 },
  { date: "2022-01-01", category: "gold", value: 30 },
  { date: "2022-01-01", category: "green", value: 40 },
  { date: "2022-01-02", category: "red", value: 5 },
  { date: "2022-01-02", category: "blue", value: 15 },
  { date: "2022-01-02", category: "gold", value: 25 },
  { date: "2022-01-02", category: "green", value: 35 }
];

我需要创建一个堆叠条形图。为此,我使用了d3 stack()函数。我需要的结果是:

const stackedDataset = [
  { date: "2022-01-01", category: "red", value: 10, start: 0, end: 10 },
  { date: "2022-01-02", category: "red", value: 5, start: 0, end: 5 },
  { date: "2022-01-01", category: "blue", value: 20, start: 10, end: 30 },
  { date: "2022-01-02", category: "blue", value: 15, start: 5, end: 20 },
  { date: "2022-01-01", category: "gold", value: 30, start: 30, end: 60 },
  { date: "2022-01-02", category: "gold", value: 25, start: 20, end: 45 },
  { date: "2022-01-01", category: "green", value: 40, start: 60, end: 100 },
  { date: "2022-01-02", category: "green", value: 35, start: 45, end: 80 }
]

因此,相同的数据,但startend属性由d3计算。
我创建了一个函数,它接受输入dataset并返回stackedDataset

export function getStackedSeries(dataset: Datum[]) {
  const categories = uniq(dataset.map((d) => d[CATEGORY])) as string[];
  const datasetGroupedByDateFlat = flatDataset(dataset);
  const stackGenerator = d3.stack().keys(categories);
  const seriesRaw = stackGenerator(
    datasetGroupedByDateFlat as Array<Dictionary<number>>
  );
  const series = seriesRaw.flatMap((serie, si) => {
    const category = categories[si];
    const result = serie.map((s, sj) => {
      return {
        [DATE]: datasetGroupedByDateFlat[sj][DATE] as string,
        [CATEGORY]: category,
        [VALUE]: datasetGroupedByDateFlat[sj][category] as number,
        start: s[0] || 0,
        end: s[1] || 0
      };
    });
    return result;
  });
  return series;
}

export function flatDataset(
  dataset: Datum[]
): Array<Dictionary<string | number>> {
  if (dataset.length === 0 || !DATE) {
    return (dataset as unknown) as Array<Dictionary<string | number>>;
  }
  const columnToBeFlatValues = uniqBy(dataset, CATEGORY).map(
    (d) => d[CATEGORY]
  );
  const datasetGroupedByDate = groupBy(dataset, DATE);
  const datasetGroupedByMainCategoryFlat = Object.entries(
    datasetGroupedByDate
  ).map(([date, datasetForDate]) => {
    const categoriesObject = columnToBeFlatValues.reduce((acc, value) => {
      const datum = datasetForDate.find(
        (d) => d[DATE] === date && d[CATEGORY] === value
      );
      acc[value] = datum?.[VALUE];
      return acc;
    }, {} as Dictionary<string | number | undefined>);
    return {
      [DATE]: date,
      ...categoriesObject
    };
  });
  return datasetGroupedByMainCategoryFlat as Array<Dictionary<string | number>>;
}

正如你所看到的,这些函数是Datum类型特有的。有没有办法修改它们,使它们适用于至少有三个字段date, category, value的泛型类型T
我的意思是,我想有这样的东西:

interface StackedStartEnd {
  start: number
  end: number
}

function getStackedSeries<T>(dataset: T[]): T extends StackedStartEnd

显然,这段代码应该被重构以使其更通用:

{
  [DATE]: ...,
  [CATEGORY]: ...,
  [VALUE]: ...,
  start: ...,
  end: ...,
}

Here工作代码。
我不是一个TypeScriptMaven,所以我需要一些帮助。老实说,我试图做的是修改函数签名,但我失败了,无论如何,我想使函数尽可能通用,我不知道如何开始。我需要传递给函数也使用列名?
非常感谢您的光临

jrcvhitl

jrcvhitl1#

我尝试了一个更通用的方法,因为你建议混合两个函数。默认情况下,似乎你的getStackedSeries函数不需要知道datecategory属性,你可以使用泛型类型来确保只有value属性,因为我们需要知道它来计算startend值。
完整的实现可通过here on codesandbox查看。

export function getStackedSeries<T extends Datum>(
  data: T[],
  groupByProperty: PropertyType<T>
) {
  const groupedData = groupBy(data, (d) => d[groupByProperty]);

  const acumulatedData = Object.entries(groupedData).flatMap(
    ([_, groupedValue]) => {
      let acumulator = 0;

      return groupedValue.map(({ value, ...rest }) => {
        const obj = {
          ...rest,
          value: value,
          start: acumulator,
          end: acumulator + value
        };

        acumulator += value;

        return obj;
      });
    }
  );

  return acumulatedData;
}

getStackedSeries()现在接收扩展Datum类型的data属性,该属性为:

export interface Datum {
  value: number;
}

有了这个属性和第二个属性groupByProperty,我们就可以定义groupBy子句并返回所有由flatMap表示的flatten。
您可能注意到返回类型现在是由typescript通过使用泛型<T>动态定义的。

const dataGroupedByDate: (Omit<{
    date: string;
    category: string;
    value: number;
}, "value"> & {
    value: number;
    start: number;
    end: number;
})[]

您也可以键入函数的这一部分,但让编译器为您工作并根据输入自动生成类型是有意义的。

xwbd5t1u

xwbd5t1u2#

您可以按开始/结束日期进行分组,然后按类别对结果集进行另一次分组。
第一个

相关问题