手动实现一个速度仪表盘

news/2024/7/6 1:41:44

前言

最近正在学习数据可视化, 这里记录一下一些心得与成果, 采用的技术是 (svg + react + d3)。 这种实现可视化方式本人个人感觉超级不错,如果你是有一定的基础的同学,强烈推荐一下。

效果

整体效果如下:

clipboard.png

这个是普通的速度仪表盘,有没有开发太多的动态交互,唯一的交互是点击图片最上面的加速减速就能够调整速度了。

开发思路

搭建开发环境

使用create-react-app创建一个新的项目,添加依赖d3

yarn add d3

初始化数据

这里速度范围是[0, 200], 对应角度范围个人设置是[150, 390], 很明显这是一个线性比例尺。速度间隔是2。代码如下

const scale = d3.scaleLinear().domain([0, 200]).range([150, 360 + 30])
const ticks = scale.ticks(100) // 200 / 2 => 100 

构建外部圈

    
function Chart(props) {
  const { width, height, margin } = props
  return (
    <svg width={width} height={height}>
      <g transform={`translate(${margin.left}, ${margin.top})`}>
        {
          props.children
        }
      </g>
    </svg>
  )
}

......
export default class Meter extends Component { 
    ...     
    render () {
        // config => {width: xxx, height: xxx, margin: xxx}
        return (
            <div className={'container'}>
                <Chart {...config}>
                    <g>
                        <circle cx={0} cy={0} r={204} fill={'rgba(158, 158, 158, .4)'}></circle>
            <circle cx={0} cy={0} r={196} fill={'#FFF'}></circle>
            <circle cx={0} cy={0} r={200} fill={'transparent'} stroke={'#000'}></circle>
                    </g>
                </Chart>
            </div>
        )
    }
}

上面其实是绘画了三个圆, 利用SVG后面的绘制的图画,会覆盖前面的图画的特性。先画最外面,然后最里面,最后中间的圆。 就把最外层的圈给描绘出来了,效果如下:
clipboard.png

描绘刻度尺

接着上面的代码结构,我们开始刻画刻度尺

    ......
export default class Meter extends Component { 
    ...     
    render () {
        // config => {width: xxx, height: xxx, margin: xxx}
        return (
            <div className={'container'}>
                <Chart {...config}>
                    <g>
                        <circle cx={0} cy={0} r={204} fill={'rgba(158, 158, 158, .4)'}></circle>
            <circle cx={0} cy={0} r={196} fill={'#FFF'}></circle>
            <circle cx={0} cy={0} r={200} fill={'transparent'} stroke={'#000'}></circle>
                    </g>
                    <g fill={'transport'} stroke={'#000000'}>
                          {
                            ticks.map((tick) => {
                              let IS_20_TIME = tick % 20 === 0
                              let title = IS_20_TIME ? <text x={160} dominantBaseline={'middle'} textAnchor={'end'}>{tick}</text> : ''
                              return (
                                <g transform={`rotate(${scale(tick)})`} key={tick}>
                                  <path d={`M165, 0L185,0`} strokeWidth={IS_20_TIME ? 3 : 1}></path>
                                  {title}
                                </g>
                              )
                            })
                          }
                    </g>
                </Chart>
            </div>
        )
    }
}

这里刻画刻度尺,我的思路很简单,刻度尺是对速度大小的描述,而速度跟角度又是线性相关,反过来,速度对应角度。所以,我只是需要根据速度所对应的角度,而对水平刻度进行旋转即可。效果大家可以看到:
clipboard.png

指向针

指向针其实就是一个圆 + 三角形的组合,代码如下:

 <circle cx={0} cy={0} r={10} fill={'#'}></circle>
            <path d={`M-20, 5L-20, -5L130, 0Z`} transform={`rotate(150)`}>
              <animateTransform ></animateTransform>
            </path>

上面本人实现的比较粗糙,大家可以把这个封装成一个shape, 以后可以直接复用的,后面如果需要旋转,可以通过<g>元素来实现。

到这一步,一个简单的仪表盘就初具原型了

clipboard.png

控制指针转动

指针的转动是根据速度来的,所以我们需要先定义一个speed的状态。

constructor(props) {
    super(props)
    
    this.state = {
      speed: 0
    }
  }

接下来,我们需要把speed映射到指针上面。怎么处理呢
还记得前面定义的scale,这个是一个线性比例尺,通过它我们能够获取不同速度对应的角度
我们把上面的指向针代码进行改造

const {speed} = this.state
......
<circle cx={0} cy={0} r={10} fill={'#'}></circle>
<path d={`M-20, 5L-20, -5L130, 0Z`} transform={`rotate(${scale(speed)})`}>
  <animateTransform ></animateTransform>
</path>

这样我们设置不同的speed就能在页面控制指针指向不同的刻度尺。

速度标识区间

所谓的速度标识区间,其实就是几段圆弧,通过不同的颜色来告知进入不同的速度区间。
这里我对圆弧进行了封装,底层通过d3的arc方法来创建圆弧。

function LArc(props) {
  const { start, end, color } = props
  
  let _arc = d3.arc()({
    innerRadius: 165,
    outerRadius: 185,
    startAngle: Math.PI * 2 * (scale(start) + 90) / 360,
    endAngle: Math.PI * 2 * (scale(end) + 90) / 360
  })
  
  return (
    <path d={_arc} fill={color}></path>
  )
}

这里其实还有一个问题,就是需要先加载速度标识区间,然后再去添加刻度尺,不然标识区间会覆盖刻度尺(切记)。
这样一个基本速度仪表盘就出来了

clipboard.png

如果你能明白上面的实现思路,我觉得你可以自己实现一个时钟

如果你想了解更多:比如指示器如何实现的
请参考
https://github.com/cookhot/i-...
(本人会在里面不定期增加新图表哦)


http://www.niftyadmin.cn/n/681376.html

相关文章

活在幻梦中的你我

其实仔细想想,人类和地球上的其它物种有什么不同呢?可能仅有的不同是,人类会去相信那本来并不存在的事情. 并且会为了那种虚幻的东西为止拼搏、努力。比如科技的发展&#xff0c;不就是人类在实现自己想象中的事物么&#xff0c;飞机、轮船、家电、计算机等等&#xff0c;无一…

Odoo10实战一:模块创建

Odoo10实战一&#xff1a;模块创建 一&#xff1a;配置准备 我们在odoo10源码的debian目录下找到odoo.conf文件&#xff0c;这就是odoo10的配置文件。我们可以拷贝它到项目根目录下进行修改&#xff0c;然后在pycharm中指定项目的启动配置为根目录下的odoo.conf。也可以直接修改…

软件工程--软件测试(白盒)

what 白盒测试是结构测试&#xff0c;逻辑驱动测试&#xff0c;我们都知道黑盒指不可见&#xff0c;那么在测试中与之相对应的白盒呢这是可视的&#xff0c;可以清楚内部是如何运作的&#xff0c;这也就达成了对程序内部逻辑结构、对逻辑路径测试。这也就提到了白盒测试法是一…

FATAL: no pg_hba.conf entry for host ::1 odoo10连接postgre navicate连接问题

FATAL: no pg_hba.conf entry for host "::1" 2016-03-09 15:20:42 作者&#xff1a;MangoCool 来源&#xff1a;MangoCool 之前集群安装的开源版的Greenplum&#xff0c;在安装gpperfmon-cc-web时候&#xff0c;遇到的问题始终解决不了&#xff0c;于是就暂时…

PAT A1055

水题&#xff0c;还是字典排序&#xff0c;没神马好说的&#xff1b; #include<iostream> #include<stdlib.h> #include<stdio.h> #include<string> #include<cstring> #include<vector> #include<algorithm> using namespace std; …

《JavaScript高级程序设计》笔记:新兴的API

requestAnimationFrame() 大多数电脑显示器的刷新频率60HZ&#xff0c;大概相当于每秒钟重绘60次。因此&#xff0c;最平滑动画的最佳循环间隔是1000ms/60&#xff0c;约等于17ms。 mozRequestAnimationFrame() mozRequestAnimationFrame()方法接收一个参数&#xff0c;即在重绘…

Axure 引入元件库

AXURE 里的元件不够使用&#xff0c;想引入一些个性化或符合自己需求的元件怎么办呢&#xff1f;我们可以通过自己建立元件或者引入下载的元件库&#xff0c;那么怎么引入呢&#xff1f;引入的方法有两种&#xff0c;只是形式不一样&#xff0c;但是根本性的原理是一样的。我为…

psql: FATAL: role “postgres” does not exist 解决方案 postgre 无法连接的问题

psql: FATAL: role “postgres” does not exist 解决方案 当时想做的事情&#xff0c;是运行一个创建数据库的脚本。找到的解决方案差不多和下面这个链接相同。 http://stackoverflow.com/questions/15301826/psql-fatal-role-postgres-does-not-exist 实际上做的事情就是logi…