zauberzeug / nicegui-highcharts

MIT License
13 stars 5 forks source link

Adding and removing chart series confuses Highcharts #1

Open metaperl opened 11 months ago

metaperl commented 11 months ago

Description

image

In the screenshot attached you see:

  1. "bob" and "john" in the legend of the graph
  2. "alice" and "john" in the chips of the multi-select

this is a bug because they should be the same.

how to reproduce

  1. run the program below
  2. repeatedly select and de-select a certain element (e.g. select and de-select 'alice')
  3. after three repetitions of step 2, a mismatch will occur between the legend and the chips displayed.

This video shows the same thing. Regrettably, the capture window did not capture the deselections and selections of the same element - https://www.youtube.com/watch?v=KOKuhgMA9dk

from nicegui import ui

# initial data
data1 = {'name': 'alice', 'data': [31, 148, 33, 68, 75, 43]}
data2 = {'name': 'bob', 'data': [3, 28, 7, 5, 8, 3]}
data3 = {'name': 'john', 'data': [11, 42, 10, 18, 9, 0]}

all_data = dict(alice=data1, bob=data2, john=data3)

print(f"keys = {list(all_data.keys())}")

chart = None
chart_options = {
    'chart': {
        'type': 'spline',
    },
    'legend': {
        'enabled': True,
        'layout': 'vertical',
        'align': 'right',
        'verticalAlign': 'middle',
    },
    'navigator': {
        'enabled': True
    },
    'scrollbar': {
        'enabled': False
    },
    'series': [data1, data2, data3]
}

def plot():
    global chart, chart_options
    chart = ui.chart(chart_options, type='stockChart', extras=['stock'])

def update_chart(selections):
    global chart

    datums = [all_data[selection] for selection in selections]

    chart.options['series'] = datums
    chart.update()

plot()
ui.select(list(all_data.keys()), multiple=True, value=list(all_data.keys()),
          label='with chips', on_change=lambda e: update_chart(e.value)
          ).classes('w-64').props('use-chips')

ui.run(reload=False)
falkoschindler commented 11 months ago

Wow, this is strange. I boiled it down to this:

def toggle():
    chart.options['series'] = [{'data': [1]}, {'data': [2]}] if len(chart.options['series']) == 1 else [{'data': [1]}]
    chart.update()

chart = ui.chart({'series': [{'data': [1]}]})
ui.button('Toggle', on_click=toggle)

Even though the options always contain a data point at 1, toggling multiple times leads to a single point at 2.

I wonder if we can reproduce the problem with a pure Highcharts example without NiceGUI...

falkoschindler commented 11 months ago

No, without NiceGUI the example is working:

<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="container"></div>
<button onclick="toggle()">Toggle</button>
<script>
  let chart = Highcharts.chart("container", { series: [{ data: [1] }] });
  function toggle() {
    if (chart.series.length === 1) {
      chart.addSeries({ data: [2] });
    } else {
      chart.series[1].remove();
    }
  }
</script>

Now I'm pretty sure the issue is caused by update_chart() in chart.js which follows a pretty naive heuristic to add and remove series: https://github.com/zauberzeug/nicegui/blob/eea7d8d4ce91be270edfacb29e26b385ba447d0f/nicegui/elements/chart.js#L40-L52

falkoschindler commented 11 months ago

Minimum reproduction without NiceGUI (but with the same update algorithm from chart.js):

<html>
  <head>
    <script src="https://code.highcharts.com/highcharts.js"></script>
  </head>
  <body>
    <div id="container"></div>
    <button onclick="toggle()">Toggle</button>
    <script>
      let options = { series: [{ data: [1] }] };
      let chart = Highcharts.chart("container", options);
      let seriesCount = options.series ? options.series.length : 0;

      function toggle() {
        if (options.series.length === 1) {
          options.series.push({ data: [2] });
        } else {
          options.series.pop();
        }
        update();
      }

      function update() {
        while (seriesCount > options.series.length) {
          chart.series[0].remove();
          seriesCount--;
        }
        while (seriesCount < options.series.length) {
          chart.addSeries({}, false);
          seriesCount++;
        }
        chart.update(options);
      }
    </script>
  </body>
</html>

The question is, how to improve the update() function.

falkoschindler commented 11 months ago

I created a test page for manipulating Highcharts options:

test.html ```html
```

Currently all series collapse into a point at -1 when clicking "insert low". And a lengthy discussion with ChatGPT about improving the update() function wasn't successful.

falkoschindler commented 11 months ago

I still don't have a good solution for fixing this issue. In order to focus on a quick release of upcoming version 1.4, I'll postpone this one to 1.4.1.

falkoschindler commented 10 months ago

I'm currently thinking about how to solve an update like this:

<html>
  <head>
    <script src="https://code.highcharts.com/highcharts.js"></script>
  </head>
  <body>
    <div id="container"></div>
    <button onclick="add()">Add</button>
    <script>
      let options = { series: [{ data: [1] }, { data: [2] }, { data: [3] }] };
      let chart = Highcharts.chart("container", options);

      function add() {
        options.series.unshift({ data: [0] });
        chart.update(options);
      }
    </script>
  </body>
</html>

The chart simply collapses, which doesn't make any sense and looks like a bug in Highcharts. But it's hard to find a related bug report. This one might be relevant, maybe not. With 1.1K open and 13K closed issues it's hard to tell if this behavior is known and/or already worked on.

By the way: A conversation with their AI didn't yield much insight.