Создайте общую функцию, которая создает набор данных с накоплением, используя d3

У меня есть этот набор данных:

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 }
]

Итак, те же данные, но со свойствами start и end, вычисленными 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. Есть ли способ изменить их, чтобы они работали для универсального типа T, который имеет как минимум три поля date, category, value?

Я имею в виду, что я хотел бы иметь что-то вроде этого:

interface StackedStartEnd {
  start: number
  end: number
}

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

Очевидно, что этот фрагмент кода следует реорганизовать, чтобы сделать его более универсальным:

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

Вот рабочий код.

Я не эксперт по TypeScript, поэтому мне нужна помощь. Честно говоря, я пытался изменить сигнатуру функции, но мне это не удалось, и в любом случае я хотел бы сделать функции как можно более общими, и я не знаю, с чего начать. Нужно ли передавать функциям имена используемых столбцов?

Большое спасибо

Как использовать API парсинга квитанций с помощью JavaScript за 5 минут?
Как использовать API парсинга квитанций с помощью JavaScript за 5 минут?
В этом руководстве вы узнаете, как использовать API парсинга квитанций за 5 минут с помощью JavaScript. Eden AI предоставляет простой и удобный для...
Хук useOnClickOutside в ReactJS
Хук useOnClickOutside в ReactJS
Как разработчик ReactJS, вы, возможно, сталкивались с ситуацией, когда вам нужно закрыть модальное или выпадающее меню, когда кто-то щелкает за его...
Хуки (часть-2) - useEffect
Хуки (часть-2) - useEffect
Хук useEffect - один из самых мощных и универсальных инструментов в арсенале разработчика React. Он позволяет вам управлять побочными эффектами в...
Простое руководство по тестированию взаимодействия с пользователем с помощью библиотеки тестирования React
Простое руководство по тестированию взаимодействия с пользователем с помощью библиотеки тестирования React
В предыдущем посте я показал вам на примерах, как писать базовые тесты в React. Важнейшей частью пользовательского интерфейса приложений является...
Как конвертировать HTML в PDF с помощью jsPDF
Как конвертировать HTML в PDF с помощью jsPDF
В этой статье мы рассмотрим, как конвертировать HTML в PDF с помощью jsPDF. Здесь мы узнаем, как конвертировать HTML в PDF с помощью javascript.
Создайте титры как в звездных войнах с помощью CSS и Javascript
Создайте титры как в звездных войнах с помощью CSS и Javascript
Если вы веб-разработчик (или хотите им стать), то вы наверняка гик и вам нравятся "Звездные войны". А как бы вы хотели, чтобы фоном для вашего...
1
0
81
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я попытался сделать более общий подход, поскольку вы предлагаете смешивать две функции. По умолчанию кажется, что вашей функции getStackedSeries не нужно знать о свойствах date и category, вы можете использовать универсальный тип, чтобы обеспечить только свойство value, так как нам нужно знать это для вычисления значений start и end.

Полную реализацию можно посмотреть здесь на codeandbox.

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() теперь получает свойство data, которое расширяет тип Datum, а именно:

export interface Datum {
  value: number;
}

С помощью этого и второго свойства с именем groupByProperty мы можем определить предложение groupBy и вернуть все сглаживание по flatMap.

Вы, наверное, заметили, что тип возвращаемого значения теперь определяется машинописным текстом динамически с использованием универсального <T>. Например:

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

Вы также можете ввести эту часть функции, но имеет смысл позволить компилятору работать за вас и автоматически генерировать типы на основе ввода.

Вы можете сгруппировать по дате начала/окончания и выбрать другую группировку по категориям для набора результатов.

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 }],
    result = Object
        .values(dataset
            .reduce((r, { date, category, value }) => {
                const
                    start = r.date[date]?.at(-1).end ?? 0,
                    end = start + value,
                    object = { date, category, value, start, end };

                (r.date[date] ??= []).push(object);
                (r.category[category] ??= []).push(object);
                return r;
            }, { date: {}, category: {} })
            .category
        )
        .flat();

console.info(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Другие вопросы по теме