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

Synchronizer plugin vertical zoom ignorance fix #1037

Merged
merged 7 commits into from
Feb 4, 2025

Conversation

n-gist
Copy link
Contributor

@n-gist n-gist commented Apr 18, 2023

Having several graphs synced, I wanted their vertical zooming to be independent. Setting synchronizer range option to false currently does that, but only partially.

  1. If you are not x-zoomed in, then it works when you y-zoom one of the graphs for the first time, but if you y-zoom another graph after that, it resets first one.
  2. If you are x-zoomed in, then it partially preserves vertical zooms, but still reset graphs depending on their position in the syncing graphs array.

For the first case I found that the source is arraysAreEqual(a, b) returning false while comparing x-axis arrays when both arrays are null. Since in dygraph null x axis (dateWindow) array means 'fully zoomed out', this should be treaten as equal zooming ranges. Adding a check like this

function arraysAreEqual(a, b) {
  if (a === null && b === null) return true
  ...
}

solved first case, but... [it would take too long to explain, deleted] it was still very buggy.

In short, I figured out that other arrays should be used when comparing. Using xAxisRange() instead of getOption('dateWindow') for that gives consistent expected result, so here's the fix.

The only issue I found after, is that it still resets graphs sometimes after x-zooming in and out due to precision error. For example, in my case, graphs had the ranges for x-axis equal to [1.5e-7, 0.00015] and after x-zooming in and out for one of them it changes to [1.5e-7, 0.00015000000000000001]. Thus, comparing the ranges after that, it founds them are different, resetting graphs as a result. It would be more right to fix this somewhere inside zooming handler in dygraph core.

@mirabilos
Copy link
Collaborator

I had quite a look around this as well, see commit e854c3d and earlier, but this is not the right way forward, unfortunately, as this causes other issues. This also reverts fixes for some other problems.

@mirabilos mirabilos closed this Apr 19, 2023
@n-gist
Copy link
Contributor Author

n-gist commented Apr 19, 2023

@mirabilos, null checks code I provided there is not the final fix I had come to. I found it causing bugs and reverted it as you did in commit you linked. I added it to the topic just to explain the path and options it took me while investigating.
Apologies if providing this snippet got the main focus and set back the real solution.
After the fix, I tested the plugin with and without the 'range' option, and didn't find any problems, except for the one I described in the last paragraph, which I don't think is related to how the plugin works.

@mirabilos
Copy link
Collaborator

Hmm hmmm, so the code I merged then reverted was not the final version somehow? But it introduced a regression? OK, then let’s have a look into this again.

I think I need to understand the problem first. The description is… a bit confusing if I haven’t seen it myself, probably.

Could you make a minimal example to exhibit the problem?

I also don’t understand how/why the fix works and why opts.dateWindow is a problem but opts.valueRange isn’t… (it would so help to be the initial author and know what was meant with this, but alas…)

I'm using dygraph with all these fixes combined for over a year in my project and it is stable.

The question is, does the change break anything else which you don’t happen to have in your project, too? It’s been two years, so I admit I don’t recall the exact problems…

@n-gist
Copy link
Contributor Author

n-gist commented Jan 15, 2025

@mirabilos thanks for looking at this, I will try to explain the problem more precisely, and I have prepared examples for testing

The behavior I want to achieve is to make synchronizer affect X-zooming only, and make it not touching other graphs when one of them Y-zoomed

I found this statement in synchronizer.js:

You may also set range: false if you wish to only sync the x-axis.
The range option has no effect unless zoom is true (the default).

It sounds exactly what was needed, so I used it and found some bugs. Although my fix solves them, I can't say that it don't affects other behaviors (though I didn't met any, but I use synchronizer with these options only), and it may be the wrong way to fix it. So here are the details for tests to reproduce the problem.

Examples for testing (I just copied synchronizer.js code directly to JavaScript field):
Current state, no fix - https://jsfiddle.net/n03ta7eL/
Fix - https://jsfiddle.net/f2wLqtrh/ (I have commented original code in place of changes, so you can check both changes separately by uncommenting)

Tests

Each test should be performed after full reinitialization (hitting orange Run button works)

Test 1:
1) Repeat Y-zooming on any of graphs randomly

Result: other graphs Y-zooming state resets.
Expected: other graphs should keep their y-zoom state, since we have used option to not sync it.

Test 2:
1) X-zoom one of graphs
2) Y-zoom same graph
3) Y-zoom other graph

Result: previous graph y-zooming resets.
Expected: previous graph should keep its y-zoom state.
However: any subsequent random Y-zooming shows the expected behavior. Each graph keeps its Y-zooming state.

Test 3:
1) X-zoom one of graphs
2) Y-zoom other graph
3) Repeat Y-zooming any of graphs randomly
Result: expected behavior.

Performing tests in fixed version shows consistent behavior. I can't explain exactly how I come to this solution, I actually wrote it in first post, but it was so big and complex, so I have deleted it. I just left what was the first source of the problem I have found:

For the first case I found that the source is arraysAreEqual(a, b) returning false while comparing x-axis arrays when both arrays are null. Since in dygraph null x axis (dateWindow) array means 'fully zoomed out', this should be treaten as equal zooming ranges

But there is another sources, probably.

I also don’t understand how/why the fix works and why opts.dateWindow is a problem but opts.valueRange isn’t… (it would so help to be the initial author and know what was meant with this, but alas…)

Fix consists of two changes, one is changing the array we are comparing in arraysAreEqual() and this fixes Test 2.
Second change is removing if (!me.isZoomed('x')) opts.dateWindow = null; line and this fixes Test 1

So, in general, what we are trying to achieve - tell synchronizer to not touch other graphs on performing Y-zooming of one of them if range option is set to false. Maybe there should be more proper way to do this?

@n-gist
Copy link
Contributor Author

n-gist commented Jan 15, 2025

One of possible reasons to use xAxisRange instead of dateWindow could be that it is not initialized at first interaction with graph, but xAxisRange already gives correct result. Maybe need same change for using yAxisRange instead of valueRange, but this problem just invisible because synchronizer don't have option to not sync by x axis, and thus this never happens. May be wrong

@n-gist
Copy link
Contributor Author

n-gist commented Jan 17, 2025

@mirabilos I took a fresh view on this, and I think I have found right way to fix it
Here is fixed example for testing https://jsfiddle.net/2rz9qhxv/

The problem was that we just have to pass valueRange option to updateOptions() to preserve y-zooming. If we pass only dateWindow, then valueRange resets. So I split code of where it checks for the need of update and added the case needed. This preserves the use of getOption(). Also streamlined creation of options variable a little

opts.valueRange = me.yAxisRange();

var opts = {};
opts.dateWindow = me.isZoomed('x') ? me.xAxisRange() : null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this null assignment deliberate, or do you mean…

if (me.isZoomed('x'))
  opts.dateWindow = me.xAxisRange();

… instead?

Copy link
Contributor Author

@n-gist n-gist Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is necessary, it is working in my project without (I have deleted the null assignment line in previous fix to make Test 1 fixed), but now it just keeps previous code:
If graph is not zoomed, it assigns null. And I just added the same for y to make logic for both consistent

var opts = { dateWindow: me.xAxisRange() };
if (!me.isZoomed('x')) opts.dateWindow = null;

This is how it was before, I just combined it to
opts.dateWindow = me.isZoomed('x') ? me.xAxisRange() : null;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is just a little optimization actually. It doesn't call xAxisRange() now if it is just going to use null

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, that was in the original code. Thanks. (It’s late.)

@mirabilos
Copy link
Collaborator

I posed a question (if it’s deliberate, may want to add a comment about the way (deliberate reset?), if not, if is better), and I’d prefer to not have the then-statement of an if-block in the same line (and I think you changed the indent of the block as well, Dygraphs mostly uses two spaces for some reason).

I’ll be without laptop until the 29th or so and will look at it later.

@n-gist
Copy link
Contributor Author

n-gist commented Jan 17, 2025

Fixed coding style
I don't know the reason of setting nulls for these options, it was there before me, so, can't comment that. It works without, but maybe optimizes something

@n-gist
Copy link
Contributor Author

n-gist commented Jan 17, 2025

Or should I add {} for every then and else? It just going to be bulky there

@mirabilos
Copy link
Collaborator

Nah, no curly braces for just one command in a then-block, at least in my book ☻

I actually use KNF, but in this project, a somewhat different style is pre-existing, so I keep to that as I see it.

@n-gist
Copy link
Contributor Author

n-gist commented Jan 17, 2025

Oh, right. This should be right then?
I use my own, so it is always wrong haha

@n-gist n-gist requested a review from mirabilos February 1, 2025 12:16
Comment on lines 196 to 198
var opts = {
dateWindow: me.xAxisRange()
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this, and why not…

var opts = {};
if (me.isZoomed('x'))
  opts.dateWindow = me.xAxisRange();

var opts = {
dateWindow: me.xAxisRange()
};
if (syncOpts.range)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarily, no isZoomed check any more… is this deliberate (and do things still work correctly without them)?

@n-gist
Copy link
Contributor Author

n-gist commented Feb 4, 2025

@mirabilos I assume you have missed the review I just posted on previous commit? I described all details there. isZoomed has a bug that should be fixed inside it, then we can keep it used by synchronizer. But it still needs additional checks of x axis borders of graphs to be able to pass nulls, because graphs may have misaligned x axes, and passing nulls makes them zoomed out and not aligned. So, using actual values is just simpler and always correct.

@mirabilos
Copy link
Collaborator

I admit I have quite a hard time wrapping my head around this one :/

Let’s just trust you on this and merge it and see what breaks, if any, at this point.

@mirabilos mirabilos merged commit 767b454 into danvk:master Feb 4, 2025
@n-gist
Copy link
Contributor Author

n-gist commented Feb 4, 2025

@mirabilos thank you!
Check please this last one of my PRs #1035
It doesn't change default behavior, but adds new option. I have added example of what it does in last comment

@mirabilos
Copy link
Collaborator

mirabilos commented Feb 4, 2025 via email

@n-gist
Copy link
Contributor Author

n-gist commented Feb 5, 2025

On Tue, 4 Feb 2025, n-gist wrote: Check please this last one of my PRs #1035
I have that in combination with #917 on my TODO, but I’m a bit short in time for all this, unfortunately.

Ok, thank you. Took a look into #917, it doesn't interferes with synchronizer. Both PRs makes sense.

@mirabilos
Copy link
Collaborator

mirabilos commented Feb 5, 2025 via email

@n-gist
Copy link
Contributor Author

n-gist commented Feb 7, 2025

Thankfully the Firefox issue turned out to be nothing, so perhaps, after merging these, I could push out a new minor release, and then tackle the documentation/website thing (apparently, the version of Twitter-Bootstrap it uses is so old I got asked by the Debian packagers of it to stop using it, and I’d rather get rid of Twitter stuff entirely than upgrade, anyway, but I’m not a “frontend dev” or CSS expert…)

Do you mean to get rid of

<link rel="stylesheet" type="text/css" href=".jslibs/bootstrap.min.css" />
<script type="text/javascript" src=".jslibs/bootstrap.min.js"></script>

from docs/header.html and adding CSS code for other htmls which were using it?
I am more or less familiar with CSS and I wanted to check it. It doesn't look like there is a lot to replace, from a first view. But didn't found how to actually test changes.
I have installed what is needed to compile dygraphs under Ubuntu using WSL in Windows, and got build and build-jsonly npm scripts working. Installed Chrome in Ubuntu, and it seems like demo htmls from tests folder are working normally.

When it comes to documentation, I see .jslibs symbolic links (or what is it) in VS Code, but when I try to open them I get the error
Screenshot_1
So, it is a bit dark forest for me. I could just copy them from https://dygraphs.com/.jslibs/ though. Or just check needed resulting styles using Chrome DevTools. But I wanted to look at their sources, and not minified versions.
So, I was thinking to just remove bootstrap ccs and js, and compare how docs are looking. Find out what looks broken and fix it by copy/mimic CSS inside css/dygraph.css or add it to separate css file.
Seems like it needs a configured web server running to be able to open docs.

@mirabilos
Copy link
Collaborator

mirabilos commented Feb 7, 2025 via email

@n-gist
Copy link
Contributor Author

n-gist commented Feb 9, 2025

@mirabilos thanks for so much detailed answer, I was interested to just get everything up to get some experience with things I never did, and this helped me. The problem was that I just forgot about DEVELOP.md file. I saw it, but when I actually decided to try to setup everything, I forgot that I should follow it

So, I have configured and got it running, thus I checked for how bootstrap and jQuery can be removed. I did some and will add PR. I was just interested to try and didn't delve into problem areas just in case, so it is ok to close it or continue from it as starting point

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

Successfully merging this pull request may close these issues.

2 participants