diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34b16a6..ebe4a66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,7 @@ name: CI on: push: branches: [master] + tags: '*' pull_request: types: [opened, synchronize, reopened] jobs: diff --git a/Project.toml b/Project.toml index 375b006..3c06ab2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "MetaGraphs" uuid = "626554b9-1ddb-594c-aa3c-2596fe9399a5" -version = "0.7.2" +version = "0.8.0" [deps] Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" @@ -9,7 +9,7 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [compat] Graphs = "1.4" -JLD2 = "0.1.11, 0.2, 0.3, 0.4" +JLD2 = "0.1.11, 0.2, 0.3, 0.4, 0.5" julia = "1" [extras] diff --git a/README.md b/README.md index b2a2a2f..9d8ce3e 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,26 @@ # MetaGraphs -[![Build Status][build-img]][build-url] -[![Codecov branch][codecov-img]][codecov-url] -[![](https://img.shields.io/badge/docs-latest-blue.svg)](https://juliagraphs.org/MetaGraphs.jl/dev/) +[![Stable docs](https://img.shields.io/badge/docs-stable-blue.svg)](https://juliagraphs.org/MetaGraphs.jl/) +[![Dev docs](https://img.shields.io/badge/docs-dev-blue.svg)](https://juliagraphs.org/MetaGraphs.jl/dev/) +[![Build Status](https://github.com/JuliaGraphs/Metagraphs.jl/workflows/CI/badge.svg?branch=master)](https://github.com/JuliaGraphs/Metagraphs.jl/actions?query=workflow%3ACI) +[![Codecov](http://codecov.io/github/JuliaGraphs/Metagraphs.jl/coverage.svg?branch=master)](http://codecov.io/github/JuliaGraphs/Metagraphs.jl?branch=master) -[MetaGraphs.jl](https://github.com/JuliaGraphs/MetaGraphs.jl) graphs with arbitrary metadata. +A flexible package for graphs with arbitrary metadata. -[build-img]: https://github.com/JuliaGraphs/Metagraphs.jl/workflows/CI/badge.svg?branch=master -[build-url]: https://github.com/JuliaGraphs/Metagraphs.jl/actions?query=workflow%3ACI -[codecov-img]: http://codecov.io/github/JuliaGraphs/Metagraphs.jl/coverage.svg?branch=master -[codecov-url]: http://codecov.io/github/JuliaGraphs/Metagraphs.jl?branch=master +> For a more performant option, check out [MetaGraphsNext.jl](https://github.com/JuliaGraphs/MetaGraphsNext.jl) -## Documentation -Full documentation is available at [GitHub Pages](https://juliagraphs.github.io/MetaGraphs.jl/latest). -Documentation for methods is also available via the Julia REPL help system. +## Getting started -## Installation Installation is straightforward: from the Julia `pkg` prompt, ```julia -add MetaGraphs +pkg> add MetaGraphs ``` -## Example Usage +Full documentation is available [here](https://juliagraphs.org/MetaGraphs.jl/). +Documentation for methods is also available via the Julia REPL help system. + +## Example use + ```julia julia> using Graphs, MetaGraphs @@ -72,6 +71,10 @@ Dict{Symbol,Any} with 2 entries: julia> get_prop(mg, 2, :name) "John" +# set a default value to return in case the property does not exist +julia> get_prop(mg, 2, :nonexistent_prop, "default value") +"default value" + # delete a specific property julia> rem_prop!(mg, 1, :name) Dict{Symbol,Any} with 1 entry: diff --git a/src/MetaGraphs.jl b/src/MetaGraphs.jl index 7eddade..06c3b3a 100644 --- a/src/MetaGraphs.jl +++ b/src/MetaGraphs.jl @@ -74,7 +74,7 @@ has_vertex(g::AbstractMetaGraph, x...) = has_vertex(g.graph, x...) inneighbors(g::AbstractMetaGraph, v::Integer) = inneighbors(g.graph, v) outneighbors(g::AbstractMetaGraph, v::Integer) = fadj(g.graph, v) -issubset(g::T, h::T) where T <: AbstractMetaGraph = issubset(g.graph, h.graph) +issubset(g::T, h::T) where {T<:AbstractMetaGraph} = issubset(g.graph, h.graph) """ add_edge!(g, u, v, s, val) @@ -135,10 +135,10 @@ function rem_vertex!(g::AbstractMetaGraph, v::Integer) lasteoutprops = Dict(n => props(g, lastv, n) for n in outneighbors(g, lastv)) lasteinprops = Dict(n => props(g, n, lastv) for n in inneighbors(g, lastv)) for ind in g.indices - if haskey(props(g,lastv),ind) + if haskey(props(g, lastv), ind) pop!(g.metaindex[ind], get_prop(g, lastv, ind)) end - if haskey(props(g,v),ind) + if haskey(props(g, v), ind) v != lastv && pop!(g.metaindex[ind], get_prop(g, v, ind)) end end @@ -191,7 +191,7 @@ function rem_vertex!(g::AbstractMetaGraph, v::Integer) return true end -struct MetaWeights{T <: Integer,U <: Real} <: AbstractMatrix{U} +struct MetaWeights{T<:Integer,U<:Real} <: AbstractMatrix{U} n::T weightfield::Symbol defaultweight::U @@ -203,7 +203,7 @@ show(io::IO, z::MIME"text/plain", x::MetaWeights) = show(io, x) MetaWeights(g::AbstractMetaGraph) = MetaWeights{eltype(g),eltype(g.defaultweight)}(nv(g), g.weightfield, g.defaultweight, g.eprops, is_directed(g)) -function getindex(w::MetaWeights{T,U}, u::Integer, v::Integer)::U where T <: Integer where U <: Real +function getindex(w::MetaWeights{T,U}, u::Integer, v::Integer)::U where {T<:Integer} where {U<:Real} _e = Edge(u, v) e = !w.directed && !Graphs.is_ordered(_e) ? reverse(_e) : _e !haskey(w.eprops, e) && return w.defaultweight @@ -253,20 +253,33 @@ props(g::AbstractMetaGraph, v::Integer) = get(PropDict, g.vprops, v) props(g::AbstractMetaGraph, u::Integer, v::Integer) = props(g, Edge(u, v)) """ - get_prop(g, prop) - get_prop(g, v, prop) - get_prop(g, e, prop) - get_prop(g, s, d, prop) + get_prop(g, prop::Symbol) + get_prop(g, prop::Symbol, default) + + get_prop(g, v, prop::Symbol) + get_prop(g, v, prop::Symbol, default) + + get_prop(g, e, prop::Symbol) + get_prop(g, e, prop::Symbol, default) + get_prop(g, s, d, prop::Symbol) + get_prop(g, s, d, prop::Symbol, default) Return the property `prop` defined for graph `g`, vertex `v`, or edge `e` (optionally referenced by source vertex `s` and destination vertex `d`). -If property is not defined, return an error. +Use the version with `default`, to return a default value if the property is not defined. Otherwise, it will return an error. """ get_prop(g::AbstractMetaGraph, prop::Symbol) = props(g)[prop] +get_prop(g::AbstractMetaGraph, prop::Symbol, default) = get(props(g), prop, default) + get_prop(g::AbstractMetaGraph, v::Integer, prop::Symbol) = props(g, v)[prop] +get_prop(g::AbstractMetaGraph, v::Integer, prop::Symbol, default) = get(props(g, v), prop, default) + get_prop(g::AbstractMetaGraph, e::SimpleEdge, prop::Symbol) = props(g, e)[prop] +get_prop(g::AbstractMetaGraph, e::SimpleEdge, prop::Symbol, default) = get(props(g, e), prop, default) get_prop(g::AbstractMetaGraph, u::Integer, v::Integer, prop::Symbol) = get_prop(g, Edge(u, v), prop) +get_prop(g::AbstractMetaGraph, u::Integer, v::Integer, prop::Symbol, default) = get_prop(g, Edge(u, v), prop, default) + """ has_prop(g, prop) @@ -300,7 +313,7 @@ end function set_props!(g::AbstractMetaGraph, v::Integer, d::Dict) if has_vertex(g, v) - for (prop,val) in d + for (prop, val) in d index_available(g, v, prop, val) || error("':$prop' index already contains $val") end if !_hasdict(g, v) @@ -308,7 +321,7 @@ function set_props!(g::AbstractMetaGraph, v::Integer, d::Dict) else merge!(g.vprops[v], d) end - for prop in intersect(keys(d), g.indices) + for prop in intersect(keys(d), g.indices) g.metaindex[prop][d[prop]] = v end return true @@ -317,7 +330,7 @@ function set_props!(g::AbstractMetaGraph, v::Integer, d::Dict) end # set_props!(g::AbstractMetaGraph, e::SimpleEdge, d::Dict) is dependent on directedness. -set_props!(g::AbstractMetaGraph{T}, u::Integer, v::Integer, d::Dict) where T = set_props!(g, Edge(T(u), T(v)), d) +set_props!(g::AbstractMetaGraph{T}, u::Integer, v::Integer, d::Dict) where {T} = set_props!(g, Edge(T(u), T(v)), d) """ set_prop!(g, prop, val) @@ -339,7 +352,7 @@ set_prop!(g::AbstractMetaGraph, v::Integer, prop::Symbol, val) = begin end set_prop!(g::AbstractMetaGraph, e::SimpleEdge, prop::Symbol, val) = set_props!(g, e, Dict(prop => val)) -set_prop!(g::AbstractMetaGraph{T}, u::Integer, v::Integer, prop::Symbol, val) where T = set_prop!(g, Edge(T(u), T(v)), prop, val) +set_prop!(g::AbstractMetaGraph{T}, u::Integer, v::Integer, prop::Symbol, val) where {T} = set_prop!(g, Edge(T(u), T(v)), prop, val) """ rem_prop!(g, prop) @@ -355,7 +368,7 @@ rem_prop!(g::AbstractMetaGraph, prop::Symbol) = delete!(g.gprops, prop) rem_prop!(g::AbstractMetaGraph, v::Integer, prop::Symbol) = delete!(g.vprops[v], prop) rem_prop!(g::AbstractMetaGraph, e::SimpleEdge, prop::Symbol) = delete!(g.eprops[e], prop) -rem_prop!(g::AbstractMetaGraph{T}, u::Integer, v::Integer, prop::Symbol) where T = rem_prop!(g, Edge(T(u), T(v)), prop) +rem_prop!(g::AbstractMetaGraph{T}, u::Integer, v::Integer, prop::Symbol) where {T} = rem_prop!(g, Edge(T(u), T(v)), prop) """ default_index_value(v, prop, index_values; exclude=nothing) @@ -416,7 +429,7 @@ function set_indexing_prop!(g::AbstractMetaGraph, v::Integer, prop::Symbol, val: haskey(g.metaindex[prop], val) && error("':$prop' index already contains $val") if !haskey(g.vprops, v) - push!(g.vprops, v=>Dict{Symbol,Any}()) + push!(g.vprops, v => Dict{Symbol,Any}()) end if haskey(g.vprops[v], prop) delete!(g.metaindex[prop], g.vprops[v][prop]) @@ -438,7 +451,7 @@ clear_props!(g::AbstractMetaGraph, v::Integer) = _hasdict(g, v) && delete!(g.vpr clear_props!(g::AbstractMetaGraph, e::SimpleEdge) = _hasdict(g, e) && delete!(g.eprops, e) clear_props!(g::AbstractMetaGraph) = g.gprops = PropDict() -clear_props!(g::AbstractMetaGraph{T}, u::Integer, v::Integer) where T = clear_props!(g, Edge(T(u), T(v))) +clear_props!(g::AbstractMetaGraph{T}, u::Integer, v::Integer) where {T} = clear_props!(g, Edge(T(u), T(v))) """ weightfield!(g, prop) @@ -514,7 +527,7 @@ filter_vertices(g::AbstractMetaGraph, prop::Symbol) = filter_vertices(g::AbstractMetaGraph, prop::Symbol, val) = filter_vertices(g, (g, x) -> has_prop(g, x, prop) && get_prop(g, x, prop) == val) -function _copy_props!(oldg::T, newg::T, vmap) where T <: AbstractMetaGraph +function _copy_props!(oldg::T, newg::T, vmap) where {T<:AbstractMetaGraph} for (newv, oldv) in enumerate(vmap) p = props(oldg, oldv) if !isempty(p) @@ -540,21 +553,21 @@ function _copy_props!(oldg::T, newg::T, vmap) where T <: AbstractMetaGraph return nothing end -function induced_subgraph(g::T, v::AbstractVector{U}) where T <: AbstractMetaGraph where U <: Integer +function induced_subgraph(g::T, v::AbstractVector{U}) where {T<:AbstractMetaGraph} where {U<:Integer} inducedgraph, vmap = induced_subgraph(g.graph, v) newg = T(inducedgraph) _copy_props!(g, newg, vmap) return newg, vmap end -function induced_subgraph(g::T, v::AbstractVector{U}) where T <: AbstractMetaGraph where U <: SimpleEdge +function induced_subgraph(g::T, v::AbstractVector{U}) where {T<:AbstractMetaGraph} where {U<:SimpleEdge} inducedgraph, vmap = induced_subgraph(g.graph, v) newg = T(inducedgraph) _copy_props!(g, newg, vmap) return newg, vmap end -induced_subgraph(g::T, filt::Iterators.Filter) where T <: AbstractMetaGraph = +induced_subgraph(g::T, filt::Iterators.Filter) where {T<:AbstractMetaGraph} = induced_subgraph(g, collect(filt)) # TODO - would be nice to be able to apply a function to properties. Not sure @@ -563,7 +576,7 @@ induced_subgraph(g::T, filt::Iterators.Filter) where T <: AbstractMetaGraph = ==(x::AbstractMetaGraph, y::AbstractMetaGraph) = x.graph == y.graph -copy(g::T) where T <: AbstractMetaGraph = deepcopy(g) +copy(g::T) where {T<:AbstractMetaGraph} = deepcopy(g) include("metadigraph.jl") include("metagraph.jl") diff --git a/src/persistence.jl b/src/persistence.jl index 709c308..7f25788 100644 --- a/src/persistence.jl +++ b/src/persistence.jl @@ -16,6 +16,26 @@ end loadgraph(fn::AbstractString, gname::String, ::MGFormat) = loadmg(fn) savegraph(fn::AbstractString, g::AbstractMetaGraph) = savemg(fn, g) +# escaping unescaped quotation marks +# i.e. replacing `"`` with `\"` while leaving `\"` as is +escape_quotes(s::AbstractString) = replace(s, r"([^\\])\"" => s"\1\\\\\"") + +# According to the DOT language specification https://graphviz.org/doc/info/lang.html +# we can quote everyhthing that's not an XML/HTML literal +function quote_prop(p::AbstractString) + if occursin(r"<+.*>+$", p) + # The label is an HTML string, no additional quotes here. + return p + else + return "\"" * escape_quotes(p) * "\"" + end +end +# if the property value is _not_ a string it cannot be XML/HTML literal, so just put it in quotes +quote_prop(p::Any) = "\"" * escape_quotes(string(p)) * "\"" +# NOTE: down there I only quote property _values_. DOT allows quoting property _names_ too +# I don't do that as long as names are Symbols and can't have spaces and commas and stuff. +# That will break if someone uses a DOT keyword as a property name, as they must be quoted. + function savedot(io::IO, g::AbstractMetaGraph) if is_directed(g) @@ -27,24 +47,18 @@ function savedot(io::IO, g::AbstractMetaGraph) end for p in props(g) - write(io, "$(p[1])=$(p[2]);\n") + write(io, "$(p[1])=$(quote_prop(p[2]));\n") end for v in vertices(g) write(io, "$v") if length(props(g, v)) > 0 write(io, " [ ") - end - for p in props(g, v) - key = p[1] - if key .=== :label && occursin(r"<+.*>+$", p[2]) - # The label is an HTML string, no additional quotes here. - write(io, "$key=$(p[2]),") - else - write(io, "$key=\"$(p[2])\",") + + for p in props(g, v) + write(io, "$(p[1])=$(quote_prop(p[2])), ") end - end - if length(props(g, v)) > 0 + write(io, "];") end write(io, "\n") @@ -53,7 +67,7 @@ function savedot(io::IO, g::AbstractMetaGraph) for e in edges(g) write(io, "$(src(e)) $dash $(dst(e)) [ ") for p in props(g,e) - write(io, "$(p[1])=$(p[2]), ") + write(io, "$(p[1])=$(quote_prop(p[2])), ") end write(io, "]\n") end diff --git a/test/diagram_ref.dot b/test/diagram_ref.dot index 0814c2a..1faaaf7 100644 --- a/test/diagram_ref.dot +++ b/test/diagram_ref.dot @@ -1,56 +1,56 @@ digraph G { -pack=true; -1 [ color="orange",style="filled",penwidth="2.0",fillcolor="#dddddd",name="weather",label="NOAA\nWeather",shape="record",]; -2 [ color="orange",style="filled",penwidth="2.0",fillcolor="#dddddd",name="cost",label="Vaccination\nCost",shape="record",]; -3 [ color="orange",style="filled",penwidth="2.0",fillcolor="#dddddd",name="demo",label="Census\nDemographics",shape="record",]; -4 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="fed",label="Fed Forcast",shape="record",]; -5 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="epi",label="SIR",shape="record",]; -6 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="ode",label="ODE Solver",shape="record",]; -7 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="rate",label="{Transition\nRate}",shape="record",]; -8 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="unit",label="Unit",shape="record",]; -9 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="personper",label="Person/s",shape="record",]; -10 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="person",label="Person",shape="record",]; -11 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="second",label="second (s)",shape="record",]; -12 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="dollars",label="$",shape="record",]; -13 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="inf",label="Infection\nRate",shape="record",]; -14 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="mort",label="Mortality\nRate",shape="record",]; -15 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="birth",label="Birth\nRate",shape="record",]; -16 [ color="#DD1133",style="filled",penwidth="2.0",fillcolor="#dddddd",name="twenty",label="0.2 Persons/s",shape="record",]; -17 [ color="#DD1133",style="filled",penwidth="2.0",fillcolor="#dddddd",name="thirty",label="0.3 Persons/s",shape="record",]; -18 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="ind",label="Individual\nContact\nModel",shape="record",]; -19 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="temp",label="Temperature",shape="record",]; -20 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="age",label="Age",shape="record",]; -21 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="dGDP",label="Economic Growth",shape="record",]; -22 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="cases",label="Flu\nCases",shape="record",]; -23 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="prices",label="Vacc\nPrice",shape="record",]; -24 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="regres",label="Regression",shape="record",]; -25 [ color="#000000",style="filled",penwidth="2.0",fillcolor="#dddddd",name="html",label=<Title
Some text.>,shape="record",]; -1 -> 19 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -3 -> 15 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -3 -> 20 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -4 -> 21 [ color=missing, dir=none, penwidth=missing, style=missing, ] -5 -> 22 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -6 -> 5 [ color=missing, dir=none, penwidth=missing, style=missing, ] -7 -> 9 [ color=missing, dir=none, penwidth=missing, style=missing, ] -9 -> 8 [ color=missing, dir=none, penwidth=missing, style=missing, ] -10 -> 8 [ color=missing, dir=none, penwidth=missing, style=missing, ] -11 -> 8 [ color=missing, dir=none, penwidth=missing, style=missing, ] -12 -> 2 [ color=missing, dir=none, penwidth=missing, style=missing, ] -12 -> 8 [ color=missing, dir=none, penwidth=missing, style=missing, ] -13 -> 7 [ color=black, dir=none, penwidth=2.0, style=dashed, ] -13 -> 18 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -14 -> 7 [ color=black, dir=none, penwidth=2.0, style=dashed, ] -14 -> 18 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -15 -> 7 [ color=black, dir=none, penwidth=2.0, style=dashed, ] -15 -> 18 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -16 -> 14 [ color=missing, dir=none, penwidth=missing, style=missing, ] -17 -> 13 [ color=missing, dir=none, penwidth=missing, style=missing, ] -18 -> 5 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -19 -> 13 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -20 -> 14 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -21 -> 23 [ color=missing, dir=none, penwidth=missing, style=missing, ] -22 -> 24 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -23 -> 24 [ color=missing, dir=none, penwidth=missing, style=missing, ] -24 -> 2 [ color=orange, dir=none, penwidth=4.0, style=solid, ] -25 -> 4 [ color=black, dir=none, penwidth=2.0, style=solid, ] +pack="true"; +1 [ color="orange", style="filled", penwidth="2.0", fillcolor="#dddddd", name="weather", label="NOAA\nWeather", shape="record", ]; +2 [ color="orange", style="filled", penwidth="2.0", fillcolor="#dddddd", name="cost", label="Vaccination\nCost", shape="record", ]; +3 [ color="orange", style="filled", penwidth="2.0", fillcolor="#dddddd", name="demo", label="Census\nDemographics", shape="record", ]; +4 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="fed", label="Fed Forcast", shape="record", ]; +5 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="epi", label="SIR", shape="record", ]; +6 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="ode", label="ODE Solver", shape="record", ]; +7 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="rate", label="{Transition\nRate}", shape="record", ]; +8 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="unit", label="Unit", shape="record", ]; +9 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="personper", label="Person/s", shape="record", ]; +10 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="person", label="Person", shape="record", ]; +11 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="second", label="second (s)", shape="record", ]; +12 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="dollars", label="$", shape="record", ]; +13 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="inf", label="Infection\nRate", shape="record", ]; +14 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="mort", label="Mortality\nRate", shape="record", ]; +15 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="birth", label="Birth\nRate", shape="record", ]; +16 [ color="#DD1133", style="filled", penwidth="2.0", fillcolor="#dddddd", name="twenty", label="0.2 Persons/s", shape="record", ]; +17 [ color="#DD1133", style="filled", penwidth="2.0", fillcolor="#dddddd", name="thirty", label="0.3 Persons/s", shape="record", ]; +18 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="ind", label="Individual\n\"Contact\"\nModel", shape="record", ]; +19 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="temp", label="Temperature", shape="record", ]; +20 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="age", label="Age", shape="record", ]; +21 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="dGDP", label="Economic Growth", shape="record", ]; +22 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="cases", label="Flu\nCases", shape="record", ]; +23 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="prices", label="Vacc\nPrice", shape="record", ]; +24 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="regres", label="Regression", shape="record", ]; +25 [ color="#000000", style="filled", penwidth="2.0", fillcolor="#dddddd", name="html", label=<Title
Some text.>, shape="record", ]; +1 -> 19 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ] +3 -> 15 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ] +3 -> 20 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ] +4 -> 21 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +5 -> 22 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ] +6 -> 5 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +7 -> 9 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +9 -> 8 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +10 -> 8 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +11 -> 8 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +12 -> 2 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +12 -> 8 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +13 -> 7 [ color="black", dir="none", penwidth="2.0", label="", style="dashed", ] +13 -> 18 [ color="orange", dir="none", penwidth="4.0", label="one, other", style="solid", ] +14 -> 7 [ color="black", dir="none", penwidth="2.0", label="", style="dashed", ] +14 -> 18 [ color="orange", dir="none", penwidth="4.0", label="cause", style="solid", ] +15 -> 7 [ color="black", dir="none", penwidth="2.0", label="", style="dashed", ] +15 -> 18 [ color="orange", dir="none", penwidth="4.0", label="one, other, and third one", style="solid", ] +16 -> 14 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +17 -> 13 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +18 -> 5 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ] +19 -> 13 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ] +20 -> 14 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ] +21 -> 23 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +22 -> 24 [ color="orange", dir="none", penwidth="4.0", label="with single \" here", style="solid", ] +23 -> 24 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ] +24 -> 2 [ color="orange", dir="none", penwidth="4.0", label="with \"quoted\" word", style="solid", ] +25 -> 4 [ color="black", dir="none", penwidth="2.0", label="", style="solid", ] } diff --git a/test/dotformat.jl b/test/dotformat.jl index fad114a..71a5b9b 100644 --- a/test/dotformat.jl +++ b/test/dotformat.jl @@ -3,35 +3,35 @@ using Test using Graphs property_set=[ - (src="thirty",dst="inf",color="missing",penwidth="missing",style="missing",), - (src="twenty",dst="mort",color="missing",penwidth="missing",style="missing",), - (src="rate",dst="personper",color="missing",penwidth="missing",style="missing",), - (src="person",dst="unit",color="missing",penwidth="missing",style="missing",), - (src="personper",dst="unit",color="missing",penwidth="missing",style="missing",), - (src="second",dst="unit",color="missing",penwidth="missing",style="missing",), - (src="dollars",dst="unit",color="missing",penwidth="missing",style="missing",), - (src="dGDP",dst="prices",color="missing",penwidth="missing",style="missing",), - (src="prices",dst="regres",color="missing",penwidth="missing",style="missing",), - (src="fed",dst="dGDP",color="missing",penwidth="missing",style="missing",), - (src="ode",dst="epi",color="missing",penwidth="missing",style="missing",), - (src="dollars",dst="cost",color="missing",penwidth="missing",style="missing",), - (src="mort",dst="rate",color="black",penwidth="2.0",style="dashed",), - (src="inf",dst="rate",color="black",penwidth="2.0",style="dashed",), - (src="birth",dst="rate",color="black",penwidth="2.0",style="dashed",), - (src="mort",dst="ind",color="orange",penwidth="4.0",style="solid",), - (src="ind",dst="epi",color="orange",penwidth="4.0",style="solid",), - (src="ind",dst="epi",color="orange",penwidth="4.0",style="solid",), - (src="inf",dst="ind",color="orange",penwidth="4.0",style="solid",), - (src="birth",dst="ind",color="orange",penwidth="4.0",style="solid",), - (src="temp",dst="inf",color="orange",penwidth="4.0",style="solid",), - (src="age",dst="mort",color="orange",penwidth="4.0",style="solid",), - (src="demo",dst="birth",color="orange",penwidth="4.0",style="solid",), - (src="epi",dst="cases",color="orange",penwidth="4.0",style="solid",), - (src="cases",dst="regres",color="orange",penwidth="4.0",style="solid",), - (src="weather",dst="temp",color="orange",penwidth="4.0",style="solid",), - (src="demo",dst="age",color="orange",penwidth="4.0",style="solid",), - (src="regres",dst="cost",color="orange",penwidth="4.0",style="solid",), - (src="html",dst="fed",color="black",penwidth="2.0",style="solid",) + (src="thirty",dst="inf",color="missing",penwidth="missing",style="missing",label=""), + (src="twenty",dst="mort",color="missing",penwidth="missing",style="missing",label="",), + (src="rate",dst="personper",color="missing",penwidth="missing",style="missing",label="",), + (src="person",dst="unit",color="missing",penwidth="missing",style="missing",label="",), + (src="personper",dst="unit",color="missing",penwidth="missing",style="missing",label="",), + (src="second",dst="unit",color="missing",penwidth="missing",style="missing",label="",), + (src="dollars",dst="unit",color="missing",penwidth="missing",style="missing",label="",), + (src="dGDP",dst="prices",color="missing",penwidth="missing",style="missing",label="",), + (src="prices",dst="regres",color="missing",penwidth="missing",style="missing",label="",), + (src="fed",dst="dGDP",color="missing",penwidth="missing",style="missing",label="",), + (src="ode",dst="epi",color="missing",penwidth="missing",style="missing",label="",), + (src="dollars",dst="cost",color="missing",penwidth="missing",style="missing",label="",), + (src="mort",dst="rate",color="black",penwidth="2.0",style="dashed",label="",), + (src="inf",dst="rate",color="black",penwidth="2.0",style="dashed",label="",), + (src="birth",dst="rate",color="black",penwidth="2.0",style="dashed",label="",), + (src="mort",dst="ind",color="orange",penwidth="4.0",style="solid",label="cause",), + (src="ind",dst="epi",color="orange",penwidth="4.0",style="solid",label="two words",), + (src="ind",dst="epi",color="orange",penwidth="4.0",style="solid",label="",), + (src="inf",dst="ind",color="orange",penwidth="4.0",style="solid",label="one, other",), + (src="birth",dst="ind",color="orange",penwidth="4.0",style="solid",label="one, other, and third one",), + (src="temp",dst="inf",color="orange",penwidth="4.0",style="solid",label="",), + (src="age",dst="mort",color="orange",penwidth="4.0",style="solid",label="",), + (src="demo",dst="birth",color="orange",penwidth="4.0",style="solid",label="",), + (src="epi",dst="cases",color="orange",penwidth="4.0",style="solid",label="",), + (src="cases",dst="regres",color="orange",penwidth="4.0",style="solid",label="with single \" here",), + (src="weather",dst="temp",color="orange",penwidth="4.0",style="solid",label="",), + (src="demo",dst="age",color="orange",penwidth="4.0",style="solid",label="",), + (src="regres",dst="cost",color="orange",penwidth="4.0",style="solid",label="with \"quoted\" word",), + (src="html",dst="fed",color="black",penwidth="2.0",style="solid",label="",) ] # name, label, color @@ -53,7 +53,7 @@ vprops = [ ("birth","Birth\\nRate","#66AA55"), ("twenty","0.2 Persons/s","#DD1133"), ("thirty","0.3 Persons/s","#DD1133"), - ("ind","Individual\\nContact\\nModel","#5DADE2"), + ("ind","Individual\\n\"Contact\"\\nModel","#5DADE2"), ("temp","Temperature","#5DADE2"), ("age","Age","#5DADE2"), ("dGDP","Economic Growth","#5DADE2"), @@ -79,17 +79,18 @@ set_indexing_prop!(g, :name) # add edges for prop in property_set src, dst = g[prop.src, :name], g[prop.dst, :name] - add_edge!(g, src,dst) + add_edge!(g, src, dst) set_prop!(g, src, dst, :color, prop.color) set_prop!(g, src, dst, :penwidth, prop.penwidth) set_prop!(g, src, dst, :style, prop.style) + set_prop!(g, src, dst, :label, prop.label) end # set global edge properties for e in edges(g) set_prop!(g, e, :dir, :none) end -# set global vertex properties +# set global vertex properties for v in vertices(g) set_prop!(g, v, :shape, :record) set_prop!(g, v, :style, :filled) @@ -112,7 +113,7 @@ end # - <...> : OK # - both : NOK ("< or >" as bounding characters, resulting HTML in file will not be parsed correctly by dot.) quote_regex = r"label\s*=\s*\"(?:[^\"\\]|\\.)*\"" # source: https://stackoverflow.com/questions/249791/regex-for-quoted-string-with-escaping-quotes - html_regex = r"label\s*=\s*<.*>" + html_regex = r"label\s*=\s*<.*>" invalid_quote_regex = r"label\s*=\s*\"<(?:[^\"\\]|\\.)*>\"" for line in eachline(fp) test_val = false @@ -126,7 +127,7 @@ end # no worries, proper HTML surrounding brackets found. test_val = true end - @test test_val + @test test_val end end diff --git a/test/metagraphs.jl b/test/metagraphs.jl index a6bf948..59fc799 100644 --- a/test/metagraphs.jl +++ b/test/metagraphs.jl @@ -1,7 +1,7 @@ using MetaGraphs import Graphs: SimpleGraphs import Base64: - stringmime + stringmime @testset "MetaGraphs" begin @@ -21,7 +21,7 @@ import Base64: mg = MetaGraph() @test add_vertex!(mg, :color, "red") && get_prop(mg, nv(mg), :color) == "red" @test add_vertex!(mg, Dict(:color => "red", :prop2 => "prop2")) && props(mg, nv(mg)) == Dict(:color => "red", :prop2 => "prop2") - @test add_edge!(mg, 1, 2, :color, "blue") && get_prop(mg, 1, 2, :color) == "blue" + @test add_edge!(mg, 1, 2, :color, "blue") && get_prop(mg, 1, 2, :color) == "blue" @test add_vertex!(mg) && add_edge!(mg, 1, 3, Dict(:color => "red", :prop2 => "prop2")) && props(mg, 1, 3) == Dict(:color => "red", :prop2 => "prop2") for g in testgraphs(gx) @@ -78,6 +78,31 @@ import Base64: U = @inferred(weighttype(mg)) @test @inferred(nv(MetaGraph{T,U}(6))) == 6 + # get_prop with default argument + # vertices + set_prop!(mg, nv(mg), :testprop, "exists") + @test get_prop(mg, nv(mg), :testprop, "nonexistent") == "exists" + @test get_prop(mg, nv(mg), :testprop_nonexist, "nonexistent") == "nonexistent" + @test get_prop(mg, nv(mg) + 100, :testprop_nonexist, "nonexistent") == "nonexistent" + + # edges + set_prop!(mg, 1, 2, :testedgeprop, "5 meters") + @test get_prop(mg, 1, 2, :testedgeprop, "0 meters") == "5 meters" + @test get_prop(mg, 1, 2, :testedgeprop_nonexist, "0 meters") == "0 meters" + @test get_prop(mg, 2, 4, :testedgeprop_nonexist, "0 meters") == "0 meters" + @test get_prop(mg, 2, 1, :testedgeprop, "0 meters") == "5 meters" + @test get_prop(mg, 2, 1, :testedgeprop_nonexist, "0 meters") == "0 meters" + + @test get_prop(mg, Edge(1, 2), :testedgeprop, "0 meters") == "5 meters" + @test get_prop(mg, Edge(1, 2), :testedgeprop_nonexist, "0 meters") == "0 meters" + @test get_prop(mg, Edge(2, 4), :testedgeprop_nonexist, "0 meters") == "0 meters" + @test get_prop(mg, Edge(2, 1), :testedgeprop, "0 meters") == "5 meters" + @test get_prop(mg, Edge(2, 1), :testedgeprop_nonexist, "0 meters") == "0 meters" + + # graph + set_prop!(mg, :testgraphpprop, "linegraph") + @test get_prop(mg, :testgraphpprop, "circlegraph") == "linegraph" + @test get_prop(mg, :testgraphpprop_nonexist, "circlegraph") == "circlegraph" end for g in testdigraphs(dgx) @@ -135,6 +160,32 @@ import Base64: T = @inferred(eltype(mg)) U = @inferred(weighttype(mg)) @test @inferred(nv(MetaDiGraph{T,U}(6))) == 6 + + # get_prop with default argument + # vertices + set_prop!(mg, nv(mg), :testprop, "exists") + @test get_prop(mg, nv(mg), :testprop, "nonexistent") == "exists" + @test get_prop(mg, nv(mg), :testprop_nonexist, "nonexistent") == "nonexistent" + @test get_prop(mg, nv(mg) + 100, :testprop_nonexist, "nonexistent") == "nonexistent" + + # edges + set_prop!(mg, 1, 2, :testedgeprop, "5 meters") + @test get_prop(mg, 1, 2, :testedgeprop, "0 meters") == "5 meters" + @test get_prop(mg, 1, 2, :testedgeprop_nonexist, "0 meters") == "0 meters" + @test get_prop(mg, 2, 4, :testedgeprop_nonexist, "0 meters") == "0 meters" + @test get_prop(mg, 2, 1, :testedgeprop, "0 meters") == "0 meters" + @test get_prop(mg, 2, 1, :testedgeprop_nonexist, "0 meters") == "0 meters" + + @test get_prop(mg, Edge(1, 2), :testedgeprop, "0 meters") == "5 meters" + @test get_prop(mg, Edge(1, 2), :testedgeprop_nonexist, "0 meters") == "0 meters" + @test get_prop(mg, Edge(2, 4), :testedgeprop_nonexist, "0 meters") == "0 meters" + @test get_prop(mg, Edge(2, 1), :testedgeprop, "0 meters") == "0 meters" + @test get_prop(mg, Edge(2, 1), :testedgeprop_nonexist, "0 meters") == "0 meters" + + # graph + set_prop!(mg, :testgraphpprop, "linegraph") + @test get_prop(mg, :testgraphpprop, "circlegraph") == "linegraph" + @test get_prop(mg, :testgraphpprop_nonexist, "circlegraph") == "circlegraph" end for gbig in [SimpleGraph(0xff), SimpleDiGraph(0xff)] @@ -299,7 +350,7 @@ import Base64: @test weightfield!(mg, :weight) == :weight @test enumerate_paths(dijkstra_shortest_paths(mg, 1), 3) == [1, 2, 3] - @test set_props!(mg, 1, 2, Dict(:color => :blue, :action => "knows")) + @test set_props!(mg, 1, 2, Dict(:color => :blue, :action => "knows")) @test length(props(mg, 1, 2)) == 3 @test rem_edge!(mg, 1, 2) @test length(props(mg, 1, 2)) == 0 @@ -352,12 +403,12 @@ import Base64: for v in vertices(mga) set_prop!(mga, v, :name, string(v)) end - set_indexing_prop!(mga,:name) + set_indexing_prop!(mga, :name) @test get_prop(mga, 1, :name) == "1" @test get_prop(mga, 5, :name) == "5" @test rem_vertex!(mga, 1) @test get_prop(mga, 1, :name) == "5" - @test mga["5",:name] == 1 + @test mga["5", :name] == 1 @test isempty(props(mga, 5)) # test for #22 @@ -373,19 +424,19 @@ import Base64: # test for #72 - Multiple indicies that are not all used let - test_graph = x-> begin - g=MetaGraph() - set_indexing_prop!(g,:IndexA) - set_indexing_prop!(g,:IndexB) - add_vertex!(g,:IndexA,"A") - x && add_vertex!(g,:IndexA,"B") - x && set_indexing_prop!(g,nv(g),:IndexB,"B") - add_vertex!(g,:IndexB,"C") + test_graph = x -> begin + g = MetaGraph() + set_indexing_prop!(g, :IndexA) + set_indexing_prop!(g, :IndexB) + add_vertex!(g, :IndexA, "A") + x && add_vertex!(g, :IndexA, "B") + x && set_indexing_prop!(g, nv(g), :IndexB, "B") + add_vertex!(g, :IndexB, "C") g end - mga=test_graph(true) - rem_vertex!(mga,2) - @test mga==test_graph(false) + mga = test_graph(true) + rem_vertex!(mga, 2) + @test mga == test_graph(false) end mga = MetaDiGraph(path_digraph(4)) @@ -482,7 +533,7 @@ end @test MetaGraphs.index_available(dG, 7, :name, "dgnode_8-anothername") == true @test_throws ErrorException set_props!(G, 11, Dict(:name => "gnode_3", :other_name => "something11")) - @test_throws ErrorException set_props!(dG,11, Dict(:name => "dgnode_3", :other_name => "something11")) + @test_throws ErrorException set_props!(dG, 11, Dict(:name => "dgnode_3", :other_name => "something11")) @test_throws KeyError get_prop(G, 11, :other_name) @test_throws KeyError get_prop(dG, 11, :other_name)