Bootstrap

Echarts风向玫瑰图

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;
}

;