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

Export to SVG extension #639

Closed
acardona opened this issue Jul 3, 2014 · 30 comments
Closed

Export to SVG extension #639

acardona opened this issue Jul 3, 2014 · 30 comments
Labels
help-wanted Issues that would make great pull requests

Comments

@acardona
Copy link

acardona commented Jul 3, 2014

Write an extension that builds up svg objects based on the computed style of each of the elements and returns a root svg element.

Using an automatic canvas-to-svg library will not work.

Original content follows:

Would be wonderful to be able to export to SVG, completing this fantastic library that is cytoscapejs. Is an export to SVG perhaps in the roadmap?

@maxkfranz
Copy link
Member

maxkfranz commented Jul 3, 2014

Yes, this has been considered. The reason it's not there currently is that we didn't want to maintain a separate SVG renderer that only gets used for export.

We initially considered using a library for abstracting rendering so it could be output to canvas, WebGL, SVG, etc. However, those don't give us enough control for the kind of interactivity and performance we want.

This could be done as an extension with an external library dependency if there is a library that creates a canvas 2D context API that renders to SVG properly.

If you're interested in experimenting right now, you could try something like SVGCanvas with the undocumented/private cy.renderer().renderTo( cxt, zoom, pan, pxRatio ) function.

@unidesigner
Copy link

I'm trying to use SVGCanvas and the renderTo function. I'm running into an issue where cytoscape.js calls

context.translate(centerX, centerY);

and SVGKit implements:

SVGKit.prototype.translate = function(elem, tx, ty) {
    /***
        SVGKit.prototype.:
        translate(' translate( 1 ,2 ) ', -10,-20)
        translate(' translate(1) ', -10,-20)
        translate(' translate(10,20) ', 0, -20)
        translate('translate(10,10) rotate(20)', 10, 10)  == 'translate(10,10) rotate(20)translate(10,10)'
        translate('translate(10,10)', -10, -10) ==  ''
        translate('translate(10)', -10)  == ''
    ***/
    var element = MochiKit.DOM.getElement(elem);
    if (MochiKit.Base.isUndefinedOrNull(element)) {
        return this._twoParameter(elem, tx, ty, 
                                   SVGKit.translateRE, 'translate')
    }
    var old_transform = element.getAttribute('transform')
    var new_transform = this._twoParameter(old_transform, tx, ty, 
                                            SVGKit.translateRE,'translate');
    element.setAttribute('transform', new_transform);
    return new_transform;
}

where elem becomes centerX and tx becomes centerY. What would be the proper modification on the SVGKit side to properly pass the context?

Also, SVGKit does not implement setTransform, but has its own currentTransformationMatrix and transformations attribute. I'm trying to understand SVGKit's transformation handling better.

@maxkfranz
Copy link
Member

Maybe a different SVG library would be worth exploring?

If I recall correctly, SVG expects transforms to be on groups (<g>), so elem is probably expected to be a <g> element. Because transforms in canvas stack, you could try embedding a group after each new set of transforms. It sounds like SVGKit/SVGCanvas isn't very complete then...

@unidesigner
Copy link

Do you know another SVG library that could do the trick?

It was my mistake that I instantiated an SVGKit instead of an SVGCanvas. The translate/scale transformations worked then fine. However, the library does not implement e.g. the fillText method to draw text . Adding a dummy fillText method to SVGCanvas let me create an SVG with the correct transformation, but other attributes (e.g. stroke width of cytoscape nodes) were not rendered correctly out of the box.

@maxkfranz
Copy link
Member

I don't know of any libraries off the top of my head, so it would probably require more research.

Another alternative in the short term is to use a high-res PNG. Maybe we could add/document some options for cy.png() so you can specify the dimensions or pixel ratio.

@unidesigner
Copy link

Indeed, options to specify dimensions with cy.png() would come in handy. It would be nice if you could add that. And thanks for this great library!

@acardona
Copy link
Author

Hi all,
just to comment that I managed to successfully export with SVGCanvas, with proper transformations and text. It required manglying two API mismatches:

SVGCanvas.prototype.transform = SVGCanvas.prototype.translate;
SVGCanvas.prototype.fillText = SVGCanvas.prototype.text;

... and filtering the resulting SVG DOM to fix up small issues, for example:

  1. The text of a node is rendered not centered in SVGCanvas. Had to add text-anchor: middle.
  2. Nodes are rendered with the wrong M starting point of the path, almost 0.00 ... that creates an artificial line to 0,0. And with two paths rather than one: one for the contour and one for the fill.
  3. 'triangle' arrowheads have a supernumerary point that makes the 4-point polygon fold over itself, which, in combination with a stroke of 'none', makes them invisible: no area.

These were easy to fix, fortunately, with a bit of ad-hoc post-processing.

The code is here:
catmaid/CATMAID@13d5a30

Hope it helps others out there.

Best,

Albert

@maxkfranz
Copy link
Member

@acardona You may want to package your code as a reusable SVG extension

@unidesigner I've added a ticket for specifying zoom in cy.png() : #659

@acardona
Copy link
Author

acardona commented Oct 6, 2014

Hi @maxkfranz , my workaround stopped working. Now, all paths rendered are like arrowhead paths--with the properties still correct as if they were the expected edges, arrowheads and node circles.

Any ideas what could be going wrong?

We use cytoscapejs 2.2.8.

@maxkfranz
Copy link
Member

I think that with the new performance enhancements and new features, we use APIs that are not supported in those canvas-to-svg libs.

Probably, the most stable and least invasive way to export to SVG would be to add a SVG option to the canvas renderer. This would involve adding a SVG option to the canvas renderer and adding SVG generating code to each of the low-level draw functions:

(1) node
(2) edge
(3) edge arrows

Then the renderer would be renamed to something other than "canvas". Because the canvas renderer handles interaction on a low level, interaction with the SVG graphs would be free and probably much more performant than adding listeners on SVG elements. The main tricky part is reusing SVG elements for performance.

I don't have time to look at this now, but I could fork the unstable branch if someone is interested in trying to get SVG output from the renderer. And I would be able to help out a bit if needed.

tomka added a commit to catmaid/CATMAID that referenced this issue Oct 7, 2014
We recently found out that the SVG export was broken, but couldn't really find a
commit that caused this. Even commits that were known to work in the past
suddenly failed. As it turns out (after stepping for quite a while through
Cytoscape and SVGKit) does Cytoscape use Path2D to cache paths if it is
available. SVGKit, however, is not able to use it and silently fails to draw
paths if Cytoscape uses Path2D. This in turn makes our export function crash.

The reason why this didn't show up earlier is that Chrome only recently marked
Path2D to be not experimental anymore and made it available in the global
namespace. Firefox, had it enabled for quite some time already, but we
apparently never tested the SVG export in it. It now works there as well.

This is related to the discussion in cytoscape/cytoscape.js#639.
@tomka
Copy link

tomka commented Oct 8, 2014

Extending the canvas renderer would be nice indeed. If time permits I might have a look at it.

The linked commit was in response to @acardona, because I fixed the workaround to work again. Like described in the commit message, it was Path2D which suddenly became a problem: The Chrome browser made it available by default. Cytoscape uses it for path caching as it seems, but SVGKit (which we use) can't deal with it. So I monkey-patched Cytoscape for the export to not use Path2D for the export and it works.

@maxkfranz maxkfranz changed the title Export to SVG Export to SVG / SVG rendering output Nov 4, 2014
@maxkfranz maxkfranz added priority-2-medium help-wanted Issues that would make great pull requests labels Nov 4, 2014
@DSin52
Copy link

DSin52 commented Jul 8, 2015

I noticed that in the recent versions, an option for JPG export has been added in. Is there any hope that an SVG option will also get implemented soon considering this issue was created several months ago?

@maxkfranz
Copy link
Member

Unfortunately, it would be a very large undertaking and I'm not sure whether it will fit into the next release -- given the extensive changes needed in the renderer. It also would double maintenance costs of the renderer for little pragmatic benefit over high-res PNGs.

You could use @tomka's method and indeed his change to disable Path2D for export could make it into a minor, patch release to make things more straightforward.

If you're interested in native support in the renderer sooner, I can review a PR that covers the changes I've mentioned earlier.

@miskar
Copy link

miskar commented Jul 18, 2015

Is it possible to have an example code that uses the SVGcanvas approach as developed by acardona and tomka? I am a rookie in javascript and had difficulty in implementing the svg export functionality. Thanks a lot in advance.

@tomka
Copy link

tomka commented Jul 18, 2015

Hi @miskar, the commit linked right before my last comment in this issue contains the changes needed to make it work (at least for us). You basically need to do the following:

// Create a new SVGKit SVG canvas of desired size, it will be used to render from Cytoscape.
var svg = new SVGCanvas(width, height);

// Cytoscape uses Path2D if it is available. Unfortunately, SVGKit isn't able
// to make use of this as well and silently fails to draw paths. We therefore
// have to monkey-patch Cytoscape to not use Path2D by overriding its test.
// We reset to the original function after the graph has been rendered.
var CanvasRenderer = cytoscape('renderer', 'canvas');
var orignalUsePaths = CanvasRenderer.usePaths;
CanvasRenderer.usePaths = function() { return false; };

// Assuming your cytoscape instance is available as 'cy', render it to the SVG canvas
// created earlier.
cy.renderer().renderTo( svg, 1.0, {x: 0, y: 0}, 1.0 ); 

// Reset Path2D test of Cytoscape
CanvasRenderer.usePaths = orignalUsePaths;

@sepro
Copy link

sepro commented Nov 10, 2015

I've tried the solution from @tomka , but ran into an error with setTransform (which isn't implemented in SVGKit as mentioned by @unidesigner). How did you get around this?

@maxkfranz
Copy link
Member

I've tried SVGCanvas and it works only in very, very simple cases. It's not a reliable solution...

@eelco2k
Copy link

eelco2k commented Apr 7, 2016

I'm also getting some error's when trying to convert canvas to svg with canvas2svg.js

canvas2svg.js:515 Error: Invalid value for <path> attribute d="L 54.1865707939973 -189.17190648836421 L 60.196982187342144 -196.81961561481185 L 50.47256805017713 -197.03931925361914"

So i added this piece of code to make sure a path always starts with an "M". on line 515 of canvas2svg.js

d=d.replace(/^L/, 'M');

i'm using Canvas 2 Svg v1.0.6

@jelmerjellema
Copy link

jelmerjellema commented Jan 25, 2017

When I use canvs2svg I do not get errors. The first tests however give me a svg that is filled with PNG objects for each node (our nodes have SVG backgrounds). Is cytoscapeJS rendering background SVG as PNG? In that case there is no use for exporting to SVG, because it will always contain bitmaps.

This is my code, which does not yet deliver a good SVG anyway.

var ctx = new C2S(1000,1000);
cy.renderer().renderTo( ctx);
var mySerializedSVG = ctx.getSerializedSvg();

@tomka
Copy link

tomka commented Jan 25, 2017

Yeah we noticed the same. Cytoscape caches previously rendered elements as images. We had some success with overriding the caches during SVG export so that cache look-ups would always fail, which in turn causes the actual drawing routines to be called. However, this also didn't allow previous hack to work completely due to some problems with SVGCanvas.

Since we only need to export relatively simple nodes and edges and their labels, we wrote our own SVG exporter in the end:

https://github.com/catmaid/CATMAID/blob/dev/django/applications/catmaid/static/libs/catmaid/svg-factory.js

On the calling side, all Cytoscape nodes and edges are walked and rendered one by one. Luckily Cytoscape caches some rendering information, so we barely need to do any math ourselves (for now):

https://github.com/catmaid/CATMAID/blob/dev/django/applications/catmaid/static/js/widgets/compartment_graph_widget.js#L2364

Depending on how complex your graph is and the features you use, a similar approach might work for you.

@maxkfranz
Copy link
Member

nrnb/GoogleSummerOfCode#61

This project will use a similar approach with new calculated rendered values in 3.1:

#1552

#1551

The GSOC project (if accepted) will give a good initial version of a SVG export extension. It will probably support all rendering features in 3.1. For core versions beyond that, the extension will probably require community PRs for new rendering features added to the core.

@tfjmp
Copy link

tfjmp commented Apr 7, 2017

Is there any update on svg export? My apology if it is already supported through an extension.

@jelmerjellema
Copy link

jelmerjellema commented Apr 21, 2017

As far as I know there is no update.

There are two roads that people seem to follow:

  1. Use a pseudocanvas library like Canvas2Svg (the one I tried) and make the native cytoscape.js code render to this canvas. (using cy.renderer().renderTo(...))
  2. Create special code to directly create a svg from the information in cytoscape.

I tried the first route, because the second one is like building a new graph library for svg, including styling etc. I found out the cytoscape uses a lot of bitmap caching for speed, and as you can read above, people have tried to hack the renderer to forget the caches. What I did was just create a new cytoscape instance, fill this with the same elements - cy2.json(cy1.json()); - and render this to the pseudocanvas.

This kind of worked, but not enough. The real problem in our project was, that we use SVG background images for nodes. These are rendered by CytoscapeJs using canvas.drawImage, with as argument an . At this point, any SVG paths are already turned into bitmaps, so the final SVG will contain a bitmapped image in the right dimensions for the current zoom. Not scalable at all...

So I am ready to give up on this. The alternative might be finding a good way to export a PNG in a given resolution.

The only thing I can think of right now (using route 1) is make the cytoscape renderer render svg images not through an image tag, but by converting the SVG into canvas. Maybe through Path2D (https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D) or some library?
This library seems to have this purpose: https://code.google.com/archive/p/canvas-svg/source/default/source.

@gexiaowei
Copy link

gexiaowei commented May 20, 2017

@jelmerjellema I try your way to use your code

var ctx = new C2S(1000,1000);
cy.renderer().renderTo( ctx);
var mySerializedSVG = ctx.getSerializedSvg();

it has no error happend, but it just output and svg with base64 encode image
if I use ctx.getSvg(); it has an error say Uncaught Error: Attempted to apply path command to node g
I just want to know if I can output an svg with nodes

@maxkfranz
Copy link
Member

maxkfranz commented Jun 26, 2017

Trying to use any of the automatic canvas-to-svg conversion libs isn't going to work. It was only semi-working when cyjs just directly drew everything with the canvas fill/stroke apis. Now cyjs is more advanced. In future, cyjs will use webgl in places.

At this point, using a canvas-to-svg is unrealistic and hacky.

The only way to have this working is to build a svg exporter extension that builds up svg objects based on the stylesheet and the other computed style values (like bezier points).

Please keep further discussion in this thread on the topic of an exporter extension for svg.

@maxkfranz maxkfranz changed the title Export to SVG / SVG rendering output Export to SVG extension Jun 26, 2017
@alexlenail
Copy link

@maxkfranz I just downloaded cytoscape and it seems like this is a feature on the current release. Might be suitable to close this issue. Thanks for providing this feature!

@VGSebastian
Copy link

@zfrenchee I don't see any mention of this in the release notes or other documentation of cytoscape.js. Are you maybe confusing cytoscape.js and cytoscape? The former is a JavaScript library while the latter is a standalone desktop app. See also http://manual.cytoscape.org/en/stable/Cytoscape.js_and_Cytoscape.html

@alexlenail
Copy link

My mistake @VGSebastian!

@kinimesi
Copy link

I have created an extension that exports current graph as an SVG image. It works for most of the simple graphs. Here is a simple demo.

It uses canvas2svg library. I know it is hacky, but until we have something better, we can make use of it.

@cancerberoSgx
Copy link

Hi, I just arrived to the conversation, but I would also in investigate using dagre graphlib / dot file format .(https://github.com/dagrejs) file format. Since the library already renders to dagre, would not be possible to extract the .dot file or graphlib representation ? . If so then it could be translated to a .doc file or to a .good quality svg.

Dagre itself won't render but this project http://viz-js.com/, (emsripten port of graphbi) will render graphlib/.dot to svg

Again, I'm new on these libraries and don't know the status of the project although the demo looks nice, but it could help people that just need to transform graphs offline. Regarding this, my advice would be not to invest trying to transform canvas/bitmaps to svg..My two cents. will try to investigate more, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help-wanted Issues that would make great pull requests
Projects
None yet
Development

No branches or pull requests