【项目-喵内0∞0记账-meowney-07】功能扩展包 DLC-使用统计图表 echarts
大纲
- 统计图表
- 【数据可视化】
Echarts
使用指南 - 在
Vue
记账中加入ECharts
- 【数据可视化】
- 代码仓库xmasuhai/echarts-demo-1
[toc]
统计图表
【数据可视化】Echarts
使用指南
分别介绍
- JS里使用
echarts
- Vue里使用
echarts
- React里使用
echarts
echarts
图表绘制思路
- 获取一个有宽高的
DOM
元素 - 初始化
echarts
实例(const myChart = echarts.init(chartDom)
) - 指定图表的配置项和数据(
option={...}
) - 使用指定的配置项和数据显示图表(
myChart.setOption(chartOptions)
))
引入 echarts
无打包工具(webpack/parcel
)用于快速调试demo
- 直接在
index.html
引入<script src="[path]/echarts.min.js"></script>
,使用bootCDN
- 使用全局变量
window.echarts
src/index.html
|
|
打包工具搭建/调试测试项目
- 安装
http-server
|
|
- 使用
http-server
打开src/index.html
|
|
使用webpack/parcel
(vue/cli
vite
) + TS
用于一般项目
安装(以使用
Parcel 1+
为例)
|
|
- 安装
yarn global add parcel-bundler
而 不是yarn global add parcel
yarn global add parcel
安装的是Parcel 2+
- 安装
yarn add --dev @types/echarts
是为了在WebStorm
中输入代码会有提示 - 遇坑,不显示属性名提示(灰色表示)
- 保证安装包安装在项目目录下的
node_modules
,而不是安装在~/
根目录 - 去掉
设置 > 终端 > “将'node_modules/.bin'从项目根添加到%PATH%”
前面的勾选 - 重新初始化一遍项目
- 保证安装包安装在项目目录下的
src/index.html
中引入入口文件main.js
|
|
src/main.js
|
|
import * as echarts from 'echarts';'
全局引入
或者按需导入模块:
|
|
src/main.js
|
|
使用
parcel
运行
|
|
- 无缓存完整编译完成后,查看效果点击
Server running at http://localhost:1234
CRM学习法
- Copy 去官网炒栗子
- Run 在自己的项目里运行
- Modify 修改代码,理解作用(半黑箱)
echarts
第一个例子
index.html
|
|
- 在绘图前需要为
ECharts
准备一个具备高宽的 DOM 容器<div id="barChart" style="width: 600px; height:400px;"></div>
./modules/barChart.js
|
|
legend.data
与series.name
的名称必须相互对应 ,一致时才能显示项目名称
src/main.js
|
|
增加一个线形图
src/index.html
|
|
src/main.js
|
|
./modules/lineChart.js
|
|
echarts
功能
换主题
ecahrts.init
支持传第二个参数echarts.init(xxx, 'dark')
- 主题默认
default
- 暗系主题
dark
- 亮系主题
light
- 主题默认
WebStorm/VSCode
技巧
- 安装
@types/echarts
加强代码提示- 遇坑,不显示属性名提示(灰色表示)
- 保证安装包安装在项目目录下的
node_modules
,而不是安装在~/
根目录 - 去掉
设置 > 终端 > “将'node_modules/.bin'从项目根添加到%PATH%”
前面的勾选 - 重新初始化一遍项目
- 保证安装包安装在项目目录下的
- 遇坑,不显示属性名提示(灰色表示)
WebStorm
查看历史版本local history > show history
,比较对应变更
细节配置
- 如何修改、提示等
- 如何查看
echarts
术语速查手册
以重构
lineCharts.js
为例
|
|
- 直接将配置写在
myChart.setOption
里,由于安装了@types/echarts
,写属性时会有代码提示
echarts
如何改外观/数据
echarts
术语速查手册- 改线型 项目点样式
series.lineStyle
series.itemStyle
等 - 显示提示文字
tooltip.show
|
|
echarts
更新数据
目前只能显示静态的数据,需要更新数据,只需更新配置后,再次
setOption
即可
option
只需更新需要改的部分配置属性- 但是 坐标轴 等数据需要包括 原来的旧数据 和 新数据
echarts
会自动找出差异,并更新图表- 更复杂的示例
封装模拟数据
storage/chartData.js
|
|
src/modules/lineCharts.js
|
|
src/utils/loadMoreButton.js
|
|
main.js
|
|
index.html
|
|
echarts
展示 loading
- 使用内置的
loading
动画:showLoading() / hideLoading()
显示/隐藏加载中动画 - 事件锁控制触发事件的时机,防止频繁触发
let isLoading = false
- 设定计时器前
myChart.showLoading(); isLoading = true
- 计时器完毕
myChart.hideLoading(); isLoading = false
- 设定计时器前
src/utils/loadMoreButton.js
|
|
echarts
点击事件
让用户可以和图表进行交互
在初始化后的 echarts 实例(
const myChart = echarts.init(chartDom, 'light')
)上使用 APIon
即可
- 只有图表中允许用户操作(比如点击)的部分才能设置用户交互
- 获取
dataIndex
和seriesIndex
- 其他查看文档:ECharts 中的事件和行为
src/utils/clickChart.js
|
|
src/main.js
|
|
echarts
移动端适配
常规技巧
meta:viewport
抄淘宝手机版- 用
JS
获取屏幕宽度设置在div
上- 设定宽高比
- 使用
echarts
提供的媒体查询API
main.m.taobao.com
|
|
iPhoneX
的宽度375px
PC端设计稿是1920*1080
- 用代码获取屏幕宽度,来适配
|
|
echarts
提供的媒体查询功能
baseOption
+media
- 将共有的选项放入
baseOption: {...}
- 将独有的选项放入
media: [{query: {...}, option: {...}}, ...]
- 将共有的选项放入
封装自动获取容器尺寸的方法
./src/utils/fitScreen.js
|
|
src/modules/lineChart.js
|
|
字体
按比例缩放字体
- 图表中的
fontSize
和legend
的大小等默认都是px
单位legend
中的itemWidth
,itemHeight
,itemGap
- 柱状图中的
barWidth
,坐标系中的axisLine
的width
- 传入
vw
,rem
单位是没有用 - 定位方式传的单位可以是百分比,大小尺寸不能
- 不只是字体的问题 各种图的比例也是
- 将实际窗口的大小与设计图窗口大小做比得到要给相对的比率
- 每个单位数值和这个比率相乘即可
|
|
参考
Vue里使用 echarts
自己封装组件
准备一个
vue-index.html
|
|
安装演示用的依赖
|
|
遇坑
- vue版本依赖包(
vue@2.6.11
) (vue-template-compiler@2.6.14
)不匹配 - 把版本号改成一样
yarn add --dev vue-template-compiler@2.6.11
- 在 vue 工程中,安装依赖时,需要
vue
和vue-template-compiler
版本必须保持一致,否则会报错
入口文件
vue-main.js
|
|
入口组件
vue-app.vue
|
|
启动服务运行,自动安装所需依赖
|
|
遇坑
- 注意之前安装的是
yarn global add parcel-bundler
,而 不是yarn global add parcel
- 查看版本是否一致:
parcel --version
或parcel -V
,返回的是否是1.12.5
,非@2.0.0beta
版 - 官方地址:https://www.parces.cn/
- 否则会报错
console: [@vue/compiler-sfc] compileTemplate now requires the
idoption.
.`xxx Uncaught TypeError: _vue.withScopeId is not a function
更换Vue 版本:完整版/runtime版(默认)
package.json
|
|
Vue
引入外部 js 变量和方法
- Vue中引入静态JS文件需要在有特殊含义的路径下,否则无效
store
数据view
展示页面components
组件utils
工具函数vendor
或者libs
第三方库- 脚本代码不放在
assets
或者static
目录下- assets 和 static 的区别- webpack 模板的文档 - Handing Static Assets
assests
放置的是组件的资源,static
放置的是非组件的资源assests
-> bundle(编译到一起) 内容会被 webpack 打包到一起static
下的文件资源作为src路径传入组件中 -> resources(远程URL请求) 浏览器直接去请求文件
Vue
中局部使用 echarts
演示组件
./view/vue-charts.vue
|
|
Vue
中,通过另一种方式获取组件的DOM
,代替使用document.getElementById('...')
- 因为
Vue
是单页面应用,如果将以上的组件使用两次,一个页面内 id 是不允许相同 - 否则会出现第一个组件正常显示,第二个组件无法显示
- 因为
- 使用
Vue
的$refs
对象,只要将组件注册属性ref="xxx"
- 在
mounted
时调用this.$refs.xxx
,避免echarts
的容器还没有生成就进行初始化
引入到
vue-app.vue
|
|
拆开并封装组件
./src/modules/lineChart.js
和./src/store/options/lineChartOptions.js
重构
./src/store/chartData.js
和./src/utils/loadMoreButton.js
|
|
src/modules/lineChart.js
|
|
chartData.js
|
|
src/utils/loadMoreButton.js
|
|
vue-charts.vue
|
|
- 将初始化后的
chart
挂在this
上:this.chart = myChart.setOption({...})
- 可访问
this.chart
- 当外部数据
moreData
改变,触发自定义事件giveMoreData
给父组件vue-app.vue
,并传参this.chart
- 可访问
- 外部数据
props
传入echars
所需的option
vue-app.vue
|
|
Vue
中全局使用 echarts
在项目文件的入口js文
main.js
中引入echarts
,并使用该插件,这样就可以对其进行全局使用
|
|
|
|
封装一个动态渲染数据的Echarts
折线图组件
|
|
引入其第三方封装好的库
参考
- Vue-ECharts v6 发布
- Vue封装引入外部脚本的组件
- Vue.js引入 外部CSS样式 和 外部JS文件 的方法
- Vue 中如何正确引入 第三方模块
- Vue引入第三方js包及调用方法
- Vue 动态加载 远程js 完美实践
- Vue引入 远程JS文件
- Vue 2.x 中的片段 vue-fragment 额外的节点包装器技术 Vue v3 中引入片段功能
- vue页面引入外部js文件遇到的问题
- vue组件内部引入远程js文件
- vue组件内部引入外部js文件的方法
- Parcel+vue 入门实战
代码仓库
React里使用 echarts
自己封装组件
引入其他封装
参考
总结
echarts x Vue x TypeScript
- 监听图表
option
的变化,执行setOption({...})
- 监听图表
echarts x React x TypeScript
什么是数据可视化
- 用
echarts
做页面是最初级的可视化 - 深入
d3.js
- 大屏项目
参考
在 Vue 记账项目中加入 ECharts
- 先完善
Money.vue
和FormItem.vue
组件,添加日期选择组件 - 使用
vue-charts
的官方封装 - 自己封装
echarts
组件
参考
- 设计稿:Figma
- 初始代码:https://github.com/FrankFang/morney-test-9
- 最终代码:https://github.com/FrankFang/morney-test-vue-echarts
添加日期选择组件
重构
FormItem.vue
,添加外部数据type
,控制<input/>
的类型
|
|
Money.vue
|
|
- 查看
<input type="datetime-local"> MDN
兼容性 - 检查
{{record.createdAt}}
显示为202X-XX-XXTXX:XX:XX.***Z
- 去掉毫秒部分
.***Z
之后的值传给input
的value
就可以正常显示日期了 - 格式化
dayjs(isoString).format('YYYY-MM-DDTHH:mm:ss')
- 精确日期到分钟
.format('YYYY-MM-DDTHH:mm')
- 去掉毫秒部分
- 只要日期部分,改用
<input type="date"> MDN
使用vue-charts
的官方封装
安装
echarts@4.8.0
、vue-echarts@4.1.0
和@types/echarts
|
|
引入
|
|
- 由于
vue-echarts
无TS类型声明,改用const ECharts = require('vue-echarts').default;
|
|
配置
Vue CLI 3+
在vue.config.js
中的transpileDependencies
增加vue-echarts
及resize-detector
|
|
抄一个示例
./src/view/Statistics.vue
|
|
使用封装好的组件
<ECharts :options="showEChart"/>
|
|
调整样式
在外部包裹一层
div
,加上echarts-wrapper
样式
|
|
|
|
拉长图表,可滚动显示
- 父元素加上
overflow: auto;
可滚动 - 计算一屏显示7个数据
30 / 7
->4---3
|
|
隐藏滚动条
- 使用伪类选择器
::-webkit-scrollbar {display: none;}
|
|
实现滚动到最新数据
|
|
参考
https://www.codeleading.com/article/80172487941/ | vue设置scrollLeft 一直为0的原因 - 代码先锋网 https://www.geek-share.com/detail/2789848542.html | vue设置scrollLeft 一直为0的原因 - 极客分享 https://www.jc2182.com/javascript/javascript-element-scrollleft-attr.html | JavaScript Element scrollLeft 属性 - 蝴蝶教程 https://blog.csdn.net/a393007511/article/details/103026943 | vue设置scrollLeft 一直为0的原因_a393007511的博客-CSDN博客 https://www.codenong.com/js3a2317be4a44/ | vue绑定scroll-top、scroll-left属性 | 码农家园 https://codepen.io/Jayesh_v/pen/oMgwRO | VueJs Scroll Horizontally https://www.programmersought.com/article/1465747270/ | The VUE acquires the scroll bar position of the element or component according to the ref. - Programmer Sought https://js.devexpress.com/Documentation/ApiReference/UI_Components/dxScrollView/Methods/ | Documentation 21.1: DevExtreme - JavaScript Scroll View Methods https://stackoverflow.com/questions/51222035/horizontal-scroll-using-buttons-in-vuejs | javascript - Horizontal Scroll Using Buttons in VueJS - Stack Overflow https://forum.vuejs.org/t/help-with-scrollleft-interactions/3209 | Help with scrollLeft interactions - Get Help - Vue Forum https://www.cnblogs.com/liAnran/p/12069953.html | vue 横向滚动样式&&$ref.scrollLeft初始化数据滚动位置 - liAnran - 博客园 https://segmentfault.com/q/1010000016689396 | vue关于设置scrollLeft值问题 - SegmentFault 思否 https://blog.csdn.net/qq_39224266/article/details/107958068 | vue 中横向滚动设置scrollLeft,并且加上过渡动画_小白阿里里的博客-CSDN博客 https://my.oschina.net/u/4405061/blog/3326999 | vue 横向滚动样式&&$ref.scrollLeft初始化数据滚动位置 - osc_ozlday8e的个人空间 - OSCHINA - 中文开源技术交流社区 https://github.com/metawin-m/vue-scroll-sync/blob/master/src/ScrollSync.vue | vue-scroll-sync/ScrollSync.vue at master · metawin-m/vue-scroll-sync https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetLeft | HTMLElement.offsetLeft - Web API 接口参考 | MDN https://blog.csdn.net/qq_22222499/article/details/52863951 | 完美理解csss中offsetLeft,offsetWidth,scrollLeft区别。_csdn问鼎-CSDN博客 https://blog.csdn.net/qq_43353619/article/details/86703253 | JS中的offsetLeft和clientLeft和scrollLeft的一些区别_小傲哥哥的博客-CSDN博客 https://stackoverflow.com/questions/18498652/scrollleft-to-end-of-main-div-not-offset-left | javascript - scrollLeft: to END OF MAIN DIV not offset().left - Stack Overflow https://github.com/pramper/blog/issues/10 | 一张图彻底掌握scrollTop, offsetTop, scrollLeft, offsetLeft…… · Issue #10 · pramper/Blog https://juejin.cn/post/6844903443383975949 | 一张图彻底掌握 scrollTop, offsetTop, scrollLeft, offsetLeft…… https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollLeft | Element.scrollLeft - Web API 接口参考 | MDN https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame | window.requestAnimationFrame - Web API 接口参考 | MDN https://stackoverflow.com/questions/41205139/javascript-element-scrollleft-not-working | scroll - javascript element.scrollLeft not working - Stack Overflow https://developer.mozilla.org/zh-CN/docs/Web/API/Window/scroll | Window.scroll() - Web API 接口参考 | MDN https://developer.mozilla.org/zh-CN/docs/Web/API/ScrollToOptions | ScrollToOptions - Web API 接口参考 | MDN https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions#examples | ScrollToOptions - Web APIs | MDN https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollBy | Element.scrollBy() - Web API 接口参考 | MDN
自己封装echarts
./src/components/Chart.vue
|
|
import Chart from '@/components/Statistics/Chart.vue'
中会报错的情况<script lang="ts">
中不写TS'@/components/Statistics/Chart.vue'
路径中没写.vue
构造数据数据排序(计数排序的变形) 前端用到的算法
- 遍历对象时,遍历的顺序是否固定
- 遍历对象的
key
,遍历的顺序是否固定 hashTable
中的顺序是用户输入得到的,输入顺序不一定符合预期
- 遍历对象的
JS 中对象的
key
是有顺序的
- JS 中对象的字段遍历顺序是没有保证的,例如
Object.keys
函数产生的数组顺序没有保证 - 实际上在
ES2015
之后标准规定了key
的顺序(准确的说是规定了[[OwnPropertyKeys]]()
这个内部方法返回的key
的顺序)
结论:
keys
数组分为三个部分:
- 可以作为数组索引的
key
按照升序排列,例如1、2、3
。 - 是字符串不是 symbol 的 key,按照创建顺序排列。
- symbol 类型的 key 也按照创建顺序排列。
- 参考链接 ECMAScript 9.1.11
|
|
原始数据必须转换为一个数组,才能排序
- 按时间排序,近的排在前面
- 显示的顺序为 今天 昨天 (本年内)具体日期 (去年以及之前)具体日期
|
|
首先拷贝原始数据
clone.ts
|
|
- TS 声明类型:
type HashTableValue = { title: string; items: RecordItem[] };
- 查询到对应的
title
,将recordList
排序,依次推入数组 recordList
是RecordItem
的数组,进行排序.sort((a, b) => {})
const n = recordList.sort((a, b) => {a.createdAt});
a.createdAt
的值为字符串,按ASCII
顺序比较大小,不是预期的顺序,'a' - 'b' // NaN
不能用字符串的减法- 使用
.sort()
必须变为数字类型,用.valueOf()
:dayjs(a.createdAt).valueOf()
const newList = recordList.sort((a: RecordItem, b: RecordItem) => ( dayjs(a.createdAt).valueOf() - dayjs(b.createdAt).valueOf() ));
- 需要逆向从近期到远期的
( dayjs(b.createdAt).valueOf() - dayjs(a.createdAt).valueOf() )
- 注意
.sort()
会 改变原数组自身,保留原数组,使用 深克隆clone.ts
- 导入之前的工具函数
import clone from '@/lib/clone.ts';
的clone()
function clone(data: any) { return JSON.parse(JSON.stringify(data)) as RecordItem; }
- 由于接受的参数类型是
any
,而JSON.parse()
返回值类型也是any
- 使用泛型
function clone<T>(data: T): T {...}
统一入参和返回值的类型- 在尖括号中声明类型
- 之后使用
.sort()
TS 会自动推断类型
- 导入之前的工具函数
重构
Statistic.vue
时间排序逻辑
|
|
- 先将局部数据
recordList
排序,再push
到数据中
数据排序后分组
- 判断数组长度
if(recordList.length === 0) {return []};
,确保存在可操作数据 - 取出新的
newList
的第一个数据const x = [{title: dayjs(recordList[0].createdAt).format('YYYY-MM-DD'), items: [recordList[0]]}];
- 从第二个数据的
.createdAt
和第一个数据的title
和开始循环比较- 新的数据和分组的
title
是否一致- 一致放入当前组的
items
- 不一致,作为新的一组的
title
,放入新分组的items
- 一致放入当前组的
- 新的数据和分组的
|
|
统一显示时间戳比较 - 标记问题ok
const localDay = dayjs(current.createdAt.split('T')[0]);
- 由于有时区的概念
dayjs(current.createdAt)
算上时间部分的日期会延后一天 - 封装倒时差函数
clearJetLag(new Date(), '-')
src/lib/clearJetLag.ts
|
|
重构
src/store/modules/recordStore.ts
|
|
重构命名
get groupedList() { ... return rusult;}
更改template
循环渲染的:key
- 数据由对象变为数组 方便排序
li v-for="(group, index) in groupedList" :key="index">
类似桶排序
echarts
的 axisLabel
|
|
当 optioins
变化时,更新 chart
|
|
|
|
完整代码
./src/view/Statistics.vue
|
|
参考