• 原创


    react + TS + d3.js 实现曲线图 报错问题解决

    1. 结构

    1.1 整体结构

    // lemon
    import React, { useRef, useState, useEffect, useReducer } from "react";
    import * as d3 from "d3";
    interface Data {}
    const SimpleLine: React.FC = () => {
      const ref = useRef<SVGSVGElement>(null);
      useEffect(() => {});
      return (
        <>
          <svg ref={ref}></svg>
        </>
      );
    };
    export { SimpleLine };
    

    1.2 SimpleLine函数组件

    在SimpleLine组件中的svgDOM结构中进行SVG图表的绘制。由于绘制SVG图表中会产生副作用(直接操作DOM结构),所以这部分操作将在Effect Hook中执行。

    我们想使用d3.js在svg标签中绘制数据图表,所以需要先获取到组件渲染后的真实DOM。这里使用ref来访问。

    在TS环境下使用useRef()时,需要使用泛型声明需要保存的DOM类型。这里想要保存svg,对应的泛型为SVGSVGElement。可能不完全正确。

    1.3 interface Data{}

    这个接口用于声明在d3.js中需要操作的数据元(datum),可视化的数据集由许许多多的datum组成。

    使用d3.js可视化的操作中,我们往往使用的是datum中的某个数据,使用Data来标识datum的类型(属性)可以避免误操作、报错、无法获取属性。

    2. 代码

    2.1 完整代码

    import React, { useRef, useEffect, useState } from "react";
    import * as d3 from "d3";
    interface IData {
      date: Date;
      value: number;
    }
    const SimpleLine: React.FC = () => {
      const ref = useRef<SVGSVGElement>(null);
      const [width, setWidth] = useState(1600);
      const [height, setHeight] = useState(800);
      const [margin, setMargin] = useState({
        top: 160,
        right: 80,
        bottom: 160,
        left: 80,
      });
      const innerWidth = width - margin.left - margin.right;
      const innerHeight = height - margin.top - margin.bottom;
      useEffect(() => {
        const svgSelection = d3.select(ref.current);
        var data: IData[] = [
          { date: new Date(2007, 3, 24), value: 93.24 },
          { date: new Date(2007, 3, 25), value: 95.35 },
          { date: new Date(2007, 3, 26), value: 98.84 },
          { date: new Date(2007, 3, 27), value: 99.92 },
          { date: new Date(2007, 3, 30), value: 99.8 },
          { date: new Date(2007, 4, 1), value: 99.47 },
        ];
        data.forEach((d) => {
          console.log(d.date);
        });
        const xValue = (d: IData): Date => d.date;
        const yValue = (d: IData): number => d.value;
        let xScale: d3.ScaleTime<number, number>,
          yScale: d3.ScaleLinear<number, number>;
        let datesKeys: Date[];
        const g = svgSelection
          .append("g")
          .attr("id", "maingroup")
          .attr("transform", `translate(${margin.left}, ${margin.top})`);
        const init = (data: IData[]) => {
          svgSelection.attr("width", width).attr("height", height);
          let minX = d3.min(data, xValue) || new Date();
          let maxX = d3.max(data, xValue) || new Date();
          let minY = d3.min(data, yValue) || 0;
          let maxY = d3.max(data, yValue) || 0;
          xScale = d3
            .scaleTime()
            .domain([minX, maxX])
            .range([0, innerWidth])
            .nice();
          yScale = d3
            .scaleLinear()
            .domain([maxY, minY])
            .range([0, innerHeight])
            .nice();
          datesKeys = Array.from(new Set(data.map((d) => d.date)));
          // Adding axes
          const xAxis = d3
            .axisBottom(xScale)
            .tickValues(Array.from(new Set(data.map((e) => e.date))))
            .tickSize(-innerHeight);
          const yAxis = d3.axisLeft(yScale).tickSize(-innerWidth);
          const xAxisGroup = g
            .append("g")
            .call(xAxis)
            .attr("transform", `translate(0, ${innerHeight})`);
          const yAxisGroup = g.append("g").call(yAxis);
          g.selectAll(".tick text").attr("font-size", "1em");
          g.append("path").attr("id", "alterPath");
        };
        const renderUpdate = (data: IData[]) => {
          const line = d3
            .line<IData>()
            .x((d: IData) => {
              return xScale(xValue(d)) || 0;
            })
            .y((d: IData) => {
              return yScale(yValue(d)) || 0;
            })
            .curve(d3.curveCardinal.tension(0.5));
          // lineEmpty is typically used for the first animation that raise the line up;
          const lineEmpty = d3
            .line<IData>()
            .x((d: IData) => {
              return xScale(xValue(d)) || 0;
            })
            .y((d: IData) => {
              return yScale(d3.min(data, yValue) || 0) || 0;
            })
            .curve(d3.curveCardinal.tension(0.5));
          // .curve(d3.curveCardinal.tension(0.5));
          const maingroup = d3.select("#maingroup");
          // maingroup
          //   .append("path")
          //   .attr("d", line(data) || "")
          //   .attr("stroke", "black")
          //   .attr("fill", "none");
          const pathUpdate = maingroup.selectAll(".datacurve").data(data);
          const pathEnter = pathUpdate
            .enter()
            .append("path")
            .attr("class", "datacurve")
            .attr("fill", "none")
            .attr("stroke", "steelblue")
            .attr("stroke-width", 2.5)
            .attr("d", line(data) || "");
          pathUpdate
            .merge(d3.selectAll(".datacurve"))
            .transition()
            .duration(2000)
            .ease(d3.easeLinear)
            .attr("d", lineEmpty(data) || "");
          console.log(line(data));
          console.log(lineEmpty(data));
        };
        init(data);
        renderUpdate(data);
      });
      return (
        <>
          <svg ref={ref}></svg>
        </>
      );
    };
    export { SimpleLine };
    

    2.2 报错解决

    2.2.1 domain()
    let yScale = d3.scaleLinear().domain([d3. min(data, yValue), d3.max(data, yValue)]).range([0, innerHeight]).nice();
    

    报错
    Type 'number | undefined' is not assignable to type 'NumberValue'.
    Type 'undefined' is not assignable to type 'NumberValue'.ts(2322)

    说参数有可能是undefined,不合理。

    let minY = d3.min(data, yValue) || 0;
    let maxY = d3.max(data, yValue) || 0;
    
    let yScale = d3.scaleLinear().domain([minY, maxY)]).range([0, innerHeight]).nice();
    
    2.2.2 line()
    
     const line = d3
            .line()
            .x((d: IData) => {
              return xScale(xValue(d)) || 0;
            })
            .y((d: IData) => {
              return yScale(yValue(d)) || 0;
            })
            .curve(d3.curveCardinal.tension(0.5));
    
     const pathEnter = pathUpdate
            .enter()
            .append("path")
            .attr("class", "datacurve")
            .attr("fill", "none")
            .attr("stroke", "steelblue")
            .attr("stroke-width", 2.5)
            .attr("d", line(data) || "");
    
    

    报错

    1. x(d: IData)处:
      Type '[number, number]' is not assignable to type 'IData'
    2. attr("d", line(data) || ""):
      Argument of type 'IData[]' is not assignable to parameter of type '[number, number][]'

    都是说类型不匹配

    const line = d3
            .line<IData>()
            .x((d: IData) => {
              return xScale(xValue(d)) || 0;
            })
            .y((d: IData) => {
              return yScale(yValue(d)) || 0;
            })
            .curve(d3.curveCardinal.tension(0.5));
    
    
     const pathEnter = pathUpdate
            .enter()
            .append("path")
            .attr("class", "datacurve")
            .attr("fill", "none")
            .attr("stroke", "steelblue")
            .attr("stroke-width", 2.5)
            .attr("d", line(data) || "");
    

    d3.line()用于生成一个line生成器,由于生成器不知道它将来会调用什么样的datum来生成line,所以使用泛型在编译阶段来限制类型。

    2.2.3 selection.merge()

    const pathUpdate = maingroup.selectAll(".datacurve").data(data);
    
    const pathEnter = pathUpdate
            .enter()
            .append("path")
            .attr("class", "datacurve")
            .attr("fill", "none")
            .attr("stroke", "steelblue")
            .attr("stroke-width", 2.5)
            .attr("d", line(data) || "");
    
    pathUpdate
            .merge(pathEnter)
            .transition()
            .duration(2000)
            .ease(d3.easeLinear)
            .attr("d", lineEmpty(data) || "");
    

    报错
    pathEnter和pathUpdate泛型不相同,无法合并
    pathEnter: d3.Selection<SVGPathElement, IData, d3.BaseType, unknown>
    pathUpdate: d3.Selection<d3.BaseType, IData, d3.BaseType, unknown>

    pathUpdate
            .merge(d3.selectAll(".datacurve"))
            .transition()
            .duration(2000)
            .ease(d3.easeLinear)
            .attr("d", lineEmpty(data) || "");
    

    这里我们使用selectAll来获得泛型相同的DOM

    结束

    拥抱TS是一个漫长且疼苦的过程。使用TS来操作第三方库时,单单熟悉API是不够的,还要了解设计的思想(声明的泛型和类型)避免陷入类型陷阱。

    整个DBUG的过程足足有一下午,尝试过查看官方文档、接口声明,最终将问题定位到泛型上。

    原文地址:https://www.cnblogs.com/xiaoxu-xmy/p/13762730.html
    GitHub: https://github.com/lemon-Xu/Learning-d3.-Js
    作者: lemon

  • 相关阅读:
    Express框架学习总结
    Node.js学习心得
    清理svn.bat
    mysql sql语句大全
    SQL语句优化原则
    mysql数据库忘记密码时如何修改
    MySQL索引基础
    SQL语句优化技术分析
    mysql索引的类型和优缺点
    谷歌浏览器查询缓存视频图片
  • 原文地址:https://www.cnblogs.com/xiaoxu-xmy/p/13762730.html
Copyright © 2020-2023  润新知