22、D3与ES6、TypeScript及相关应用实践

D3与ES6、TypeScript及相关应用实践

1. SVG编辑与导入

在处理SVG文件时,并非所有SVG程序在编辑导出的SVG文件时都能完美工作。例如,当在Adobe Illustrator中打开某些导出的SVG文件时,可能会出现问题。这是因为Adobe Illustrator虽然支持我们在导出时添加的嵌入式样式,但它只支持非常特定的类名。若将所有类名从 .link 更改为 cls - 1 形式,就可以使用Illustrator加载导出的SVG文件。

下面介绍如何从Inkscape导入SVG并在D3中使用。以一个老虎的SVG图像为例,先使用Inkscape将其旋转,然后通过以下代码导入并展示:

d3.xml('data/tiger.svg', loaded);
function loaded(err, tiger) {
    var tigerSVG =
        d3.select(tiger.documentElement.querySelector("g")).attr("transform", null).node();
    svg.append("g").node().appendChild(tigerSVG);
}

在浏览器中打开该示例,就能看到旋转后的老虎图像,之后便可以使用D3对这个SVG进行修改。

2. 使用画笔选择元素

可以使用 d3.brush 轻松选择SVG元素上的一个区域。打开示例 <DVD3>/src/chapter - 08/src/D08 - 06.js ,会看到200个随机放置的符号。使用鼠标左键点击并拖动,就能选择一个区域,这个正方形区域就是 d3.brush ,可以通过鼠标轻松调整其大小和位置。

以下是实现该功能的详细代码:

// 创建符号数组
var symbols = [
    {name: 'Cross', symbol: d3.symbolCross},
    {name: 'Circle', symbol: d3.symbolCircle},
    {name: 'Diamond', symbol: d3.symbolDiamond},
    {name: 'Square', symbol: d3.symbolSquare},
    {name: 'Star', symbol: d3.symbolStar},
    {name: 'Triangle', symbol: d3.symbolTriangle},
    {name: 'Wye', symbol: d3.symbolWye}
];
// 生成数据
var data = d3.range(0, 200).map(function (d) {
    return {
        n: d,
        x: Math.random() * width,
        y: Math.random() * height,
        item: symbols[d % 7]
    }
});
// 定义颜色比例尺
var color = d3.scaleOrdinal()
    .domain(symbols.map(function (s) {return s.name}))
    .range(d3.schemeCategory10);

// 添加200个元素
svg.selectAll("symbol")
    .data(data).enter()
    .append("g")
    .attr("transform", function (d) {return "translate(" + d.x + " " + d.y + " )"})
    .attr("class", "symbol")
    .append("path")
    .attr("d", function (d) {return d3.symbol().type(d.item.symbol)()})
    .attr("fill", function (d) { return color(d.item.name)});

// 创建画笔并监听事件
var brush = d3.brush()
    .on("brush", brushed)
svg.append("g")
    .attr("class", "brush")
    .call(brush);

function brushed() {
    var extent = d3.event.selection;
    var x0 = extent[0][0],
        y0 = extent[0][1],
        x1 = extent[1][0],
        y1 = extent[1][1];
    data.forEach(function (d, i) {
        if (x0 <= d.x && d.x <= x1 && y0 <= d.y && d.y <= y1) {
            d3.selectAll(".symbol:nth-child(" + (i + 1) + ")")
                .select("path")
                .attr("stroke", "black")
                .attr("stroke-width", "2");
        } else {
            d3.selectAll(".symbol:nth-child(" + (i + 1) + ")")
                .select("path")
                .attr("stroke", null)
                .attr("stroke-width", null);
        }
    });
}

在上述代码中,首先创建了200个随机放置的符号并为其着色。然后创建了一个 d3.brush 并将其附加到SVG元素上,同时定义了一个回调函数 brushed ,当画笔移动、调整大小或关闭时会调用该函数。在 brushed 函数中,获取选择区域的坐标,遍历数据元素,判断哪些元素落在选择区域内,若在则为其添加边框高亮显示,否则移除边框。

需要注意的是, d3.zoom d3.brush 都需要响应鼠标点击和拖动事件,若要同时使用这两个功能,可参考示例https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172 中的方法。

3. D3与ES6的结合使用
3.1 开发环境搭建

在开发ES6时,要考虑目标浏览器的支持情况。大多数现代浏览器对ES6标准有很好的支持,但模块支持方面几乎没有。因此,需要将ES6代码转换为ES5代码,这一过程通常称为转译。可以使用webpack来实现代码的转译和依赖打包。

在目录 <DVD3>/chapter - 09/es6/ 中可以找到支持代码转译和打包的所有配置。按照以下步骤操作:
1. 确保安装了正确的依赖:

$ npm install
  1. 启动webpack服务器:
$ npm run start

执行上述命令后,会看到类似以下的输出:

> d3.js - es6@1.0.0 start /Users/jos/dev/git/dataviz - d3js/src/chapter - 09/es6
> webpack - dev - server --progress
10% building modules 1/1 modules 0 active
Project is running at http://localhost:8080/
webpack output is served from /
Version: webpack 2.2.1
Time: 4043ms
             Asset       Size  Chunks                    Chunk Names
    main.bundle.js    1.04 MB       0  [emitted]  [big]  main
main.bundle.js.map    2.08 MB       0  [emitted]         main
        index.html  185 bytes          [emitted]
           app.css    8 bytes          [emitted]
chunk    {0} main.bundle.js, main.bundle.js.map (main) 1.01 MB [entry]
[rendered]
       [21] ./~/d3 - scale/src/linear.js 1.59 kB {0} [built]
      [104] ./app/js/index.js 2.47 kB {0} [built]
      [105] (webpack) - dev - server/client?http://localhost:8080 5.03 kB {0}
[built]
      [149] ./~/d3 - scale/index.js 5.01 kB {0} [built]
      [167] ./~/d3 - selection/index.js 3.17 kB {0} [built]
      [169] ./~/d3 - selection/src/local.js 660 bytes {0} [built]
      [200] ./~/d3 - selection/src/touch.js 734 bytes {0} [built]
      [201] ./~/d3 - selection/src/touches.js 650 bytes {0} [built]
      [217] ./~/d3/build/d3.js 488 kB {0} [built]
      [256] ./~/strip - ansi/index.js 161 bytes {0} [built]
      [258] ./~/url/url.js 23.1 kB {0} [built]
      [260] (webpack) - dev - server/client/overlay.js 3.6 kB {0} [built]
      [261] (webpack) - dev - server/client/socket.js 872 bytes {0} [built]
      [262] (webpack)/hot/emitter.js 89 bytes {0} [built]
      [263] multi (webpack) - dev - server/client?http://localhost:8080
./app/js/index.js 40 bytes {0} [built]
         + 249 hidden modules
webpack: Compiled successfully.

这样就完成了代码的转译,并启动了一个简单的Web服务器,可以直接在浏览器中查看ES6应用。

3.2 导入模块

D3的发行版被分成多个模块。在没有ES6的情况下,需要在HTML文件顶部添加各个库作为依赖,例如:

<script src="../libs/d3 - selection.v1.js"></script>
<script src="../libs/d3 - scale - chromatic.v1.min.js"></script>
<script src="../libs/d3 - geo - projection.v1.min.js"></script>

然后在JavaScript文件中使用 d3 命名空间调用函数:

d3.select('.chart');
d3.geoGringortenQuincuncial();

使用ES6和模块时,可以在JavaScript文件中使用 import 语句导入所需模块,例如:

import {scaleOrdinal} from "d3 - scale";
import {select} from "d3 - selection";

这样就可以直接使用这些函数,而无需使用 d3. 前缀。代码示例如下:

// 标准方式
var svg = d3.select(".chart")
var color = d3.scaleOrdinal()...
// 使用ES6
var svg = select(".chart") 
var color = scaleOrdinal()...

若要导入完整的D3库,可使用以下方式:

import * as d3 from "d3";

导入D3(或其部分模块)的另一个优点是,编辑器能更方便地提供正确的代码补全功能。例如,使用 import * as d3scale from "d3 - scale" 导入 d3 - scale 模块后,就能轻松查看和访问所有不同的比例尺。

更多关于ES6模块支持的信息可参考https://github.com/lukehoban/es6features#modules 。

3.3 箭头函数和方法简写

ES6中可以使用箭头函数定义函数,这是一个非常实用的特性。在传统的JavaScript和D3中,定义函数的方式如下:

symbolGroups.append("path")
    .attr("fill", function (d) {
        return color(d.name)
    })
    .attr("d", function (d) {
        return d3.symbol()
            .size(2400)
            .type(d.symbol)();
    });

使用箭头函数可以将其改写为:

symbolGroups.append("path")
    .attr("fill", d => color(d.name))
    .attr("d", d => {
        return d3.symbol()
            .size(2400)
            .type(d.symbol)()
    });

对于 fill 属性,由于只有一个表达式,可直接返回结果;对于 d 属性,使用花括号包裹函数体时需要添加 return 语句。这种方式能使代码更加简洁,尤其在处理D3的各种回调函数时,能让代码更加易读。

需要注意的是,在箭头函数中使用 this 时,它会使用周围代码的作用域。在D3中,有时会使用 this 传递信息,例如访问正在处理的当前节点。以下代码展示了这种差异:

symbolGroups.append("path")
    .attr("fill", d => color(d.name))
    .attr("attr1", d => { console.log(this); return "attr1"})
    .attr("attr2", function(d) { console.log(this); return "attr1" })
    .attr("d", d => {
        return d3.symbol()
            .size(2400)
            .type(d.symbol)()
    });

运行该代码会发现,箭头函数中的 this 是未定义的,而普通函数中的 this 是有定义的。在这种情况下,仍需要使用普通函数来访问传递的 this 上下文。

ES6还为在对象上定义函数提供了简写方式,例如:

var obj = {
    doSomething(name = 'd3') {
    },
    doSomethingElse(count = 100) {
    }
}

这里定义了两个函数 doSomething doSomethingElse ,并展示了ES6允许为函数定义默认参数的特性。

3.4 块级作用域绑定(let + const)

在JavaScript中,使用 var 声明变量是函数作用域的,这可能导致一些难以发现的错误。例如以下代码:

function varTest() {
    for (i = 10 ; i < 20 ; i++) {
        console.log(i)
    }
    if (i < 10) { var i; }
}

由于 var i 声明会被提升到函数顶部,这段代码是合法的,但这种行为不符合传统编程习惯。

ES6引入了 let const 关键字。 let 确保变量仅在定义它的代码块中使用, const 也是块级作用域,用于定义不可重新声明的值。示例代码如下:

const margin = {top: 20, bottom: 20, right: 20, left: 30};
const width = 600 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
let svg = select(".chart")

虽然这样的代码在可读性上没有明显提升,但能避免许多难以捕获的错误。

3.5 字符串插值

在D3中,经常使用 translate 属性将 g 元素移动到特定位置,传统代码如下:

let svg = select(".chart")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

使用箭头函数时,代码如下:

var symbolGroups = svg.selectAll(".symbol").data(symbols)
    .enter()
    .append("g")
    .attr("class", "symbol")
    .attr("transform", d => "translate(" + xBand(d.name) + " 40)")

使用ES6的字符串插值功能,可以避免使用 + 来拼接字符串和变量。代码改写为:

...
.attr("transform", `translate(${margin.left}, ${margin.top})`);
...
.attr("transform", d => `translate(${xBand(d.name)} 40)`)

通过在字符串前后添加单反引号,并使用 ${variable} 引用变量或表达式,使代码更加简洁易读。

综上所述,D3与ES6的结合使用能为数据可视化开发带来诸多便利,提高开发效率和代码质量。同时,使用 d3.brush 和从Inkscape导入SVG等功能也为数据可视化的实现提供了更多的可能性。

4. D3与TypeScript的结合

TypeScript 为 JavaScript 添加了类型系统,使得开发更加容易,并能避免许多常见的运行时错误。在使用 D3 和 TypeScript 时,同样有一些要点需要关注。

首先,TypeScript 需要进行编译,将其转换为普通的 JavaScript 代码才能在浏览器中运行。可以使用 tsc (TypeScript 编译器)来完成这个任务。

在项目中使用 D3 和 TypeScript 时,需要安装 D3 的类型定义文件,这样 TypeScript 编译器才能理解 D3 的 API。可以通过以下命令安装:

npm install @types/d3

以下是一个简单的使用 D3 和 TypeScript 的示例:

import * as d3 from 'd3';

const width = 600;
const height = 400;

const svg = d3.select('body')
  .append('svg')
  .attr('width', width)
  .attr('height', height);

const data = [10, 20, 30, 40, 50];

const scale = d3.scaleLinear()
  .domain([0, d3.max(data) as number])
  .range([0, width]);

svg.selectAll('rect')
  .data(data)
  .enter()
  .append('rect')
  .attr('x', 0)
  .attr('y', (d, i) => i * 25)
  .attr('width', (d) => scale(d))
  .attr('height', 20)
  .attr('fill', 'blue');

在上述代码中,我们使用 TypeScript 编写了一个简单的柱状图。通过导入 D3 并使用类型定义,TypeScript 编译器可以检查代码中的类型错误,提高代码的可靠性。

5. 外部 D3 图表库

有许多基于 D3 的外部图表库,下面介绍几个常见的库。

库名 特点
nvd3 提供了一系列预定义的可交互图表,如折线图、柱状图、饼图等,易于使用,适合快速创建常见类型的图表。
C3 基于 D3 的简单图表库,具有简洁的 API,支持多种图表类型,并且可以方便地与数据绑定。
Dimple.js 专注于简化 D3 的使用,通过简单的配置即可创建复杂的图表,适合初学者。
MetricsGraphics.js 专门用于创建时间序列和统计图表,具有良好的性能和交互性。

这些库都建立在 D3 的基础之上,为开发者提供了更高级的抽象和更简单的 API,能够帮助开发者更快地创建出高质量的可视化图表。

6. D3 命令行工具

D3 提供了一些命令行工具,可以直接从命令行访问部分 API。下面介绍如何使用这些工具创建一个地图。

首先,确保已经安装了 D3 命令行工具:

npm install -g d3-command-line

创建地图的步骤如下:
1. 准备地理数据,通常是 GeoJSON 格式。可以从公开数据源获取,例如 Natural Earth 提供的地理数据。
2. 使用 D3 命令行工具将 GeoJSON 数据转换为 SVG 地图。以下是一个简单的示例命令:

d3-geo-projection -w 800 -h 600 < input.geojson > output.svg

这个命令将输入的 GeoJSON 文件 input.geojson 转换为一个宽度为 800 像素、高度为 600 像素的 SVG 地图,并保存为 output.svg

  1. 可以进一步使用 D3 的命令行工具对 SVG 地图进行样式调整和交互添加。例如,使用 d3-selection 来选择 SVG 元素并添加样式:
d3-selection -f output.svg -s 'path { fill: lightblue; stroke: black; }' > styled-output.svg

这个命令将选择 output.svg 中的所有 path 元素,并为它们添加填充颜色和边框颜色,最终保存为 styled-output.svg

通过使用 D3 命令行工具,可以在不编写大量 JavaScript 代码的情况下,快速创建和定制地图等可视化图表。

7. 总结

D3 是一个强大的数据可视化库,与 ES6、TypeScript 的结合使用以及外部图表库和命令行工具的应用,为数据可视化开发提供了更多的选择和便利。

  • 与 ES6 结合 :通过模块导入、箭头函数、块级作用域绑定和字符串插值等特性,使代码更加简洁、易读和易于维护。
  • 与 TypeScript 结合 :利用类型系统提高代码的可靠性,减少运行时错误。
  • 外部图表库 :提供了更高级的抽象和简单的 API,适合快速创建常见类型的图表。
  • 命令行工具 :可以直接从命令行访问部分 D3 API,快速创建和定制可视化图表。

在实际开发中,可以根据项目的需求和特点,选择合适的技术和工具,以实现高质量的数据可视化效果。

graph LR
    A[D3] --> B[ES6]
    A --> C[TypeScript]
    A --> D[外部图表库]
    A --> E[命令行工具]
    B --> F[模块导入]
    B --> G[箭头函数]
    B --> H[块级作用域]
    B --> I[字符串插值]
    C --> J[类型检查]
    D --> K[nvd3]
    D --> L[C3]
    D --> M[Dimple.js]
    D --> N[MetricsGraphics.js]
    E --> O[创建地图]

这个流程图展示了 D3 与 ES6、TypeScript、外部图表库和命令行工具之间的关系,以及它们各自的一些关键特性和应用场景。通过综合运用这些技术和工具,可以充分发挥 D3 的优势,实现丰富多样的数据可视化效果。

内容概要:本文详细介绍了“秒杀商城”微服务架构的设计实战全过程,涵盖系统从需求分析、服务拆分、技术选型到核心功能开发、分布式事务处理、容器化部署及监控链路追踪的完整流程。重点解决了高并发场景下的超卖问题,采用Redis预减库存、消息队列削峰、数据库乐观锁等手段保障数据一致性,并通过Nacos实现服务注册发现配置管理,利用Seata处理跨服务分布式事务,结合RabbitMQ实现异步下单,提升系统吞吐能力。同时,项目支持Docker Compose快速部署和Kubernetes生产级编排,集成Sleuth+Zipkin链路追踪Prometheus+Grafana监控体系,构建可观测性强的微服务系统。; 适合人群:具备Java基础和Spring Boot开发经验,熟悉微服务基本概念的中高级研发人员,尤其是希望深入理解高并发系统设计、分布式事务、服务治理等核心技术的开发者;适合工作2-5年、有志于转型微服务或提升架构能力的工程师; 使用场景及目标:①学习如何基于Spring Cloud Alibaba构建完整的微服务项目;②掌握秒杀场景下高并发、超卖控制、异步化、削峰填谷等关键技术方案;③实践分布式事务(Seata)、服务熔断降级、链路追踪、统一配置中心等企业级中间件的应用;④完成从本地开发到容器化部署的全流程落地; 阅读建议:建议按照文档提供的七个阶段循序渐进地动手实践,重点关注秒杀流程设计、服务间通信机制、分布式事务实现和系统性能优化部分,结合代码调试监控工具深入理解各组件协作原理,真正掌握高并发微服务系统的构建能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值