d3.js--LineChart

1,309 阅读5分钟

d3.js

d3.js是一个Javascript库,全称为Data-Driven Documents,重点是数据驱动 ,结合SVG做数据可视化,且相较echarts/charts等更为灵活,可定制。

d3.js官网

Following Fullstack Data Visualization with D3 (Amelia Wattenberger)

绘折线图

工具:Vscode(插件live server)

准备工作

html

  • 图表的外层div

  • d3.js引入(可官网下载、直接网址引入、按需引入)

  • js文件引入

    <!--html可直接在Vscode中右击选择“Open with Live Server”在浏览器中打开-->
    
    <div id="wrapper"></div>
    
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script src="./lineChart.js"></script>
    

lineChart.js

  • 数据可视化必不可少的就是数据啦,书写逻辑前,准备好需要可视化的数据。

  • 数据文件可以是.json、.csv、.xml等格式,d3有相应的方法去解析它们d3.json()、d3.csv()、d3.xml()等。

    async function drawLineChart(){
        //...
    const dataset = await d3.json("./../../weather_data.json")
        //...
    }
    drawLineChart()
    
  • 书中所用数据为json文件,整体为一个数组,里面每个对象元素格式如下:

    {"time": 1514782800, "summary": "Clear throughout the day.", "icon": "clear-day", "sunriseTime": 1514809280, "sunsetTime": 1514842810, "moonPhase": 0.48, "precipIntensity": 0, "precipIntensityMax": 0, "precipProbability": 0, "temperatureHigh": 18.39, "temperatureHighTime": 1514836800, "temperatureLow": 12.23, "temperatureLowTime": 1514894400, "apparentTemperatureHigh": 17.29, "apparentTemperatureHighTime": 1514844000, "apparentTemperatureLow": 4.51, "apparentTemperatureLowTime": 1514887200, "dewPoint": -1.67, "humidity": 0.54, "pressure": 1028.26, "windSpeed": 4.16, "windGust": 13.98, "windGustTime": 1514829600, "windBearing": 309, "cloudCover": 0.02, "uvIndex": 2, "uvIndexTime": 1514822400, "visibility": 10, "temperatureMin": 6.17, "temperatureMinTime": 1514808000, "temperatureMax": 18.39, "temperatureMaxTime": 1514836800, "apparentTemperatureMin": -2.19, "apparentTemperatureMinTime": 1514808000, "apparentTemperatureMax": 17.29, "apparentTemperatureMaxTime": 1514844000, "date": "2018-01-01"}
    

画图(每天最高温度的折线图)

y轴--左侧--温度值

x轴--底部--时间

  1. 从数据文件中取所需值

    const yAccessor = d => d.temperatureMax
    // const xAccessor = d => d.date 由于d.date值为字符串,如"2018-01-01",需要转换成日期格式
    const dateParser = d3.timeParse("%Y-%m-%d")
    const xAccessor = d => dateParser(d.date)
    

    d3.timeParse() 类似new Date()

    • %Y - year with century as a decimal number, such as 1999
    • %m - month as a decimal number [01,12]
    • %d - zero-padded day of the month as a decimal number [01,31]
  2. 画图

    两层:

    • wrapper中(svg视口)包含坐标轴、标签、数据等所需展示的所有内容
    • bounds中(g)是可视化的数据
  • 仿照css定义值,用作宽/高/边距
    let dimensions = {
        width: window.innerWidth * 0.9, //wrapper
        height: 500,
        margin:{                       //bounds
            top: 100,
            right: 50,
            bottom: 100,
            left: 100
        }
    };
    dimensions.boundedWidth = dimensions.width - dimensions.margin.left - dimensions.margin.right;
    dimensions.boundedHeight = dimensions.height - dimensions.margin.top - dimensions.margin.bottom;
    

  • 创建画图区(svg视口)

    • 在wrapper下添加子元素svg

      • d3.select()选择元素,()里是css选择器(#id、.class、...)。
      • 使用d3.select("#wrapper")选中最外层div,添加子元素.append("svg")。
      • svg默认是300px * 150px ,因此需要按需设置svg元素的属性width/height,d3中可链式简洁表示如下:
      const wrapper = d3.select("#wrapper")
          .append("svg")
              .attr("width",dimensions.width)
              .attr("height",dimensions.height)
      
    • 在svg下添加g元素,包裹数据视图,并使元素在合适的位置(可使用transform属性)

      const bounds = wrapper.append("g")
          .style("transform",`translate(${dimensions.margin.left}px,${dimensions.margin.top}px)`)
      
    • 创建比例尺映射(原数据——>坐标轴)(domain->range)

      • 由于数据可能在大小上差异过大,直接反映在坐标轴上可能会可视化效果较差,因此对数据进行一个比例映射,映射到坐标轴相应像素(px)位置:

      对于 continuous(连续的) 定量数据,通常会使用 linear scale(线性比例尺)。 对于时间序列则使用 time scale(时间比例尺).

      连续比例尺可以将连续的、定量的输入 domain [min,max] 映射到连续的输出 range [min,max]。

      svg以页面的左上角为(0,0)坐标点,坐标以像素为单位,x轴正方向是向右,y轴正方向是向下。

      const yScale = d3.scaleLinear()
          .domain(d3.extent(dataset, yAccessor)).range([dimensions.boundedHeight,0])  //yAccessor取dataset中所需的所有温度值 d3.extent()返回数组中的最小值和最大值 由于svg-y坐标轴正方向向下,所以映射出range是从大到小的范围
      const xScale = d3.scaleTime().domain(dataset,xAccessor).range([0,dimensions.boundedWidth])
      
    • 画折线

      SVG中所有基本形状都是path的简写形式,但是建议使用简写形式,使SVG文档更可为语义化。path元素更通用,可以通过制定一系列相互连接的线、弧、曲线来绘制任意形状的轮廓,这些轮廓也可以填充或者绘制轮廓线,也可以用来定义裁剪区域或蒙版。

      path元素有d属性,决定绘制的形状。d含有一些命令,大写表示绝对坐标位置,小写表示相对坐标位置。 M移动到指定点; L画线到给定坐标; Z连线到起始点;......

      例如: bounds.append("path").attr("d","M 100 100 L 100 200 L 150 150 L 150 50 Z")

      绘图如下(SVG默认填充黑色):

      • 数据点字符串生成(数据点——>映射坐标数据——>string)

      xAccessor/yAccessor取数据并转换为所需格式;xScale/yScale(比例尺)将数据映射到坐标轴;d3.line()可将数据点转换为d字符串

        ```
        const lineGenerator = d3.line()
                            .x(d => xScale(xAccessor(d)))
                            .y(d => yScale(yAccessor(d)))
        const line = bounds.append("path")
                    .attr("d",lineGenerator(dataset))
                    .attr("fill", "none")               //svg默认填充黑色,所需为连线不需填充
                    .attr("stroke","purple")            //线色
                    .attr("stroke-width", 2)            //不设线宽,会无显示
        ```
      
    • 画坐标轴

      d3中有d3-axis模块,可根据给定的比例尺生成坐标轴。四个方向都可以生成:axisTop axisRight axisBottom axisLeft。根据需要,需要生成左y、下x轴,则:

      const yAxisGenerator = d3.axisLeft().scale(yScale);
      const xAxisGenerator = d3.axisBottom().scale(xScale);
      

      需要注意的是:生成坐标轴时,会同时创建很多元素,需要使用g元素包裹

      //const yAxis = bounds.append("g");
      //yAxisGenerator(yAxis); //在g元素内画y轴
      const yAxis = bounds.append("g").call(yAxisGenerator); //调用call(),写成链式; xxx.call(xx)以xxx作为第一个参数执行xx函数
      const xAxis = bounds.append("g").call(xAxisGenerator)
                  .style("transform",`translateY(${dimensions.boundedHeight}px)`);    //生成x轴时,默认在最上方,需要挪到所需底部位置