Echarts柱状极坐标风向玫瑰图的实现
echarts代码
本来看到玫瑰图,就看到了echarts示例中饼图的南丁格尔玫瑰图。
发现不太像。后来发现是柱状图极坐标系堆叠图。 一开始用了多个series,图例是能实现,但是显示的图有问题。柱太细了数据一多根本看不见。 使用一个series,图例又没办法绑定到数据。
还好图例比较简单,决定直接自己写一个。
eharts的配置项真的太难了。找的很头疼,实现的也很头疼。
一辈子不想写图表了。
图例的实现
首先根据数据生成一个title数组,因为要判断当前点击和未点击,设置一个flag参数,默认值为1。表示彩色的是未点击。 接下来就是颜色绑定。echarts配置项colors是一个颜色数组。数据会依次从中取色。超过就从头再来。 但是经过测试发现,colors数组的长度最多是16个。超过16个后就继续重头开始了。
然后就是点击图例icon,先判断flag,来修改icon颜色,然后获取到点击的index,来设置点击的那个值为0。再次点击从原始的数据根据index中获取到对应的值在赋上去就好了。然后就是图例颜色的判断。超过16个从头取。 然后就是图例的分页,我这一页显示12个,根据长度判断一下页数,点击下一页加一,上一页减一,正常的分页。没什么难的。
主要就是配置项比较烦。一定要到echarts配置项中一个一个找。
数据中的方位和极坐标的方位对准。series中data,是个二维数组,第一个表示数据,第二个就表示,对应的angleAxis的下标。 这边根据值,通过findIndex从数组中取一下就好了。
**这边注意:**我一开始用的filter和splice。这样虽然可以实现,但是会改变数组长度,图表的tooltip取得数据的下标,改变了会有问题。
效果如下:
import React, {FC, useEffect, useState} from 'react';
import ReactECharts from 'echarts-for-react';
import styles from '../styles.module.scss';
import {color, pathArr} from '../constant';
interface Title {
title: string;
flag: number;
}
const EchartsPageB: FC<any> = ({picData}) => {
const [titleArr, setTitleArr] = useState<Title[]>([]);
const [echartsData, setEchartsData] = useState<any[]>([]);
const [page, setPage] = useState<number>(1);
const [currentPage, setCurrentPage] = useState<number>(1);
useEffect(() => {
setEchartsData(picData.series);
const count = picData.series.length % 12;
if (count === 0) {
setPage(picData.series.length / 12);
} else {
const str = String(picData.series.length / 12);
setPage(Number(parseInt(str, 10)) + 1);
}
setTitleArr(picData.series.map((item: Title) => ({title: item.title, flag: 1})));
}, [picData]);
const option = {
grid: {
left: 50,
right: 50,
bottom: 0,
},
angleAxis: {
type: 'category',
axisTick: false,
boundaryGap: false,
splitLine: {
show: true,
lineStyle: {
color: '#8d979d',
},
},
axisLabel: {color: '#8d979d'},
data: pathArr,
},
tooltip: {
trigger: 'item',
position(pos: number[], params: any, dom: any, rect: any, size: any) {
interface Obj {
left: string | number;
top: number | string;
bottom: number | string;
}
const obj: Obj = {left: '', top: '', bottom: ''};
const count: boolean = pos[1] < size.viewSize[1] / 2;
if (count) {
obj.bottom = 10;
} else {
obj.top = 10;
}
obj.left = pos[0] < size.viewSize[0] / 2 ? pos[0] : pos[0] - size.contentSize[0];
return obj;
},
backgroundColor: 'rgba(1,9,13,0.7)',
borderWidth: 1,
borderColor: '#2E3C44',
formatter: (params: any) => {
const data = picData.series[params.dataIndex].tips[0];
const infoHtml = `<div style="width:250px;padding:0;">
<div style="color:white;padding:10px 9px 10px 9px;font-size:16px;font-weight:bold">风向玫瑰图</div>
<div class=${styles.tooltipHr}></div>
<div class=${styles.styleA}>
<span class=${styles.styleE}>风向:</span>
<div class=${styles.styleB}>
<span class=${styles.styleC}>${data[0].path}</span>
<span class=${styles.styleD}></span>
</div>
</div>
<div class=${styles.styleA}>
<span class=${styles.styleE}>风速区间:</span>
<div class=${styles.styleB}>
<span class=${styles.styleC}>${data[2].value}</span>
<span class=${styles.styleD}>m/s</span>
</div>
</div>
<div class=${styles.styleA}>
<span class=${styles.styleE}>频次:</span>
<div class=${styles.styleB}>
<span class=${styles.styleC}>${data[0].value}</span>
<span class=${styles.styleD}>次</span>
</div>
</div>
<div class=${styles.styleA}>
<span class=${styles.styleE}>频次占比:</span>
<div class=${styles.styleB}>
<span class=${styles.styleC}>${data[3].value}</span>
<span class=${styles.styleD}>%</span>
</div>
</div>
</div>`;
return infoHtml;
},
},
polar: {
radius: '80%',
center: ['42%', '50%'],
},
radiusAxis: {},
series: [
{
type: 'bar',
data: echartsData.map((item: any) => {
const pathIndex = pathArr.findIndex((k) => k === item.tips[0][0].path);
const newValue = item.values[0];
const dataArr = [newValue, pathIndex];
return dataArr;
}),
coordinateSystem: 'polar',
colorBy: 'data',
stack: 'path',
},
],
color,
};
const changeLegend = (item: Title, index: number) => {
if (item.flag === 1) {
const newTitleArr = titleArr;
newTitleArr[index].flag = 0;
setTitleArr(newTitleArr);
const newArr = echartsData.map((k: any, j: number) => {
const obj = {...k};
if (index === j) {
obj.values = [0];
}
return obj;
});
setEchartsData(Object.assign([...newArr]));
} else if (item.flag === 0) {
const newTitleArr = titleArr;
newTitleArr[index].flag = 1;
setTitleArr(newTitleArr);
const arr = picData.series.filter((k: any) => k.title === item.title);
const newArr = echartsData.map((x: any, j: number) => {
const obj = {...x};
if (index === j) {
obj.values = arr[0].values;
}
return obj;
});
setEchartsData(Object.assign([...newArr]));
}
};
let count = -1;
return (
<div className={styles.windRose}>
<div className={styles.title}>
<span className={styles.dot} />
<span className={styles.stationName}>{picData.title}</span>
</div>
<ReactECharts option={option} style={{height: '400px'}} />
<div className={styles.legend}>
{titleArr.map((item: Title, index: number) => {
if (count === 15) {
count = -1;
}
count += 1;
if (index + 1 > (currentPage - 1) * 12 && index + 1 <= currentPage * 12) {
return (
<div className={styles.legendItem} key={item.title}>
<span
className={styles.legendIcon}
style={{background: item.flag ? color[count] : '#6c7379'}}
onClick={() => {
changeLegend(item, index);
}}
/>
<span className={styles.legendName}>{item.title}</span>
</div>
);
}
return '';
})}
<div className={styles.changePage}>
<span
className={styles.changePageA}
onClick={() => {
if (currentPage === 1) {
return;
}
setCurrentPage(currentPage - 1);
}}>
{'<'}
</span>
<span className={styles.changePageB}>{currentPage}</span>
<span className={styles.changePageC}>{`/ ${page}`}</span>
<span
className={styles.changePageD}
onClick={() => {
if (currentPage === page) {
return;
}
setCurrentPage(currentPage + 1);
}}>
{'>'}
</span>
</div>
</div>
</div>
);
};
export default EchartsPageB;
constant代码
export const pathArr = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'];
export const color = [
'#5470c6',
'#91aa75',
'#fac858',
'#ee6666',
'#73c0de',
'#3bd272',
'#fc8452',
'#9a60b4',
'#ea7ccc',
'#f00a5d',
'#9a60b4',
'#e07ccc',
'#df0a1a',
"#c14cac",
'#f08300',
'#91ee75',
];
样式代码
.windRose {
display: flex;
position: relative;
flex-direction: column;
width: 32%;
margin-bottom: 10px;
.title {
background-color: #0a445a;
display: flex;
align-items: center;
border-radius: 7px;
height: 30px;
margin-bottom: 10px;
.dot {
background-color: #097488;
margin-left: 10px;
margin-right: 10px;
border-radius: 2px;
width: 5px;
height: 17px;
}
.stationName {
font-size: 17px;
}
}
.legend {
position: absolute;
right: 0px;
top: 60px;
display: flex;
flex-direction: column;
width: 110px;
height: 350px;
overflow: hidden;
.legendItem {
display: flex;
width: 110px;
align-items: center;
justify-content: space-between;
margin-bottom: 7px;
.legendIcon {
width: 12px;
height: 12px;
border-radius: 2px;
cursor: pointer;
}
.legendName {
color: #fff;
width: 80px;
}
}
.changePage {
display: flex;
align-items: center;
justify-content: space-between;
height: 30px;
width: 100%;
position: absolute;
bottom: 0;
right: 0;
background-color: #0a445a;
.changePageA {
width: 25px;
height: 25px;
text-align: center;
border: 1px solid #00e4ff;
border-radius: 4px;
color: #00e4ff;
cursor: pointer;
}
.changePageB {
width: 25px;
height: 25px;
text-align: center;
border: 1px solid #00e4ff;
border-radius: 4px;
color: #fff;
}
.changePageC {
color: #fff;
}
.changePageD {
width: 25px;
height: 25px;
text-align: center;
border: 1px solid #00e4ff;
border-radius: 4px;
color: #00e4ff;
cursor: pointer;
}
}
}
}
.echartsBox {
overflow-y: scroll;
height: calc(100% - 48px);
.windFrqBox {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.windRoseBox {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
}
.tooltipHr {
background: #4c555a;
width: 250px;
height: 2px;
margin-bottom: 15px;
}
.styleA {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 9px 0 9px;
margin-bottom: 10px;
}
.styleE {
color: #fff;
font-size: 14px;
font-weight: bold;
}
.styleB {
display: flex;
align-items: center;
}
.styleC {
color: #00e4ff;
font-size: 14px;
margin-right: 8px;
}
.styleD {
color: #6c7379;
width: 30px;
}
|