ogobrecht / d3-force-apex-plugin

Oracle APEX Region Type Plugin: D3 Force Network Chart
https://ogobrecht.github.io/d3-force-apex-plugin
MIT License
24 stars 9 forks source link

Adding Images to nodes significantly reduces render performance #35

Closed Ignacius68 closed 5 years ago

Ignacius68 commented 5 years ago

Hi Ottmar,

I just attempted an upgrade to some of my graphs, to add icons/images to the nodes. I have tried several strategies, and different image types (e.g. .svg, .jpg, .gif, .png), but every attempt I've made dramatically slows down the render ticks.

Without the images, the ticks are so fast that the nodes "flow" until they settle to final position, with the time to completion being 1-3 seconds. WITH the images, the ticks slow down to about 1 per second, making the render take a couple of minutes.

Are there tricks to this? For instance, maybe I need .svg icons that are designed to be circular rather than square? I'm not certain what else to try, so much appreciated if you can give me any tips.

Ignacius68 commented 5 years ago

ADDENDUM: I should add that I'm trying this on summary graphs that have a maximum of 40 nodes. I'm not trying this yet on the larger graphs with 250+ nodes, unless I can get it working well.

I also just thought of an alternate angle. Is there perhaps a way to reduce the render ticks to only 1 or 2? With my solution, I'm saving and enforcing "views" that set the positions on load of the graph, so the solution use case doesn't require the dynamic arranging that the renders provide. Maybe this is a way to end around this?

ogobrecht commented 5 years ago

Hi Ignacius,

I know the problem with the background images. I can't do anything for this. It depends on the browsers performance. Which browser do you use?

I think one problem can be the image size. Should be faster, when the images are small.

As a workaround you can try to disable the background images when the force is running and enable it again, when the force is stopping:

d3_force_emp.onForceStartFunction(
  function () {
    d3_force_emp.nodes().each(function (node) {
      elem = d3.select(this);
      node.fill_backup = elem.style("fill");
      elem.style("fill", "silver")
    });
  }
);

d3_force_emp.onForceEndFunction(
  function () {
    d3_force_emp.nodes().each(function (node) {
      d3.select(this).style("fill", node.fill_backup);
    });
  }
);

Please align the graph variable d3_force_emp to the one you have on your page (d3_force_YourStaticRegionID). With this workaround you should be able to run also very large networks.

Hope this helps... let me know your findings...

Best regards Ottmar

ogobrecht commented 5 years ago

Hi Ignacius,

sorry for my late answer regarding your second question. If you want to speed up the the termination of the force you can try something like this:

d3_force_emp.onForceStartFunction(function () {
  setTimeout(function () {
    d3_force_emp.inspect().main.force.stop();
  }, 100);
})

Notes:

We use here the inspect function, which exposes the internal graph variable. This API method was created by me to be able to look what is going on inside the graph - more for debugging. But you can use it also to access the force variable and then call the stop method on it. If you are interested, you can simply call d3_force_emp.inspect() and inspect the graph internals in your browser console.

You can play around with the time setting - I use here 100ms. If you go too short, the graph is maybe not rendered correctly. I tried 20ms, which was not enough.

I would use both ideas together: hide the images when the force is running and stopping the force early if you have all positions precalculated. Without precalculated positions the graph looks a bit ugly because of the random start positions for new data - here an example:

force-stop-after-100ms

Hope this helps, best regards
Ottmar

Ignacius68 commented 5 years ago

Hi!

Been experimenting further, and I'm having luck in Chrome with images that are 25 px or less. I also seem to have slightly better luck with images that have rounded backgrounds, rather than square.

If I get to a larger graph that I want to include icons in, I'll be using your backup-and-reapply method there, because of course that's what you'd do. (I'm a little miffed I didn't think of that myself!!) Thus far, it is only useful to have icons on summary graphs, with the lower level detail graphs being color coded.

As to the stopping of the force, that should accomplish just what I need for larger graphs that take longer for a force to finish. This also gives me the option to allow things to be "free form" until a view is created and saved for a new model. I'd love to figure a way to minimize the number of force ticks through the configurations, but I will save that for another potential day. :)

Closing this issue with this comment, since my use case is handled quite well!

ogobrecht commented 5 years ago

Hi Ignacius,

thank you for your feedback and for closing the issue. I created a note for me to implement a new option in the future - something like numberTicksToStopForce. You will here from me, when I am done with it.

Best regards Ottmar

Ignacius68 commented 5 years ago

While you're looking at things like this, it doesn't appear that you can have both an image and a background fill color. Am I missing the option perhaps? Is there something I could tweak to get a background fill color as well as the image? I assumed that was possible, but doesn't seem to be what I'm seeing as output?

Ignacius68 commented 5 years ago

As to the ending of the force, I was hoping not to limit the number of ticks directly, but somehow tweak the other configurations such that the number of ticks was minimal. In my imagination, that would look like adjusting the settings so the nodes don't have to move very much, OR to be able to set a "time limit", much like zoom, where the ticks "cover more ground" by lowering the amount of time allowed for the force. Are either of those approaches possible, or something similar?

I'm hesitant about number of ticks, since that could leave a "free form" graph looking odd if terminated too prematurely....

ogobrecht commented 5 years ago

Hi Ignacius,

regarding the background image and color:

For HTML and CSS this is correct. For SVG you have the fill property which refers to a HTML color code or to an URL, which points to a SVG pattern, in which the real image URL is used. The graph creates for each provided node with an image URL an own pattern - you can see the result of this when the fill attribute of the node is created - the snipped from the graph code:

v.main.nodes
  .attr("r", function (n) {
    return n.radius;
  })
  .attr("fill", function (n) {
    return (n.IMAGE ? // We check here, if the IMAGE attribute was provided in the data.
      "url(#" + v.dom.containerId + "_pattern_" + n.ID + ")" : // If so, construct the URL to the predefined pattern...
      v.tools.color(n.COLORVALUE) // or calculate the color from the provided data
    );
  });

As a result, this means you can deliver both in your data, but only one can be used as the fill attribute in the graph at the same time. When you use background images, then the color was never calculated for your node. But you can do it afterwords - you know the trick to access the color calculation function (which takes into account your choosen colorScheme):

example.nodes().each(function (node) {
  d3.select(this).style("fill", example.inspect().tools.color(node.COLORVALUE))
});

Hope this helps for the color question, best regards Ottmar

ogobrecht commented 5 years ago

Hi Ignacius,

regarding the force runtime:

I have played around a little bit with the simulation parameters. At the end I think your idea with the time limit is the best approach. I will implement something like forceTimeLimit in milliseconds. Maybe there are some other options - I will think about and come back to you at least with the time limit API option.

Thank you for your idea :-)

Best regards Ottmar

Ignacius68 commented 5 years ago

Hi Ottmar,

Back to the images, and also having background color. I've come up with 2 ideas that I'd like to run by you. Please let me know if you'd like me to do a more detailed writeup as a different issue.

Idea 1: Add a background color to the img or pattern tag, whichever works, that would allow the color legend to be applied and show through with images that have transparent background.

pro: flexibility, and images tie in with the color legend, allowing a 2-tier organized presentation con: requires specific image properties, namely the transparent background

Idea 2: Use the stroke property of the node object to show the color around the image. When highlighting a node, then, reduce the stroke size to show the highlighting.

pro: still organizes based on the color legend con 1: may require additional logic, and the reduced stroke size might not provide the same user-accessible visual highlighting con 2: likely also requires additional parameters and logic to "center" the image within the stroke area, and the developer of the graph would have to manage image size and centering parameters

I think you may intuit that I favor Idea 1, if it's at all possible. Perhaps, though, with your familiarity with this code base, you can think of something merging or "in between" the two options?

I eagerly anticipate your thoughts on the matter.

Your faithful beta tester, Ignacius :)

ogobrecht commented 5 years ago

Hi Ignacius,

I have tested to add the background color to the pattern - it works. Good idea!

I need some more time to polish the things and try alternative ways... You will hear from me...

Best regards Ottmar

Ignacius68 commented 5 years ago

Brilliant!! You're a god! :)

I look forward to it.

Best, Ig

ogobrecht commented 5 years ago

Hi Ignacius,

please have a look at branch v3.1 - there you can find the latest plugin. I wait for your response before I publish the final new version. Hope, you find no new issues ;-)

Best regards Ottmar

Ignacius68 commented 5 years ago

Hi Ottmar!

I have been testing, and I can find no bugs. My apologies for the delay, I've also been looking into adding short labels to the links, which I have written as a separate issue.

In the near future, I'll be posting a short clip of what you've helped me accomplish. :)

Ignacius68 commented 5 years ago

Hi again,

I think I have stumbled on an issue. The zoom and center on force end is frequently not operating on hidden graphs. It took me a while to notice, and then to eliminate whether I was unintentionally interrupting the centering.

I don't think anything I've done is causing that, and it does center sometimes, but not always. I'm uncertain why it would be intermittent.

ogobrecht commented 5 years ago

Hi Ignacius,

sorry for my late answer. I need some time to have a look at your mentioned issue with the centering of the graph. Also for the link labels - let me see, what I can do for you.

I am really interested to see what you are doing with my plugin :-)

You will hear from me the next days.

Best regards Ottmar