爲何我再也不使用 D3.js

D3 在 2011 發佈的時候,可謂是一項極大的創新。那時候仍是 jQuery 和 Backbone 的天下,瀏覽器也只是實現了一些簡單的 css 標準如 「transitions」 等,像如今更爲複雜的 」flex」 佈局還遙遙無期。D3 經過數據驅動來繪製圖形的方式解決了這方面的難題,迅速籠絡人心。css

然而時代終究仍是變了。如今的框架採用了虛擬 DOM 等更爲靈活和強大的設計理念,CSS 在佈局和動畫方面也遊刃有餘。前端

讓我再多給你舉幾個例子。node

D3的學習曲線

過去幾年我一直在使用 D3,並用它繪製了各類各樣的圖形曲線。然而一個問題就是,雖然我理解關於 D3 的基本概念,但我仍是難以作到輕車熟路,我身邊的同事跟我也是一樣的感覺。和大多數人同樣,許多時候,咱們都是從網上找到一個示例,而後將它修改成實際工程中所須要的。react

若是咱們想要添加一些新奇的功能,就須要不停的搜索,不停的嘗試,不停的修改,直到它看起來是咱們想要的了。編程

聽起來是否是很熟悉?如今的開發者們已經很是熟悉虛擬 DOM 和模板編程。掌握這麼一個設計理念與思考方式與如今其餘庫截然不同的技巧,看起來並沒什麼卵用。canvas

繪製曲線,它實際上並不難

若是讓你本身從頭寫一個圖表的話,你大概會感到不安和緊張,就好像本身要面臨一個很是複雜的問題,但當你開始寫的時候一切都變得簡單起來。咱們先來看一下如何使用 D3 畫一副折線圖:瀏覽器

 1// set the dimensions and margins of the graph
2var margin = {top: 20, right: 20, bottom: 30, left: 50},
3    width = 960 - margin.left - margin.right,
4    height = 500 - margin.top - margin.bottom;
5// parse the date / time
6var parseTime = d3.timeParse("%d-%b-%y");
7// set the ranges
8var x = d3.scaleTime().range([0, width]);
9var y = d3.scaleLinear().range([height, 0]);
10// define the line
11var valueline = d3.line()
12    .x(function(d) { return x(d.date); })
13    .y(function(d) { return y(d.close); });
14// append the svg obgect to the body of the page
15// appends a 'group' element to 'svg'
16// moves the 'group' element to the top left margin
17var svg = d3.select("body").append("svg")
18    .attr("width", width + margin.left + margin.right)
19    .attr("height", height + margin.top + margin.bottom)
20  .append("g")
21    .attr("transform",
22          "translate(" + margin.left + "," + margin.top + ")");
23// Get the data
24d3.csv("data.csv", function(error, data) {
25  if (error) throw error;
26// format the data
27  data.forEach(function(d) {
28      d.date = parseTime(d.date);
29      d.close = +d.close;
30  });
31// Scale the range of the data
32  x.domain(d3.extent(data, function(d) { return d.date; }));
33  y.domain([0, d3.max(data, function(d) { return d.close; })]);
34// Add the valueline path.
35  svg.append("path")
36      .data([data])
37      .attr("class", "line")
38      .attr("d", valueline);
39// Add the X Axis
40  svg.append("g")
41      .attr("transform", "translate(0," + height + ")")
42      .call(d3.axisBottom(x));
43// Add the Y Axis
44  svg.append("g")
45      .call(d3.axisLeft(y));
46});

咱們再來看一下在 Preact 中如何繪製:app

 1/* @jsx h */
2let { Component, h, render } = preact
3function getTicks (count, max) {
4    return [...Array(count).keys()].map(d => {
5        return max / (count - 1) * parseInt(d);
6    });
7}
8class LineChart extends Component {
9    render ({ data }) {
10        let WIDTH = 500;
11        let HEIGHT = 300;
12        let TICK_COUNT = 5;
13        let MAX_X = Math.max(...data.map(d => d.x));
14        let MAX_Y = Math.max(...data.map(d => d.y));
15
16        let x = val => val / MAX_X * WIDTH;
17        let y = val => HEIGHT - val / MAX_Y * HEIGHT;
18        let x_ticks = getTicks(TICK_COUNT, MAX_X);
19        let y_ticks = getTicks(TICK_COUNT, MAX_Y).reverse();
20
21        let d = `
22          M${x(data[0].x)} ${y(data[0].y)} 
23          ${data.slice(1).map(d => {
24              return `L${x(d.x)} ${y(d.y)}`;
25          }).join(' ')}
26        `;
27
28        return (
29            <div 
30                class="LineChart" 
31                style={{
32                    width: WIDTH + 'px',
33                    height: HEIGHT + 'px'
34                }}
35            >
36                <svg width={WIDTH} height={HEIGHT}>
37                    <path d={d} />
38                </svg>
39                <div class="x-axis">
40                    {x_ticks.map(v => <div data-value={v}/>)}
41                </div>
42                <div class="y-axis">
43                    {y_ticks.map(v => <div data-value={v}/>)}
44                </div>
45            </div>
46        );
47    }
48}
49let data = [
50    {x: 0, y: 10}, 
51    {x: 10, y: 40}, 
52    {x: 20, y: 30}, 
53    {x: 30, y: 70}, 
54    {x: 40, y: 0}
55];
56render(<LineChart data={data} />, document.querySelector("#app"))

CSS 代碼:框架

 1body {
2    margin: 0;
3    padding: 0;
4    font-family: sans-serif;
5    font-size: 14px;
6}
7.LineChart {
8    position: relative;
9    padding-left: 40px;
10    padding-bottom: 40px;
11}
12svg {
13    fill: none;
14    stroke: #33C7FF;
15    display: block;
16    stroke-width: 2px;
17    border-left: 1px solid black;
18    border-bottom: 1px solid black;
19}
20.x-axis {
21    position: absolute;
22    bottom: 0;
23    height: 40px;
24    left: 40px;
25    right: 0;
26    display: flex;
27    justify-content: space-between;
28}
29.y-axis {
30    position: absolute;
31    top: 0;
32    left: 0;
33    width: 40px;
34    bottom: 40px;
35    display: flex;
36    flex-direction: column;
37    justify-content: space-between;
38    align-items: flex-end;
39}
40.y-axis > div::after {
41    margin-right: 4px;
42    content: attr(data-value);
43    color: black;
44    display: inline-block;
45}
46.x-axis > div::after {
47    margin-top: 4px;
48    display: inline-block;
49    content: attr(data-value);
50    color: black;
51}

看起來有許多的代碼,但你應該注意到,我是用本身已經掌握的 Preact 框架(其餘框架也同樣,React、Vuew、Angular 等等)和 CSS 來繪製的。若是使用 D3 的話,那麼首先你要了解一大堆的概念 … 但如今你只須要掌握本身正在使用的庫,就能基於它作出更多的修改了。dom

看看它的打包大小

最壞狀況下,D3 可能要引入大概 70+ 字節的代碼。若是你僅僅是想調用一行函數,那麼引入這麼大的一個第三方庫真的合適嗎?

Canvas 和 HTML 優於 SVG

不知道你有沒有注意到,上述代碼我使用了 SVG 來幫助我繪製圖形,繪製圖形的時候人們總想用大量的 SVG 來完成任務。然而 CSS 已經今非昔比,它的崛起讓 SVG 相形見絀。例如,在 SVG 中實現文字環繞的效果須要本身使用 JavaScript 代碼動態計算:

 1function wrap(text, width) {
2  text.each(function() {
3    var text = d3.select(this),
4        words = text.text().split(/\s+/).reverse(),
5        word,
6        line = [],
7        lineNumber = 0,
8        lineHeight = 1.1, // ems
9        y = text.attr("y"),
10        dy = parseFloat(text.attr("dy")),
11        tspan = text.text(null)
12           .append("tspan")
13           .attr("x", 0)
14           .attr("y", y)
15           .attr("dy", dy + "em");
16    while (word = words.pop()) {
17      line.push(word);
18      tspan.text(line.join(" "));
19      if (tspan.node().getComputedTextLength() > width) {
20        line.pop();
21        tspan.text(line.join(" "));
22        line = [word];
23        tspan = text.append("tspan")
24           .attr("x", 0)
25           .attr("y", y)
26           .attr("dy", ++lineNumber * lineHeight + dy + "em")
27           .text(word);
28      }
29    }
30  });
31}

 

而使用 CSS 僅須要一行 white-space: normal。利用 CSS 的 transform 和 border-radius 基本上能夠繪製任何基本圖形。我如今之因此仍是用 SVG 的惟一緣由就是 <path> 標籤,它是建立任意圖案的絕佳助手。

 

若是你想要再進一步提高性能,那麼能夠選擇在 Canvas 中進行繪製,相比於其它兩種方式,它能佔用更少的內存,更新也更快。

 

你可能會反駁我說 Canvas 不能像 SVG 那樣任意放大和縮小圖案,當放大 Canvas 的時候,頁面上的圖案開始變得模糊不清。這是由於你在放大頁面的時候,並無相應的修改 Canvas 的寬度和高度,如下是這一問題的解決方案:

 

 1onResize() {
2    let canvas = this.base.querySelector('canvas');
3    let ctx = canvas.getContext('2d');
4    let PIXEL_RATIO = window.devicePixelRatio;
5    canvas.width = canvas.offsetWidth * PIXEL_RATIO;
6    canvas.height = canvas.offsetHeight * PIXEL_RATIO;
7    ctx.setTransform(PIXEL_RATIO, 0, 0, PIXEL_RATIO, 0, 0);
8
9    this.props.onDraw(ctx, canvas.offsetWidth, canvas.offsetHeight);
10}

總結

如上所示,有種種緣由代表 D3 如今已通過時了,自它發佈之日到如今,前端已經發生了鉅變。若是你只是畫一些簡單的圖標,例如條形圖、折線圖等,那就先思考一下如何在你正在使用的框架中完成這些任務。並且,在代碼維護方面這樣也更加便捷。

相關文章
相關標籤/搜索