数据可视化——从0-1实现折线图(一)

简介: 前言终于又到周末了,上一周的一篇3d文章 带你入门three.js——从0到1实现一个3d可视化地图很开心😺收到了这么多小伙伴的喜欢,这是对我知识输出的肯定。再次感谢大家!这周我又来了,这次给大家分享一下可视化图表比较简单的图表📈但同时我们又不得不学会的 那就是————「折线图」。读完本篇文章你可以学到什么js实现直线方程折线图的表达canvas的一些api灵活的运用直线折线图我们先去非常有名的Echarts 官网看一看,他的折线图是什么样子的?如图:图片echats折线图从图中可以得到以下2d图形元素:直线(两个端点是圆的)直线(两个端点是直线的)文字好

前言



终于又到周末了,上一周的一篇3d文章  带你入门three.js——从0到1实现一个3d可视化地图很开心😺收到了这么多小伙伴的喜欢,这是对我知识输出的肯定。再次感谢大家!这周我又来了,这次给大家分享一下可视化图表比较简单的图表📈但同时我们又不得不学会的 那就是————「折线图」。读完本篇文章你可以学到什么


  1. js实现直线方程


  1. 折线图的表达


  1. canvas的一些api灵活的运用


直线折线图



我们先去非常有名的Echarts 官网看一看,他的折线图是什么样子的?如图:


image.png

echats折线图


从图中可以得到以下2d图形元素:


  1. 直线(两个端点是圆的)


  1. 直线(两个端点是直线的)


  1. 文字


好像仔细分析一下也没什么嘛,其实就是画直线和加文字。OK, 问下自己canvas如何画直线?是不是有一个ctx.LineTo的方法,但是他画出来的是直线没有端点的所以呢?我们以此基础进行封装,并且直线的端点的图形可控, 同时还有文字位于直线的位置是不是可以画出这样的图形呢?我们接下来进行实操环节。


画布的创建


第一步我们肯定是进行画布的创建,这里没什么好讲的。这里我在html 新建一个canvas, 我新建了一个类叫「lineChart」 直接上代码:


class lineChart {
        constructor(data, type) {
          this.get2d()
        }
        get2d() {
          const canvas = document.getElementById('canvas')
          this.ctx = canvas.getContext('2d')
        }
      }


上面代码没什么好讲的,然后我在为canvas 画布设置背景色。代码如下:


<style>
      * {
        padding: 0;
        margin: 0;
      }
      canvas {
        background: aquamarine;
      }
    </style>


canvas绘图操作复习


其实折线图,本质上就是一个画直线,只不过在原有画直线的能力上,给他做一些增强。我用一个画三角形的例子:带你熟悉一下画线操作。


先看下api:


lineTo(x, y)


绘制一条从当前位置到指定x以及y位置的直线。


直线一般是由两个点组成的,该方法有两个参数:x以及y ,代表坐标系中直线结束的点。开始点和之前的绘制路径有关,之前路径的结束点就是接下来的开始点,等等。。。开始点也可以通过moveTo()函数改变。


moveTo 是什么就在画布中移动笔触, 也就是你开始画的第一个点,或者你可以想象一下在纸上作业,一支钢笔或者铅笔的笔尖从一个点到另一个点的移动过程。


moveTo(*x*, *y*)


将笔触移动到指定的坐标x以及y上。


介绍完毕, 开始实战环节:


drawtriangle() {
  this.ctx.moveTo(25, 25)
  this.ctx.lineTo(105, 25)
  this.ctx.lineTo(25, 105)
}


我们先移动一个点, 然后再画条直线, 然后再画条直线。如果写到你认为结束了,你就错了


「你还差一个很重要的一步就是画布描边或者是填充, 我刚开始学也会忘记这个」


这里給大家整理下canvas 的整个画图流程


  1. 首先,你需要创建路径起始点。


  1. 然后你使用画图命令去画出路径。


  1. 之后你把路径封闭。


  1. 「一旦路径生成,你就能通过描边或填充路径区域来渲染图形。」


也就是我们刚才所做的一切只是在准备路径,所以我们需要「描边」或者「填充」来渲染图形, 我们来看下这两个api。


// 通过线条来绘制图形轮廓。
ctx.stroke() 
// 通过填充路径的内容区域生成实心的图形。
ctx.fill()


我们把填充加上去:看下效果:


image.png


填充三角形


我们看下描边效果:


image.png


未闭合


你会发现为什么没有闭合?,代码是这样的:


this.moveTo(25, 25)
this.lineTo(105, 25)
this.lineTo(25, 105)
this.stroke()


这就说明了一个重要问题就是什么呢?


描边是默认不闭合的,需要我们手动闭合 填充默认会帮我们闭合图形, 并且填充


既然发现了问题,我们就需要解决问题,那么canvas 如何闭合路径呢??


closePath:


闭合路径之后图形绘制命令又重新指向到上下文中。


代码如下:


this.moveTo(25, 25)
this.lineTo(105, 25)
this.lineTo(25, 105)
this.closePath()
this.stroke()


这时候效果图已经出来了:


image.png


闭合三角形


有closePath?难道没有开始路径?答案是当然有的:


// 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。
this.beginPath()


这里会问这个有什么作用呢?


首先 生成路径的第一步叫做beginPath()。本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。


「注意:当前路径为空,即调用beginPath()之后,或者canvas刚建的时候,第一条路径构造命令通常被视为是moveTo(),无论实际上是什么。出于这个原因,你几乎总是要在设置路径之后专门指定你的起始位置。」


closePath 其实也不是必须的,如果图形已经是闭合的,就不需要调用, 到这里canvas的基本绘图操作复习就到这里,后面还有一些实战api : 我就例子中给大家讲解, 不然会显得很生硬。


封装画直线方法


再次之前,我把canvas中每一个点的位置都用一个point2d 点去表示并且写了一些方法,我在之前的文章都有仔细讲过这里我就不展开说了: 3千字长文canvas实现任意正多边形的移动(点、线、面) 这一篇文章。这里我就直接放上代码:


export class Point2d {
  constructor(x, y) {
    this.x = x || 0
    this.y = y || 0
    this.id = ++current
  }
  clone() {
    return new Point2d(this.x, this.y)
  }
  equal(v) {
    return this.x === v.x && this.y === v.y
  }
  add2Map() {
    pointMap.push(this)
    return this
  }
  add(v) {
    this.x += v.x
    this.y += v.y
    return this
  }
  abs() {
    return [Math.abs(this.x), Math.abs(this.y)]
  }
  sub(v) {
    this.x -= v.x
    this.y -= v.y
    return this
  }
  equal(v) {
    return this.x === v.x && this.y === v.y
  }
  rotate(center, angle) {
    const c = Math.cos(angle),
      s = Math.sin(angle)
    const x = this.x - center.x
    const y = this.y - center.y
    this.x = x * c - y * s + center.x
    this.y = x * s + y * c + center.y
    return this
  }
  distance(p) {
    const [x, y] = this.clone().sub(p).abs()
    return x * x + y * y
  }
  distanceSq(p) {
    const [x, y] = this.clone().sub(p).abs()
    return Math.sqrt(x * x + y * y)
  }
  static random(width, height) {
    return new Point2d(Math.random() * width, Math.random() * height)
  }
  cross(v) {
    return this.x * v.y - this.y * v.x
  }
}


分别对应的是一些静态方法、叉乘、 两个点之间求距离哇等等。


我们先在画布上画一条基础的直线, 我们先用random, 在画布上重新生成两个点,然后画出一条随机的直线, 代码如下:


new lineChart().drawLine(
  Point2d.random(500, 500),
  Point2d.random(500, 500)
)
// 画直线
drawLine(start, end) {
  const { x: startX, y: startY } = start
  const { x: endX, y: endY } = end
  this.beginPath()
  this.moveTo(startX, startY)
  this.lineTo(endX, endY)
  this.stroke()
}


js实现直线方程


这里没有好展示的,我们还是分析下echarts 官方的折线图直线,直线两旁是两个圆的,想一想?其实这边涉及到一个数学知识,各位小伙伴,Fly再一次化身数学老师给大家讲解,主要是帮有些小伙伴复习复习。这里我们已经知道直线的开始点和结束点,在数学中我们可以确定一条直线方程,那么我们就可以求出直线上任意一点的(x,y)坐标。那么直线的两个端点的圆心我们就可以确定?半径也可以确定了就是圆心分别到开始点和结束点的距离。


第一步:实现直线方程


我们先看下直线方程的几种表达方式:


  1. 一般式:「Ax+By+C=0」(A、B不同时为0)【适用于所有直线】


  1. 点斜式:「y-y0=k(x-x0) 【「适用于不垂直于x轴的直线」】」 表示斜率为k,且过(x0,y0)的直线


  1. 截距式:「x/a+y/b=1」【适用于不过原点或不垂直于x轴、y轴的直线】


  1. 两点式:表示过(x1,y1)和(x2,y2)的直线 【适用于不垂直于x轴、y轴的直线】 「(x1≠x2,y1≠y2)」
    image.gif两点式


这里很明显我们适合第四种:已经知道直线的起始点和结束点可以求出直线方程。我给出以下代码:


export function computeLine(p0, p1, t) {
  let x1 = p0.x
  let y1 = p0.y
  let x2 = p1.x
  let y2 = p1.y
  // 说明直线平行 y轴
  if (x1 === x2) {
    return new Point2d(x1, t)
  }
  // 平行X轴的情况
  if (y1 === y2) {
    return new Point2d(t, y1)
  }
  const y = ((t - x1) / (x2 - x1)) * (y2 - y1) + y1
  return new Point2d(t, y)
}


p0、p1、 对应的两个直线点 t 就是参数,对应直线的x,我们求出y,返回新的点就好了 。我们默认以开始点和结束点的 x 位置分别 减去或者加一个固定的值 , 求得圆心。直接看下图吧:


image.png


草稿图


这个图已经很明显了, 1和2 之间的距离就是半径, 所以我们只要求出点1 和点4 好像 就OK了, canvas 中是怎么画圆呢有一个arc 这个api :


arc(x, y, radius, startAngle, endAngle, anticlockwise)


画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。


「注意:arc()函数中表示角的单位是弧度,不是角度。角度与弧度的js表达式:」


「弧度=(Math.PI/180)*角度。」


圆肯定就是从0-360度, 代码如下:


drawCircle(center, radius = 4) {
  const { x, y } = center
  this.ctx.beginPath()
  this.ctx.arc(x, y, radius, 0, Math.PI * 2, true) // 绘制
  this.ctx.fill()
}


准备工作都做好了, 我们就开始实现话带圆的直线吧。画图的步骤就是


  1. 先画开始圆


  1. 画直线


  1. 画结束圆


画开始圆和画结束圆其实可以封装成一个方法:他们最主要的区别其实就是起始点的不同,代码如下:


drawLineCircle(start, end, type) {
  const flag = type === 'left'
  const { x: startX, y: startY } = start
  const { x: endX, y: endY } = end
  const center = this.getOnePointOnLine(
    start.clone(),
    end.clone(),
    flag ? startX - this.distance : endX + this.distance
  )
  // 两点之间的距离  不熟悉的小伙伴可以看下上面的文章
  const radius = (flag ? start : end).clone().distanceSq(center)
  this.drawCircle(center, radius)
}


这样我们就可以画圆了。先看下效果图:


image.png


直线两端圆点


到这里我们就已经结束了折线图的第一个部分, 紧接着进入第二部分:

相关文章
|
Web App开发 数据可视化 BI
数据可视化D3系列——饼状图
饼状图是数据统计中经常用到的另一类图表,饼图可以直观地显示一个数据系列中各项的大小与各项总和的比例,本文将使用D3上手制作一个简单的饼状图 什么是布局 布局是D3中非常重要的内容,有了布局D3才能画出复杂的矢量图。但布局并不是直接绘制图形,只是将初始数据转换成容易画图的图形语言,画图工具能读懂图形语言来进行绘制。 在绘制饼状图中,例如有一组数据[1, 2, 3],只依靠这些数据是画不出的,需要将这些数据转化为圆形的起始角度和终止角度,第一块的角度区域为[0, π/3],第二块的角度区域为[π/3, π]……绘制工具能根据这些角度值进行绘制。「布局只进行数据转换」 D3还提供其他常用图表的
数据可视化D3系列——饼状图
|
5月前
|
机器学习/深度学习 数据可视化 搜索推荐
Matplotlib数据可视化图表
【7月更文挑战第11天】Python的Matplotlib库是数据可视化的首选工具,支持创建各种图表,如折线图、柱状图、散点图、饼图、箱线图、热图等。安装Matplotlib可使用`conda`或`pip`。通过简单代码示例展示了如何绘制这些图表,包括自定义样式、动态更新及保存图表为图片文件。数据可视化对于理解和传达数据洞察至关重要。
|
6月前
|
数据可视化 Python
利用Matplotlib绘制数据可视化图表
**摘要:** 本文介绍了Python的绘图库Matplotlib在数据分析和科学计算中的重要性。Matplotlib是一个开源库,提供类似MATLAB的接口,支持静态、动态和交互式图表的绘制,并能保存为多种格式。文章详细讲解了Matplotlib的基本用法,包括安装库、导入模块和绘制简单折线图的步骤。还展示了如何绘制柱状图并添加数据标签。通过这些例子,读者可以了解如何利用Matplotlib进行数据可视化,并对其进行自定义以满足特定需求。
67 4
|
7月前
|
数据可视化
Seaborn数据可视化(三)
Seaborn数据可视化(三)
42 0
|
7月前
|
数据可视化 Python
Seaborn数据可视化(二)
Seaborn数据可视化(二)
103 0
|
7月前
|
数据可视化 Python
Seaborn数据可视化(一)
Seaborn数据可视化(一)
85 0
|
7月前
|
数据可视化
Seaborn数据可视化(四)
Seaborn数据可视化(四)
65 0
|
数据采集 数据可视化 BI
28个数据可视化图表的总结和介绍
在这篇文章中,我们将整理我们能看到的所有数据可视化图表。如果你是数据科学初学者,那么本文将是最适合你的。
215 0
28个数据可视化图表的总结和介绍
|
数据可视化
数据可视化平台Datart-创建柱状图
数据可视化平台、Datart
592 0
|
数据可视化 数据挖掘
14个Seaborn数据可视化图(下)
14个Seaborn数据可视化图
122 0
14个Seaborn数据可视化图(下)