chartjs 表格刷新

实现功能, 点击排序按钮,按照表格的高度依次排序

 

 再次点击按钮,按照底部的label(01A, 02B...排序)

 

主要难点,修改完数据后,页面的chart不刷新

解决方案 React.useRef和chart的ref绑定,然后调用chart.update方法

代码如下

import * as React from "react";
import {Chart} from "react-chartjs-2";
import {
  Chart as ChartJS,
  Title,
  Tooltip,
  Legend,
  CategoryScale,
  BarController,
  BarElement,
  ActiveElement,
  ChartData,
} from "chart.js";
import {ILogger} from "../logging/ILogger";
import {from} from "linq-to-typescript";
import {IChartDataList} from "./IChartDataList";
import {ITranslator} from "../translations/ITranslator";
import {ILinkPosition} from "./common";
import {ILink} from "./ILink";
import {ItemWithLoading} from "./ItemWithLoading";

// Register the elements to display ChartJs. Enables tree-shaking reducing bundle size.
// See https://www.chartjs.org/docs/latest/getting-started/integration.html#bundlers-webpack-rollup-etc
ChartJS.register(Title, Tooltip, Legend, CategoryScale, BarController, BarElement);

export interface ISum {
  subscript: number;
  totalReject: number[];
  label: string;
  sumOfTotalReject?: number;
}

export interface IBarChartProps {
  logger: ILogger;
  translator: ITranslator;
  /** The state of showing loading icon while sending request */
  showLoading: boolean;
  /** get link box position */
  handleLinkPosition: (linksPosition: ILinkPosition) => void;
  /** get link words of link box */
  getLinksList: (linksList: ILink[]) => void;
  /** height of chart */
  chartHeight: number;
  /** current bar data */
  itemData: IChartDataList;
  /** sort data by label or total reject, if true, sort by total reject */
  sortByReject: boolean;
}

export const BarChart: React.FC<IBarChartProps> = (props: IBarChartProps) => {
  const chartRef = React.useRef<ChartJS>(null);

  const comparer = (x: number, y: number) => y - x;
  //这个库的方法来排序linq-to-typescript
  const sortByRejectOrLabel = (list: any[]) => {
    if (props.sortByReject) {
      // sort data by sum of total reject (按照高度排序)
      list = from(list)
        .orderBy<number>((x) => x.sumOfTotalReject as number, comparer)
        .toArray();
    } else {
      // sort data by label(按照label排序(字母顺序))
      list = from(list)
        .orderBy<string>((x) => x.label)
        .toArray();
    }
    props.logger.debug("sorted list is", list);
    return list;
  };

  /**这里是最复杂的逻辑
   * @param chartData 当前chart
   * @returns 排好序的新的表格数据
   */
  const handleSortByReject = (chart) => {
    if (
      chart.data.labels !== undefined &&
      chart.data.labels.length !== 0 &&
      chart.data.datasets !== undefined &&
      chart.data.datasets.length !== 0
    ) {
      let labelAndRejectArr: {label: string; sumOfTotalReject: number}[] = [];
      //  表格只有一个颜色的数据
      if (chart.data.datasets.length === 1) {
        chart.data.datasets[0].data.forEach((e, i) => {
          const label = chart.data.labels ? (chart.data.labels[i] as string) : "";
          const totalRejectValue = e === null ? 0 : (e as number);
          labelAndRejectArr.push({sumOfTotalReject: totalRejectValue, label});
        });

        labelAndRejectArr = sortByRejectOrLabel(labelAndRejectArr);

        const labels: string[] = [];
        const totalRejects: number[] = [];
        labelAndRejectArr.forEach((e) => {
          labels.push(e.label);
          totalRejects.push(e.sumOfTotalReject);
        });

        // re-set label
        chart.data.labels = labels;
        props.logger.debug("new labels is", chart.data.labels);

        // re-set data
        chart.data.datasets[0].data = totalRejects;
        props.logger.debug("new data is", chart.data.datasets);
      } else {
        //表格有多个颜色的数据
        handleSortByRejectForMultipleArray(chart);
      }
    }

    return chart;
  };

  /**
   * 数据整合
   * * eg:
   * 两个数组:
   * ```
   * [11, null, null]
   * [null, 2, 8]
   * ```
   * 变成下面这样:
   *
   * ```
   * [
   * {subscript: 0, totalReject: [11, 0], label: "1A"},
   * {subscript: 1, totalReject: [0, 2], label: "2A"},
   * {subscript: 2, totalReject: [0, 8], label: "4D"},
   * ]
   *
   * ```
   */
  const integrateTotalReject = (chart: {data: {labels?: string[]; datasets?: any}}) => {
    const {datasets} = chart.data;
    const newArr: ISum[] = [];
    datasets.forEach((e) => {
      if (e.data.length !== 0) {
        e.data.forEach((j, k) => {
          const totalReject = j === null ? 0 : (j as number);
          let label = "";
          if (chart.data.labels) {
            label = chart.data.labels[k];
          }

          // new item include subscript and totalReject
          const values: ISum = {
            subscript: k,
            totalReject: [totalReject],
            label,
          };
          const sameSubscript = newArr.findIndex((c) => c.subscript === k);
          if (sameSubscript !== -1) {
            newArr[sameSubscript].totalReject.push(totalReject);
          } else {
            newArr.push(values);
          }
        });
      }
    });
    props.logger.debug("integrate totalReject list is", newArr);
    return newArr;
  };

  /** 求和
   * ```
   * [
   * {subscript: 0, totalReject: [11, 0], label: "1A"},
   * {subscript: 1, totalReject: [0, 2], label: "2A"},
   * {subscript: 2, totalReject: [0, 8], label: "4D"},
   * ]
   * ```
   * 变成
   * ```
   * [
   * {subscript: 0, totalReject: [11, 0], label: "1A", sumOfTotalReject: 11},
   * {subscript: 1, totalReject: [0, 2], label: "2A", sumOfTotalReject: 2},
   * {subscript: 2, totalReject: [0, 8], label: "4D", sumOfTotalReject: 8},
   * ]
   * ```
   */
  const sumTotalReject = (newArr: ISum[]): ISum[] => {
    return newArr.map((e) => {
      const sum = e.totalReject.reduce((total, num) => total + num);
      return {...e, sumOfTotalReject: sum};
    });
  };

  /**
   * 分开数据
   * * eg:
   * 下面是原数组:
   *
   * ```
   * [
   * {subscript: 0, totalReject: [11, 0], label: "1A", sumOfTotalReject: 11},
   * {subscript: 1, totalReject: [0, 2], label: "2A", sumOfTotalReject: 2},
   * {subscript: 2, totalReject: [0, 8], label: "4D", sumOfTotalReject: 8},
   * ]
   *
   * ```
   * 变成下面这样:
   * ```
   * [11, null, null]
   * [null, 2, 8]
   * ```
   */
  const partTotalReject = (sumArrWithLabels: ISum[]) => {
    const arr: {subscript: number; data: (null | number)[]}[] = [];
    sumArrWithLabels.forEach((e) => {
      e.totalReject.forEach((j, k) => {
        const data = j === 0 ? null : j;
        const values = {
          subscript: k,
          data: [data],
        };
        const sameSubscript = arr.findIndex((c) => c.subscript === k);
        if (sameSubscript !== -1) {
          arr[sameSubscript].data.push(data);
        } else {
          arr.push(values);
        }
      });
    });
    props.logger.debug("part totalReject list is", arr);
    return arr;
  };

  /**
   * @param chart 当前chart
   */
  const handleSortByRejectForMultipleArray = (chart) => {
    const newData = integrateTotalReject(chart);
    let sumArrWithLabels = sumTotalReject(newData);
    sumArrWithLabels = sortByRejectOrLabel(sumArrWithLabels);

    const labels: string[] = [];
    sumArrWithLabels.forEach((e) => {
      labels.push(e.label);
    });
    // re-set label
    chart.data.labels = labels;
    props.logger.debug("new labels is", chart.data.labels);

    const arr = partTotalReject(sumArrWithLabels);
    // re-set data
    chart.data.datasets.forEach((e, i) => {
      e.data = arr[i].data;
    });
    props.logger.debug("new data is", chart.data.datasets);
  };

  /**
   * @returns options property or bar chart
   */
  const getOptionBarChart = (titleKey?: string) => {
    return {
      maintainAspectRatio: false,
      scales: {
        x: {
          stacked: true,
        },
        y: {
          stacked: true,
        },
      },
      plugins: {
        title: {
          display: true,
          text: props.translator.translate(titleKey as string),
        },
      },
    };
  };

  /**
   * 官方方法
   * 1. [update.html](https://www.chartjs.org/docs/latest/developers/updates.html)
   * 2. [chartRef-demo](https://react-chartjs-2.netlify.app/examples/chart-ref/)
   */
  const updateChartData = (chart) => {
    if (chart.data !== null && chart.data !== undefined) {
      chart = handleSortByReject(chart);
      props.logger.debug("new chart is", chart);
      chart.update();
    }
  };

  React.useEffect(() => {
    const chart = chartRef.current;
    if (chart !== null) {
      updateChartData(chart);
    }
  }, [props.sortByReject, props.itemData.chartData.labels, props.showLoading, props.chartHeight]);

  return (
    <>
      {/** 发送请求时,显示loading, 获取到请求结果后,显示ele中的元素*/}
      <ItemWithLoading
        height={props.chartHeight}
        showLoading={props.showLoading}
        ele={
          props.itemData.chartData !== undefined &&
          props.itemData.chartData.labels &&
          props.itemData.chartData.labels.length !== 0 &&
          props.itemData.chartData.datasets.length !== 0 && (
            /** 表格,可以参考官网
             * [chartRef-demo](https://react-chartjs-2.netlify.app/examples/chart-ref/)
             */
            <Chart
              type="bar"
              ref={chartRef}
              height={props.chartHeight}
              data={props.itemData.chartData}
              options={getOptionBarChart(props.itemData.titleKey)}
            />
          )
        }
      />
    </>
  );
};

这是ItemWithLoading

import * as React from "react";
import {AppContext} from "../app/IAppContext";
import {Loading} from "../app/Loading";
import "../public/style/datalight/ItemWithLoading.scss";

export interface IItemWithLoadingProps {
  height: number;
  /** if true, show loading icon */
  showLoading: boolean;
  ele?: JSX.Element | boolean;
}

/** render no data */
export const ItemWithLoading: React.FC<IItemWithLoadingProps> = (props: IItemWithLoadingProps) => (
  // before render element, show loading
  <AppContext.Consumer>
    {(ctx) =>
      props.showLoading ? (
        <Loading />
      ) : props.ele ? (
        props.ele
      ) : (
        //  if ele not available, show 'no data'
        <div className="tes-datalight-noData" style={{height: props.height}}>
          {ctx.translator.translate("tes_fe_gallery_filter_nodata")}
        </div>
      )
    }
  </AppContext.Consumer>
);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值