使用d3.js画一个BoxPlot

news2024/12/27 11:39:19

Box Plot

在画Box Plot之前,先来了解下Box Plot是什么?

箱线图(Box Plot)也称盒须图、盒式图或箱型图,是一种用于展示数据分布特征的统计图表。

它由以下几个部分组成:

  1. 箱子:表示数据的四分位数范围,箱子的上下边界分别为上四分位数(Q3)和下四分位数(Q1)。
  2. 中间的横线:表示中位数。
  3. 胡须:也称为触须,分别延伸到最小值和最大值。

箱线图的优点包括:

  1. 直观展示数据的分布情况,包括中心位置、离散程度等。
  2. 快速比较多个数据集的特征。
  3. 检测异常值。

它常用于:

  1. 展示一组数据的分布特征。
  2. 比较不同组数据的分布情况。
  3. 识别可能的异常值。

通过箱线图,可以快速了解数据的关键特征,帮助分析和解释数据。

效果图

在这里插入图片描述

代码实现

第一步,先来绘制画布。
  const chart_id = '#box_plot'

  const margin = ({top: 10, right: 10, bottom: 20, left: 30})
  const height = 600

  document.getElementById('box_plot').innerHTML = ''

  const width = d3
      .select(chart_id).node().getBoundingClientRect().width


  const boxWidth = 50
  const rd = dataset.slice().sort((a, b) => d3.ascending(a.name, b.name))
  
  const chart = d3
      .select(chart_id)
      .attr('height', height)
第二步,生成 x 轴 和 y 轴比例尺
  const yScale = d3.scaleLinear()
      .domain(d3.extent(rd, d => d.value))
      .range([height - margin.bottom, margin.top])
      .nice()

  const xScale = d3.scaleBand()
      .domain(boxes().map(d => d.key))
      .range([margin.left, width - margin.right])
      .paddingInner(1)
      .paddingOuter(.5)
第三步,生成 box
/* 生成 box 方法 */
const boxes = () => {
  let arrMap = Array.from(d3.group(dataset, d => d.name), ([key, dat]) => ({key, dat}))
  arrMap.map(o => {
    const values = o.dat.map(d => d.value);
    const min = d3.min(values);
    const max = d3.max(values);
    const q1 = d3.quantile(values, .25);
    const q2 = d3.quantile(values, .5);
    const q3 = d3.quantile(values, .75);
    const iqr = q3 - q1;
    const r0 = Math.max(min, q1 - iqr * 1.5);
    const r1 = Math.min(max, q3 + iqr * 1.5);
    o.quartiles = [q1, q2, q3];
    o.range = [r0, r1];
    o.outliers = values.filter(v => v < r0 || v > r1);
    return o;
  });
  return (arrMap)
};
第四步,添加 box 组,设置其偏移量
  const groups = chart.selectAll("g")
      .data(boxes())
      .join("g")
      .attr("transform", d => `translate(${xScale(d.key)}, 0)`)
      .attr('class', 'ind')
第五步,添加垂直方向上的线
  groups
      .selectAll("vertLine")
      .data(d => [d.range])
      .join("line")
      .attr("class", "vertLine")
      .attr("stroke", "#7e7e7e")
      .attr('stroke-width', '1px')
      .attr("x1", 0)
      .attr("x2", 0)
      .attr("y1", d => yScale(d[0]))
      .attr("y2", d => yScale(d[1]))
第六步,添加水平方向上的线

水平方向上的三条线分别是 q1(第一四分位),median(中位数),q3(第三四分位),有的需求的第二条线不一定是中位数,也有可能是平均数(mean)。

  groups
      .selectAll('horizontalLine')
      .data((d) => [d.range[0], d.quartiles[1], d.range[1]])
      .join('line')
      .attr('class', 'horizontalLine')
      .attr('stroke', '#7e7e7e')
      .attr('stroke-width', '1px')
      .style('width', boxWidth)
      .attr('x1', -boxWidth / 2)
      .attr('x2', boxWidth / 2)
      .attr('y1', (d) => yScale(d))
      .attr('y2', (d) => yScale(d))
第七步,添加数据点
 groups
      .selectAll("points")
      .data(d => d.dat)
      .join("circle")
      .attr("cx", () => 0 - 30 / 2 + Math.random() * 30)
      .attr("cy", d => yScale(d.value))
      .attr("r", 2)
      .style("fill", "#1867c0")
      .attr("fill-opacity", 1)
第八步,添加盒子
  groups
      .selectAll("box")
      .data(d => [d])
      .join("rect")
      .attr("class", "box")
      .attr("x", -boxWidth / 2)
      .attr("y", d => yScale(d.quartiles[2]))
      .attr("height", d => yScale(d.quartiles[0]) - yScale(d.quartiles[2]))
      .attr("width", boxWidth)
      .attr("stroke", "#545454")
      .style("fill", "#1890ff")
      .style("fill-opacity", 0.3)
第九步,添加 X 轴 和 Y 轴
/* Y 轴 */
chart.append("g")
  .style("font", "12px")
  .style('stroke-width', '1px')
  .call(d3.axisLeft(yScale).tickSizeOuter(0))
  .attr('transform', `translate(${margin.left},0)`)
  .call(g => g.selectAll('.tick line').clone()
      .attr('x2', width - margin.left - margin.right)
      .attr('stroke-opacity', 0.2)
  )

/* X 轴 */
chart.append('g')
  .style('font', '12px')
  .style('stroke-width', '1px')
  .attr("transform", `translate(0,${height - margin.bottom})`)
  .call(d3.axisBottom(xScale))

整体代码

<template>
  <div style="width: 100%">
    <svg id="box_plot" style="width: 100%"/>
  </div>
</template>

<script setup>
import {onMounted} from "vue";
import * as d3 from "d3";
import dataset from "@/mock/dataset_boxplot"


onMounted(() => {
  drawBoxPlot()
})


function drawBoxPlot() {

  const chart_id = '#box_plot'

  const margin = ({top: 10, right: 10, bottom: 20, left: 30})
  const height = 600

  document.getElementById('box_plot').innerHTML = ''

  const width = d3
      .select(chart_id).node().getBoundingClientRect().width


  const boxWidth = 50
  const rd = dataset.slice().sort((a, b) => d3.ascending(a.name, b.name))

  const yScale = d3.scaleLinear()
      .domain(d3.extent(rd, d => d.value))
      .range([height - margin.bottom, margin.top])
      .nice()

  const xScale = d3.scaleBand()
      .domain(boxes().map(d => d.key))
      .range([margin.left, width - margin.right])
      .paddingInner(1)
      .paddingOuter(.5)


  const chart = d3
      .select(chart_id)
      .attr('height', height)


  const groups = chart.selectAll("g")
      .data(boxes())
      .join("g")
      .attr("transform", d => `translate(${xScale(d.key)}, 0)`)
      .attr('class', 'ind')
  groups
      .selectAll("vertLine")
      .data(d => [d.range])
      .join("line")
      .attr("class", "vertLine")
      .attr("stroke", "#7e7e7e")
      .attr('stroke-width', '1px')
      .attr("x1", 0)
      .attr("x2", 0)
      .attr("y1", d => yScale(d[0]))
      .attr("y2", d => yScale(d[1]))
  groups
      .selectAll('horizontalLine')
      .data((d) => [d.range[0], d.quartiles[1], d.range[1]])
      .join('line')
      .attr('class', 'horizontalLine')
      .attr('stroke', '#7e7e7e')
      .attr('stroke-width', '1px')
      .style('width', boxWidth)
      .attr('x1', -boxWidth / 2)
      .attr('x2', boxWidth / 2)
      .attr('y1', (d) => yScale(d))
      .attr('y2', (d) => yScale(d))
  groups
      .selectAll("points")
      .data(d => d.dat)
      .join("circle")
      .attr("cx", () => 0 - 30 / 2 + Math.random() * 30)
      .attr("cy", d => yScale(d.value))
      .attr("r", 2)
      .style("fill", "#1867c0")
      .attr("fill-opacity", 1)

  // 添加盒子
  groups
      .selectAll("box")
      .data(d => [d])
      .join("rect")
      .attr("class", "box")
      .attr("x", -boxWidth / 2)
      .attr("y", d => yScale(d.quartiles[2]))
      .attr("height", d => yScale(d.quartiles[0]) - yScale(d.quartiles[2]))
      .attr("width", boxWidth)
      .attr("stroke", "#545454")
      .style("fill", "#1890ff")
      .style("fill-opacity", 0.3)

  /* Y 轴 */
  chart.append("g")
      .style("font", "12px")
      .style('stroke-width', '1px')
      .call(d3.axisLeft(yScale).tickSizeOuter(0))
      .attr('transform', `translate(${margin.left},0)`)
      .call(g => g.selectAll('.tick line').clone()
          .attr('x2', width - margin.left - margin.right)
          .attr('stroke-opacity', 0.2)
      )

  /* X 轴 */
  chart.append('g')
      .style('font', '12px')
      .style('stroke-width', '1px')
      .attr("transform", `translate(0,${height - margin.bottom})`)
      .call(d3.axisBottom(xScale))


  const tooltip = d3.select(chart_id).append('div')

  /* 设置鼠标进入显示提交框 */
  chart.selectAll('.ind').on("mousemove", function (event) {
    tooltip
        .attr('class', 'tooltip')
        .style('opacity', 1)
        .style('transform', `translate(${event.clientX - 50}px,${event.clientY - 50}px)`)
        .text('test: tooltip')
  })
  groups.on("mouseleave", function () {
    tooltip
        .style('opacity', 0)
  })

  return chart.node()
}


/* 生成 box 方法 */
const boxes = () => {
  let arrMap = Array.from(d3.group(dataset, d => d.name), ([key, dat]) => ({key, dat}))
  arrMap.map(o => {
    const values = o.dat.map(d => d.value);
    const min = d3.min(values);
    const max = d3.max(values);
    const q1 = d3.quantile(values, .25);
    const q2 = d3.quantile(values, .5);
    const q3 = d3.quantile(values, .75);
    const iqr = q3 - q1;
    const r0 = Math.max(min, q1 - iqr * 1.5);
    const r1 = Math.min(max, q3 + iqr * 1.5);
    o.quartiles = [q1, q2, q3];
    o.range = [r0, r1];
    o.outliers = values.filter(v => v < r0 || v > r1);
    return o;
  });
  return (arrMap)
};


</script>

源码地址

源码和 mock 数据都在git仓库上,想要的小伙伴可以自己去git上拉一下。

gitee:https://gitee.com/li-jiayin167/data-visualization.git

github:https://github.com/Jane167/Data-visualization.git

如果觉得不错的话,点个 star

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

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

相关文章

【圆桌论坛】个人作为嘉宾参与问答环节的总结,Create 2024百度AI开发者大会之AI智能体开发与应用论坛

目录 ⭐前言⭐讨论话题✨本质和价值✨端侧部署✨应用商业模式✨商业模式 ⭐主题总结⭐有趣分享 ⭐前言 首先&#xff0c;非常荣幸和开心作为开发者和创业者代表参加百度Create AI大会分论坛圆桌论坛的问答环节。 在分论坛活动开始前&#xff0c;参加了文心智能体平台&#xff…

【iOS】类与对象底层探索

文章目录 前言一、编译源码二、探索对象本质三、objc_setProperty 源码探索四、类 & 类结构分析isa指针是什么类的分析元类元类的说明 五、著名的isa走位 & 继承关系图六、objc_class & objc_objectobjc_class结构superClassbitsclass_rw_tclass_ro_tro与rw的区别c…

【无法运行 AutoCAD,原因可能如下1) 此版本的 AutoCAD 装不正确】

错误提示如下 打开autoremove&#xff0c;点击扩展&#xff0c;输入 无法运行&#xff0c;点击搜索 如果出现这个提示&#xff0c;请重启电脑再点击一遍此按钮 出现修复成功即可 ​如果没提示修复成功可以联系技术人员。 ps&#xff1a;autoremove每周六登录方式用其他登…

警惕侵权风险,张驰课堂六西格玛培训产品请认准官方平台购买

亲爱的朋友们&#xff1a; 在知识经济的时代&#xff0c;知识产权的保护显得尤为重要。我们深知&#xff0c;知识是创新的源泉&#xff0c;而知识产权则是保障创新成果得以合理运用的重要法律手段。然而&#xff0c;近期我们公司&#xff08;张驰课堂&#xff09;却遭遇了一起…

Web3革命:区块链如何重塑互联网

引言 互联网的发展已经深刻地改变了我们的生活方式&#xff0c;而现在&#xff0c;Web3和区块链技术正在为我们提供一个全新的数字世界的视角。本文将带你深入了解Web3的核心概念、技术特性以及它如何正在重塑我们的互联网体验。 从Web1.0到Web3&#xff1a;数字革命的演进 W…

C++ 面向对象-封装

C 是一种多范式编程语言&#xff0c;它支持面向对象编程&#xff08;OOP&#xff09;范式。面向对象编程是一种程序设计思想&#xff0c;其中程序由对象组成&#xff0c;每个对象都是一个实例&#xff0c;具有数据和相关操作。在C中&#xff0c;实现面向对象编程主要通过类和对…

物理机中没有VMNet1和VMNet8虚拟网卡

控制面板——网络连接——网络适配器 VMware Network Adapter VMnet1 VMware Network Adapter VMnet8 如果没有这两个虚拟网卡&#xff0c;虚拟机的网络会出现问题 # 解决办法-恢复虚拟网卡默认设置 1、下载并打开ccleaner&#xff0c;ccleaner官网&#xff1a;CCleaner M…

jdbc操作数据库 and 一个商品管理页面

文章目录 1. 介绍1.1 应用知识介绍1.2 项目介绍 2. 文件目录2.1 目录2.2 介绍以下&#xff08;从上到下&#xff09; 3. 相关代码3.1 DBConnection.java3.2 MysqlUtil.java3.3 AddServlet.java3.4 CommodityServlet.java3.5 DelectServlet.java3.6 SelectByIdServlet.java3.7 S…

2024 OceanBase 开发者大会:OceanBase 4.3正式发布,打造PB级实时分析数据库

4月20日&#xff0c;2024 OceanBase开发者大会盛大召开&#xff0c;吸引了50余位业界知名的数据库专家和爱好者&#xff0c;以及来自全国各地的近600名开发者齐聚一堂。他们围绕一体化、多模、TP与AP融合等前沿技术趋势展开深入讨论&#xff0c;分享场景探索的经验和最佳实践&a…

同旺科技 USB TO SPI / I2C适配器读写24LC256--字节写

所需设备&#xff1a; 1、USB 转 SPI I2C 适配器&#xff1b;内附链接 2、24LC256芯片 适应于同旺科技 USB TO SPI / I2C适配器升级版、专业版&#xff1b; 00地址写入一个字节数据AA&#xff0c;并读回验证&#xff1b; 单字节写时序&#xff1a; 读字节时序&#xff1a; …

架构师系列-MYSQL调优(七)- 索引单表优化案例

索引单表优化案例 1. 建表 创建表 插入数据 下面是一张用户通讯表的表结构信息,这张表来源于真实企业的实际项目中,有接近500万条数据. CREATE TABLE user_contacts (id INT(11) NOT NULL AUTO_INCREMENT,user_id INT(11) DEFAULT NULL COMMENT 用户标识,mobile VARCHAR(50…

2016年新华三杯复赛实验试题

2016年新华三杯复赛实验试题 拓扑图 配置需求 考生根据以下配置需求在 HCL 中的设备上进行相关配置。 以太网接口配置 将 S1、S2 的以太网接口 G1/0/1 至 G1/0/16 的模式用命令 combo enable copper 激活为电口。 虚拟局域网 为了减少广播&#xff0c;需要规划并配置 VLA…

Zabbix监控系统:基础配置及部署代理服务器

目录 前言 一、自定义监控内容 1、在客户端创建自定义key 2、在服务端验证新建的监控项 3、在web界面创建自定义监控项模版 3.1 创建模版 3.2 创建应用集&#xff08;用于管理监控项&#xff09; 3.3 创建监控项 3.4 创建触发器 3.5 创建图形 3.6 将主机与模板关联…

利用selenium发挥vip残存的价值

历史版本谷歌浏览器驱动下载地址 https://chromedriver.storage.googleapis.com/index.html 找到与你电脑当前谷歌浏览器版本一致的驱动然后下载下来(大版本一致即可)。我本地版本是 99.0.04844.51 我这里把 chromedriver 放到 /usr/local/bin 下面了。 启动测试窗口 这里需要…

【软件测试】认识测试|测试岗位|软件测试和开发的区别|优秀的测试人员需要具备的素质

一、什么是测试 测试在⽣活中处处可⻅ 1.生活中的测试场景 案例⼀&#xff1a;对某款购物软件进⾏测试 *启动测试&#xff1a;点击软件图标&#xff0c;测试软件是否可以正常打开 搜索测试&#xff1a;点击输入框&#xff0c;输入关键词&#xff0c;点击搜索 商品测试&#…

【Linux】IO多路转接技术Epoll的使用

【Linux】IO多路转接技术Epoll的使用 文章目录 【Linux】IO多路转接技术Epoll的使用前言正文接口介绍工作原理LT模式与ET模式边缘触发&#xff08;ET&#xff09;水平触发&#xff08;LT&#xff09; 理解ET模式和非阻塞文件描述符ET模式epoll实现TCP服务器简单地封装epoll系统…

python创建线程和结束线程

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 python创建线程和结束线程 在 Python 中&#xff0c;线程是一种轻量级的执行单元&#xff…

【C++学习】STL之空间配置器之一级空间配置器

文章目录 &#x1f4ca;什么是空间配置器✈STL 提供六大组件的了解&#x1f440;为什么需要空间配置器&#x1f44d;SGI-STL空间配置器实现原理&#x1f302;一级空间配置器的实现 &#x1f4ca;什么是空间配置器 空间配置器&#xff0c;顾名思义就是为各个容器高效的管理空间…

“五之链”第十六期沙龙活动在呆马科技成功举办

2024年4月19日&#xff0c;由临沂呆码区块链网络科技有限公司&#xff08;呆马科技&#xff09;承办的第十六期“五之链”物流主题沙龙活动成功举办。此次活动邀请了政府相关部门、知名科研院所、物流企业等20余家单位参与&#xff0c;共同探讨物流数据要素流通与智能应用的发展…

C语言----链表

大家好&#xff0c;今天我们来看看C语言中的一个重要知识&#xff0c;链表。当然大家可以先从名字中看出来。就是一些表格用链子连接。那么大家是否想到了我们以前学的数组&#xff0c;因为数组也是相连的呀。是吧。但是链表与数组还是有区别的&#xff0c;那么链表是什么有什么…