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

Proposed API changes: Promises, Web Workers, WebAssembly #113

Closed
mdaines opened this issue Mar 16, 2018 · 24 comments
Closed

Proposed API changes: Promises, Web Workers, WebAssembly #113

mdaines opened this issue Mar 16, 2018 · 24 comments
Milestone

Comments

@mdaines
Copy link
Owner

mdaines commented Mar 16, 2018

Hi everyone. I'm considering some API changes for future Viz.js versions and I wanted to get your feedback.

The goals of these changes are:

  • Viz.js should support Web Workers out of the box.
  • Viz.js should support WebAssembly with a similar API.
  • Return values should be more useful and their types clearer.
  • It should be easier to render a graph from data.
  • It should be easier to work with Viz.js in bundlers.

Secondarily:

  • Synchronous usage should still be possible.
  • Usage from Node should still be possible. (Although I don't currently have tests for this.)

Here's what I've come up with so far:

  • Deprecate the Viz() function in favor of format-specific rendering functions under the Viz namespace.
  • Rendering functions accept a module option. This is a path to a file that contains the code generated by Emscripten, with a wrapper that allows it to be used as a worker or included as a <script> tag.
  • Rendering functions return a Promise.
  • Rendering functions accept an object (similar to the Graphviz JSON output) as well as a DOT string.
  • If you want to call Viz.js without Web Workers, you'll need to include two files: the main file and a module (either the full build or the "lite" build).

Here's what rendering SVG might look like:

Viz.svgElement("digraph g { a -> b; }", { module: "viz-module.js" }).then(function(element) {
  document.body.appendChild(element);
});

Or specifying the graph as an object:

var graph = {
  name: "g",
  directed: true,
  objects: [
    { name: "a" },
    { name: "b" }
  ],
  edges: [
    { head: 0, tail: 1 }
  ]
};

Viz.svgElement(graph, { module: "viz-module.js" }).then(function(element) {
  document.body.appendChild(element);
});

To use the "lite" version, you'd use "viz-module-lite.js" instead.

To use without a Web Worker, you'd include the module script and omit the module option. The return value would still be a promise.

I think that in webpack, you'd probably use file-loader to get a path to the module you want. This way, webpack only looks at the wrapper or support code when bundling Viz.js, which is much smaller than the code generated by Emscripten. This is what it might look like:

var Viz = require('viz.js');
var vizModulePath = require('file-loader!viz.js/viz-module.js');

Viz.svgElement("digraph g { a -> b; }", { module: modulePath }).then(function(element) {
  document.body.appendChild(element);
});

This is the interface I have in mind:

namespace Viz {

  function svgElement(src: string | object, options: Options) : Promise<SVGElement>

  function imageElement(src: string | object, options: ImageOptions) : Promise<HTMLImageElement>

  function imageBase64(src: string | object, options: ImageOptions) : Promise<string>

  function jsonObject(src: string | object, options: Options) : Promise<object>

  function svgString(src: string | object, options: Options) : Promise<string>

  function jsonString(src: string | object, options: Options) : Promise<string>

  function xdot(src: string | object, options: Options) : Promise<string>

  function ps(src: string | object, options: Options) : Promise<string>

  function plain(src: string | object, options: Options) : Promise<string>

  interface Options {
    // current options plus module
  }

  interface ImageOptions extends Options {
    // adds scale and content type
  }

}
@broofa
Copy link

broofa commented Mar 16, 2018

'Not sure if this is in-scope here, but every time I've used this library I find myself rendering the graph as SVG, then doing a lot of DOM/CSS work to work with the graph dynamically. E.g. handle a click on a node, highlight connected nodes, etc.

I.e. a high(er)-level API for working with an existing [svg] graph would be helpful. That said, I recognize that this might make more sense as an addon module rather than as part of the core.

For bonus points, an API for modifying an existing graph and animating the changes would be slick. Again, probably better implemented as an addon. (e.g. a module that takes two SVG DOMs and figures out how to do the minimal set of operations needed to morph the first into the second would be a really cool, general-purpose, utility.)

@mdaines
Copy link
Owner Author

mdaines commented Mar 16, 2018

@broofa What about using D3 to work with the SVG?

@magjac
Copy link

magjac commented Mar 16, 2018

@broofa @mdaines d3-graphviz already utilizes viz.js and D3 to animate changes between one graph and another and I'm currently working on an API for modifying the graph that utilizes that animation (@broofa's bonus point) and an example application (that later might turn into a new D3 plugin) to show how to use it interactively. Enhancement requests with details are welcome as d3-graphviz issues

@broofa
Copy link

broofa commented Mar 16, 2018

My only issue with something like d3-graphviz is the D3 dependency. It's not small, and there's a lot of stuff in there that's orthogonal to this particular problem. I mean, I get it, d3 does some nice heavy lifting for DOM-manipulation, but it's not exactly a micro-framework.

d3-graphviz is great if you're already using D3 but less so otherwise, is my point I guess.

[Edit: I don't know what I'm talking about. See comments below]

@magjac
Copy link

magjac commented Mar 16, 2018

Since v4, D3 core is modular and d3-graphviz uses just a few of them. The size is almost negligible compared to viz.js.

@broofa
Copy link

broofa commented Mar 16, 2018

@magjac Ah, I see. Nice! Looks like minified+gzipped d3-graphviz is what, like 7-8K? Yeah, that's trivial.

For anyone following along, a visualization of how d3-graphviz depends on a small subset of d3 core.

@magjac
Copy link

magjac commented Mar 16, 2018

@broofa I've opened magjac/d3-graphviz#40. Let's continue the discussion there and not pollute this thread.

@mdaines
Copy link
Owner Author

mdaines commented Mar 17, 2018

@magjac I'd certainly like to hear any ideas you have for making Viz.js work better with D3 in this discussion, since that would probably affect the API.

@magjac
Copy link

magjac commented Mar 18, 2018

@mdaines Absolutely. I just didn't want to hijack this thread for something it wasn't meant for (The possibility to build an API for modifying an existing graph and animating the changes as @broofa suggested above into d3-graphviz).

I'm quite a newbie in the Javascript world so I probably won't be able to contribute so much knowledge about stuff outside of d3-graphviz.

My implementation of web worker use in d3-graphviz was mostly trial and horror, but what I wanted to accomplish was that the viz.js script should be loaded when the page is loaded, but not JIT compiled until it's referenced. The reason being that otherwise, if it was going to be used in a web worker, it was compiled twice. There might be more clever ways to do this. If you'll add web worker support in Viz.js, I'll be happy to give you feedback and adapt d3-graphviz to it.

I have been thinking of adding support for rendering a graph from data to d3-graphviz, but if you are going to implement it in viz.js, I'll use that. I wasn't aware of the Graphviz JSON output format. Even though it looks a bit odd, it would be nice to use it in order to be able to go back and forth between DOT and JSON. I had planned to look at dotparser for the same reason.

@mdaines mdaines added this to the v2.0 milestone Mar 19, 2018
@magjac
Copy link

magjac commented Apr 1, 2018

@broofa I've added a draw API to d3-graphviz that allows you to modify an existing graph and animate the changes.

Check out the D3 Graphviz GUI editor proof-of-concept made with it.

@thari
Copy link

thari commented Apr 4, 2018

Super excited about the proposed features especially webworker and webassembly. In fact, I am in an urgent need of a webworker supported version. If you can commit your progress, I can be your early adopter/tester.

BTW: Will you be interested in adding a scalajs facade to this repo? I can provide you one.

@magjac
Copy link

magjac commented Apr 4, 2018

@mdaines Would it be possible to expose other parts of Graphviz through Viz.js? I'm not yet sure exactly which function I would need, but perhaps shape_desc *bind_shape(char *name, node_t * np).

@mdaines
Copy link
Owner Author

mdaines commented Apr 4, 2018

@thari The gh-pages branch of this repository uses Web Workers, but doesn't define a reusable API or anything like that. The solution is just "have a web worker that replies to messages with the result of calling Viz()". I'm working on an update to that branch that implements some of the ideas here, including using workers and returning promises.

A scalajs facade is welcome; just open a pull request, or an issue if you prefer. Do you know if there's a way to provide "tests" for this, like with the TypeScript declaration? https://github.com/mdaines/viz.js/blob/master/test-types/viz-tests.ts

@mdaines
Copy link
Owner Author

mdaines commented Apr 4, 2018

@magjac I think it would be possible. I have thought about that, but I'm not sure I want to do it with this library, since I like the idea that it's more like the Graphviz command-line. But I can imagine a different, lower-level library that would do that.

I haven't really worked with custom shapes -- is it possible to use the DOT shapefile attribute and the Viz.js files option for what you're doing?

@magjac
Copy link

magjac commented Apr 4, 2018

@mdaines Thanks for considering my idea and for your suggestion. I realized I wasn't at all clear about what I wanted to achieve, which was kind of opposite to your suggestion.

What I wanted to do was to retrieve data about how Graphviz draws its standard shapes in order to be able draw an exact copy of such a shape on top of a graph already laid out by Viz.js and then call Viz.js again and let the drawn shape make an animated transition into the new layout. You can see this in action in the D3 Graphviz GUI editor proof-of-concept for the simple ellipse and box shapes. I've started to work on more complex shapes, but already fairly simple shapes like the triangle and the pentagon pose some difficulties, not to speak of the even more complex ones further down the Node Shapes list.

However, I've experimented with another approach which uses Viz.js to create an extra, temporary SVG for a graph containing only the shape I want to draw and then, after translating the coordinates, insert that shape into the graph. This seems feasible since Viz.js is fast enough to do a "layout" of a single-node graph without adding a noticeable delay for the user.

Again, thanks for considering the idea.

@mdaines
Copy link
Owner Author

mdaines commented Apr 4, 2018

@magjac That looks very interesting! I'm not sure how you're accomplishing the transition, but I would have expected it would be possible to extract the SVG element you're interested in by selecting it using getElementById?

@magjac
Copy link

magjac commented Apr 4, 2018

@mdaines Sorry, I don't think I really understand your question. I'm using d3-transition for the transition and d3-selection to select and extract the SVG element I'm interested in. Under the hood d3.select() uses querySelector.

@mdaines
Copy link
Owner Author

mdaines commented Apr 5, 2018

@magjac I was wondering why you need more information from Graphviz about how it draws its shapes and what information you're interested in getting, since you can query for the elements using D3 or querySelector, etc.

@mdaines
Copy link
Owner Author

mdaines commented Apr 5, 2018

I've posted a proof-of-concept here: https://github.com/mdaines/viz-ui

This is a reworking of the current Viz.js website using a vanilla Create React App project.

The relevant API is in this directory https://github.com/mdaines/viz-ui/tree/master/src/viz and I make use of it in https://github.com/mdaines/viz-ui/blob/master/src/Graph.js

Usage boils down to something like this:

import Viz from './viz';
import workerUrl from './viz/viz.worker';

let viz = new Viz({ worker: workerUrl });

let src = 'digraph { a -> b; }';

viz.renderString(src, { format: 'plain' })
.then(str => console.log(str));

viz.renderSVGElement(src, { engine: 'circo' })
.then(element => document.body.appendChild(element));

viz.renderImageElement(src, { scale: 1 })
.then(element => document.body.appendChild(element));

This is a departure from the current single function API, but my thinking is that it shows better what's going on, since Viz now holds on to an instance of Worker.

The odd viz.worker import is kind of a hack -- this copies the Viz.js Emscripten module and worker into the final build as-is and returns the path so a Viz instance can be created. Because the file doesn't end in ".js", Webpack doesn't consider it to be JavaScript.

@magjac
Copy link

magjac commented Apr 5, 2018

@mdaines I want to be able to instantly draw a new node shape on top an already existing graph without having to let Viz.js compute a new layout immediately. Then, sometime later, I let Viz.js compute the new layout and let the old graph transition into the new layout.

In my first approach (which I have now abandoned), I needed the information in order to be able to draw that new shape correctly by inserting SVG elements directly. I couldn't query it since at that point it didn't exist yet.

In the new approach that I've now taken (described above), I don't need that information directly from Graphviz, since I'm creating an extra, temporary SVG, from which I can retrieve it (as you correctly point out above).

@mdaines
Copy link
Owner Author

mdaines commented Apr 5, 2018

@magjac I see! Sounds like a good approach. Maybe somewhat in the spirit of Viz.js also. 😄

As far as deeper access to Graphviz, one place I can see a possible benefit would be providing input as something other than a DOT string. That could mean exposing the Agraph stuff somehow.

@yuanqingfei
Copy link

@thari I am a newbie in ScalaJs. I wonder if you could help with a facade of d3-graphviz. If so, that will be much better

@thari
Copy link

thari commented Apr 24, 2018

@yuanqingfei I am still, trying to find time to work on the promised Scala.js facade for this library. Although I am proficient in Scala, I am new to js and web workers. I am sure it will be fun to build a facade for d3-graphviz however, I don't think I can work on it any time soon.
@mdaines Yes, I am working on a uTest based testing framework.

Sorry to pollute here :(

@mdaines
Copy link
Owner Author

mdaines commented Apr 24, 2018

I think 2.0.0 has settled down a bit at this point, so I'm going to close this. Please check out the latest prerelease in the Releases section or on npm and open a separate issue if you have feedback.

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

No branches or pull requests

5 participants