Advertisement

使用 React 和 ECharts 创建地球模拟扩散和飞线效果

阅读量:

在本博客中,我们将学习如何使用 React 和 ECharts 创建一个酷炫的地球模拟扩散效果。我们将使用 ECharts 作为可视化库,以及 React 来构建我们的应用。地球贴图在文章的结尾。

最终效果

3d地球最终效果

准备工作

首先,确保你已经安装了 React,并创建了一个新的 React 应用。如果你还没有安装 React,可以使用以下命令:

复制代码
    npx create-react-app earth-echarts-demo
    
    
      
    
    AI写代码

然后进入项目目录:

复制代码
    cd earth-echarts-demo
    
    
      
    
    AI写代码

接下来,我们需要安装 ECharts:

复制代码
    npm install echarts --save
    npm install echarts-gl --save
    
    
      
      
    
    AI写代码

创建index.d.ts文件

在typescript中,为了使用不包含typescript类型的库,需要创建一个types文件夹,在下面创建一个index.d.ts文件,然后引入需要使用的库。

  • index.d.ts
复制代码
    declare module 'echarts-gl/components';
    declare module 'lodash';
    
    
      
      
    
    AI写代码

创建 EChartOption 类型

复制代码
    // 声明ECharts的数据类型
    import * as echarts from 'echarts/core';
    import {
      DatasetComponentOption,
      DataZoomComponentOption,
      GridComponentOption,
      LegendComponentOption,
      TitleComponentOption,
      ToolboxComponentOption,
      TooltipComponentOption,
      GeoComponentOption
    } from 'echarts/components';
    import {
      BarSeriesOption,
      LineSeriesOption,
      PieSeriesOption,
      GaugeSeriesOption,
      ScatterSeriesOption
    } from 'echarts/charts';
    
    export type EChartOption = echarts.ComposeOption<
      | DatasetComponentOption
      | DataZoomComponentOption
      | GridComponentOption
      | LegendComponentOption
      | TitleComponentOption
      | ToolboxComponentOption
      | TooltipComponentOption
      | LineSeriesOption
      | BarSeriesOption
      | PieSeriesOption
      | GaugeSeriesOption
      | GeoComponentOption
      | ScatterSeriesOption
    >;
    
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

创建 CommonEcahrt 组件

复制代码
    import React, {
      ForwardedRef,
      useEffect,
      useImperativeHandle,
      useRef
    } from 'react';
    import * as echarts from 'echarts/core';
    import { EChartsType } from 'echarts/core';
    import {
      DatasetComponent,
      DataZoomComponent,
      GeoComponent,
      GraphicComponent,
      GridComponent,
      LegendComponent,
      MarkPointComponent,
      PolarComponent,
      TitleComponent,
      ToolboxComponent,
      TooltipComponent
    } from 'echarts/components';
    import {
      BarChart,
      EffectScatterChart,
      GaugeChart,
      LineChart,
      MapChart,
      PieChart,
      RadarChart,
      ScatterChart
    } from 'echarts/charts';
    import { UniversalTransition } from 'echarts/features';
    import { CanvasRenderer } from 'echarts/renderers';
    import { ECElementEvent } from 'echarts/types/src/util/types';
    import { EChartOption } from '../EChartOption';
    import { CircularProgress } from '@mui/material';
    import { GlobeComponent } from 'echarts-gl/components';
    import _ from 'lodash';
    
    // 注册 ECharts 组件和图表类型
    echarts.use([
      BarChart,
      CanvasRenderer,
      DatasetComponent,
      DataZoomComponent,
      EffectScatterChart,
      GaugeChart,
      GlobeComponent,
      GeoComponent,
      GraphicComponent,
      GridComponent,
      LegendComponent,
      LineChart,
      MapChart,
      MarkPointComponent,
      PieChart,
      PolarComponent,
      RadarChart,
      ScatterChart,
      TitleComponent,
      TooltipComponent,
      ToolboxComponent,
      UniversalTransition
    ]);
    
    // 定义组件属性和方法
    export interface CommonChartProps {
      option: EChartOption | null | undefined;
      width?: number | string;
      height?: number | string;
      merge?: boolean;
      loading?: boolean;
      empty?: React.ReactElement;
    
      onClick?(event: ECElementEvent): any;
    }
    
    export interface CommonChartRef {
      instance(): EChartsType | undefined;
    }
    
    // 定义组件内部实现
    const CommonChartInner: React.ForwardRefRenderFunction<
      CommonChartRef,
      CommonChartProps
    > = (
      { option, width, height, loading = false, onClick },
      ref: ForwardedRef<CommonChartRef>
    ) => {
      const chartRef = useRef<HTMLDivElement>(null); // 保存 DOM 节点
      const chartInstanceRef = useRef<EChartsType>(); // 保存 ECharts 实例
      const resizeObserverRef = useRef<any>(null); // 保存 ResizeObserver 实例
    
      // 初始化注册组件,监听 chartRef 和 option 变化
      useEffect(() => {
    if (chartRef.current) {
      // 校验 Dom 节点上是否已经挂载了 ECharts 实例,只有未挂载时才初始化
      chartInstanceRef.current = echarts.getInstanceByDom(chartRef.current);
      if (!chartInstanceRef.current && chartInstanceRef) {
        // eslint-disable-next-line no-undefined
        chartInstanceRef.current = echarts.init(chartRef.current);
    
        // 监听点击事件并触发回调函数
        chartInstanceRef.current.on('click', (event) => {
          const ec = event as ECElementEvent;
    
          if (ec && onClick) {
            onClick(ec);
          }
        });
      }
    
      // 设置配置项
      if (!_.isEmpty(option) && option) {
        chartInstanceRef.current?.setOption(option, false, true);
      }
    }
    return () => {
      chartInstanceRef.current?.setOption({}); // 清空配置项
      //如果将清空配置放在这里,那么每次option变化就会注销
      // 导致视图的数据如果有变化的话,页面会经常重新加载,所以还是只在初始化的时候注销
      // chartInstanceRef.current?.dispose();
    };
      }, [chartRef, option]);
    
      // 重新适配大小并开启过渡动画
      const resize = () => {
    if (chartInstanceRef) {
      chartInstanceRef.current?.resize({
        animation: { duration: 300 }
      });
    }
      };
    
      // 监听窗口大小变化重绘
      useEffect(() => {
    window.addEventListener('resize', resize);
    return () => {
      window.removeEventListener('resize', resize);
    };
      }, [option]);
    
      // 监听高度变化
      useEffect(() => {
    if (chartRef?.current) {
      resize();
    }
      }, [width, height]);
    
      // 监听父元素高度变化
      useEffect(() => {
    resizeObserverRef.current = new ResizeObserver(() => {
      resize();
    });
    resizeObserverRef?.current.observe(chartRef?.current as any);
    
    // 取消所有被 ResizeObserver 对象监听的节点
    return () => {
      resizeObserverRef?.current?.disconnect();
    };
      }, []);
    
      // 获取实例
      const instance = () => {
    return chartInstanceRef.current;
      };
    
      // 对父组件暴露的方法
      useImperativeHandle(
    ref,
    () => ({
      instance
    }),
    [chartInstanceRef.current]
      );
    
      // 渲染组件
      return loading ?
    <div style={{ position: 'relative', height: '100%' }}>
      <div
        style={{
          position: 'absolute',
          top: '50%',
          left: '50%',
          transform: 'translate(-50%, -50%)'
        }}
      >
        <CircularProgress />
      </div>
    </div> :
    <div
      ref={chartRef}
      style={{
        width: width,
        height: height,
        cursor: 'pointer',
        minHeight: '1px',
        minWidth: '1px'
      }}
    />
      ;
    };
    
    // 对外暴露组件
    const CommonChart = React.forwardRef(CommonChartInner);
    
    export default React.memo(CommonChart);
    
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

创建 EarthEcharts 组件

在你的 React 应用中,创建一个名为 EarthEcharts.ts 的组件文件,并将以下代码添加到该文件中:

复制代码
    import React from 'react';
    iimport { EChartOption } from '../EChartOption';
    import CommonChart from '../CommonChart';
    import { Box } from '@mui/material';
    import 'echarts-gl';
    
    export default function EarthEcharts() {
      // 这里放入你提供的 EarthEcharts 组件代码
    }
    
    export default EarthEcharts;
    
    
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

组件代码解析

现在让我们来解析 EarthEcharts 组件的代码。

数据准备

首先,我们需要准备一些地点的数据和连接这些地点的数据。这些数据将用于创建地球上的点和飞线效果。

复制代码
    const areaPointes = [
      {
    name: '杭州',
    point: [120.12, 30.16],
    itemStyleColor: '#ff9917',
    labelText: '杭州'
      },
      {
    name: '德国',
    point: [13.402393, 52.518569, 0],
    itemStyleColor: '#ff9917',
    labelText: '德国'
      },
      {
    name: '美国',
    point: [-100.696295, 33.679979, 0],
    itemStyleColor: '#ff9917',
    labelText: '美国'
      }
    ];
    
    // 设置地理坐标映射
    let geoCoordMap: any = {
       杭州: [120.12, 30.16],
       美国: [-100.696295, 33.679979],
       德国: [13.402393, 52.518569],
       加拿大: [-102.646409, 59.994255]
     };
      
    const HZData = [
      [{ name: '杭州' }, { name: '加拿大', value: 80 }],
      [{ name: '杭州' }, { name: '美国', value: 100 }],
      [{ name: '杭州' }, { name: '德国', value: 95 }]
    ];
    
    let convertData = function (data: any) {
       let res = [];
    
       for (let i = 0; i < data.length; i++) {
     let dataItem = data[i];
     let fromCoord = geoCoordMap[dataItem[1].name];
     let toCoord = geoCoordMap[dataItem[0].name];
    
     if (fromCoord && toCoord) {
       res.push([fromCoord, toCoord]);
     }
       }
    return res;
      };
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

创建 ECharts 图表

然后,我们根据上面的数据创建 ECharts 图表。在 render 方法中,我们设置了地球的外观和视角控制参数,并创建了散点和线条系列。

复制代码
     const series = areaPointes.map((item) => {
    return {
      name: item.name, // 是否显示左上角图例
      type: 'scatter3D',
      coordinateSystem: 'globe',
      blendMode: 'source-over',
      symbol: 'circle',
      animation: true,
      symbolSize: 10, // 点位大小
      itemStyle: {
        color: item.itemStyleColor, // 各个点位的颜色设置
        opacity: 1, // 透明度
        borderWidth: 0, // 边框宽度
        borderColor: 'rgba(255,255,255,0.8)', //rgba(180, 31, 107, 0.8)
        shadowBlur: 20, // 设置发光效果的模糊程度
        shadowColor: 'rgba(255, 153, 23, 0.8)', // 设置发光的颜色
        emphasis: {
          // 强调显示效果
          label: {
            show: true
          },
          itemStyle: {
            color: '#fff',
            borderColor: 'red',
            borderWidth: 20
          }
        }
      },
    
      animationDelay: 1000, // 动画延迟1秒播放
      label: {
        show: false, // 是否显示字体
        position: 'left', // 字体位置。top、left、right、bottom
        formatter: item.labelText, // 具体显示的值
        textStyle: {
          color: '#fff', // 字体颜色
          borderWidth: 0, // 字体边框宽度
          borderColor: '#fff', // 字体边框颜色
          fontFamily: 'sans-serif', // 字体格式
          fontSize: 18, // 字体大小
          fontWeight: 700 // 字体加粗
        }
      },
      data: [item.point] // 数据来源
    };
      });
    
    // 设置飞线
    const lineSeries = [];
    [['杭州', NNData]].forEach(function (item) {
      lineSeries.push({
    type: 'lines3D',
    effect: {
      show: true,
      period: 3,
      trailLength: 0.1
    },
     lineStyle: {
        //航线的视图效果
        color: '#ff9917',
        width: 2,
        opacity: 0.7
      },
    data: convertData(item[1])
      });
    });
    //  设置扩散坐标样式
    const middleSeries = series.map((item) => {
    return {
      ...item,
      symbolSize: 20,
      itemStyle: {
        ...item.itemStyle,
        opacity: 0.4 // 透明度
      }
    };
      });
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

最终配置参数

最后,我们将所有的系列合并到 ECharts 的配置对象中,并返回一个包含地球图和图例的 React 组件。

复制代码
    const option = {
      backgroundColor: 'transparent',
      //地球配置
      globe: {
      //地球的半径。单位相对于三维空间
      globeRadius: 56,
      // 基础图片
      baseTexture: '/src/assets/images/widget-images/earth-skin-blue.jpg',
      // heightTexture: '/src/assets/images/widget-images/lines.png',
      // 地球顶点位移的大小。
      displacementScale: 0.1,
      // 地球中三维图形的着色效果
      // 'color' 只显示颜色,不受光照等其它因素的影响。
      // 'lambert' 通过经典的 lambert 着色表现光照带来的明暗。
      // 'realistic' 真实感渲染
      shading: 'lambert',
      //环境贴图。支持纯色、渐变色、全景贴图的 url
      // environment: '/src/assets/images/widget-images/earth-background.jpg',
      // displacementTexture: '/src/assets/images/widget-images/lines.png',
      //roughness属性用于表示材质的粗糙度,0为完全光滑,1完全粗糙,中间的值则是介于这两者之间
      realisticMaterial: {
        roughness: 0.1
      },
      atmosphere: {
        show: false // 大气层
      },
      light: {
        // 场景主光源的设置
        main: {
          // 主光源的颜色
          color: '#fff', // 光照颜色
          intensity: 0.8, // 光照强度
          shadow: true, // 是否显示阴影
          alpha: 40, // 主光源绕 x 轴,即上下旋转的角度
          beta: -30 //主光源绕 y 轴,即左右旋转的角度。
        },
        // 全局的环境光设置。
        ambient: {
          // /环境光的强度
          intensity: 1
        }
      },
      viewControl: {
        center: [0, 15, 0],
        autoRotate: true, // 是否开启视角绕物体的自动旋转查看
        autoRotateSpeed: 2, //物体自转的速度,单位为角度 / 秒,默认为10 ,也就是36秒转一圈。
        autoRotateAfterStill: 2, // 在鼠标静止操作后恢复自动旋转的时间间隔,默认 3s
        rotateSensitivity: 2, // 旋转操作的灵敏度,值越大越灵敏.设置为0后无法旋转。[1, 0]只能横向旋转.[0, 1]只能纵向旋转
        targetCoord: [116.46, 15], // 定位到北京
        zoomSensitivity: 0 // 禁止缩放
      }
    },
      series: [...series, ...middleSeries, ...lineSeries]
    } as EChartOption;
    
    return (
      <Box
    sx={{
      width: '100%',
      height: '100%',
      position: 'relative'
    }}
      >
    <CommonChart option={option} width="100%" height="100%" />
      </Box>
    );
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

引入 EarthEcharts 组件

最后,将 EarthEcharts 组件引入到你的应用中的任何页面或组件中。你可以在需要的地方使用它,例如在一个页面组件中:

复制代码
    import React from 'react';
    import EarthEcharts from './EarthEcharts';
    
    function App() {
      return (
    <div className="App">
      <EarthEcharts />
    </div>
      );
    }
    
    export default App;
    
    
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

现在,你的 React 应用应该显示一个带有地球模拟扩散效果的图表了!

这就是如何使用 React 和 ECharts 创建地球模拟扩散效果的简要教程。希望这个示例对你有所帮助,你可以根据自己的需求进

背景图

背景图

地球贴图

在这里插入图片描述

全部评论 (0)

还没有任何评论哟~