JuliaPlots / GraphRecipes.jl

Graph-related recipes to be used with Plots.jl
Other
168 stars 28 forks source link

plot directed graph #55

Closed babaq closed 5 years ago

babaq commented 5 years ago

I tried to graphplot(g) where g is a SimpleDiGraph in LigthGraphs, but it seems doesn't do direction arrow connections but just lines between nodes.

JackDevine commented 5 years ago

This can already be done with:

using GraphRecipes
using Plots
using LightGraphs
pyplot()

g = DiGraph(4)
add_edge!(g, 1, 2)
add_edge!(g, 2, 3)
add_edge!(g, 4, 3)
add_edge!(g, 1, 4)

graphplot(g, names=[1,2,3,4], markersize=2, arrow=arrow(:closed, :head, 1, 1))

directed_graph

For some reason, I can only make this work with the pyplot backend, I will look into that problem.

The current behavior is suboptimal for two reasons:

  1. graphplot should have a method so that graphplot(g) actually draws arrows like suggested by OP
  2. The result is not great aesthetically because the arrows end at the center of the node.

Problem 1 should be pretty easy to fix, but 2 will probably take a bit of doing.

JackDevine commented 5 years ago

I have had a bit of luck with the shorten kwarg.

using GraphRecipes
using Plots
using LightGraphs
pyplot()

g = DiGraph(4)
add_edge!(g, 1, 2)
add_edge!(g, 2, 3)
add_edge!(g, 2, 4)
add_edge!(g, 4, 3)
add_edge!(g, 1, 4)
add_edge!(g, 4, 1)

graphplot(g, names=[1,2,3,4], markersize=0.1, arrow=arrow(:closed, :head, 1, 1), shorten=0.023)

directed_graph You could probably make do by playing around with markersize and shorten until a better solution comes along.

mkborregaard commented 5 years ago

@JackDevine there are two possibilities for a fix here:

  1. Put the arrowhead in the middle of the line. Easy fix. Just draw two lines of half length with arrow on the first.
  2. The actual fix is to change the drawing of vertices. Right now, we use Plots' marker (or scatter) functionality to show vertices. They are a point, but the extent is shown in screen coordinates not data coordinates, so you don't know where the edges of the vertex markers are (marker sizes also differ among backends). They can stretch a little with the position and size of the text, but as you can see it's imperfect - it's not completely centered on the font, and the stretched hexagons look a little weird. An ambitious change would be to draw the markers instead using shapes, where you specify the vertices of the polygons explicitly - that would make alignment with text much easier I think.

If you think this is too much, I think fixing the stretching and alignment of markers to be exact, and just stopping the arrows well before they reach the marker at all (so there's a little space) would still be a marked improvement. Thoughts?

JackDevine commented 5 years ago

Thanks for the pointers, I really appreciate it. I was actually about to ask if it was possible to get the extent of a marker, luckily you have answered that!

  1. Is an easier fix, but I am not aware of many graph visualization libraries that do that.
  2. Seems like we would be duplicating a lot of Plots functionality, mostly stuff in src/components.jl

I will have an honest attempt at 2 and see how that goes.

mkborregaard commented 5 years ago

Might be useful to have @diegozea and @daschw 's input here before you commit too much energy that ends up replicating functionality.

diegozea commented 5 years ago

MATLAB looks to be doing 1 but it is not the most common representation. The idea of using shapes instead of markers sounds good. Right now, I think that chorddiagram is the only one using shape instead of scatter as seriestype: https://github.com/JuliaPlots/GraphRecipes.jl/blob/e3180a85f1ffaed1055be6b6fc107ec7d407406e/src/graphs.jl#L397

mkborregaard commented 5 years ago

Gephi (IMHO the world't best open source graph software) does both: https://gephi.org/images/screenshots/neighbours.png and https://gephi.org/images/screenshots/preview3.png

JackDevine commented 5 years ago

Thanks for the examples of libraries using strategy 1, I was not aware of those.

Right now, GraphRecipes has an arcshape for chord diagrams as pointed out by @diegozea . However, to handle markershapes, we would need all of the functionality for the Shape type in Plots.jl. I looked into copying the Shape type into GraphRecipes, however, it looks to me like we would need multiple files worth of duplicated code to get everything.

Possible ways forward:

  1. Just use strategy 1. I am warming to this idea now that you have shown me examples of other libraries using it.
  2. Only have circular nodes. Once we have the ability to scale shapes based on the aspect ratio, we will be able to do this.
  3. Copy the Shape type into GraphRecipes. This would be a lot of duplicated code, which seems unfortunate to me.
  4. Put the Shape type into PlotUtils? Is this even an option or do I misunderstand the point of PlotUtils?
  5. Other ideas? There are probably a lot of ways to move forward here.
mkborregaard commented 5 years ago

Not quite sure why you'd need to copy the Shape type? There's a series recipe for shapes in Plots, so you can just pass a vector of vertices (ending with the first to form a closed polygon) then set the seriestype to shape.

JackDevine commented 5 years ago

Thanks for explaining that, things are not as bad as I had thought.

That said we would still need to create vertices for all of the possible shapes that somebody could pass to the nodeshape argument. Right now GraphRecipes has the functionality to handle arcs and circles, however, users might ask for any of :circle, :rect, :star5, :diamond, :hexagon, :cross, :xcross, :utriangle, :dtriangle, :rtriangle, :ltriangle, :pentagon, :heptagon, :octagon, :star4, :star6, :star7, :star8

Also, would we be OK with the nodes being distorted if the aspect ratio is not equal to one? Otherwise we would need to copy the scale function from Plots.jl.

mkborregaard commented 5 years ago

Hm, aspect_ratio - the question is whether we could get away with always requiring that to be 1 when plotting graphs? I don't think we need to reimplement every single marker shape, do you? circles, ellipses, rectangles and hexagons should do the trick

JackDevine commented 5 years ago

Yes, having the aspect ratio being 1 when plotting graphs might turn out to be OK. One thing that I have been considering is rescaling the x and y axes so that the xlims and ylims are the same.

I am glad that you think that all of the markershapes would be overkill. I agree with the list that you have provided, of course the documentation should mention that nodeshape is a subset of markershape. That can be a part of #59 .

Based on our discussion, I I now have enough information to try fixing this issue. First, I will finish off #65 .

Thank you for your input everyone, if you have anymore insights/suggestions then please let me know and I will try take them on board.

babaq commented 5 years ago

I think always fix aspect ratio=1 will prevent some use case. I found recently needed to plot a neural network of cortex estimated by imaging of neurons, in a 2d or 3d , that the positions of neurons are accurately set by user as x, y, z coordinates. these positions may stretch along specific dimensions, like x=1:1000, but y=20:90. always setting aspect ratio=1 make some dimension impossible to see. also there may be some labelling or annotations needed alongside the graph, for example, several hline!() that mark the layer borders of the cortex.

if we could combine the normal 2d/3d plots together with graph plot, that will open a lot of new use case, cause the node is usually a abstraction of real world entity and often plotted with real world coordinates and with other visual guides.

here is basically what I want to plot: image

JackDevine commented 5 years ago

the positions of neurons are accurately set by user as x, y, z coordinates

Note that your use case is quite different from what GraphRecipes does. In GraphRecipes, the user gives graphplot information about how nodes are connected and then graphplot will work out the x, y, z positions itself. That is why graphplot does not show the x an y axis scales. There are definitely some graph structures where graphplot will calculate x, y positions that are very skewed in one direction. For example, if there is a graph that has a path from node 1 through to node n and no other connections, then graphplot may return a layout where the xlims are very small and the ylims are very large. My suspicion is that these situations can be dealt with by rescaling the xlims and the ylims, although I could be wrong.

I am not sure that GraphRecipes is a good fit for your particular problem. If you already have the coordinates of your nodes and you want to use a range of visual guides, then I would recommend Plots.jl.

babaq commented 5 years ago

I guess I could do something similar in Plots, since it's just a scatter plot with line-arrow segments. for a general GraphRecipes, the only information is the node and edges, then the coordinates has to be worked out to sufficiently visualize the whole graph, the use case I mentioned is just a simpler one, cause the coordinates are already known. all other visual guide are just parts of the Plots, which maybe not shown for a general graph by default, but user should be able to add them as optional arguments.

JackDevine commented 5 years ago

Yes, you are right, it seems that I am always learning new things about GraphRecipes.

n = 20
x = rand(n); y = 30*rand(n)
g = WheelGraph(n)

graphplot(g, x=x, y=y)
vline!([0.5])
hline!([15])

xy Having aspect_ratio=1 would make this example look very bad.

I think that the solution is that if x and y are not provided, then we scale the xlims and ylims, otherwise we don't do any scaling.

Worst case scenario is that we would have to copy the scale function from Plots and use that the same way that markers use it. That doesn't seem too bad to me.

JackDevine commented 5 years ago

This was fixed in #67