Search code examples
javascriptreactjssvgecharts

React Echarts Error for render custom Svg floor plan


I use react to visualise a Svg floor plan (heatmap) in reactjs. However, it will return error

cannot read properties of undefined (reading regions)

Code:

import React, { useRef, useEffect } from "react";
import * as echarts from "echarts";
import mapData from "./map.svg";

export default function Heatmap(){

const ref = useRef(null);

let mapInstance = null;

const option = {
    tooltip: {},
    visualMap: {
    left: 'center',
    bottom: '10%',
    min: 5,
    max: 100,
    orient: 'horizontal',
    text: ['', 'Price'],
    realtime: true,
    calculable: true,
    inRange: {
        color: ['#dbac00', '#db6e00', '#cf0000']
        }
    },

series: [
  {
    name: 'French Beef Cuts',
    type: 'map',
    map: 'testing',
    roam: true,
    emphasis: {
      label: {
        show: false
      }
    },
    selectedMode: false,
    data: [
      { name: 'Queue', value: 15 },
      { name: 'Langue', value: 35 },
      { name: 'Plat de joue', value: 15 },
      { name: 'Gros bout de poitrine', value: 25 },
      { name: 'Jumeau à pot-au-feu', value: 45 },
      { name: 'Onglet', value: 85 },
      { name: 'Plat de tranche', value: 25 },
      { name: 'Araignée', value: 15 },
      { name: 'Gîte à la noix', value: 55 },
      { name: "Bavette d'aloyau", value: 25 },
      { name: 'Tende de tranche', value: 65 },
      { name: 'Rond de gîte', value: 45 },
      { name: 'Bavettede de flanchet', value: 85 },
      { name: 'Flanchet', value: 35 },
      { name: 'Hampe', value: 75 },
      { name: 'Plat de côtes', value: 65 },
      { name: 'Tendron Milieu de poitrine', value: 65 },
      { name: 'Macreuse à pot-au-feu', value: 85 },
      { name: 'Rumsteck', value: 75 },
      { name: 'Faux-filet', value: 65 },
      { name: 'Côtes Entrecôtes', value: 55 },
      { name: 'Basses côtes', value: 45 },
      { name: 'Collier', value: 85 },
      { name: 'Jumeau à biftek', value: 15 },
      { name: 'Paleron', value: 65 },
      { name: 'Macreuse à bifteck', value: 45 },
      { name: 'Gîte', value: 85 },
      { name: 'Aiguillette baronne', value: 65 },
      { name: 'Filet', value: 95 }
    ]
  }
]
}

const renderMap = (myChart) => {

    /*const renderedMapInstance = echarts.getInstanceByDom(ref.current);
    if (renderedMapInstance) {
      mapInstance = renderedMapInstance;
    } else {
      mapInstance = echarts.init(ref.current);
    }
    mapInstance.setOption(option);*/
    myChart.setOption(option)
  };
  useEffect(() => {
    fetch(mapData)
      .then((response) => response.text())
      .then((svgText) => {
        echarts.registerMap("testing", { svg: svgText });
      });
    var chartDom = document.getElementById('beef');
    var myChart = echarts.init(chartDom);
    renderMap(myChart);
  }, []);

/*

  useEffect(() => {
    window.onresize = function () {
    };
    return () => {
      mapInstance && mapInstance.dispose();
    };
  }, []);  mapInstance.resize();
    
*/

  return (
    <div>
      <div style={{ width: "100%", height: "50vh" }} ref={ref} id='beef'></div>
    </div>
  );
}

Svg file

<svg
    xmlns="http://www.w3.org/2000/svg"
    width="591"
    height="373"
    preserveAspectRatio="xMidYMid meet"
    viewBox="0 0 376 237"
>

    <g transform="translate(-7.69,10.06)">

    <path
      
      d="m 536.375,106.46875 c 5.2885,14.34201 10.52945,33.1769 13.78125,43.21875 -0.0655,0.0771 -0.13181,0.1476 -0.21875,0.21875 l 16.5625,0.9375 10.5,-8.5 11,-17 -13.5,-18 -38.125,-0.875 z"
      stroke="#A5A5A5" 
      stroke-width="0.75"
      stroke-linecap="round"
      fill="#FFFFFF"
      fill-rule="evenodd"
      id="path3702"
      name="Langue" 
      transform="matrix(0.6362812,0,0,0.6362812,7.6936433,-10.065285)"
    />
    
    </g>  
</svg> 

Solution

  • The error you displayed appears to be due to attempting to access (the regions properties of) an unregistered map.

    In your code, you use the options object through myChart.setOption(option) on the main thread, so it's executed always before the asynchronous fetch .then chain; so myChart.setOption(option) (that uses its map: 'testing') is called before echarts.registerMap('testing', .....

    Following you setting, you have to move the call to setOptions inside the then handlers:

    useEffect(() => {
        fetch('./map.svg') // "map.svg" is in the public directory
            .then((response) => response.text())
            .then((svgText) => {
                echarts.registerMap('testing', { svg: svgText });
                let myChart = echarts.init(document.getElementById('beef'));
                myChart.setOption(option);
           })
           .catch(e => console.error('fetch error', e));
    });
    

    You may also use await with async like in these examples.

    There some more details on how to avoid calling the init method on an already initialized chart/container. I included a skech of a solution for those in this stackblitz which is a simplified working version of your code. Feel free to fork it and use in further enquiries as a reproducible example.