【实战篇】38 # 如何使用数据驱动框架 D3.js 绘制常用数据图表?

news2024/12/24 2:36:27

说明

【跟月影学可视化】学习笔记。

图表库 vs 数据驱动框架

  • 图表库只要调用 API 就能展现内容,灵活性不高,对数据格式要求也很严格,但方便
  • 数据驱动框架需要手动去完成内容的呈现,灵活,不受图表类型对应 API 的制约,但不方便

在这里插入图片描述

数据驱动框架不要求固定格式的数据格式,而是通过对原始数据的处理和对容器迭代、创建新的子元素,并且根据数据设置属性,来完成从数据到元素结构和属性的映射,然后再用渲染引擎将它最终渲染出来。当需求比较复杂,或者样式要求灵活多变的时候,可以考虑使用数据驱动框架。

文档

d3js 文档以及 spritejs 文档

  • https://d3js.org/
  • http://spritejs.com/#/

d3-selection 依赖于 DOM 操作,所以 SVG 和 SpriteJS 这种与 DOM API 保持一致的图形系统,使用起来会更加方便一些。下面将使用这个两个库进行demo的演示

使用 D3.js 绘制条形图

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用 D3.js 绘制条形图</title>
    <style>
        html, body {
            width: 100%;
            height: 100%;
            overflow: hidden;
            padding: 40px;
            margin: 0;
        }
        #stage {
            display: inline-block;
            width: 1200px;
            height: 600px;
            border: 1px dashed salmon;
        }
    </style>
</head>

<body>
    <div id="stage"></div>

    <script src="https://unpkg.com/spritejs/dist/spritejs.min.js"></script>
    <script src="https://d3js.org/d3.v6.js"></script>

    <script>
        const { Scene, SpriteSvg } = spritejs;

        const container = document.getElementById('stage');
        // 先创建一个 Scene 对象
        const scene = new Scene({
            container,
            width: 600,
            height: 600,
        });

        // 数组数据
        const dataset = [125, 121, 127, 193, 309];

        // 使用 D3.js 的方法对数据进行映射
        // scale 函数把一组数值线性映射到某个范围,下面就是将数值映射到 500 像素区间,数值是从 100 到 309。
        const scale = d3.scaleLinear()
            .domain([100, d3.max(dataset)])
            .range([0, 500]);

        // 创建了一个 fglayer,它对应一个 Canvas 画布
        const fglayer = scene.layer('fglayer');
        // 将对应的 fglayer 元素经过 d3 包装后返回
        const s = d3.select(fglayer);

        const colors = ['#fe645b', '#feb050', '#c2af87', '#81b848', '#55abf8'];
        // 在 fglayer 元素上进行迭代操作,selectAll 用来返回 fglayer 下的 sprite 子元素,表示一个图形
        // 通过执行 enter() 和 append(‘sprite’),在 fglayer 下添加了 5 个 sprite 子元素。
        // 再给每个 sprite 元素迭代设置属性,不同的值,就通过迭代算子来设置。
        const chart = s.selectAll('sprite')
            .data(dataset)
            .enter()
            .append('sprite')
            .attr('x', 20)
            .attr('y', (d, i) => {
                return 40 + i * 95;
            })
            .attr('width', scale)
            .attr('height', 80)
            .attr('bgcolor', (d, i) => {
                return colors[i];
            });

        // 添加坐标轴
        // 通过 d3.axisBottom 创建一个底部的坐标,通过 tickValues 给坐标轴传要显示的刻度值 100, 200, 300
        // 返回的 axis 函数用来绘制坐标轴,它是使用 svg 来绘制坐标轴的
        const axis = d3.axisBottom(scale).tickValues([100, 200, 300]);
        // SpriteSvg 可以绘制一个 SVG 图形,然后将这个图形以 WebGL 或者 Canvas2D 的方式绘制到画布上。
        const axisNode = new SpriteSvg({
            x: 0,
            y: 520,
        });
        // 通过 d3.select 选中 axisNode 对象的 svg 属性进行 svg 属性设置和创建 svg 元素操作
        d3.select(axisNode.svg)
            .attr('width', 600)
            .attr('height', 520)
            .append('g')
            .attr('transform', 'translate(20, 0)')
            .call(axis);

        axisNode.svg.children[0].setAttribute('font-size', 20);
        // 将 axisNode 添加到 fglayer 上
        fglayer.append(axisNode);
    </script>

</body>

</html>

实现效果如下:

在这里插入图片描述

使用 D3.js 绘制力导向图

力导向图通过模拟节点之间的斥力,来保证节点不会相互重叠。不仅能够描绘节点和关系链,而且在移动一个节点的时候,图表各个节点的位置会跟随移动,避免节点相互重叠。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用 D3.js 绘制力导向图</title>
    <style>
        html,
        body {
            width: 100%;
            height: 100%;
            overflow: hidden;
            padding: 0;
            margin: 0;
        }

        #stage {
            display: inline-block;
            width: 100%;
            height: 0;
            padding-bottom: 75%;
        }

        #stage canvas {
            background-color: seashell;
        }
    </style>
</head>

<body>
    <div id="stage"></div>

    <script src="https://unpkg.com/spritejs/dist/spritejs.min.js"></script>
    <script src="https://d3js.org/d3.v6.js"></script>

    <script>
        const { Scene } = spritejs;
        console.log(Scene);
        const container = document.getElementById('stage');
        // 先创建一个 Scene 对象
        const scene = new Scene({
            container,
            width: 1200,
            height: 900,
            mode: 'stickyWidth'
        });

        // 创建了一个 fglayer,它对应一个 Canvas 画布
        const layer = scene.layer('fglayer', {
            handleEvent: false,
            autoRender: false,
        });

        // 创建一个 d3 的力模型对象 simulation
        const simulation = d3.forceSimulation()
            .force('link', d3.forceLink().id(d => d.id)) //节点连线 
            .force('charge', d3.forceManyBody()) // 多实体作用
            .force('center', d3.forceCenter(400, 300)); // 力中心

        // 用 d3.json 来读取数据,它返回一个 Promise 对象
        d3.json('./data/FeHelper-20230106175037.json').then(graph => {
            console.log(graph);
            function ticked() {
                d3.select(layer).selectAll('path')
                    .attr('d', (d) => {
                        const [sx, sy] = [d.source.x, d.source.y];
                        const [tx, ty] = [d.target.x, d.target.y];
                        return `M${sx} ${sy} L ${tx} ${ty}`;
                    })
                    .attr('strokeColor', 'salmon')
                    .attr('lineWidth', 1);
                d3.select(layer).selectAll('sprite')
                    .attr('pos', (d) => {
                        return [d.x, d.y];
                    });
                layer.render();
            }
            // 先用力模型来处理数据
            simulation.nodes(graph.nodes).on('tick', ticked);
            simulation.force('link').links(graph.links);
            // 再绘制节点
            d3.select(layer).selectAll('sprite')
                .data(graph.nodes)
                .enter()
                .append('sprite')
                .attr('pos', (d) => {
                    return [d.x, d.y];
                })
                .attr('size', [10, 10])
                .attr('border', [1, 'salmon'])
                .attr('borderRadius', 5)
                .attr('anchor', 0.5);
            // 再绘制连线
            d3.select(layer).selectAll('path')
                .data(graph.links)
                .enter()
                .append('path')
                .attr('d', (d) => {
                    const [sx, sy] = [d.source.x, d.source.y];
                    const [tx, ty] = [d.target.x, d.target.y];
                    return `M${sx} ${sy} L ${tx} ${ty}`;
                })
                .attr('name', (d, index) => {
                    return `path${index}`;
                })
                .attr('strokeColor', 'salmon');

            function dragsubject() {
                const [x, y] = layer.toLocalPos(event.x, event.y);
                return simulation.find(x, y);
            }
            // 将三个事件处理函数注册到 layer 的 canvas 上
            d3.select(layer.canvas)
                .call(d3.drag()
                    .container(layer.canvas)
                    .subject(dragsubject)
                    .on('start', dragstarted)
                    .on('drag', dragged)
                    .on('end', dragended)
                );
        });

        // dragstarted 处理开始拖拽的事件
        function dragstarted(event) {
            // 通过前面创建的 simulation 对象启动力模拟,记录一下当前各个节点的 x、y 坐标
            if (!event.active) simulation.alphaTarget(0.3).restart();
            const [x, y] = [event.subject.x, event.subject.y];
            event.subject.fx0 = x;
            event.subject.fy0 = y;
            event.subject.fx = x;
            event.subject.fy = y;
            // 通过 layer.toLocalPos 方法将它转换成相对于 layer 的坐标
            const [x0, y0] = layer.toLocalPos(event.x, event.y);
            event.subject.x0 = x0;
            event.subject.y0 = y0;
        }
        // dragged 处理拖拽中的事件
        function dragged(event) {
            // 转换 x、y 坐标,计算出坐标的差值,然后更新 fx、fy
            const [x, y] = layer.toLocalPos(event.x, event.y),
                { x0, y0, fx0, fy0 } = event.subject;
            const [dx, dy] = [x - x0, y - y0];

            event.subject.fx = fx0 + dx;
            event.subject.fy = fy0 + dy;
        }
        // dragended 处理拖住结束事件,清空 fx 和 fy
        function dragended(event) {
            if (!event.active) simulation.alphaTarget(0);
            event.subject.fx = null;
            event.subject.fy = null;
        }
    </script>

</body>

</html>

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/147312.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Smart Finance成为火必投票竞选项目,参与投票获海量奖励

最近&#xff0c;Huobi推出了新一期的“投票上币”活动&#xff0c;即用户可以通过HT为候选项目投票&#xff0c;在投票截止后&#xff0c;符合条件的优质项目将直接上线Huobi。而Smart Finance成为了新一期投票上币活动的竞选项目之一&#xff0c;并备受行业关注&#xff0c;与…

C++ 命令模式

什么是命令模式&#xff1f; 将请求转换为一个包含与请求相关的所有信息的独立对象。从而使你可以用不同的请求方法进行参数化&#xff0c;并且能够对请求进行排队、记录请求日志以及撤销请求操作。命令模式属于行为设计模式 如何理解命令模式 命令模式很像我们订外卖&#…

Hudi(10):Hudi集成Spark之并发控制

目录 0. 相关文章链接 1. Hudi支持的并发控制 1.1. MVCC 1.2. OPTIMISTIC CONCURRENCY 2. 使用并发写方式 3. 使用Spark DataFrame并发写入 4. 使用Delta Streamer并发写入 0. 相关文章链接 Hudi文章汇总 1. Hudi支持的并发控制 1.1. MVCC Hudi的表操作&#xff0c;如…

阿里云 EDAS Java服务日志中打印调用链TraceId

最近要搭建阿里云的日志服务SLS&#xff0c;收集服务日志&#xff0c;进行统一的搜索查询。但遇到一个问题如何在日志中打印链路的TraceId&#xff0c;本文章记录一下对EDAS免费的解决方法。 先看一下阿里官方文档 业务日志关联调用链的TraceId信息 从文档上看&#xff0c;想要…

基于SSM的资源发布系统

项目介绍&#xff1a; 该系统基于SSM技术&#xff0c;数据层为MyBatis&#xff0c;数据库使用mysql&#xff0c;MVC模式&#xff0c;B/S架构&#xff0c;具有完整的业务逻辑。系统共分为管理员&#xff0c;用户两种角色&#xff0c;主要功能&#xff1a;登陆注册&#xff0c;用…

数据结构:跳表

文章目录跳表跳表的由来单链表的查找效率太低提高单链表的查找效率跳表的时间复杂度分析跳表的空间复杂度分析跳表的插入操作跳表的删除操作跳表索引动态更新跳表 对链表进行改造&#xff0c;在链表上加多级索引的结构就是跳表&#xff0c;使其可以支持类似“二分”的查找算法。…

Redis查询之RediSearch和RedisJSON讲解

文章目录1 Redis查询1.1 RedisMod介绍1.2 安装Redis1.3 RediSearchRedisJSON安装1.3.1 下载安装1.3.2 修改配置1.4 RedisJSON操作1.4.1 基本操作1.4.1.1 保存操作JSON.SET1.4.1.2 读取操作JSON.GET1.4.1.3 批量读取操作JSON.MGET1.4.1.4 删除操作JSON.DEL1.4.1.5 其他命令1.4.1…

鲲鹏Bigdata pro之Hive的基本操作(创建表、查询表)

1 介绍 本文主要依据《鲲鹏Bigdata pro之Hive集群部署》实验教程上的Hive操作例子讲解&#xff0c;方便大数据学员重用相应的操作语句。同时对实验过程中出现的问题给以解决方法&#xff0c;重现问题解决的过程。以让大家认识到&#xff0c;出现问题很正常&#xff1b;同时&am…

Java设计模式中接口隔离原则是什么?迪米特原则又是什么,啥又是合成复用原则,这些又怎么运用

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 3.5 接口隔离原则 3.5.1 特点 使用的类不应该被迫依赖于不想使用的方法&#xff0c;应该依赖接口方法 3.5.2 案例(安全门) 防火功能代码 public interface Fi…

第一章:统计学习方法概论

大纲1.1统计学习的特点1.2统计学习方法步骤1.3 统计学习的分类基本分类&#xff1a;1.4 监督学习方法的三要素模型&#xff1a;条件概率分布P(Y∣X)P(Y|X)P(Y∣X)或决策分布Yf(X)Yf(X)Yf(X)策略&#xff1a;在所有假设空间中选择一个最优模型注意事项&#xff1a;算法&#xff…

Java设计模式中适配器模式是什么/适配器模式可以干什么/又如何实现

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 5.3 适配器模式 5.3.1 概述 将一个类的接口转换为客户希望的另一种接口&#xff0c;使得原本由于接口不兼容而不能一起工作的那些类能一起工作分为类适配器模式和…

一套采用ASP.NET开发的工作通OA协同办公系统源码 流程审批 公文流转 文档管理

分享一套采用ASP.NET基于C#开发&#xff0c;使用桌面式的OA协同办公系统&#xff0c;超好用户体验效果的后台管理界面&#xff0c;集成 资讯、邮件、日程、文档&#xff08;在线文件档案管理&#xff09;、流程审批、公文流转、沟通与分享&#xff08;在线聊天和内部论坛&#…

基于LLVM的C编译器--lcc——以CLion用SSH连接WSL Ubuntu22.04为例

Windows 10 22H2CLion 2022.3.1Ubuntu 20.04 &#xff08;Microsoft Store内的WSL发行版&#xff09; 一、下载WSL&#xff0c;换源&#xff0c;切换到WSL2 1.1 保证windows版本 在设置->系统->关于中查看 必须是win10及以上对于x64系统&#xff1a;版本1903或更高版…

ArcGIS基础实验操作100例--实验63由图片创建点符号

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验63 由图片创建点符号 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

Java设计模式中代理模式是什么/JDK动态代理分为哪些,静态代理又怎么实现,又适合哪些场景

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 5.结构型模式 5.1 概述 根据如何将类或对象按某种布局组成更大的结构&#xff0c;分为类结构模式和对象结构模式&#xff0c;前者采用继承机制来组织接口和类&am…

视频序列对比学习

前言 视频embedding化也即表征有很多实际的应用场景&#xff0c;比如文本-视频 pair的检索等等。由于视频一般来说较长&#xff0c;所以对于给定的一段话&#xff0c;其中的某些sentence句子一般对应着视频中某几个clip片段&#xff0c;之前常规的做法都是去匹配所有的sentence…

人工服务、人工智能和分析是联络中心的主要趋势

数字联络中心提供商 IPI 宣布了其对 2023 年的预测。IPI 非常重视提供卓越的客户联系&#xff0c;认为未来一年将由以下趋势定义&#xff1a;专注于人工服务&#xff1b;增加对人工智能和自动化的采用&#xff1b;以及更多地使用数据和分析。 关注人性化服务 据 IPI 称&#…

实现QTreeView、QTableView子项中的复选框勾选/取消勾选功能

1.前言本博文所说的技术点适用于同时满足下面条件的所有视图类&#xff1a;模型类从 QAbstractItemModel派生。代理类从QStyledItemDelegate派生。故本博文所说的技术点也适用于QTableView。2.需求提出基于Qt的model/view framework技术&#xff0c;利用QTreeView树视图实现业务…

【异常】SpringSecurity登录失败:Full authentication is required to access this resource

一、报错提示 SpringSecurity提示如下内容&#xff1a; 2023-01-07 06:08:51.843 [cdi-ids-commonprovider] [http-nio-9092-exec-14] WARN com.desaysv.tsp.logic.ids.config.MyAuthenticationEntryPoint - 登录失败&#xff1a;Full authentication is required to acces…

基于Java+Jsp+SpringMVC漫威手办商城系统设计和实现

基于JavaJspSpringMVC漫威手办商城系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联…