Giter VIP home page Giter VIP logo

Comments (16)

asinghvi17 avatar asinghvi17 commented on July 18, 2024 4

@greimel, it seems like you only need to get the boundingbox and maximum width of the text. I suggest the following basic modification, which allows you to automatically find the text size:

Code
function text_bbox(textstring::AbstractString, textsize::Union{AbstractVector, Number}, font, align, rotation, justification, lineheight)
    glyph_collection = Makie.layout_text(
            textstring, textsize,
            font, align, rotation, justification, lineheight,
            RGBAf(0,0,0,0), RGBAf(0,0,0,0), 0f0
        )

    return Rect2f(Makie.boundingbox(glyph_collection, Point3f(0), Makie.to_rotation(rotation)))
end

# ╔═╡ a5be84d8-d9d5-4c1d-8b2c-7eca3857f2fb
function numbered_graphplot!(ax, g; 
	minimal=false, extend_limits=automatic,
	nlabels = vertices(g),
	layout = Spring(), node_color = (:lightgray, 1), node_attr = (;), node_markersize = automatic, node_marker = :rect, node_font = Makie.defaultfont(), node_textsize = 16, kwargs...)
	n = nv(g)

    # Extract text sizes and layout accordingly
    label_sizes = [widths(text_bbox(string(label), node_textsize, node_font, (:center, :center), 0f0, 0, 0)) for label in nlabels]

    # label_diags = collect(sqrt.(sum.([label_size .^ 2 for label_size in label_sizes])))
    # max_size = maximum(first.(label_sizes))# .+ node_textsize

    # We can specify markersize as a Vec2f, which is the eltype of label_sizes.
    # Thus, we can explicitly cause the node drawing to be large enough to accomodate
    # the marker size.
	node_attr = (; 
        node_attr...,
        marker = node_marker, color = node_color, 
        strokewidth = 1, markersize = node_markersize == automatic ? map(x -> x .+ node_textsize, label_sizes) : node_markersize, 
        markerspace = :pixel,
    )

	if minimal
		hidedecorations!(ax)
		hidespines!(ax)
	end

	if extend_limits !== automatic
		positions = layout(g)
		ax.limits[] = extended_box(positions, extend_limits)
	else
		ax.limits[] = (nothing, nothing, nothing, nothing)
	end
	
	#poly!(ax, Circle.(layout(g), 0.07px), color = (:white, 0))
	graphplot!(ax, g; node_attr, layout, kwargs...) #node_marker = '1':'5')

	text!(ax, string.(nlabels); position=layout(g), font = node_font, textsize = node_textsize, align = (:center, :center))
end

Basically, what this does is it computes the text's bounding box by using Makie's layout utilities, and passes the computed widths to markersize.

This means that all markers are sized independently.

With this,

let
    g = cycle_digraph(15)
    numbered_graphplot(g, arrow_size = 15, nlabels = string.(100 .* (1:15)))
end

iTerm2 Ze2xHw

Setting node_marker=:circle, the layout is a little tighter, but still theoretically good. There are definitely a lot of ways you can play with the markersize in order to make it look as good as possible.
iTerm2 Rl5KO0

from graphmakie.jl.

greimel avatar greimel commented on July 18, 2024 3

I needed something similar, so I've written code for it. It's only good for one character labels, though. And doesn't cover anything that @hexaeder has thought about above: No interaction, no handling of overlapping nodes, not even proper text boxes.

I thought I share it here, in case somebody finds this useful.

image

image

code
# ╔═╡ 0de55944-f737-4a4f-849d-3a925b1eceb3
using Makie: automatic, Figure, Axis, hidedecorations!, hidespines!, text!

# ╔═╡ 7fd33644-b26a-439b-8a63-29535abc1dd3
using GraphMakie: graphplot!

# ╔═╡ c2bf3f66-b017-44ac-844e-cd2350828ddb
using NetworkLayout: Spring

# ╔═╡ 40ef5e6a-5b73-4963-bc71-5faad1948081
using Graphs: vertices, nv

# ╔═╡ 9fbfe652-e31b-4a09-99ed-62db24bc03c5
function numbered_graphplot(g; minimal = true, figure = (;), axis = (;), kwargs...)
	fig = Figure(; figure...)
	
	ax = Axis(fig[1,1]; axis...)
	
	numbered_graphplot!(ax, g; minimal, kwargs...)

	
	fig
end

# ╔═╡ a5be84d8-d9d5-4c1d-8b2c-7eca3857f2fb
function numbered_graphplot!(ax, g; 
	minimal=false, extend_limits=automatic,
	nlabels = vertices(g),
	layout = Spring(), node_color = (:lightgray, 1), node_attr = (;), kwargs...)
	n = nv(g)
	
	node_attr = (; node_attr..., marker = :circle, color = node_color, strokewidth = 1, markersize = n < 10 ? 35 : 45)

	if minimal
		hidedecorations!(ax)
		hidespines!(ax)
	end

	if extend_limits !== automatic
		positions = layout(g)
		ax.limits[] = extended_box(positions, extend_limits)
	else
		ax.limits[] = (nothing, nothing, nothing, nothing)
	end
	
	#poly!(ax, Circle.(layout(g), 0.07px), color = (:white, 0))
	graphplot!(ax, g; node_attr, layout, kwargs...) #node_marker = '1':'5')

	text!(ax, string.(nlabels); position=layout(g), align = (:center, :center))
end

# ╔═╡ 0442bc7c-9c74-4325-9f8f-5f77b169a495
md"""
## Utils
"""

# ╔═╡ da154cf8-0939-4505-a8b7-3cb979a0a371
function extended_extrema(points, margin = 0.05)
	min, max = extrema(points)
	ε = (max - min) * margin

	if ε > 0
		return min - ε, max + ε
	else
		return nothing, nothing
	end
end

# ╔═╡ 6cb5f932-ee5c-40db-bed7-ec592532cfff
function extended_box(points, margin = 0.05)
	(extended_extrema(first.(points), margin)..., 
	 extended_extrema(last.(points), margin)...)
end

# ╔═╡ c05bf8a6-d5db-43d7-a855-ed1a98d71057
md"""
## Tests
"""

# ╔═╡ 53ac8dd8-26d1-4c8b-ba50-580a7086dbf4
using Graphs: complete_digraph, cycle_graph, cycle_digraph

# ╔═╡ d10d10bf-ae56-480c-b60c-232c0e0ad3ce
using NetworkLayout: Shell

# ╔═╡ fbd2ab88-7f46-4e64-8954-99539a7fb75e
using CairoMakie: DataAspect

# ╔═╡ 5554c7be-7e00-47c1-b1cb-fc11c84acd9f
begin
	g = complete_digraph(5)
	
	fig = Figure(font = "CMU")
	ax = Axis(fig[1,1], aspect = DataAspect())
	
	#hidedecorations!(ax)

	numbered_graphplot!(ax, g; layout = Shell(), arrow_size = 15)

	fig
end

# ╔═╡ 3a5e4d4e-204f-44d4-924f-9d85abef79c1
let
	g = cycle_digraph(5)
	
	numbered_graphplot(g, arrow_size = 15)
end

# ╔═╡ 700d5452-c2ce-4ab1-aa40-2a8ffaa0f6bf
let
	g = cycle_graph(5)
	
	numbered_graphplot(g, nlabels = 'A':'E')
end

from graphmakie.jl.

asinghvi17 avatar asinghvi17 commented on July 18, 2024 2

Yea, just select the maximum width instead of using both :)

from graphmakie.jl.

hexaeder avatar hexaeder commented on July 18, 2024 2

I think this would be a great addition, even without having all the features. I think there are good reasons for both: extending the current recipe and creating a second recipe.

Extending the recipe comes with a less clear interface. For example, what should happen if you combine nlabels_align = (:top, :center) with node_size = :text_size? This means that you really have to know your keywords to achieve the style. Having a new recipe would help in providing sane defaults for this specific kind of graphplot, something like textboxgraphplot(g, nlabels; kwargs...) could work out of the box.

On the other hand, the different parts of the graph plots need to be more modular in order to play well with different root recipes. I started this by creating a separate edgeplot recipe which is used internally, but for example the edge labels should move to that recipe as well, otherwise we'll end up with code duplication.

from graphmakie.jl.

hdavid16 avatar hdavid16 commented on July 18, 2024 2

@greimel @asinghvi17 are either of you planning on writing a PR for this?

from graphmakie.jl.

greimel avatar greimel commented on July 18, 2024 1

@hexaeder This looks very good, doesn't it? Still it doesn't address all of your concerns above.

There are some things which need to be done

  • implement some kind of textbox recipe which will take the node labels and places them in shapes drawn as poly
  • potentially update some layouts that can handle a "node size" parameter (avoid placing nodes to close to each other which would lead to overlapping polys)
  • think about anchors on the textboxes, ie where should the edges start and end

What do you think about this now? Also, what about

Finally i am not sure whether this can be achieved as part of the graphplot recipe. Maybe it would be wise to create a new recipe for that.

One could integrate this using existing keywords. E.g.

nlabel_align = :inside
node_size = :text_size

Do would you think about a draft PR that tries to integrate this into GraphMakie?

from graphmakie.jl.

hexaeder avatar hexaeder commented on July 18, 2024

Currently not really, no. Each graph plot is based on a scatter + some connecting lines in GraphMakie. You might want to check out GraphRecipes for that. I think, they have this kind of feature.
However you could use the propertie nlabels_align on a per-node basis to place the labels on the right sides of the nodes (see Julia AST example). With nlabels_offset and nlabels_distance you may further tweak the position of the labels.

from graphmakie.jl.

roland-KA avatar roland-KA commented on July 18, 2024

Thank's for the hints!

Do GraphRecipes also work with Makie? I've only used them with Plots and I didn't find any references to Makie in the documentation.

from graphmakie.jl.

hexaeder avatar hexaeder commented on July 18, 2024

No they don't. It is Plots only...

from graphmakie.jl.

roland-KA avatar roland-KA commented on July 18, 2024

Ok thank's, then I have all the information needed so far!

from graphmakie.jl.

hexaeder avatar hexaeder commented on July 18, 2024

I'll keep this open because eventually I'd like to expand GraphMakie in that direction. This won't happen to soon though.

There are some things which need to be done

  • implement some kind of textbox recipe which will take the node labels and places them in shapes drawn as poly
  • potentially update some layouts that can handle a "node size" parameter (avoid placing nodes to close to each other which would lead to overlapping polys)
  • think about anchors on the textboxes, ie where should the edges start and end

Finally i am not sure whether this can be achieved as part of the graphplot recipe. Maybe it would be wise to create a new recipe for that.

from graphmakie.jl.

roland-KA avatar roland-KA commented on July 18, 2024

Oh, it would be fantastic, if you could realize these plans. I'm looking forward to it.

from graphmakie.jl.

roland-KA avatar roland-KA commented on July 18, 2024

Ah that looks really good! ... and demonstrates again the need for such solutions.

I'm currently playing around with Plots and GraphRecipes, using the TreePlot recipe for plotting trees. But some variants have serious issues (JuliaPlots/GraphRecipes.jl#172) and even the variants that work have some potential for improvement. E.g. the edges connecting the nodes do not attach exactly to the node boundaries. You can control them a bit using the shorten parameter. But on some nodes they don't connect at all and on other nodes they reach inside the node box (see: JuliaForMLTutorial; there "ML-Tutorial 3 .." in folder "notebooks" for some examples).

from graphmakie.jl.

greimel avatar greimel commented on July 18, 2024

@asinghvi17 thanks, that looks great! Is there a way to have proper circles (non-squished) with your way?

from graphmakie.jl.

greimel avatar greimel commented on July 18, 2024

Phantastic, thanks!

from graphmakie.jl.

Nosferican avatar Nosferican commented on July 18, 2024

See https://discourse.julialang.org/t/textbox-graph-with-networklayout-with-labels-cut-off-using-graphmakie/96566/ on the expanding limits.

from graphmakie.jl.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.