apache / echarts

Apache ECharts is a powerful, interactive charting and data visualization library for browser
https://echarts.apache.org
Apache License 2.0
60.44k stars 19.61k forks source link

How to set bounds for leaflet map? #15256

Closed eapatel closed 3 years ago

eapatel commented 3 years ago

What problem does this feature solve?

I'm using echarts-leaflet extension in my project. I want to set bounds and making map bounce back if moved away. I can get _map through this.echart.getModel().getComponent('leaflet').__map, but how can I get L and how to setMaxBounds() ?

What does the proposed API look like?

I can set bounds using Leaflet library by using below code :

const southWest = Leaflet.latLng(-89.98155760646617, -180), northEast = Leaflet.latLng(89.99346179538875, 180); const bounds = Leaflet.latLngBounds(southWest, northEast); map.setMaxBounds(bounds);

How can I achieve the same thing using 'echarts-leaflet'?

I tried using below code with echarts-leaflet extension but it's not working.

    this.option = {
        leaflet: {
        roam: true,
        maxBounds: [[-89.98155760646617, -180],[89.99346179538875, 180]],
        maxBoundsViscosity: 1,
        center: [4.9,-1.766667],
        zoom: 2,
        tiles: [{
            urlTemplate: 'assets/{z}/{x}/{y}.jpg',
            options: {
                minZoom: 2,
                maxZoom: 6,
                attribution: ''
             }
         }]
      }

Thanks in advance for any help.

echarts-bot[bot] commented 3 years ago

Hi! We've received your issue and please be patient to get responded. 🎉 The average response time is expected to be within one day for weekdays.

In the meanwhile, please make sure that it contains a minimum reproducible demo and necessary images to illustrate. Otherwise, our committers will ask you to do so.

A minimum reproducible demo should contain as little data and components as possible but can still illustrate your problem. This is the best way for us to reproduce it and solve the problem faster.

You may also check out the API and chart option to get the answer.

If you don't get helped for a long time (over a week) or have an urgent question to ask, you may also send an email to dev@echarts.apache.org. Please attach the issue link if it's a technical question.

If you are interested in the project, you may also subscribe our mailing list.

Have a nice day! 🍵

plainheart commented 3 years ago

L is in your global context, it will be window when running in the browser. To set max bounds, you could get the leaflet instance after initializing ECharts, and then invoke its method setMaxBounds.

This is a plugin contributed by a community developer, it's better to ask the original author.

eapatel commented 3 years ago

Thanks for your quick response. As mentioned earlier in my query, I've already tried setting maxBounds but it doesn't work. It seems that no such property exists.

I had already raised an issue in echarts-leaflet a few months ago (https://github.com/gnijuohz/echarts-leaflet/issues/34) but no one respond back. It's really a blocker for my project. Couldn't find any other way to solve this issue.

plainheart commented 3 years ago

@eapatel It seems buggy in setMaxBounds method. Maybe we need to do some modification.

eapatel commented 3 years ago

@plainheart I would really appreciate if you look further and update this.

plainheart commented 3 years ago

@eapatel Sure.

eapatel commented 3 years ago

Thank you @plainheart. Please update me whenever this issue get fixed.

plainheart commented 3 years ago

@eapatel Here is the new extension plugin for Leaflet. Feel free to take it a try. The following zip contains the built files and an example page.

echarts-extension-leaflet.zip

eapatel commented 3 years ago

@plainheart the provided new extension plugin for leaflet works fine. It solved my issue. Thank you so much :)

I'm using angular and I installed echarts-leaflet extension using npm. Now how can I include this new plugin?

plainheart commented 3 years ago

You could put .esm file to your project and import it.

eapatel commented 3 years ago

@plainheart I added .esm file to my project. I'm getting below errors in console this.echartsInstance is undefined this.echartsInstance.getModel() is undefined

Here is my ts code

import { EChartOption } from 'echarts'; import L from 'leaflet'; import '../../dist/echarts-extension-leaflet.esm.js';

echartsInstance: any; option: EChartOption = {};

ngAfterViewInit(): void { this.setOptions(); }

setOptions() { this.option = { leaflet: { roam: true, center: [4.9,-1.766667], zoom: 2, renderOnMoving: true, largeMode: false, }, series: [ this.getData() ] }; }

initMap() { var map = this.echartsInstance.getModel().getComponent("leaflet").getMap(); var southWest = L.latLng(-89.98155760646617, -180), northEast = L.latLng(89.99346179538875, 180); var bounds = L.latLngBounds(southWest, northEast); map.setMaxBounds(bounds);

L.tileLayer('assets/tiles/{z}/{x}/{y}.jpg', {
        minZoom: 2,
        MaxZoom: 6,
        attribution: ''
      }).addTo(map);
}

onChartInit(echarts: any) { this.echartsInstance = echarts; this.setOptions();
this.initMap(); this.resizeChart(); }

resizeChart() { if (this.echartsInstance) { this.echartsInstance.resize(); } }

And here is my template code

<div echarts
    [options]="option"
    (chartInit)="onChartInit($event)">
</div>

Can you please help me to figure out why it says instance is undefined?

plainheart commented 3 years ago

Please ensure that you have called echartsInstance.setOption() before getting the leaflet map instance. I just saw the value assignment but didn't see where the echartsInstance.setOption was invoked.

eapatel commented 3 years ago

I called this.setOption() in onChartInit() and after that getting the leaflet map instance.

onChartInit(echarts: any) {
    this.echartsInstance = echarts;
    // setting echarts option
    this.setOptions();
   // getting leaflet instance and setting options (e.g. setMaxBounds, tileLayers etc)
    this.initMap();
}
<div echarts
    [options]="option" 
    (chartInit)="onChartInit($event)"  // called when chart is initialized
>
</div>
plainheart commented 3 years ago

I'm not sure, but it seems you should call like this

echartsInstance.setOption(this.option)
eapatel commented 3 years ago

Solved all issues. Thank you @plainheart for your all support :)

plainheart commented 3 years ago

You're welcome. Glad to see your issues solved.

mzy2240 commented 2 years ago

@plainheart When I load the extension you shared with the echarts.min.js from the dist folder, there will be error of "TypeError: Cannot read properties of undefined (reading 'dataToPoint')". Do you have any idea how to fix it? Thank you in advance!

This is how I import both modules:

import * as echarts from "echarts/dist/echarts.min.js";
import "../assets/echarts-extension-leaflet.esm.js";
mzy2240 commented 2 years ago

It works fine with simply importing the echarts. However, I am trying to antibloat and minimize the bundle size as much as I can. The custom echarts.min.js is the best option even compared to tree shaking in my case. Please advice if you have any idea how to make this extension work with echarts.min.js. Thanks!

plainheart commented 2 years ago

@mzy2240 Does echarts-extension-leaflet.min.js work for you?

mzy2240 commented 2 years ago

@plainheart Thanks for the reply! Just tried but still does not work.

plainheart commented 2 years ago

@mzy2240 Could you please provide a simple example to show how you use it?

mzy2240 commented 2 years ago

@plainheart Sure.

import * as echarts from "echarts/dist/echarts.min.js";
import "../assets/echarts-extension-leaflet.esm.js";

let chart = echarts.init(document.getElementById("main"), "dark");
chart.setOption(echartsOptions);

For the echartsOptions, I have fields for leaflet and series, all the coordinateSystem are leaflet already. Here is a snippet of the options I am using:

echartsOptions = {
        leaflet: {
          // leaflet options (only primitive options, crs/layers/renderer/maxBounds are not allowed here)
          center: this.mapCenter,
          zoom: 8,
          maxZoom: 18,
          // maxBoundsViscosity: 1.0,
          // zoomAnimation: false,

          // the following options are from this plugin
          // whether the chart should always re-render when moving/zooming the map
          renderOnMoving: true,
          // whether to enable large mode (it's better to enable when data is large)
          largeMode: false,
        },
        animation: false,
        tooltip: {
          show: true,
          trigger: "item",
        },
        series: [
          {
            id: "sub",
            type: "effectScatter",
            name: "sub",
            coordinateSystem: "leaflet",
            // coordinateSystem: 'bmap',
            symbol: "circle",
            symbolSize: function (value, params) {
              if (params.data.attributes.Gen) {
                return 14;
              } else if (params.data.attributes.Shunt) {
                return 12;
              } else {
                return 10;
              }
            },
            showEffectOn: "emphasis",
            zlelve: 5,
            z: 5,
            // progressive: 40,
            // progressiveThreshold: 200,
            // zindex: 2,
            data: [],
            tooltip: {
              confine: true,
              formatter: function (params) {
                return "Substation: " + params.name;
              },
            },
...
mzy2240 commented 2 years ago

When importing like this, it works fine.

import * as echarts from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { LinesChart, EffectScatterChart, ScatterChart } from "echarts/charts";
import { TooltipComponent } from "echarts/components";
// import * as echarts from "echarts/dist/echarts.min.js";
// import "../assets/echarts-extension-leaflet.min.js";
import "../assets/echarts-extension-leaflet.esm.js";
import darkTheme from "../assets/dark.js";
echarts.registerTheme('dark', darkTheme);

echarts.use([
  CanvasRenderer,
  LinesChart,
  EffectScatterChart,
  ScatterChart,
  TooltipComponent,
]);
mzy2240 commented 2 years ago

BTW, getting echarts.min.js to work is not the final step for me, cause I still need to figure out how to minimize the echarts import size from the leaflet extension. Do you have any idea or thoughts on this? I can see echarts is imported from 'echarts/lib/echarts' in the extension.

plainheart commented 2 years ago

I'm not sure where the echarts.min.js you mentioned came from. But to get a minimal build, it's suggested to use the second method.

Importing echarts from echarts/lib/echarts is used to make this plugin backward compatible with ECharts v4. If you are using v5, you can try to replace echarts/lib/echarts with echarts/core.

mzy2240 commented 2 years ago

The echarts.min.js directly come from the echarts dist installed from npm. Tree shaking seems not perform well, though I used quite a lot components from echarts, after tree shaking I still got around 2.5MB (before tree shaking it is 3.5MB). That's why if you look at the code, I already start to import dark them manually to just try everything I could to minimize the bundle size.

mzy2240 commented 2 years ago

TypeError: Cannot read properties of undefined (reading 'dataToPoint') this kind of error is not that rare to people who forgot to include the geo field in their echart options. But since I am using leaflet extension, I don't think I could include a geo fields, but I have a feeling it could be the place to look at to find the answer. Have you manage to make the extension work with echarts.min.js?

mzy2240 commented 2 years ago

To declare, it not only does not work with echarts.min.js, but also does not work with echarts.js or any files inside the dist folder.

plainheart commented 2 years ago

It may be because the plugin is importing echarts from echarts/lib/echarts but you are importing it from echarts/dist/echarts.min.js. These two namespaces are independent. It can work if you also import echarts from echarts or echarts/lib/echarts.

// either
import * as echarts from "echarts";
// or
import * as echarts from "echarts/lib/echarts";
mzy2240 commented 2 years ago

But in that way, tree shaking won't work for sure, right?

plainheart commented 2 years ago

If you use the following way to import

import * as echarts from "echarts/lib/echarts";

the tree-shaking can work but it will import some extra modules (CanvasRenderer, DatasetComponent, LabelLayout) for compatibility.

See also https://cdn.jsdelivr.net/npm/echarts@5/lib/echarts.js

mzy2240 commented 2 years ago

Thanks for the clarification! What if I modify the plugin to import echarts from the dist folder? Do you think it is doable?

plainheart commented 2 years ago

Yeah. It's doable. But I want to say that importing echarts.min.js doesn't mean you can get a minimal build. It's just compressed by a minimizer like terser. Instead, to make tree-shaking work better, it's suggested to read this guide.

mzy2240 commented 2 years ago

Yes I did follow the guide, however the size is still unbearably 2.5MB. One thing I have to ask, to be fair, do I have to compare the gziped size with the stat size of those min.js?

plainheart commented 2 years ago

Yes. The files suffixed with .min.js were compressed and cleaned by the minimizer. You need to do similar thing before the comparison.

mzy2240 commented 2 years ago

I removed the leaflet plugin temporarily to see what the impact could be. It seems that even without the plugin the echarts bundled size is still tremendous (all the import are through tree shaking approach). Here is the snapshot from webpack report. Is it normal from your experience @plainheart ? (zrender also takes up another 500KB) image

plainheart commented 2 years ago

It seems a bit unexpected. The minimal import of ECharts can still be improved further. ECharts currently contains many features, so it is hard to avoid a large code size, perhaps more things could be done to optimize this.