echarts 支持react-native 和react-native-web两种方式

  1. 概念:
    因为react-native需要使用到echart , 同时react-native需要转换成web端使用,所以才会有这两种方式的实现

  2. echart-native组件的编写
    注意webView 需要添加 androidLayerType={'software'} 不然有个别手机会出现闪退问题出现原因是原生的webView 嵌套react-native的webView导致的,使用该参数关闭硬件加速就没问题
    common/chart.tsx

import React, {Component} from 'react';
import {ActivityIndicator, Platform, View, ViewStyle} from 'react-native';
import {WebView} from 'react-native-webview';
import debounce from 'lodash/debounce';

const defaultH5HTML = `
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.5.0/echarts.min.js"></script>
</head>
<body>
</body>
<script>
var myChart = null

if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {;
    window.postMessage = function(data) {
        var str = typeof data === 'object' ? JSON.stringify(data) : data;
        window.ReactNativeWebView.postMessage(str)
    };
    window.parent.postMessage = window.postMessage
};
function EchartCommonInit(option, wrapStyle) {
    var chartDom = document.createElement('div')
    chartDom.style.width = '100%'
    chartDom.style.height = '400px'
    document.body.innerHTML  = ''
    document.body.append(chartDom)
    if (echarts) {
        myChart = echarts.init(chartDom, null,  {renderer: 'canvas',useDirtyRect: true, useCoarsePointer: null, pointerSize: 10});
        var options = {}
        var wrapStyleObj = {}
        if (option && typeof option === 'string') {
            options = JSON.parse(option)
        }
        if (option && typeof option === 'object') {
            options = option
        }
        if (wrapStyle && typeof wrapStyle === 'string') {
            wrapStyleObj = JSON.parse(wrapStyle)
        }
        if (wrapStyle && typeof wrapStyle === 'object') {
            wrapStyleObj = wrapStyle
        }
        for (var key in wrapStyleObj) {
            var value = wrapStyleObj[key]
            chartDom.style[key] = value
        }
        
        
        myChart.clear();
        options && myChart.setOption(options);
        var myChartParams = {
            width: 320
        }
        if (wrapStyleObj.height) {
            myChartParams.height = wrapStyleObj.height
        }
        if (chartDom.offsetWidth) {
            myChartParams.width = chartDom.offsetWidth
        }
        myChart.resize(myChartParams);
        myChart.off('click')
        // 为饼图添加点击事件监听
        myChart.on('click', function (params) {
            // 控制台输出点击的数据的信息
            console.log('click',params);
            // console.log('JSON.stringify(params)', JSON.stringify(params.data))
            // console.log('window.parent.postMessage', window.parent)
            window.parent.postMessage({cmd:'rn-chart-click', clickcmd: window.clickcmd, res: JSON.stringify(params.data) },'*')
        });

    }
}
window.clickcmd = '';
window.EchartCommonInit = EchartCommonInit
window.onload=function(){EchartCommonInit(option,wrapStyle)}
window.receiveMessage = (data) => {
    console.log('html---receiveMessage:', data);
    var _data = data
    if (typeof data === 'string') {
        try {
            _data = JSON.parse(data)
        } catch (err){
        }
    }
    if (_data.cmd === 'rn-chart-update-options-render') {
        if (typeof _data.res === 'string') {
            try {
                var res = JSON.parse(_data.res)
                console.log('res', res)
                res && myChart.setOption(res);
                myChart.off('click')
                // 为饼图添加点击事件监听
                myChart.on('click', function (params) {
                    // 控制台输出点击的数据的信息
                    console.log('click',params);
                    // 你可以在这里编写你的逻辑代码
                    // 例如:弹出一个提示框显示点击的数据的名称
                    // alert('你点击了' + params.name);
                    // console.log('JSON.stringify(params)', JSON.stringify(params.data))
                    // console.log('window.parent.postMessage', window.parent)
                    window.parent.postMessage({cmd:'rn-chart-click', clickcmd: window.clickcmd, res: JSON.stringify(params.data) },'*')
                });
            }catch {}
        } else if (typeof _data.res === 'object') {
            try {
                var res = _data.res
                console.log('res', res)
                res && myChart.setOption(res);
                myChart.off('click')
                // 为饼图添加点击事件监听
                myChart.on('click', function (params) {
                    // 控制台输出点击的数据的信息
                    console.log('click',params);
                    // 你可以在这里编写你的逻辑代码
                    // 例如:弹出一个提示框显示点击的数据的名称
                    // alert('你点击了' + params.name);
                    // console.log('JSON.stringify(params)', JSON.stringify(params.data))
                    // console.log('window.parent.postMessage', window.parent)
                    window.parent.postMessage({cmd:'rn-chart-click', clickcmd: window.clickcmd, res: JSON.stringify(params.data) },'*')
                });
            }catch {}
        }
    }
}
window && window.addEventListener(
              'message',
              (event) => {
                console.log('html---Message received from Web Worker:', event);
                var data = event.data
                receiveMessage(data)
              },
            );

</script>
</html>
`;

interface SelfEChartPropsType {
  options: objPropsType;
  echartStyle?: ViewStyle;
  echartClick?: () => {};
  clickcmd?: string;
}

const replaceNoNumber = (str: string | number) => {
  if (typeof str === 'string') {
    return str.replace(/[^0-9.]+/g, '');
  } else if (typeof str === 'number') {
    return str;
  }
  return str;
};

export default class SelfEChart extends React.PureComponent<
  SelfEChartPropsType,
  any
> {
  static defaultProps = {
    echartStyle: {},
  };
  // 由于react-native-webView 的onMessage不识别window.postMessage, 需要改成window.ReactNativeWebView.postMessage, 同时injectedJavascript只支持一行压缩代码,不支持多行,请勿换行和格式化;
  injectedJavascript = `if(window.ReactNativeWebView&&window.ReactNativeWebView.postMessage){;window.postMessage=function(data){var str=typeof data==='object'?JSON.stringify(data):data;window.ReactNativeWebView.postMessage(str)};window.parent.postMessage=window.postMessage};window.parent.postMessage({cmd:'rn-init',res:{'label':'这个只是测试rn能不能走通'}},'*');`;
  isLoadEnd = 'hidden';
  IframeRef: any = React.createRef();
  webviewRef: any = React.createRef();
  constructor(props: SelfEChartPropsType) {
    super(props);
    this.state = {
      h5HTML: '',
      height: 300,
    };

    this.onMessage = debounce(this.onMessage, 300, {
      leading: true,
      trailing: false,
    });
  }

  componentDidMount(): void {
    this.updateOptions();
  }

  componentDidUpdate(
    prevProps: Readonly<SelfEChartPropsType>,
    prevState: Readonly<any>,
    snapshot?: any,
  ): void {
    if (
      JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)
    ) {
      this.updateOptions();
    }
  }

  updareRenderOptions = (options: objPropsType) => {
    console.log(`options`, options);
    Platform.select({
      web: () => {
        this.IframeRef?.current?.contentWindow?.postMessage(
          {cmd: 'rn-chart-update-options-render', res: JSON.stringify(options)},
          '*',
        );
      },
      default: () => {
        const initParams = JSON.stringify({
          cmd: 'rn-chart-update-options-render',
          res: options,
        });
        const initParamsJs = `window.receiveMessage(${initParams});true;`;
        this.webviewRef?.current?.injectJavaScript(initParamsJs);
      },
    })();
  };

  updateOptions = () => {
    const {options, echartStyle, clickcmd} = this.props;
    const {height} = this.state;
    const style = JSON.parse(JSON.stringify(echartStyle || {}));
    let _height = height;
    if (Boolean(style.height)) {
      _height = +replaceNoNumber(style.height);
    }
    if (!style.height) {
      style.height = height + 'px';
    }

    const _h5HTML = defaultH5HTML.replace(
      'window.onload=function(){EchartCommonInit(option,wrapStyle)}',
      `window.clickcmd='${clickcmd}';window.onload=function(){EchartCommonInit(${JSON.stringify(
        options,
      )},${JSON.stringify(
        style,
      )})};console.log("window", window);window.EchartCommonInit(${JSON.stringify(
        options,
      )},${JSON.stringify(style)})`,
    );
    this.setState({
      h5HTML: _h5HTML,
      height: _height,
    });
  };

  onMessage = (e: objPropsType | string) => {
    console.log('Message received from Web Worker:', e);
    this.props.echartClick && this.props.echartClick(e.nativeEvent || {});
  };

  componentWillUnmount(): void {
    this.setState({
      h5HTML: '',
    });
  }

  render() {
    const {h5HTML, height} = this.state;
    const {isLoadEnd, onMessage} = this;
    const _height = height + 20;
    return Boolean(h5HTML) ? (
      Platform.select({
        web: () => {
          window &&
            window.addEventListener(
              'message',
              (event: objPropsType | string) => {
                onMessage({
                  nativeEvent: event,
                });
              },
            );
          return (
            <iframe
              ref={this.IframeRef}
              srcDoc={h5HTML}
              frameBorder={0}
              style={{width: '100%', height: _height}}></iframe>
          );
        },
        default: () => {
          return (
            <WebView
              ref={this.webviewRef}
              originWhitelist={['*']}
              source={{html: h5HTML}} //加载的html资源
              style={{width: '100%', height: _height, visibility: isLoadEnd}}
              mixedContentMode={'compatibility'}
              scalesPageToFit={true}
              javaScriptEnabled={true}
              startInLoadingState={true}
              injectedJavaScript={this.injectedJavascript}
              androidLayerType={'software'}
              renderLoading={() => {
                return isLoadEnd === 'hidden' ? (
                  <View style={{height: _height, backgroundColor: '#fff'}}>
                    <ActivityIndicator size="large" />
                  </View>
                ) : (
                  <></>
                );
              }}
              onLoadEnd={() => {
                // console.log('loadend');
                this.isLoadEnd = 'visible';
              }}
              onMessage={onMessage}
            />
            //   <></>
          );
        },
      })()
    ) : (
      <></>
    );
  }
}



  1. 组件的使用
    饼图的实现并使用在组件中的场景

import React from 'react';
import CommonChart from '../common/chart';
import moment from 'moment';
import {getMoneySymbolAdd, isJSON} from '@/libs/utils';

interface SelfEChartPropsType {
  annularStatistic?: objPropsType;
  echartClick?: Function;
  clickcmd?: string;
}

export default class IssueChart extends React.PureComponent<
  SelfEChartPropsType,
  any
> {
  static defaultProps = {
    annularStatistic: {},
  };
  CommonChartRef = React.createRef();
  constructor(props: SelfEChartPropsType) {
    super(props);
    this.state = {
      options: {
        grid: {
          top: 10,
          bottom: 10,
        },
        legend: {
          show: true,
          right: 10,
          top: 'center',
          align: 'left',
          orient: 'vertical',
          itemWidth: 8,
          itemHeight: 8,
          itemGap: 16,
        },
        color: ['#5B8FF9', '#F6BD16', '#65789B'],
        graphic: [
          {
            type: 'text',
            left: '20%',
            top: '34%',
            style: {
              // text: '{a|2233}\n{b|¥3,234.00}\n{c|审批中}',
              text: '',
              width: 100,
              height: 100,
              textAlign: 'center',
              rich: {
                a: {
                  fill: '#B4B8BE',
                  fontSize: 12,
                  lineHeight: 16,
                },
                b: {
                  fill: '#353739',
                  fontSize: 18,
                  fontWeight: 'bold',
                  lineHeight: 24,
                },
                c: {
                  fill: '#3D3D3D',
                  fontSize: 14,
                  lineHeight: 28,
                },
              },
              fill: '#333',
              fontSize: 20,
            },
          },
        ],
        series: [
          {
            name: 'Area Mode',
            type: 'pie',
            radius: [50, 84],
            center: ['35%', '50%'],
            itemStyle: {},
            label: {
              show: true,
              normal: {
                position: 'inside',
                formatter: '{b}: {d}%',
                fontSize: 10,
              },
            },
            emphasis: {
              label: {
                show: false,
              },
            },
            data: [
              {value: 0, name: '审批中'},
              {value: 0, name: '待发放'},
              {value: 0, name: '已发放'},
            ],
          },
        ],
      },
    };
  }

  componentDidMount(): void {
    this.updateOptionsData(this.props.annularStatistic);
  }

  componentDidUpdate(
    prevProps: Readonly<SelfEChartPropsType>,
    prevState: Readonly<any>,
    snapshot?: any,
  ): void {
    if (
      JSON.stringify(this.props.annularStatistic) !==
      JSON.stringify(prevProps.annularStatistic)
    ) {
      this.updateOptionsData(this.props.annularStatistic);
    }
  }

  updateOptionsData = (annularStatistic: objPropsType = {}) => {
    const options = this.state.options;

    options.series[0].data[0].value = annularStatistic.ingApprovalAmount;
    options.series[0].data[1].value = annularStatistic.noGrantAmount;
    options.series[0].data[2].value = annularStatistic.successGrantAmount;

    options.graphic[0].style.text = `{a|${moment().format(
      'YYYY-MM',
    )}}\n{b|¥${getMoneySymbolAdd(annularStatistic.ingApprovalAmount)}}\n{c|${
      options.series[0].data[0].name
    }}`;
    if (annularStatistic.ingApprovalAmount > 1000) {
        options.graphic[0].left = '20%'
    } else {
        options.graphic[0].left = '24%'
    }
    // options.series[1].data[0].value = annularStatistic.ingApprovalAmount;
    // options.series[1].data[1].value = annularStatistic.noGrantAmount;
    // options.series[1].data[2].value = annularStatistic.successGrantAmount;
    // options.series[0].label.normal.formatter = [
    //   `{a|${moment().format('YYYY-MM')}}\n{b|¥${getMoneySymbolAdd(
    //     annularStatistic.ingApprovalAmount,
    //   )}}\n{c|${options.series[0].data[0].name}}`,
    // ].join('');

    this.setState(
      {
        options,
      },
      () => {
        this.CommonChartRef.current?.updateOptions();
      },
    );
  };

  echartClick = (e: objPropsType) => {
    let data = e.data;
    if (typeof data === 'string' && isJSON(data)) {
      data = JSON.parse(data);
    }
    if (
      data.cmd === 'rn-chart-click' &&
      data.clickcmd === this.props.clickcmd
    ) {
      let res = data.res;
      if (typeof res === 'string' && isJSON(res)) {
        res = JSON.parse(res);
      }
      const options = this.state.options;
      console.log('res', data, res);
      if (res) {
        options.graphic[0].style.text = `\{a|${moment().format(
          'YYYY-MM',
        )}\}\n\{b|¥${getMoneySymbolAdd(res.value)}\}\n\{c|${res.name}\}`
        
    if (res.value > 1000) {
        options.graphic[0].left = '20%'
    } else {
        options.graphic[0].left = '24%'
    }

        this.setState(
          {
            options,
          },
          () => {
            this.CommonChartRef.current?.updareRenderOptions({
              graphic: options.graphic,
            });
          },
        );
      }
      this.props.echartClick && this.props.echartClick(res);
    }
  };

  render() {
    const {options} = this.state;
    return (
      <CommonChart
        ref={this.CommonChartRef}
        options={options}
        echartStyle={{height: '194px'}}
        clickcmd={this.props.clickcmd}
        echartClick={this.echartClick}></CommonChart>
    );
  }
}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容