plotly.py icon indicating copy to clipboard operation
plotly.py copied to clipboard

Multi values graph do not share same ZERO

Open Saentist opened this issue 1 year ago • 4 comments

Hi all, I have strange problem when using graph with multiple value entity's They are not using same ZERO horizontal line

image

Plotly was used in Home assistance to draw graph visualisations, developer of integration @dbuezas , recommend to write here

More information can be seen here https://github.com/dbuezas/lovelace-plotly-graph-card/issues/428

Saentist avatar Aug 08 '24 10:08 Saentist

Any update over this bug?

Saentist avatar Mar 06 '25 14:03 Saentist

I don't see this as a bug. It's perhaps a feature request, to say "these axes should all put zero at the same place," but it's certainly not a requirement that overlaid axes must put zero at the same place. For example on your graph some of the traces have a lot of negative data so zero is appropriately drawn near the top of the graph, and you've got the left axis going from 200-240 so zero on that axis should always be far off-screen.

We have quite a few options for controlling autorange behavior, because there are a lot of use cases here. "match the zero location of another axis" is one we don't support today but it would fit well in that framework. Others you can use today include rangemode: 'tozero' which is nice in that if you don't have any negative data it'll put zero exactly at the bottom, but then as soon as some data goes negative it will expand to show that data, pushing zero off the bottom for that axis.

If we do decide to consider this a feature request, I'll also note that the implementation is ambiguous and we'll need to decide how to handle that. If yaxis has a zero that's lower than yaxis2, do you match them by making yaxis.range[0] more negative, making yaxis2.range[1] more positive, or some of each?

alexcjohnson avatar Mar 12 '25 14:03 alexcjohnson

What about doing both? Assuming the plotly uses d3 scales, it could be something like this:

function alignScales(scale1, scale2) {
    const range = scale1.range(); // range in pixels for the y axis

    const y1_zero = scale1(0);
    const y2_zero = scale2(0);

    const [d1_min, d1_max] = scale1.domain(); // assuming the domain is the extent of the trace in the y axis
    const [d2_min, d2_max] = scale2.domain();

    // Find how much to shift each domain so that scale1(0) === scale2(0)
    const shift = y2_zero - y1_zero;
    const shift1 = shift * (d1_max - d1_min) / (range[1] - range[0]);
    const shift2 = shift * (d2_max - d2_min) / (range[1] - range[0]);

    // Expand the domains symmetrically
    const new_d1_min = Math.min(d1_min - shift1, d1_min);
    const new_d1_max = Math.max(d1_max - shift1, d1_max);
    const new_d2_min = Math.min(d2_min + shift2, d2_min);
    const new_d2_max = Math.max(d2_max + shift2, d2_max);

    scale1.domain([new_d1_min, new_d1_max]);
    scale2.domain([new_d2_min, new_d2_max]);
}

It would make both scales map to the same y pixel coordinate at zero, while also scaling both so that no data points in either fall outside the plot area.

I bet this gets messier with more zero aligned yaxes, and even more so when zooming and scrolling around.

dbuezas avatar Mar 12 '25 18:03 dbuezas

We don’t use d3 scales but yes, it would need to be something like that and yes more than two axes adds complexity as does interactivity. Also I suspect that precise algorithm isn’t quite what we would want, since if you use symmetric expansion to try and push a zero toward the end it’s already close to it will barely move. In that case you’d be better off only expanding the other axis to push its zero closer to the center.

alexcjohnson avatar Mar 13 '25 00:03 alexcjohnson