Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Responsive support for echart #268

Open
Sanuki-073 opened this issue Sep 3, 2023 · 4 comments
Open

Responsive support for echart #268

Sanuki-073 opened this issue Sep 3, 2023 · 4 comments
Labels
good first issue Good for newcomers

Comments

@Sanuki-073
Copy link

Sanuki-073 commented Sep 3, 2023

Thanks for the great project!

"echarts" is not responsive, so I think that needs to be addressed.
In echarts, when the resize event is fired, it seems to be enough to resize this.chart as follows.

this.chart.resize()

If possible, I would appreciate it if you could support this.

reference:https://apache.github.io/echarts-handbook/en/concepts/chart-size/

@maartenbreddels
Copy link
Contributor

Hi,

Thanks for opening the issue. Too bad echarts doesn't do it by itself. @mariobuikhuizen used the ResizeObserver solution they suggest, and implemented this for bqplot:

Do you want to try to fix this yourself?
I think this can be added to create in https://github.com/widgetti/solara/blob/master/solara/components/echarts.vue#L26

Regards,

Maarten

@maartenbreddels maartenbreddels added the good first issue Good for newcomers label Sep 4, 2023
@Sanuki-073
Copy link
Author

Hi Maarten,

Thank you for your consideration of how to implement this.

I implemented it , and I checked that it works with a resizable grid.
Are there any other issues that should be tested?

echarts.vue Change to monitor by resizeobserver when responsive is true on the python side.
<template>
    <div class="solara-box" v-bind="parent_attributes">
      <div ref="echarts" class="solara-echarts" v-bind="attributes"></div>
    </div>
  </template>
  <script>
  module.exports = {
    mounted() {
      const version = "5.4.0";
      (async () => {
        const echarts = (
          await this.import([`${this.getCdn()}/echarts@${version}/dist/echarts.js`])
        )[0];
        this.echarts = echarts;
        this.create();
      })();
      console.log(this.responsive);
      if(this.responsive){
      this.resizeObserver = new ResizeObserver(entries => {
      for (let entry of entries) {
        if (entry.target === this.$refs.echarts) {
          this.handleResize();
        }
      }
    });
    this.resizeObserver.observe(this.$refs.echarts);
  };
  },
  beforeDestroy() {
    if (this.resizeObserver) {
      this.resizeObserver.unobserve(this.$refs.echarts);
      this.resizeObserver.disconnect();
    }
  },
    watch: {
      option() {
        // notMerge, otherwise we're left with axes etc
        // see https://echarts.apache.org/en/api.html#echartsInstance.setOption
        this.chart.setOption(this.option, true);
      },
    },
    methods: {
      create() {
        this.chart = this.echarts.init(this.$refs.echarts);
        console.log(this.maps);
        Object.keys(this.maps).forEach((mapName) => {
          console.log(mapName);
          this.echarts.registerMap(mapName, this.maps[mapName]);
        });
  
        this.chart.setOption(this.option, true);
        const eventProps = [
          "componentType",
          "seriesType",
          "seriesIndex",
          "seriesName",
          "name",
          "dataIndex",
          "data",
          "dataType",
          "value",
          "color",
        ];
        this.chart.on("click", (fullEvent) => {
          const eventData = {};
          eventProps.forEach((prop) => {
            eventData[prop] = fullEvent[prop];
          });
          this.on_click(eventData);
        });
        this.chart.on("mouseover", (fullEvent) => {
          const eventData = {};
          eventProps.forEach((prop) => {
            eventData[prop] = fullEvent[prop];
          });
          if (this.on_mouseover_enabled) this.on_mouseover(eventData);
        });
        this.chart.on("mouseout", (fullEvent) => {
          const eventData = {};
          eventProps.forEach((prop) => {
            eventData[prop] = fullEvent[prop];
          });
          if (this.on_mouseout_enabled) this.on_mouseout(eventData);
        });
      },
      handleResize() {
    if (this.chart) {
      this.chart.resize();
    }
  },
      import(deps) {
        return this.loadRequire().then(() => {
          if (window.jupyterVue) {
            // in jupyterlab, we take Vue from ipyvue/jupyterVue
            define("vue", [], () => window.jupyterVue.Vue);
          } else {
            define("vue", ["jupyter-vue"], (jupyterVue) => jupyterVue.Vue);
          }
          return new Promise((resolve, reject) => {
            requirejs(deps, (...modules) => resolve(modules));
          });
        });
      },
      loadRequire() {
        /* Needed in lab */
        if (window.requirejs) {
          console.log("require found");
          return Promise.resolve();
        }
        return new Promise((resolve, reject) => {
          const script = document.createElement("script");
          script.src = `${this.getCdn()}/requirejs@2.3.6/require.js`;
          script.onload = resolve;
          script.onerror = reject;
          document.head.appendChild(script);
        });
      },
      getBaseUrl() {
        if (window.solara && window.solara.rootPath !== undefined) {
          return solara.rootPath + "/";
        }
        // if base url is set, we use ./ for relative paths compared to the base url
        if (document.getElementsByTagName("base").length) {
          return "./";
        }
        const labConfigData = document.getElementById("jupyter-config-data");
        if (labConfigData) {
          /* lab and Voila */
          return JSON.parse(labConfigData.textContent).baseUrl;
        }
        let base = document.body.dataset.baseUrl || document.baseURI;
        if (!base.endsWith("/")) {
          base += "/";
        }
        return base;
      },
      getCdn() {
        return (
          (typeof solara_cdn !== "undefined" && solara_cdn) ||
          `${this.getBaseUrl()}_solara/cdn`
        );
      },
    },
  };
  </script>
  
  <style id="solara-markdown-editor">
  .solara-echarts {

  }
  .solara-box {

  }
  </style>
echarts.py Add responsive setting, and parent_container's attributes setting. Also, I want to change the height responsively as well, but it is difficult to see if it is small, so I changed the container's style default from height to min-height.
from typing import Any, Callable

import ipyvuetify
import traitlets

import solara


class EchartsWidget(ipyvuetify.VuetifyTemplate):
    template_file = (__file__, "echarts.vue")

    attributes = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True)
    parent_attributes = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True)
    responsive = traitlets.Bool(False).tag(sync=True)
    maps = traitlets.Any({}).tag(sync=True)
    option = traitlets.Any({}).tag(sync=True)
    on_click = traitlets.Callable(None, allow_none=True)
    on_mouseover = traitlets.Callable(None, allow_none=True)
    # for performance, so we don't get events from the frontend we don't care about
    on_mouseover_enabled = traitlets.Bool(False).tag(sync=True)
    on_mouseout = traitlets.Callable(None, allow_none=True)
    # same, for performance
    on_mouseout_enabled = traitlets.Bool(False).tag(sync=True)

    def vue_on_click(self, data):
        if self.on_click:
            self.on_click(data)

    def vue_on_mouseover(self, data):
        if self.on_mouseover:
            self.on_mouseover(data)

    def vue_on_mouseout(self, data):
        if self.on_mouseout:
            self.on_mouseout(data)


@solara.component
def FigureEcharts(
    option: dict = {},
    on_click: Callable[[Any], Any] = None,
    on_mouseover: Callable[[Any], Any] = None,
    on_mouseout: Callable[[Any], Any] = None,
    maps: dict = {},
    attributes={"style": "height:100%;min-height:300px"},
    parent_attributes={"style":"width: 100%; height: 100%; overflow: hidden;"},
    responsive: bool = False,
):
    """Create a Echarts figure.

    See [The Echarts website for examples](https://echarts.apache.org/)

    Note that we do not support a Python API to create the figure data.

    A library such as Pyecharts can help you with that, otherwise you can provide
    the data simply as data similarly as on the Echarts example webpage.

    # Arguments

    * option: dict, the option for the figure, see the echart documentation.
    * on_click: Callable, a function that will be called when the user clicks on the figure.
    * on_mouseover: Callable, a function that will be called when the user moves the mouse over a certain component.
    * on_mouseout: Callable, a function that will be called when the user moves the mouse out of a certain component.
    * maps: dict, a dictionary of maps to be used in the figure.
    * attributes: dict, a dictionary of attributes to be passed to the container (like style, class).
    * parent_attributes: dict, a dictionary of attributes to be passed to the parent container (like style, class).
    * responsive: bool, whether the figure should be responsive or not.


    """
    return EchartsWidget.element(
        option=option,
        on_click=on_click,
        on_mouseover=on_mouseover,
        on_mouseover_enabled=on_mouseover is not None,
        maps=maps,
        on_mouseout=on_mouseout,
        on_mouseout_enabled=on_mouseout is not None,
        attributes=attributes,
        parent_attributes=parent_attributes,
        responsive=responsive,
    )

responsive_echarts

I am not familiar with vue, so I would appreciate it if you could point out any oddities.

Regards,

Sanuki

@maartenbreddels
Copy link
Contributor

Hi Sanuki,

awesome! looks good. It would be best to continue with a PR, we put some docs on https://solara.dev/docs/development
I hope that helps.

Regards,

Maarten

@Sanuki-073
Copy link
Author

Hi Maarten,

I created the pull request

Should I edit the document (solara/website/page/...)?
If I should edit the echarts page, could you please tell me where I should edit it, because I couldn't figure it out myself.

Regards,

Sanuki

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

2 participants