为什么我不再使用 D3.js

栏目: 编程语言 · 发布时间: 7年前

内容简介:这周读到关于 D3.js 的D3 在 2011 发布的时候,可谓是一项极大的创新。那时候还是 jQuery 和 Backbone 的天下,浏览器也只是实现了一些简单的 css 标准如 “transitions” 等,像现在更为复杂的 ”flex” 布局还遥遥无期。D3 通过数据驱动来绘制图形的方式解决了这方面的难题,迅速笼络人心。

为什么我不再使用 D3.js

这周读到关于 D3.js 的 一篇文章 ,想起 D3.js 当年也是叱咤风云,而现在感觉已经消声灭迹很久了。以下就是我对它的理解与翻译。

D3 在 2011 发布的时候,可谓是一项极大的创新。那时候还是 jQuery 和 Backbone 的天下,浏览器也只是实现了一些简单的 css 标准如 “transitions” 等,像现在更为复杂的 ”flex” 布局还遥遥无期。D3 通过数据驱动来绘制图形的方式解决了这方面的难题,迅速笼络人心。

然而时代终究还是变了。现在的框架采用了虚拟 DOM 等更为灵活和强大的设计理念,CSS 在布局和动画方面也游刃有余。

让我再多给你举几个例子。

D3 的学习曲线

过去几年我一直在使用 D3,并用它绘制了各种各样的图形曲线。然而一个问题就是,虽然我理解关于 D3 的基本概念,但我还是难以做到轻车熟路,我身边的同事跟我也是同样的感受。和大多数人一样,许多时候,我们都是从网上找到一个示例,然后将它修改为实际工程中所需要的。

如果我们想要添加一些新奇的功能,就需要不停的搜索,不停的尝试,不停的修改,直到它看起来是我们想要的了。

听起来是不是很熟悉?现在的开发者们已经非常熟悉虚拟 DOM 和模板编程。掌握这么一个设计理念与思考方式与现在其他库大相径庭的技巧,看起来并没什么卵用。

绘制曲线,它实际上并不难!

如果让你自己从头写一个图表的话,你大概会感到不安和紧张,就好像自己要面临一个非常复杂的问题,但当你开始写的时候一切都变得简单起来。我们先来看一下如何使用 D3 画一副折线图:

// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%d-%b-%y");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the line
var valueline = d3.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform",
          "translate(" + margin.left + "," + margin.top + ")");
// Get the data
d3.csv("data.csv", function(error, data) {
  if (error) throw error;
// format the data
  data.forEach(function(d) {
      d.date = parseTime(d.date);
      d.close = +d.close;
  });
// Scale the range of the data
  x.domain(d3.extent(data, function(d) { return d.date; }));
  y.domain([0, d3.max(data, function(d) { return d.close; })]);
// Add the valueline path.
  svg.append("path")
      .data([data])
      .attr("class", "line")
      .attr("d", valueline);
// Add the X Axis
  svg.append("g")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x));
// Add the Y Axis
  svg.append("g")
      .call(d3.axisLeft(y));
});

我们再来看一下在 Preact 中如何绘制:

/* @jsx h */
let { Component, h, render } = preact
function getTicks (count, max) {
    return [...Array(count).keys()].map(d => {
        return max / (count - 1) * parseInt(d);
    });
}
class LineChart extends Component {
    render ({ data }) {
        let WIDTH = 500;
        let HEIGHT = 300;
        let TICK_COUNT = 5;
        let MAX_X = Math.max(...data.map(d => d.x));
        let MAX_Y = Math.max(...data.map(d => d.y));
        
        let x = val => val / MAX_X * WIDTH;
        let y = val => HEIGHT - val / MAX_Y * HEIGHT;
        let x_ticks = getTicks(TICK_COUNT, MAX_X);
        let y_ticks = getTicks(TICK_COUNT, MAX_Y).reverse();
                
        let d = `
          M${x(data[0].x)} ${y(data[0].y)} 
          ${data.slice(1).map(d => {
              return `L${x(d.x)} ${y(d.y)}`;
          }).join(' ')}
        `;
    
        return (
            <div 
                class="LineChart" 
                style={{
                    width: WIDTH + 'px',
                    height: HEIGHT + 'px'
                }}
            >
                <svg width={WIDTH} height={HEIGHT}>
                    <path d={d} />
                </svg>
                <div class="x-axis">
                    {x_ticks.map(v => <div data-value={v}/>)}
                </div>
                <div class="y-axis">
                    {y_ticks.map(v => <div data-value={v}/>)}
                </div>
            </div>
        );
    }
}
let data = [
    {x: 0, y: 10}, 
    {x: 10, y: 40}, 
    {x: 20, y: 30}, 
    {x: 30, y: 70}, 
    {x: 40, y: 0}
];
render(<LineChart data={data} />, document.querySelector("#app"))

CSS 代码:

body {
    margin: 0;
    padding: 0;
    font-family: sans-serif;
    font-size: 14px;
}
.LineChart {
    position: relative;
    padding-left: 40px;
    padding-bottom: 40px;
}
svg {
    fill: none;
    stroke: #33C7FF;
    display: block;
    stroke-width: 2px;
    border-left: 1px solid black;
    border-bottom: 1px solid black;
}
.x-axis {
    position: absolute;
    bottom: 0;
    height: 40px;
    left: 40px;
    right: 0;
    display: flex;
    justify-content: space-between;
}
.y-axis {
    position: absolute;
    top: 0;
    left: 0;
    width: 40px;
    bottom: 40px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: flex-end;
}
.y-axis > div::after {
    margin-right: 4px;
    content: attr(data-value);
    color: black;
    display: inline-block;
}
.x-axis > div::after {
    margin-top: 4px;
    display: inline-block;
    content: attr(data-value);
    color: black;
}

看起来有许多的代码,但你应该注意到,我是用自己已经掌握的 Preact 框架(其他框架也一样,React、Vuew、Angular 等等)和 CSS 来绘制的。如果使用 D3 的话,那么首先你要了解一大堆的概念 … 但现在你只需要掌握自己正在使用的库,就能基于它做出更多的修改了。

看看它的打包大小

最坏情况下,D3 可能要引入大概 70+ 字节的代码。如果你仅仅是想调用一行函数,那么引入这么大的一个第三方库真的合适吗?

通常 Canvas 和 HTML 要优于 SVG

不知道你有没有注意到,上述代码我使用了 SVG 来帮助我绘制图形,绘制图形的时候人们总想用大量的 SVG 来完成任务。然而 CSS 已经今非昔比,它的崛起让 SVG 相形见绌。例如,在 SVG 中实现文字环绕的效果需要自己使用 JavaScript 代码动态计算:

function wrap(text, width) {
  text.each(function() {
    var text = d3.select(this),
        words = text.text().split(/\s+/).reverse(),
        word,
        line = [],
        lineNumber = 0,
        lineHeight = 1.1, // ems
        y = text.attr("y"),
        dy = parseFloat(text.attr("dy")),
        tspan = text.text(null)
           .append("tspan")
           .attr("x", 0)
           .attr("y", y)
           .attr("dy", dy + "em");
    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(" "));
      if (tspan.node().getComputedTextLength() > width) {
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan")
           .attr("x", 0)
           .attr("y", y)
           .attr("dy", ++lineNumber * lineHeight + dy + "em")
           .text(word);
      }
    }
  });
}

而使用 CSS 仅需要一行 white-space: normal 。利用 CSS 的 transformborder-radius 基本上可以绘制任何基本图形。我现在之所以还是用 SVG 的唯一原因就是 <path> 标签,它是创建任意图案的绝佳助手。

如果你想要再进一步提升性能,那么可以选择在 Canvas 中进行绘制,相比于其它两种方式,它能占用更少的内存,更新也更快。

你可能会反驳我说 Canvas 不能像 SVG 那样任意放大和缩小图案,当放大 Canvas 的时候,页面上的图案开始变得模糊不清。这是因为你在放大页面的时候,并没有相应的修改 Canvas 的宽度和高度,以下是这一问题的解决方案:

onResize() {
    let canvas = this.base.querySelector('canvas');
    let ctx = canvas.getContext('2d');
    let PIXEL_RATIO = window.devicePixelRatio;
    canvas.width = canvas.offsetWidth * PIXEL_RATIO;
    canvas.height = canvas.offsetHeight * PIXEL_RATIO;
    ctx.setTransform(PIXEL_RATIO, 0, 0, PIXEL_RATIO, 0, 0);
    
    this.props.onDraw(ctx, canvas.offsetWidth, canvas.offsetHeight);
}

总结

如上所示,有种种原因表明 D3 现在已经过时了,自它发布之日到现在,前端已经发生了巨变。如果你只是画一些简单的图标,例如条形图、折线图等,那就先思考一下如何在你正在使用的框架中完成这些任务。而且,在代码维护方面这样也更加便捷。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Parsing Techniques

Parsing Techniques

Dick Grune、Ceriel J.H. Jacobs / Springer / 2010-2-12 / USD 109.00

This second edition of Grune and Jacobs' brilliant work presents new developments and discoveries that have been made in the field. Parsing, also referred to as syntax analysis, has been and continues......一起来看看 《Parsing Techniques》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码