Echarts-丝带图

Echarts-丝带图

demo地址

打开CodePen

什么是丝带图?

丝带图是Power BI中独有额可视化视觉对象,它的工具提示能展示指标当期与下期的数据以及排名。需求:使用丝带图展示"2022年点播订单表"不同月份不同点播套餐对应订单数据。

效果

在这里插入图片描述

思路

由于丝带图是Power BI中独有额可视化视觉对象,所以目前没得任何示例参考,所以只能自己构思使用echarts还原了。当然还有完善的余地,中间的连线不够平滑,可根据产品需求采用某种曲线函数去生成一组点位。

1. 以散点图画出柱状堆叠效果(柱状图的堆叠图无法满足hover小块效果)- y轴分成100个刻度,每个刻度代表1%,以控制大数据视图效果
2. 在柱状图两根柱之间构建6个点,使用面积图,连接2块柱- 柱中间点位取的是y轴的平均值- (若想构建的曲线细腻,可以使用曲线函数来构建这部分的点)
3. 再使用上面6个点中的下面点绘制透明区域

核心代码

  • 以散点图构建柱状图
function createOption(initData) {const initDataResult = createData(initData);const { list, legendData, xAxisData, seriesDataMap, max } = initDataResult;const seriesData = [];for (const seriesIndex in Object.keys(seriesDataMap)) {const name = Object.keys(seriesDataMap)[seriesIndex];const data = seriesDataMap[name];seriesData.push({name,type: 'scatter',symbol: 'rect',z: 3,itemStyle: {opacity: 1},label: {show: true,color: '#fff',formatter: (params) => formatMoney(params.data.realValue, 0)},tooltip: {trigger: 'item',formatter: (params) => {return `<div><div>年度月份:${params.name}</div><div>${params.seriesName}${formatMoney(params.data.realValue, 0)}</div></div>`;}},data: getChartData({ data, name })});}function getChartData({ data = [], name }) {const dataResult = [];data?.forEach((value, dateIndex) => {const y = maxY * (value / max);const ySize = maxHeight * (y / maxY);const offset = getOffset({ list, dateIndex, name, max });const radioValue = y + offset > 100 ? 100 : y + offset;dataResult.push({name,value: radioValue,radioValue,realValue: value,symbolOffset: [0, '50%'],symbolSize: [50, ySize]});if (dateIndex < data?.length - 1) {new Array(3).fill(0).forEach((_, lineIndex) => {dataResult.push({value: '',radioValue,realValue: value,isLine: true,lineIndex});});}});return dataResult;}const lineSeries = createLineChart({ seriesData, initDataResult });return {option: {legend: {data: legendData},xAxis: {data: xAxisData,axisTick: {show: false}},series: [...seriesData, ...lineSeries]}};
}
  • 生成折线图数据
function getLineData(data, name, isSpace = false) {const result = data?.map((_, index) => {const dateIndex = Math.floor(index / 4);const lineIndex = index % 4;const item = data?.[index] || {};const lastItem = data?.[index - (4 - lineIndex)] || {};const nextItem = data?.[index + (4 - lineIndex)] || {};const offset = getOffset({ list, dateIndex, name, max });const nextOffset = getOffset({ list, dateIndex: dateIndex + 1, name, max });let spaceValue;let value = item.radioValue - offset;switch (lineIndex) {case 0:spaceValue = offset;break;case 1:spaceValue = offset;if (!nextItem?.radioValue) {value = undefined;}break;case 2:spaceValue = (nextOffset + offset) / 2;value = (nextItem.radioValue + item.radioValue) / 2 - spaceValue;break;case 3:spaceValue = nextOffset;value = nextItem.radioValue - nextOffset;if (!lastItem?.radioValue) {value = undefined;}break;}if (!lastItem?.radioValue && !nextItem?.radioValue) {value = undefined;}// console.log(lineIndex, item, offset, nextOffset, spaceValue, value);const newItem = {...item,value: isSpace ? spaceValue : value};return newItem;});// console.log('result', result);return result;
}
  • 生成折线图配置
function createLineChart({ seriesData = [], initDataResult }) {const { list, max } = initDataResult;const spaceLineSeries = [];const lineSeries = [];// console.log('seriesData', seriesData);for (const seriesIndex in seriesData) {const seriesItem = seriesData[seriesIndex];const defaultLineSeries = {type: 'line',name: seriesItem.name,stack: `Line-${seriesIndex}`,smooth: 0.3,lineStyle: {width: 0,opacity: 0},symbol: 'none',showSymbol: false,triggerLineEvent: true,silent: true,areaStyle: {},emphasis: {focus: 'series'}};spaceLineSeries.push({...defaultLineSeries,areaStyle: {opacity: 0},data: getLineData(seriesItem?.data, seriesItem.name, true)});lineSeries.push({...defaultLineSeries,data: getLineData(seriesItem?.data, seriesItem.name)});}function getLineData(data, name, isSpace = false) {const result = data?.map((_, index) => {const dateIndex = Math.floor(index / 4);const lineIndex = index % 4;const item = data?.[index] || {};const lastItem = data?.[index - (4 - lineIndex)] || {};const nextItem = data?.[index + (4 - lineIndex)] || {};const offset = getOffset({ list, dateIndex, name, max });const nextOffset = getOffset({ list, dateIndex: dateIndex + 1, name, max });let spaceValue;let value = item.radioValue - offset;switch (lineIndex) {case 0:spaceValue = offset;break;case 1:spaceValue = offset;if (!nextItem?.radioValue) {value = undefined;}break;case 2:spaceValue = (nextOffset + offset) / 2;value = (nextItem.radioValue + item.radioValue) / 2 - spaceValue;break;case 3:spaceValue = nextOffset;value = nextItem.radioValue - nextOffset;if (!lastItem?.radioValue) {value = undefined;}break;}if (!lastItem?.radioValue && !nextItem?.radioValue) {value = undefined;}// console.log(lineIndex, item, offset, nextOffset, spaceValue, value);const newItem = {...item,value: isSpace ? spaceValue : value};return newItem;});// console.log('result', result);return result;}return [...spaceLineSeries, ...lineSeries];
}

完整代码

var dom = document.getElementById('chart-container');
var myChart = echarts.init(dom, null, {renderer: 'canvas',useDirtyRect: false
});
var app = {};var option;const defaultData = [{date: '2022年02月',list: [{name: '安列克-常州四药',value: 48196},{name: '贝克宁-成都贝特',value: 85944},{name: '瀚宝-深圳瀚宇',value: 43122},{name: '卡贝缩宫素-杭州澳亚',value: 46082},{name: '卡贝缩宫素-天吉生物',value: 28473},{name: '卡贝缩宫素-星银药业',value: 20584}]},{date: '2022年03月',list: [{name: '安列克-常州四药',value: 97775},{name: '贝克宁-成都贝特',value: 134262},{name: '瀚宝-深圳瀚宇',value: 102538},{name: '卡贝缩宫素-杭州澳亚',value: 77479},{name: '卡贝缩宫素-天吉生物',value: 59422},{name: '卡贝缩宫素-星银药业',value: 32413}]},{date: '2022年04月',list: [{name: '安列克-常州四药',value: 91399},{name: '贝克宁-成都贝特',value: 151064},{name: '瀚宝-深圳瀚宇',value: 74733},{name: '卡贝缩宫素-杭州澳亚',value: 75197},{name: '卡贝缩宫素-天吉生物',value: 46853},{name: '卡贝缩宫素-星银药业',value: 24845}]},{date: '2022年05月',list: [{name: '安列克-常州四药',value: 83667},{name: '贝克宁-成都贝特',value: 114716},{name: '瀚宝-深圳瀚宇',value: 57327},{name: '卡贝缩宫素-杭州澳亚',value: 62267},{name: '卡贝缩宫素-天吉生物',value: 38604},{name: '卡贝缩宫素-星银药业',value: 19766}]},{date: '2022年06月',list: [{name: '安列克-常州四药',value: 80524},{name: '贝克宁-成都贝特',value: 155227},{name: '瀚宝-深圳瀚宇',value: 67098},{name: '卡贝缩宫素-杭州澳亚',value: 61857},{name: '卡贝缩宫素-天吉生物',value: 44098},{name: '卡贝缩宫素-星银药业',value: 26956}]},{date: '2022年07月',list: [{name: '安列克-常州四药',value: 92172},{name: '贝克宁-成都贝特',value: 118129},{name: '瀚宝-深圳瀚宇',value: 61548},{name: '卡贝缩宫素-杭州澳亚',value: 64490},{name: '卡贝缩宫素-天吉生物',value: 38073},{name: '卡贝缩宫素-星银药业',value: 21705}]},{date: '2022年08月',list: [{name: '安列克-常州四药',value: 94615},{name: '贝克宁-成都贝特',value: 119397},{name: '瀚宝-深圳瀚宇',value: 60547},{name: '卡贝缩宫素-杭州澳亚',value: 73835},{name: '卡贝缩宫素-天吉生物',value: 37406},{name: '卡贝缩宫素-星银药业',value: 26228}]}
]function formatMoney(money) {return money
}function run({ data = defaultData, height = 500 }) {const chartHeight = height;const maxY = 100;const maxHeight = chartHeight - maxY;function createData(initData = []) {const list = initData?.map((item) => ({...item,total: item.list.reduce((pre, cur) => pre + cur.value, 0),list: item.list?.sort((a, b) => a.value - b.value)}));const legendData = [];const xAxisData = [];const seriesDataMap = {};let max = 0;// 生成x轴、图例数据for (const dateIndex in list) {const item = list[dateIndex];xAxisData.push(item.date);if (dateIndex < list?.length - 1) {new Array(3).fill(0).forEach((_, lineIndex) => {xAxisData.push(`line-${lineIndex}`);});}max = Math.max(max, item.total);for (const index in item.list) {const dataItem = item.list[index];if (!legendData?.includes(dataItem.name)) {legendData.push(dataItem.name);}}}// 根据图例生成数据for (const index in list) {const item = list[index];for (const name of legendData) {const dataItem = item?.list?.find((dataItem) => dataItem.name === name);_.set(seriesDataMap, `${name}.${index}`, dataItem?.value);}}const result = { list, legendData, xAxisData, seriesDataMap, max };// console.log('result', result);return result;}function createLineChart({ seriesData = [], initDataResult }) {const { list, max } = initDataResult;const spaceLineSeries = [];const lineSeries = [];// console.log('seriesData', seriesData);for (const seriesIndex in seriesData) {const seriesItem = seriesData[seriesIndex];const defaultLineSeries = {type: 'line',name: seriesItem.name,stack: `Line-${seriesIndex}`,smooth: 0.3,lineStyle: {width: 0,opacity: 0},symbol: 'none',showSymbol: false,triggerLineEvent: true,silent: true,areaStyle: {},emphasis: {focus: 'series'}};spaceLineSeries.push({...defaultLineSeries,areaStyle: {opacity: 0},data: getLineData(seriesItem?.data, seriesItem.name, true)});lineSeries.push({...defaultLineSeries,data: getLineData(seriesItem?.data, seriesItem.name)});}function getLineData(data, name, isSpace = false) {const result = data?.map((_, index) => {const dateIndex = Math.floor(index / 4);const lineIndex = index % 4;const item = data?.[index] || {};const lastItem = data?.[index - (4 - lineIndex)] || {};const nextItem = data?.[index + (4 - lineIndex)] || {};const offset = getOffset({ list, dateIndex, name, max });const nextOffset = getOffset({ list, dateIndex: dateIndex + 1, name, max });let spaceValue;let value = item.radioValue - offset;switch (lineIndex) {case 0:spaceValue = offset;break;case 1:spaceValue = offset;if (!nextItem?.radioValue) {value = undefined;}break;case 2:spaceValue = (nextOffset + offset) / 2;value = (nextItem.radioValue + item.radioValue) / 2 - spaceValue;break;case 3:spaceValue = nextOffset;value = nextItem.radioValue - nextOffset;if (!lastItem?.radioValue) {value = undefined;}break;}if (!lastItem?.radioValue && !nextItem?.radioValue) {value = undefined;}// console.log(lineIndex, item, offset, nextOffset, spaceValue, value);const newItem = {...item,value: isSpace ? spaceValue : value};return newItem;});// console.log('result', result);return result;}return [...spaceLineSeries, ...lineSeries];}function createOption(initData) {const initDataResult = createData(initData);const { list, legendData, xAxisData, seriesDataMap, max } = initDataResult;const seriesData = [];for (const seriesIndex in Object.keys(seriesDataMap)) {const name = Object.keys(seriesDataMap)[seriesIndex];const data = seriesDataMap[name];seriesData.push({name,type: 'scatter',symbol: 'rect',z: 3,itemStyle: {opacity: 1},label: {show: true,color: '#fff',formatter: (params) => formatMoney(params.data.realValue, 0)},tooltip: {trigger: 'item',formatter: (params) => {return `<div><div>年度月份:${params.name}</div><div>${params.seriesName}${formatMoney(params.data.realValue, 0)}</div></div>`;}},data: getChartData({ data, name })});}function getChartData({ data = [], name }) {const dataResult = [];data?.forEach((value, dateIndex) => {const y = maxY * (value / max);const ySize = maxHeight * (y / maxY);const offset = getOffset({ list, dateIndex, name, max });const radioValue = y + offset > 100 ? 100 : y + offset;dataResult.push({name,value: radioValue,radioValue,realValue: value,symbolOffset: [0, '50%'],symbolSize: [50, ySize]});if (dateIndex < data?.length - 1) {new Array(3).fill(0).forEach((_, lineIndex) => {dataResult.push({value: '',radioValue,realValue: value,isLine: true,lineIndex});});}});return dataResult;}const lineSeries = createLineChart({ seriesData, initDataResult });return {option: {legend: {data: legendData},xAxis: {data: xAxisData,axisTick: {show: false}},series: [...seriesData, ...lineSeries]}};}function getOffset({ list, dateIndex, name, max }) {const dateData = list[dateIndex]?.list || [];const itemIndex = dateData?.findIndex((item) => item.name === name);let offset = 0;for (let i = 0; i < itemIndex; i++) {const itemValue = dateData[i].value;offset += maxY * (itemValue / max);}return offset;}const { option: newOption } = createOption(data);return _.merge({grid: {top: 40,left: 20,right: 20,bottom: 40,containLabel: true},yAxis: {show: false,max: maxY},tooltip: {// show: true,// trigger: 'axis',// axisPointer: {//   type: 'none'// },// formatter: (params, ticket) => {//   // console.log('params', params, ticket);//   return '';// }},dataZoom: [{type: 'slider',filterMode: 'weakFilter',showDataShadow: false,showDetail: false,brushSelect: false,height: 20,bottom: 10,startValue: 1,endValue: 5,xAxisIndex: 0,start: 0,end: 100}],xAxis: {type: 'category',data: newOption.xAxis.data,axisLabel: {formatter: function (value) {return value?.includes('line') ? '' : value;}}}},newOption);
}function getOption(data, height) {return run({ data, height });
}option = getOption(defaultData);if (option && typeof option === 'object') {myChart.setOption(option);
}window.addEventListener('resize', myChart.resize);

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

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

相关文章

vim之一键替换

Vim的substitute命令是一个非常强大的文本替换工具&#xff0c;它允许用户在整个文件或指定范围内执行文本替换操作。 命令格式 substitute命令的基本格式如下&#xff1a; :[range]s[ubstitute]/{pattern}/{string}/[flags] 其中&#xff1a; [range] 指定替换操作的范围…

搭建HBase2.x完全分布式集群(CentOS 9 + Hadoop3.x)

Apache HBase™是一个分布式、可扩展、大数据存储的Hadoop数据库。 当我们需要对大数据进行随机、实时的读/写访问时&#xff0c;可以使用HBase。这个项目的目标是在通用硬件集群上托管非常大的表——数十亿行X数百万列。Apache HBase是一个开源、分布式、版本化的非关系数据库…

宝塔手动安装grafana

1.下载 # 进入目标目录 cd /data/prometheus/ # 下载 wget https://dl.grafana.com/oss/release/grafana-8.0.4-1.x86_64.rpm # 安装 sudo yum install grafana-8.0.4-1.x86_64.rpm 2.运行项目 # 启动 /etc/init.d/grafana-server start 3.修改配置文件全局搜索 defaults.i…

【AIGC调研系列】llama 3与GPT4相比的优劣点

Llama 3与GPT-4相比&#xff0c;各有其优劣点。以下是基于我搜索到的资料的详细分析&#xff1a; Llama 3的优点&#xff1a; 更大的数据集和参数规模&#xff1a;Llama 3基于超过15T token的训练&#xff0c;这相当于Llama 2数据集的7倍还多[1][3]。此外&#xff0c;它拥有4…

Ceph学习 -11.块存储RBD接口

文章目录 RBD接口1.基础知识1.1 基础知识1.2 简单实践1.3 小结 2.镜像管理2.1 基础知识2.2 简单实践2.3 小结 3.镜像实践3.1 基础知识3.2 简单实践3.3 小结 4.容量管理4.1 基础知识4.2 简单实践4.3 小结 5.快照管理5.1 基础知识5.2 简单实践5.3 小结 6.快照分层6.1 基础知识6.2…

【Python】上下文管理协议__enter__和__exit__函数

部分程序中&#xff0c;可能事先要准备资源&#xff0c;事后做清理工作。 with方法就是python的非常酷的语句&#xff0c;安全可靠&#xff0c;方便。我们自己的类如何具备with的能力?必须拥有__enter__()函数和一个__exit__()函数&#xff0c;本章节介绍两个函数的能力。 对于…

微信小程序使用 Vant Weapp 中 Collapse 折叠面板 的问题!

需求&#xff1a;结合Tab 标签页 和 Collapse 折叠面板 组合成显示课本和章节内容&#xff0c;并且用户体验要好点&#xff01; 如下图展示&#xff1a; 问题&#xff1a;如何使用Collapse 折叠面板 将内容循环展示出来&#xff1f; js中的数据是这样的 代码实现&#xff1…

draw.io: 开启图表绘制的无限可能

图表是沟通和呈现复杂信息的有效工具&#xff0c;在工作、学习甚至生活中都有广泛的应用。作为一款在线图表软件&#xff0c;draw.io提供了简单、直观又功能丰富的界面&#xff0c;让任何人都可以轻松创建专业水准的图表。接下来&#xff0c;我将分享我深入使用draw.io的经验&a…

【C语言】归并排序算法实现

一、归并排序原理二、归并排序实现三、归并排序优化四、总结 归并排序&#xff08;Merge Sort&#xff09;是一种经典的排序算法&#xff0c;由于其采用了分治策略&#xff0c;因此在处理大数据集时表现出了较好的性能。本文将详细介绍归并排序的原理、实现以及优化方法&#x…

Python | Leetcode Python题解之第39题组合总和

题目&#xff1a; 题解&#xff1a; from typing import Listclass Solution:def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:def dfs(candidates, begin, size, path, res, target):if target < 0:returnif target 0:res.append(p…

Stability AI 发布 SD3 API:开启人工智能新篇章

文章目录 1.Stable Diffusion 3 API开放了! 2.Stability AI Document地址3.获取API Key4.API方式调用SD3出图接口地址接口请求规范接口请求响应结果 5.Stable Diffusion 3.0、Stable Image Core、Fooocus 2.3.1、MidJounery效果查看 1.Stable Diffusion 3 API开放了! Stabilit…

竞态问题 + axios 取消请求

目录 1&#xff0c;问题描述2&#xff0c;解决1&#xff0c;丢弃错误的结果2&#xff0c;取消之前的请求在页面中使用 3&#xff0c;其他4&#xff0c;潜在的问题 1&#xff0c;问题描述 在日常开发中&#xff0c;可能会有以下场景&#xff1a; 有多个 tab 页&#xff0c;每个…

react 响应式栅格布局

遇到一个小问题 , 有很多的下拉框放在了一行的盒子里 用到了栅格思路 , 但响应式处理屏幕时候右侧的按钮会覆盖掉样式 之前我的思路是子绝父相 , 将按钮定在最右侧 , 按钮和下拉框都在同一盒子中 , 且做了栅格处理没想到还是会覆盖解决 : 后来我用到了 margin-left: auto 来让…

蓝桥杯 BASIC-25 基础练习 回形取数

蓝桥杯 BASIC-25 基础练习 回形取数 问题描述 回形取数就是沿矩阵的边取数&#xff0c;若当前方向上无数可取或已经取过&#xff0c; 则左转90度。一开始位于矩阵左上角&#xff0c;方向向下。 输入格式 输入第一行是两个不超过200的正整数m, n&#xff0c;表示矩阵的行和列。 …

RedisHttpSession反序列化UID问题跟踪

1.RedisHttpSession配置 package com.visy.configure;import org.springframework.context.annotation.Configuration; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;Configuration EnableRedisHttpSession(maxInactive…

安卓手机APP开发__媒体开发部分__直播流

安卓手机APP开发__媒体开发部分__直播流 目录 概述 检查和监控直播的播放 在直播流中的定位查找 直播播放的用户界面 配置直播播放的参数 播放速度调整 定制播放速度的调整算法 直播窗口背后的异常和ERROR_CODE_BEHIND_LIVE_WINDOW 概述 ExoPlayer没有任何特殊配置的…

蓝桥杯2024年第十五届省赛真题-宝石组合

思路&#xff1a;参考博客&#xff0c;对Ha,Hb,Hc分别进行质因数分解会发现&#xff0c;S其实就等于Ha&#xff0c;Hb&#xff0c;Hc的最大公约数&#xff0c;不严谨推导过程如下&#xff08;字丑勿喷&#xff09;&#xff1a; 找到此规律后&#xff0c;也不能枚举Ha&#xff…

OpenCV——Niblack局部阈值二值化方法

目录 一、Niblack算法1、算法概述2、参考文献二、代码实现三、结果展示OpenCV——Niblack局部阈值二值化方法由CSDN点云侠原创,爬虫自重。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、Niblack算法 1、算法概述 Niblack 算法是一种典型的局部阈值…

JavaWeb开发05-事务管理-AOP概述-AOP通知类型-通知顺序-切入点表达式-连接点-案例

一、事务管理 1.事务回顾 2.Spring事务管理 删除部门和删除部门下的员工应该绑定在一起&#xff0c;但是如果这两个操作之间出现错误&#xff0c;就会执行删除部门不删除员工&#xff0c;所以需要使用事务将两个任务绑定在一起&#xff0c;要么一起成功要么一起失败 Spring中如…

如何通过自动化采购管理软件解决 6 个采购挑战?

采购需要精确性、可预测性以及敏捷而自信地解决问题的能力。灵活采购流程可以帮助企业更快地适应市场变化和供应链中断。 借助正确的工具&#xff0c;团队可以实现这些目标。 采用流程自动化技术的主要好处 当被问及采用流程自动化工具带来的好处时&#xff0c;三分之二的高…