Skip to content

Commit

Permalink
add graphs extension
Browse files Browse the repository at this point in the history
  • Loading branch information
brucala committed Dec 1, 2024
1 parent 98e97e0 commit e37e271
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 11 deletions.
9 changes: 9 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,18 @@ REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[weakdeps]
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a"

[extensions]
GraphsExt = ["Graphs", "NetworkLayout"]

[compat]
DefaultApplication = "1.1"
Graphs = "1.12.0"
JSON = "0.21"
MultilineStrings = "0.1.1"
NetworkLayout = "0.4.7"
Tables = "1"
julia = "1.6"
4 changes: 4 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DemoCards = "311a05b2-6137-4a5a-b473-18580a3d38b5"
Deneb = "b5a61f88-a05b-4725-9526-4eca17cec623"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a"

[compat]
DemoCards = "0.5.4"
Documenter = "1.1"
Graphs = "1.12.0"
NetworkLayout = "0.4.7"
3 changes: 2 additions & 1 deletion docs/examples/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"composite_marks",
"multi_view_plots",
"layered_plots",
"interactive"
"interactive",
"graphs",
]
}
14 changes: 14 additions & 0 deletions docs/examples/graphs/graph_chart.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# ---
# cover: assets/graph_chart.png
# author: bruno
# description: Simple Graph Chart
# ---

using Deneb, Graphs, NetworkLayout

g = wheel_graph(10)

chart = plotgraph(g)

# save cover #src
save("assets/graph_chart.png", chart) #src
14 changes: 14 additions & 0 deletions docs/examples/graphs/graph_chart_labels.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# ---
# cover: assets/graph_chart_labels.png
# author: bruno
# description: Graph Chart wih Custom Labels
# ---

using Deneb, Graphs, NetworkLayout

g = smallgraph(:cubical)

chart = plotgraph(g, node_labels='a':'h')

# save cover #src
save("assets/graph_chart_labels.png", chart) #src
14 changes: 14 additions & 0 deletions docs/examples/graphs/graph_chart_layout.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# ---
# cover: assets/graph_chart_layout.png
# author: bruno
# description: Graph Chart wih Custom Layout
# ---

using Deneb, Graphs, NetworkLayout

g = smallgraph(:petersen)

chart = plotgraph(g, layout=Shell(nlist=[6:10, ]))

# save cover #src
save("assets/graph_chart_layout.png", chart) #src
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ makedocs(
"Multi-views" => "multiview.md",
"Interactive Charts" => "interactive.md",
"Data Transformations" => "transformations.md",
#"Graphs" => "graphs.jl",
# "Customization" => "customization.md",
"Themes" => "themes.md",
# "Internals" => "internals.md",
Expand Down
76 changes: 76 additions & 0 deletions ext/GraphsExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
module GraphsExt

using Deneb, Graphs, NetworkLayout

function node_data(g::AbstractGraph, pos; labels::Union{Nothing, AbstractVector}=nothing)
isnothing(labels) && return [(id=v, x=pos[v][1], y=pos[v][2]) for v in vertices(g)]
length(labels) == nv(g) || error("labels length must be equal to number of vertices")
return [
(id=v, x=pos[v][1], y=pos[v][2], label=l)
for (v, l) in zip(vertices(g), labels)
]
end

function edge_data(g::AbstractGraph, pos; labels::Union{Nothing, AbstractVector}= nothing)
isnothing(labels) && return [
(src=src(e), dst=dst(e), x=pos[src(e)][1], y=pos[src(e)][2], x2=pos[dst(e)][1], y2=pos[dst(e)][2], label=repr(e))
for e in edges(g)
]
length(labels) == ne(g) || error("labels length must be equal to number of edges")
return [
(src=src(e), dst=dst(e), x=pos[src(e)][1], y=pos[src(e)][2], x2=pos[dst(e)][1], y2=pos[dst(e)][2], label=l)
for (e, l) in zip(edges(g), labels)
]
end

function Deneb.graph_data(
g::AbstractGraph;
layout::NetworkLayout.AbstractLayout=Spring(),
node_labels::Union{Nothing, AbstractVector}=nothing,
edge_labels::Union{Nothing, AbstractVector}=nothing,
)
pos = layout(g)
nodes = node_data(g, pos; labels=node_labels)
edges = edge_data(g, pos; labels=edge_labels)
return nodes, edges
end

function Deneb.Datasets(
g::AbstractGraph;
layout::NetworkLayout.AbstractLayout=Spring(),
node_labels::Union{Nothing, AbstractVector}=nothing,
edge_labels::Union{Nothing, AbstractVector}=nothing,
)
nodes, edges = graph_data(g; layout, node_labels, edge_labels)
return Datasets(; nodes, edges)
end

function Deneb.plotgraph(
g::AbstractGraph;
layout::NetworkLayout.AbstractLayout=Spring(),
node_labels::Union{Nothing, AbstractVector}=nothing,
edge_labels::Union{Nothing, AbstractVector}=nothing,
)
base = Datasets(g; layout, node_labels, edge_labels) * Encoding(
x=field("x:q", axis=nothing),
y=field("y:q", axis=nothing),
)

ntooltip = isnothing(node_labels) ? :id : [field(:id), field(:label)]
points = Mark(
:point, size=400, fill=:lightblue, opacity=1,
) * Encoding(tooltip=ntooltip)
labels = Mark(:text) * Encoding(
text=isnothing(node_labels) ? :id : :label,
tooltip=ntooltip,
)

lines = Mark(:rule, size=3) * Encoding(x2=:x2, y2=:y2, tooltip=:label)

return base * (
Data(:name, :edges) * lines +
Data(:name, :nodes) * (points + labels)
)
end

end
21 changes: 20 additions & 1 deletion src/Deneb.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ include("themes.jl")
export
# api
spec, vlspec,
Data, Mark, Encoding, Transform, Params, Facet, Repeat,
Data, Datasets, Mark, Encoding, Transform, Params, Facet, Repeat,
field, layout, projection,
resolve, resolve_scale, resolve_axis, resolve_legend,
config, title, expr, param,
Expand All @@ -41,4 +41,23 @@ export
# themes
set_theme!, print_theme


# Graphs extension

"""
plotgraph
Method from GraphsExt, requires Graphs and NetworkLayout
"""
function plotgraph end

"""
grapdata
Method from GraphsExt, requires Graphs and NetworkLayout
"""
function graph_data end

export plotgraph, graph_data

end # module
17 changes: 17 additions & 0 deletions src/api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ vlspec(s::Spec) = VegaLiteSpec(; rawspec(s)...)
vlspec(s::NamedTuple) = VegaLiteSpec(; s...)
vlspec(; s...) = VegaLiteSpec(; s...)
vlspec(s::ConstrainedSpec) = VegaLiteSpec(; rawspec(s)...)
#vlspec(s::DatasetsSpec) = VegaLiteSpec(datasets = rawspec(s))
vlspec(s::DataSpec) = VegaLiteSpec(data = rawspec(s))
vlspec(s::TransformSpec) = VegaLiteSpec(transform = rawspec(s))
vlspec(s::ParamsSpec) = VegaLiteSpec(params = rawspec(s))
Expand Down Expand Up @@ -64,6 +65,22 @@ function Data(generator::SymbolOrString; kw...)
kw = isempty(kw) ? true : kw
Data(NamedTuple{(generator,)}((kw, )))
end
Data(key::SymbolOrString, value::SymbolOrString) = Data(NamedTuple{(key,)}((value, )))

"""
Datasets(; datasets...)
Creates a top-level `dataspec` with the given datasets (values) and names (keywords).
The datasets can be given as tables that supports the [Tables.jl interface](https://github.com/JuliaData/Tables.jl).
"""
function Datasets(; datasets...)
names = keys(datasets)
tables = (_rowtable(t) for t in values(datasets))
return vlspec(datasets=NamedTuple(zip(names, tables)))
#return DatasetsSpec(spec(NamedTuple(zip(names, tables))))
end

_rowtable(table) = Tables.istable(table) && !Tables.isrowtable(table) ? Tables.rowtable(table) : table

"""
Transform(; spec...)
Expand Down
31 changes: 22 additions & 9 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,29 @@ struct TopLevelProperties <: PropertiesSpec
autosize::Spec
config::Spec # TODO: create dedicated type?
usermeta::Spec
datasets::Spec
end
TopLevelProperties(; spec...) = ConstrainedSpec(TopLevelProperties; spec...)

#=
"""
Spec containing the `datasets` property of viewable specifications.
https://vega.github.io/vega-lite/docs/data.html
"""
struct DatasetsSpec <: ConstrainedSpec
datasets::Spec
end
DatasetsSpec(; datasets=nothing, kw...) = DatasetsSpec(Spec(datasets))
rawspec(s::DatasetsSpec) = rawspec(s.datasets)
=#

"""
Vega-Lite specification.
https://vega.github.io/vega-lite/docs/spec.html
"""
struct VegaLiteSpec{T<:ViewableSpec} <: ConstrainedSpec
toplevel::TopLevelProperties
#datasets::DatasetsSpec
viewspec::T
end
VegaLiteSpec(; spec...) = ConstrainedSpec(VegaLiteSpec; spec...)
Expand Down Expand Up @@ -135,19 +149,18 @@ DataSpec(s::Union{Spec, DataSpec}) = DataSpec(rawspec(s))
DataSpec(; data=nothing, kw...) = DataSpec(data)
function rawspec(s::DataSpec)
!Tables.istable(s.data) && return s.data
Tables.isrowtable(s.data) && return (values=s.data, )
# already in the VegaLite shape or a data generators
if s.data isa NamedTuple && (
haskey(s.data, :values)
|| haskey(s.data, :graticule)
|| haskey(s.data, :sequence)
|| haskey(s.data, :sphere)
)
return s.data
end
s.data isa NamedTuple && (
haskey(s.data, :values)
|| haskey(s.data, :graticule)
|| haskey(s.data, :sequence)
|| haskey(s.data, :sphere)
) && return s.data
Tables.isrowtable(s.data) && return (values=s.data, )
return (values=Tables.rowtable(s.data), )
end


"""
Spec containing the `mark` property of a Single-View spec.
https://vega.github.io/vega-lite/docs/mark.html
Expand Down

0 comments on commit e37e271

Please sign in to comment.