Echarts柱状极坐标风向玫瑰图的实现
echarts代码
本来看到玫瑰图,就看到了echarts示例中饼图的南丁格尔玫瑰图。
发现不太像。后来发现是柱状图极坐标系堆叠图。
一开始用了多个series,图例是能实现,但是显示的图有问题。柱太细了数据一多根本看不见。
使用一个series,图例又没办法绑定到数据。
还好图例比较简单,决定直接自己写一个。
eharts的配置项真的太难了。找的很头疼,实现的也很头疼。
一辈子不想写图表了。
图例的实现
首先根据数据生成一个title数组,因为要判断当前点击和未点击,设置一个flag参数,默认值为1。表示彩色的是未点击。
接下来就是颜色绑定。echarts配置项colors是一个颜色数组。数据会依次从中取色。超过就从头再来。
然后就是点击图例icon,先判断flag,来修改icon颜色,然后获取到点击的index,来设置点击的那个值为0。再次点击从原始的数据根据index中获取到对应的值在赋上去就好了。然后就是图例颜色的判断。超过就从头取。
然后就是图例的分页,我这一页显示12个,根据长度判断一下页数,点击下一页加一,上一页减一,正常的分页。没什么难的。
主要就是配置项比较烦。一定要到echarts配置项中一个一个找。
数据中的方位和极坐标的方位对准。series中data,是个二维数组,第一个表示数据,第二个就表示,对应的angleAxis的下标。
这边根据值,通过findIndex从数组中取一下就好了。
**这边注意:**我一开始用的filter和splice。这样虽然可以实现,但是会改变数组长度,图表的tooltip取得数据的下标,改变了会有问题。
效果如下:
import React, {FC, useEffect, useState} from 'react';
import {getNextOrgId, getDeviceListByFarmId} from '@/services/envirorment_resource';
import ReactECharts from 'echarts-for-react';
import {useNavigate} from 'react-router';
import {message} from 'antd';
import {useTranslation} from 'react-i18next';
import styles from '../styles.module.scss';
import {color, pathArr, i18PageKey} from '../constant';
interface Title {
title: string;
flag: number;
}
const EchartsPageRose: FC<any> = ({picData, dimOrg, searchFormValue, picIndex}) => {
const [titleArr, setTitleArr] = useState<Title[]>([]);
const [echartsData, setEchartsData] = useState<any[]>([]);
const [page, setPage] = useState<number>(1);
const [currentPage, setCurrentPage] = useState<number>(1);
const navigate = useNavigate();
const {t} = useTranslation();
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',
confine: true,
backgroundColor: 'rgba(1,9,13,0.7)',
borderWidth: 1,
borderColor: '#2E3C44',
formatter: (params: any) => {
const data = echartsData[params.seriesIndex];
const tips = data.tips;
// 风向下标
const pathIndex = data.values.findIndex((item: number) => item === params.data);
// 风向
const path = pathArr[pathIndex];
// 频次占比
const pathRate = tips.filter((item: any) => item[0].path === path)[0][3].value;
const infoHtml = `<div style="padding:0;">
<div style="color:white;padding:10px 9px 10px 9px;font-size:16px;font-weight:bold">${t(`${i18PageKey}windRose`)}</div>
<div class=${styles.tooltipHr}></div>
<div class=${styles.styleA}>
<span class=${styles.styleE}>${t(`${i18PageKey}windDirection`)}:</span>
<div class=${styles.styleB}>
<span class=${styles.styleC}>${path}</span>
<span class=${styles.styleD}></span>
</div>
</div>
<div class=${styles.styleA}>
<span class=${styles.styleE}>${t(`${i18PageKey}windSpeedRange`)}:</span>
<div class=${styles.styleB}>
<span class=${styles.styleC}>${params.seriesName}</span>
<span class=${styles.styleD}>m/s</span>
</div>
</div>
<div class=${styles.styleA}>
<span class=${styles.styleE}>${t(`${i18PageKey}fre`)}:</span>
<div class=${styles.styleB}>
<span class=${styles.styleC}>${params.value}</span>
<span class=${styles.styleD}>${t(`${i18PageKey}times`)}</span>
</div>
</div>
<div class=${styles.styleA}>
<span class=${styles.styleE}>${t(`${i18PageKey}ratioFre`)}:</span>
<div class=${styles.styleB}>
<span class=${styles.styleC}>${pathRate}</span>
<span class=${styles.styleD}>%</span>
</div>
</div>
</div>`;
return infoHtml;
},
},
polar: {
radius: '75%',
center: ['40%', '50%'],
},
radiusAxis: {},
series: echartsData.map((item: any) => ({
type: 'bar',
data: item.values,
coordinateSystem: 'polar',
name: item.title,
stack: 'windrose',
emphasis: {
focus: 'series',
},
colorBy: 'series',
})),
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
style={(picIndex + 1) % 3 !== 0 ? {marginRight: '2%'} : {}}
className={styles.windRose}
key={picData}
>
<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 === 22) {
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 EchartsPageRose;
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;
}