almende / vis

⚠️ This project is not maintained anymore! Please go to https://github.com/visjs
7.85k stars 1.48k forks source link

Export Network to SVG #723

Open justinharrell opened 9 years ago

justinharrell commented 9 years ago

Has been given thought to an SVG export for the network? I could not find anything in the docs or existing issues discussing it. We would like to use this for high DPI rendering to PDF using browser or PhantomJS for print. That is SVG will be vectors rather than a bitmap in the PDF allowing output resolution based on print device and saving much space in the PDF.

Looking at commercial networking graphing components such as GoJS they typically have an SVG export ability: http://gojs.net/latest/api/symbols/Diagram.html#makeSvg

If not any input on what would be involved to do this? We may be willing to contribute dev time to implement.

AlexDM0 commented 9 years ago

Hi,

I'm afraid this is not at all possible at the moment. The network uses the HTML5 canvas element, not SVG. In 4.0 this is a bit more decoupled but it would really be a lot of work switching to SVG. All the rendering code would have to be redone, for all types of nodes and edges and all options and all the user interface event handling (click etc) has to be redone. This really is a major overhaul. Depending on the amount of time you'd like to invest into this I could go further into detail.

Regards,

Alex

justinharrell commented 9 years ago

Just to be clear this would be a static SVG export, not interactive. Basically render out a static snapshot of the graph to SVG, this is what GoJs does and it's mainly for printing. So there should be no need for user interface event handling.

I knew this might be a lot of work since this is all canvas based, but I haven't looked into the code yet to see how abstract the render model is. It seems like it would be a relatively straight forward but laborious thing to basically take every draw call and implement an SVG renderer.

We are going to start to get more familiar with the code and may discuss investing time into this in the future.

Thanks for the heads up.

AlexDM0 commented 9 years ago

Hi Justin,

I'd really recommend you'd wait until the 4.0 release. Everything (and I mean everything) in the network is being redone. It will be a LOT more modular and abstracted.

We expect 4.0 around april.

Regards,

Alex

justinharrell commented 9 years ago

Thanks, will do, that is good timing for us as well.

AlexDM0 commented 9 years ago

Hi Justing,

We just released 4.0. Have fun with the svg engine ;)

Regards

fedorov commented 9 years ago

For those not HTML5 savvy - is there an example how this export to SVG works?

justinharrell commented 9 years ago

Not implemented yet. Internally we have had some luck with modifying vis.js to use https://github.com/gliffy/canvas2svg unfortunately we havent had time to make it usable enough to submit a patch. We would like to get this going when we have time as vector export and high res printing are a must for our diagrammer.

fedorov commented 9 years ago

Oh, I see, thanks! Then it sounds like a good feature request to submit to this project?

AlexDM0 commented 9 years ago

Hi,

We're not likely to implement this ourselves as it is a lot of work and we personally have no need for it. If there would be a pull request we could consider it but it would require a lot of maintenance so there are no guarantees there.

Regards

fedorov commented 9 years ago

:-(

Well, but since this is a feature that users of vis.js very likely will need, does it make sense to re-open this issue and keep it open with "wontfix" label, at least?

AlexDM0 commented 9 years ago

I don't think so, we try to keep these issues as a to-do list and I don't see this happening. I also do not think this is very important for most users. For some use cases definitely! But all in all, for a web platform vis does what is does well. There are other frameworks which offer this functionality and we don't have the time to spare to rewrite our system to support this.

Regards

justinharrell commented 9 years ago

The canvas2svg approach had very minimal impact in vis.js I believe just a few changes in _redraw most of the issue revolve around a few missing things in canvas2svg.

SrvrSide commented 8 years ago

Hi, Has there been any advancement with exporting a network map? I have the same problem as thread #806 in that many of the charts created need to be zoomed to be useful, but trying to save the image in the zoomed state only saves the visible portion. I was researching the use of fabricjs to convert the active visjs canvas to SVG on the click of a button and initiate a download. I don't understand much about the canvas internals, is this approach possible/plausible? The downloaded xml is always just "structure" no actual path data etc. I'm working through the threads looking for information but any guidance would be appreciated. -SrvrSide

justinharrell commented 8 years ago

I don't think the fabricjs approach would work based on my understanding of how it works. However I know the https://github.com/gliffy/canvas2svg will work with vis.js with some very minor changes to vis since it just emulates the canvas element. Looks like canvas2svg has improved over the last year as well and may need less changes as it is now a more complete canvas implementation.

Unfortunately we have had no time to devote to this project and hope to get back to it at some point to replace our non web based network graph. To do so we will need vector export as printing or even saving high dpi images is not useful and extremely cumbersome vs a vector format like SVG with infinite resolution.

SrvrSide commented 8 years ago

Thank you justinharrell, it's a real pity, after reading the fabricjs docs it sounded like it would do exactly what I'm after -- provide a conversion of an existing canvas to svg xml. If you don't mind I have an additional question. Being pretty new to JS and vis, how might I "find" the canvas details that are active in order to use the getSerializedSvg method? It seems straight forward if one is drawing the canvas ones self as the canvas reference is returned when the class is initiated. This also seems to be how all the examples are structured. How might this canvas reference be obtained for a canvas created by vis? Using the debugging facilities in chrome & safari I can't seem to find anything that looks likely. I have been trying to access the information through container.firstElementChild.canvas. Would you mind giving me a pointer? Thanks for the feedback anyway -SrvrSide

justinharrell commented 8 years ago

The basic premise is vis.js creates it's own canvas inside the div you give it, you have to go into vis.js and somehow swap out the normal canvas reference they create with the canvas2svg one, then tell vis.js to redraw then you can call getSerializedSvg to get the svg version.

Since vis.js has no way for you to provide your own canvas reference this requires some minor changes to their source to do so, or perhaps just monkey patch it at runtime. We just changed some code in their core _redraw function and it it worked IIRC. We had a couple of issues because canvas2svg was missing a few canvas functions, looks like at least some of them have been implemented in later releases.

felixhayashi commented 8 years ago

How might this canvas reference be obtained for a canvas created by vis? Using the debugging facilities in chrome & safari I can't seem to find anything that looks likely. I have been trying to access the information through container.firstElementChild.canvas. Would you mind giving me a pointer?

I'd suggest you use container.getElementsByTagName("canvas")[0]; where container is your network container. Since the canvas element stays the same after initialization, you just need to do this once.

SrvrSide commented 8 years ago

Thanks Gents, I'll do some research. Appreciate the assistance

justinharrell commented 8 years ago

The key line is here I believe: https://github.com/almende/vis/blob/master/lib/network/modules/CanvasRenderer.js#L143

Instead of: let ctx = this.canvas.frame.canvas.getContext('2d');

you would do something like: let ctx = new C2S(500,500);

Now the canvas context is coming from canvas2svg rather than their real canvas element that has a reference stored in this.canvas.frame.canvas.

SrvrSide commented 8 years ago

Thank you justinharrell. I think this will require JS skills considerably beyond my own (I'm getting errors about the ellipse method being unavailable). I'll have to put the map export feature on ice until I can devote more time to research. Thanks again for the assistance. -SrvrSide

justinharrell commented 8 years ago

That is ringing a bell, for some reason they attached their own shape functions to the canvas render context. This wouldn't have been the way I would do it but anyway the same functions need to be on the canvas2svg version, check out:

https://github.com/almende/vis/blob/111c9984bc4c1870d42ca96b45d90c13cb92fe0a/lib/network/shapes.js

AlexDM0 commented 8 years ago

Hi,

We have extended the canvas object with a number of methods which you'll need to mock:

https://github.com/almende/vis/blob/master/lib/network/shapes.js

Alternatively you could make a wrapper class for these in the canvas renderer that will separate the extensions and the canvas. In your case that would be better I think.

Regards

On 30 Jan 2016, at 23:30, SrvrSide notifications@github.com wrote:

Thank you justinharrell. I think this will require JS skills considerably beyond my own (I'm getting errors about the ellipse method being unavailable). I'll have to put the map export feature on ice until I can devote more time to research. Thanks again for the assistance. -SrvrSide

— Reply to this email directly or view it on GitHub.

SrvrSide commented 8 years ago

Thank you Alex, Justin, I'll look into your suggestions. Brilliant library btw! -SrvrSide

blainepalmer commented 7 years ago

I have this working for most of my use cases and most of the examples in the Network portion of vis

https://github.com/blainepalmer/vis https://github.com/blainepalmer/canvas2svg

check develop branch for source changes built a version into dist folder for easy testing/experimentation my modifications to canvas2svg are in my fork and published to NPM as canvas2svg-visjs https://www.npmjs.com/package/canvas2svg-visjs

It's probably not clean enough for PR right now because of the way I'm just copying the shape prototypes to the canvas2svg in the CanvasRenderer, among other things. If someone wants to point me in the most desirable direction for getting this PR ready I'd be happy to put some more effort into it.

wimrijnders commented 7 years ago

@blainepalmer That's pretty impressive work and likely to be of use to many. The -visjs postfix suggests that this is vis specific, is this correct? I would expect the module to work for any canvas.

Did you need to change anything in network to get this to work? If so, is it possible to give an overview of these changes? I'm sort of getting lost trying to find what's different in your vis-fork.

blainepalmer commented 7 years ago

Hey sorry for not responding sooner.

Our primary need was to capture a static version of what's currently visible in the canvas while still being able to scale it to larger sizes. So my addition is not a separate render pipeline, but an addition to the CanvasRenderer to generate an SVG of what's currently visible on the canvas. This could likely be adapted into a separate Renderer if someone so chose, but would probably be inappropriate for reasons I'll elaborate on below. I've also not added a Blob/File output at this time, only the serialized SVG, though I probably will add Blob support at a later time.

The changes I made to the network were to add C2S to the mix, copy the shape primitives to C2S (in a dirty/simplistic way right now), and to remove a strange canvas save/restore (firefox hack) that seemed to cause issues in my clients.

My changes to canvas2svg, while working for nearly all network tests (notably not the HTML/SVG node test), are hacky as the library is kinda hacky. I've realized that Canvas and SVG, while close, are not perfectly analogous. The way canvas2svg works is pretty much forward moving only and attempts to perform analogous functions in SVG as canvas methods are called. Unfortunately I believe the correct way to do Canvas -> SVG would be to have an interim layer to handle all the behaviors and edge cases that SVG has no appropriate analogy for. Either that or build an SVG renderer from scratch for vis.

While I think there's value for what I've created, it may not be exactly what others are looking for. My goal was not to create an SVG output for those lacking or avoiding canvas support but to get an SVG output of what the user sees on the screen at that point in time, for printing and/or later viewing at varying scales. (i.e. users trying to perfect the layout to tell a story using a timeline or relationship graph)

Here are links to the actual diffs https://github.com/almende/vis/compare/develop...blainepalmer:develop https://github.com/gliffy/canvas2svg/compare/master...blainepalmer:master

Given a vis Network object you should be able to call getSerializedSvg on it and receive an SVG back in a string. It takes a single, optional, boolean parameter to determine if the images are embedded in the SVG. The method is currently synchronous, but should probably be changed to return a promise or take a callback so the UI thread doesn't get blocked.

wimrijnders commented 7 years ago

Thanks for the detailed explanation. While it looks like really cool and potentially useful functionality, I don't think it's ready to be integrated into vis.js. Actually, the setup as a separate tools is a pretty good one.

Unless you commit yourself to getting the code production-ready for vis.js, I think we'll pass on it right now. Kudo's for your work on this, however!

justinharrell commented 7 years ago

Me and Blaine have been working on this some and are pretty happy with it so far, we currently have a button that can convert the current network view to a vector PDF all in the browser using vis.js->canvas2svg.js->svgtopdf.js->pdfkit.js which is great for sharing and printing a graph.

We are going to try and refactor the canvas2svg as much as possible to minimally change vis.js and hopefully make it acceptable for integration since I think it could be quite useful. Looking at implementing a modified CanvasRenderer. Its really nice just to call a single method to get a SVG snapshot of the current network.

HIMISOCOOL commented 7 years ago

The work you have done is really cool and useful. I don't have the time on my project to build vis from source right now but would love to see your work added in the future!

skanskan commented 7 years ago

Hello

Do you think we can have the ability to export Visnetwork graphs in the next months? I'm starting to write an article and this would lead me to choose visnetwork or other tools such as diagrammer to do all the plots, many. I need to get the graphs in a vectorial format embedded in a pdf. Visnetwork has the advantage of allowing you to manually manipulate the graph and the output is beautiful.

justinharrell commented 7 years ago

Do you think we can have the ability the ability to export Visnetwork graphs in the next months?

It definitely possible, we already have it working on a fork linked previously from Blaine. Currently requires more changes than I prefer to vis.js, so next step is trying to make less invasive, we are targeting November to have it working for our uses.

wimrijnders commented 7 years ago

That is ringing a bell, for some reason they attached their own shape functions to the canvas render context.

That's been an eyesore for me for some time. Would it help if those shape functions were moved away from the context? Aside from hurting my eyes, it would be a curious case of having to adjust your code for unnecessary quirks in vis.js.

justinharrell commented 7 years ago

Would it help if those shape functions were moved away from the context?

Yes a little, its not difficult to copy those functions to a different context, but one less thing to do and does seem cleaner to not mess with the context.

If that where done and there was some way to provide a outside context easily all that would be left is to make canvas2svg work properly with https://github.com/almende/vis/blob/bc6330d612ef1b5e13156eee4ef8b129d01013f6/lib/network/modules/components/nodes/util/NodeBase.js#L124 I think and like magic vis.js draws to canvas but vector SVG comes out.

wimrijnders commented 7 years ago

Hokay. For me that's reason enough to it. I suppose this is in effect a death warrant for lib/network/shapes.js.

What mechanism would you propose to swap the canvases? Would there be an API call for this? I suppose I would be expecting something like saveToSVG() and then, hey presto, an SVG pops out.

justinharrell commented 7 years ago

Not sure yet, if canvas2svg where integrated into vis.js, which is what Blaine did, then there is a getSerializedSvg on network.

To keep as a separate library it needs to go in from the outside and provide a different context for one frame then switch back so it would be something like vis2svg.getSerializedSvg (network).

Other options are formalize different renders and being able to swap them at runtime or serialize entire network state into second network with different render.

wimrijnders commented 7 years ago

OK, you're thinking about it. I like the second option the best. An demo of usages under examples would be great.

wimrijnders commented 7 years ago

Reopening because this issue is very much alive.

@justinharrell I fully intend to clean up network/shapes.js. However, I've resolved to do bugfixes only until the next release (which by all measures should be imminent), in order to work towards maximum stability. I hope you have the patience to wait that long, however long that may be.

skanskan commented 7 years ago

If vis had the ability to export to a vectorial format many people would use it to create graphs for their publications.

wimrijnders commented 7 years ago

@skansan I hope you can forgive the following writing from me, because I fully realize that I'm being an asshole. Nevertheless, I feel that I have to respond.

My first response being: 'So what?' Why should I care what these people would do if vis.js has this capability? Because I don't see any motivational reason to hurry its development because of this statement.

My second response being: 'If these people really would want to use it, why don't they chip in to make it possible'? In this case, you're lucky, because there actually are people wanting to implement it; but I can't shake of this vision of publishing people sighing and lamenting the fact of its non-existence. Do something about it then.

Your statement is ethereal in nature and does nothing to aid the development of said feature. After this rant, I'm just going to delete suchlike statements, which contribute nothing whatsoever. The issues should be for technical stuff only, which BTW excludes this writing as well. Let previous comment and this one serve as epitaphs.

HIMISOCOOL commented 7 years ago

You make all valid points. Would you prefer some kind of GitHub reaction on the top post to indicate this kind of passing interest? (I can't seem to see them on mobile but whatever) Could I stick it in the contrib file to make it somewhat official?

On 6/10/2017 12:21 AM, "wimrijnders" notifications@github.com wrote:

@skansan I hope you can forgive the following writing from me, because I fully realize that I'm being an asshole. Nevertheless, I feel that I have to respond.

My first response being: 'So what?' Why should I care what these people would do if vis.js has this capability? Because I don't see any motivational reason to hurry its development because of this statement.

My second response being: 'If these people really would want to use it, why don't they chip in to make it possible'? In this case, you're lucky, because there actually are people wanting to implement it; but I can't shake of this vision of publishing people sighing and lamenting the fact of its non-existence. Do something about it then.

Your statement is ethereal in nature and does nothing to aid the development of said feature. After this rant, I'm just going to delete suchlike statements, which contribute nothing whatsoever. The issues should be for technical stuff only, which BTW excludes this writing as well. Let previous comment and this one serve as epitaphs.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/almende/vis/issues/723#issuecomment-334436053, or mute the thread https://github.com/notifications/unsubscribe-auth/ABpTdN9GXpPalZeJilwi8y7zjdFGdKMDks5spLusgaJpZM4Dwz4R .

skanskan commented 7 years ago

@wimrijnders I guess most users aren't programmers, just users, scientists and engineers of any kind. I expected the developers of a tools like to see his tool beeing used by many people, maybe I'm wrong.

wimrijnders commented 7 years ago

OK I'll be honest, while I meant every word I said, I'm a bit ashamed of that outburst. I'm going to keep up it up for a while as a shaming banner, but I'll remove it soon. It has no place in a technical forum.

I expected the developers of a tools like to see his tool beeing used by many people, maybe I'm wrong.

It's funny how logic inverts when viewed from the other side.

'His tool' is something I can not related to, it's 'our tools'. This is a community effort, many many people have contributed and I will never call it 'mine'. I'm just the guy currently maintaining it and I fully honor and acknowledge the developers who went before me.

(This despite the occasional grumbles I have on the state of the code; I regard this as the perennial status quo and I fully expect my successors to grumble at my best efforts)

As for what I expect: funnily enough what I want is to make this project work as well as possible; on a personal level want I want is that my contribution makes a positive difference, that it works better because I got involved in it. This is strictly an engineer's view.

Please accept this as my world view on the subject. In this light, let me re-analyze your statement in a hopefully less strong wording:

I hope this clarifies a bit why I found you initial statement misplaced.


I acknowledge that this also has no place in a technical forum. I'll be true to my word and remove this discussion in, say, a month's time.

skanskan commented 7 years ago

I don't know about others but I'm just studying medicine and trying to write an article with graphs created with visnetwork (or other similar libraries) showing the relationship between diseases.

Maybe most users are just confined to use the tools, on the contrary, I also try to send my feedback to try to improve things, I don't have the knowledge to develop libraries such as vis, but I guess the development of any software also needs "betatesters". I didn't know that was going to cause any inconvenience. If so, please just remove all my messages.

Best regards

I can just say I thank you for you hard and good job.

wimrijnders commented 7 years ago

I don't have the knowledge nor the time to develop libraries such as vis.

Knowledge: I have plenty of things to do for you within the project that don't directly involve programming.

Time: Me neither, but I do it anyway, because I want to to satisfy the urge to contribute something to the world..

From my point of view, bad excuses. But thank you for appreciating my effort!

justinharrell commented 5 years ago

So we took some time to get this working again and part of it was much simpler than thought. We where able to just inject canvas2Svg into the network and call redraw. We have a modified version of canvas2svg that supports image embedding so the result is a self contained file here https://github.com/justinharrell/canvas2svg

Here is an example of using it to export a network including changing the styling before doing so:

C2S.prototype.circle = CanvasRenderingContext2D.prototype.circle;
C2S.prototype.square = CanvasRenderingContext2D.prototype.square;
C2S.prototype.triangle = CanvasRenderingContext2D.prototype.triangle;
C2S.prototype.triangleDown = CanvasRenderingContext2D.prototype.triangleDown;
C2S.prototype.star = CanvasRenderingContext2D.prototype.star;
C2S.prototype.diamond = CanvasRenderingContext2D.prototype.diamond;
C2S.prototype.roundRect = CanvasRenderingContext2D.prototype.roundRect;
C2S.prototype.ellipse_vis = CanvasRenderingContext2D.prototype.ellipse_vis;
C2S.prototype.database = CanvasRenderingContext2D.prototype.database;
C2S.prototype.arrowEndpoint = CanvasRenderingContext2D.prototype.arrowEndpoint;
C2S.prototype.circleEndpoint = CanvasRenderingContext2D.prototype.circleEndpoint;
C2S.prototype.dashedLine = CanvasRenderingContext2D.prototype.dashedLine;

function exportSvg()
{
    var graphCanvas = $("#linkChart").find("canvas")[0];
    var ctx = new C2S({width: graphCanvas.width, height: graphCanvas.height, embedImages: true});

    var canvasProto = network.canvas.__proto__;
    var currentGetContext = canvasProto.getContext;
    canvasProto.getContext = function()
    {
        return ctx;
    }
    var svgOptions = {
        nodes: {
            shapeProperties: {
                interpolation: false //so images are not scaled svg will get full image
            },
            scaling: { label: { drawThreshold : 0} }
        },
        edges: {
            scaling: { label: { drawThreshold : 0} }
        }
    };
    network.setOptions(svgOptions);
    network.redraw();
    network.setOptions(options);
    canvasProto.getContext = currentGetContext;
    ctx.waitForComplete(function()
        {
            var svg = ctx.getSerializedSvg();
            showSvg(svg);
        });
}
mojoaxel commented 5 years ago

@justinharrell Wow! Maybe you could provide an example project for this. That would be amazing!

justinharrell commented 5 years ago

Ok here is a sample project, I actually uncovered a bug when a broken image can't be found which I fixed and will submit PR for later. Its based off the images with borders example, which also shows off the bug (open the console and see endless error spam).

https://github.com/justinharrell/vis-svg

bjmillsnsx commented 5 years ago

Any plans to merge this great work?