diff --git a/.gitignore b/.gitignore index 13c6fe9..65f2d73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.vscode/ /Manifest.toml .DS_Store +_local/ \ No newline at end of file diff --git a/project/vizgadget.jl b/project/vizgadget.jl index 023bef7..f8eeeed 100644 --- a/project/vizgadget.jl +++ b/project/vizgadget.jl @@ -1,38 +1,16 @@ -using UnitDiskMapping, Graphs +using UnitDiskMapping.TikzGraph, Graphs +using UnitDiskMapping: crossing_ruleset, Pattern, source_graph, mapped_graph -function command_graph(locs, graph, pins, dx, dy, r, name) - cmd = "" +function command_graph!(canvas, locs, graph, pins, dx, dy, r, name) for (i,loc) in enumerate(locs) if count(==(loc), locs) == 2 - type = 2 - else - type = 1 + Node(loc[1]+dx, loc[2]+dy, fill="none", id="ext-$name$i", minimum_size="$(1.5*r)cm") >> canvas end - cmd *= command_node(loc[1]+dx, loc[2]+dy, type, i ∈ pins, "$name$i", r) * "\n" + Node(loc[1]+dx, loc[2]+dy, fill=i∈pins ? "red" : "black", draw="none", id="$name$i", minimum_size="$(r)cm") >> canvas end for e in edges(graph) - cmd *= command_edge("$name$(e.src)", "$name$(e.dst)") * "\n" + Line("$name$(e.src)", "$name$(e.dst)"; line_width=1.0) >> canvas end - return cmd -end - -function command_node(x, y, type::Int, ispin::Bool, id, r) - if type == 2 - return "\\node[fill=black,circle,radius=$(r)cm,inner sep=0cm, minimum size=$(r)cm] at ($x, $y) () {};\n\\node[draw=black,fill=none,circle,radius=$(1.5*r)cm,minimum size=$(1.5*r)cm,inner sep=0cm] at ($x, $y) ($id) {};" - elseif abs(type) == 1 - if ispin - color = "red" - else - color = "black" - end - return "\\node[fill=$color,circle,radius=$(r)cm,inner sep=0cm, minimum size=$(r)cm] at ($x, $y) ($id) {};" - else - error("") - end -end - -function command_edge(i, j) - return "\\draw[thick] ($i) -- ($j);" end function viz_gadget(p::Pattern) @@ -45,28 +23,22 @@ function viz_gadget(p::Pattern) xmid, ymid = Wx/2-0.5, Wy/2-0.5 dx1, dy1 = xmid-Gx, 0 dx2, dy2 = xmid+1, 0 - source_nodes = command_graph(locs1, g1, pin1, dx1, dy1, 0.3, "s") - mapped_nodes = command_graph(locs2, g2, pin2, dx2, dy2, 0.3, "d") - return """ -\\documentclass[crop,tikz]{standalone}% 'crop' is the default for v1.0, before it was 'preview' -\\begin{document} -\\begin{tikzpicture}[scale=0.8] - \\useasboundingbox (-1,-1) rectangle ($Wx,$Wy); - \\draw[step=1cm,gray,very thin] ($dx1,$(dy1)) grid ($(Gx+dx1-1),$(Gy+dy1-1)); - $source_nodes - \\draw[step=1cm,gray,very thin] ($dx2,$(dy2)) grid ($(Gx+dx2-1),$(Gy+dy2-1)); - $mapped_nodes - \\node at ($xmid, $ymid) {\$\\mathbf{\\rightarrow}\$}; -\\end{tikzpicture} - -\\end{document} -""" + return canvas(; props=Dict("scale"=>"0.8")) do c + BoundingBox(-1,Wx-1,-1,Wy-1) >> c + Mesh(dx1, Gx+dx1-1, dy1, Gy+dy1-1; step="1cm", draw=rgbcolor!(c, 100,200,200), line_width=0.5) >> c + command_graph!(c, locs1, g1, pin1, dx1, dy1, 0.3, "s") + Mesh(dx2, Gx+dx2-1, dy2, Gy+dy2-1; step="1cm", draw=rgbcolor!(c, 200,100,100), line_width=0.03) >> c + command_graph!(c, locs2, g2, pin2, dx2, dy2, 0.3, "d") + PlainText(xmid, ymid, "\$\\mathbf{\\rightarrow}\$") >> c + end end function pattern2tikz(folder::String) - for (p, sub) in UnitDiskMapping.crossing_ruleset - open(joinpath(folder, sub*"-udg.tex"), "w") do f + for p in crossing_ruleset + open(joinpath(folder, string(typeof(p).name.name)*"-udg.tex"), "w") do f write(f, viz_gadget(p)) end end -end \ No newline at end of file +end + +pattern2tikz(joinpath("_local")) \ No newline at end of file diff --git a/src/UnitDiskMapping.jl b/src/UnitDiskMapping.jl index fa2c0c2..e757af8 100644 --- a/src/UnitDiskMapping.jl +++ b/src/UnitDiskMapping.jl @@ -17,6 +17,6 @@ include("weighted.jl") include("simplifiers.jl") include("extracting_results.jl") include("pathdecomposition/pathdecomposition.jl") -#include("shrinking/compressUDG.jl") +include("tikz/tikz.jl") end diff --git a/src/gadgets.jl b/src/gadgets.jl index 59c8366..63c2fd5 100644 --- a/src/gadgets.jl +++ b/src/gadgets.jl @@ -19,8 +19,8 @@ abstract type Pattern end """ abstract type CrossPattern <: Pattern end -abstract type Node end -struct SimpleNode{T} <: Node +abstract type AbstractNode end +struct SimpleNode{T} <: AbstractNode x::T y::T end @@ -28,11 +28,11 @@ SimpleNode(xy::Tuple{Int,Int}) = SimpleNode(xy...) SimpleNode(xy::Vector{Int}) = SimpleNode(xy...) getxy(p::SimpleNode) = (p.x, p.y) chxy(p::SimpleNode, loc) = SimpleNode(loc...) -Base.iterate(p::Node, i) = Base.iterate((p.x, p.y), i) -Base.iterate(p::Node) = Base.iterate((p.x, p.y)) -Base.length(p::Node) = 2 -Base.getindex(p::Node, i::Int) = i==1 ? p.x : (@assert i==2; p.y) -offset(p::Node, xy) = chxy(p, getxy(p) .+ xy) +Base.iterate(p::AbstractNode, i) = Base.iterate((p.x, p.y), i) +Base.iterate(p::AbstractNode) = Base.iterate((p.x, p.y)) +Base.length(p::AbstractNode) = 2 +Base.getindex(p::AbstractNode, i::Int) = i==1 ? p.x : (@assert i==2; p.y) +offset(p::AbstractNode, xy) = chxy(p, getxy(p) .+ xy) export source_matrix, mapped_matrix function source_matrix(p::Pattern) @@ -53,7 +53,7 @@ function mapped_matrix(p::Pattern) locs2matrix(m, n, locs) end -function locs2matrix(m, n, locs::AbstractVector{NT}) where NT <: Node +function locs2matrix(m, n, locs::AbstractVector{NT}) where NT <: AbstractNode a = fill(empty(_cell_type(NT)), m, n) for loc in locs add_cell!(a, loc) @@ -387,7 +387,7 @@ for T in [:RotatedGadget, :ReflectedGadget] end for T in [:RotatedGadget, :ReflectedGadget] - @eval _apply_transform(r::$T, node::Node, center) = chxy(node, _apply_transform(r, getxy(node), center)) + @eval _apply_transform(r::$T, node::AbstractNode, center) = chxy(node, _apply_transform(r, getxy(node), center)) end function _apply_transform(r::RotatedGadget, loc::Tuple{Int,Int}, center) for _=1:r.n diff --git a/src/simplifiers.jl b/src/simplifiers.jl index cbca309..d68c21e 100644 --- a/src/simplifiers.jl +++ b/src/simplifiers.jl @@ -26,7 +26,7 @@ function vertices_on_boundary(locs, m, n) findall(loc->loc[1]==1 || loc[1]==m || loc[2]==1 || loc[2]==n, locs) end -struct GridGraph{NT<:Node} +struct GridGraph{NT<:AbstractNode} size::Tuple{Int,Int} nodes::Vector{NT} end diff --git a/src/tikz/tikz.jl b/src/tikz/tikz.jl new file mode 100644 index 0000000..a69ba22 --- /dev/null +++ b/src/tikz/tikz.jl @@ -0,0 +1,154 @@ +module TikzGraph +export rgbcolor!, Node, Line, BoundingBox, Mesh, Canvas, >>, command, canvas, generate_standalone, StringElement, PlainText + +const instance_counter = Ref(0) +abstract type AbstractTikzElement end + +struct Canvas + header::String + colors::Dict{String, Tuple{Int,Int,Int}} + contents::Vector{AbstractTikzElement} + props::Dict{String,String} +end + +function canvas(f; header="", colors=Dict{String,Tuple{Int,Int,Int}}(), props=Dict{String,String}()) + canvas = Canvas(header, colors, AbstractTikzElement[], props) + f(canvas) + return canvas +end + +Base.:(>>)(element::AbstractTikzElement, canvas::Canvas) = push!(canvas.contents, element) +Base.:(>>)(element::String, canvas::Canvas) = push!(canvas.contents, StringElement(element)) + +function rgbcolor!(canvas::Canvas, red::Int, green::Int, blue::Int) + instance_counter[] += 1 + colorname = "color$(instance_counter[])" + canvas.colors[colorname] = (red,green,blue) + return colorname +end +function generate_rgbcolor(name, red, green, blue) + return "\\definecolor{$name}{RGB}{$red,$green,$blue}" +end + +struct StringElement <: AbstractTikzElement + str::String +end +command(s::StringElement) = s.str + +struct BoundingBox <: AbstractTikzElement + xmin::Float64 + xmax::Float64 + ymin::Float64 + ymax::Float64 +end +function command(box::BoundingBox) + return "\\useasboundingbox ($(box.xmin),$(box.ymin)) rectangle ($(box.xmax-box.xmin),$(box.ymax-box.ymin));" +end + +struct Node <: AbstractTikzElement + x::Float64 + y::Float64 + shape::String + id::String + text::String + props::Dict{String,String} +end + +function Node(x, y; + shape::String = "circle", + id = string((instance_counter[] += 1; instance_counter[])), + text::String = "", + fill = "none", + draw = "black", + inner_sep = "0cm", + minimum_size = "0.2cm", + line_width = 0.03, + kwargs...) + props = build_props(; + fill = fill, + draw = draw, + inner_sep = inner_sep, + minimum_size = minimum_size, + line_width = line_width, + kwargs...) + return Node(x, y, shape, id, text, props) +end +function build_props(; kwargs...) + Dict([replace(string(k), "_"=>" ")=>string(v) for (k,v) in kwargs]) +end + +function command(node::Node) + return "\\node[$(node.shape), $(command(node.props))] at ($(node.x), $(node.y)) ($(node.id)) {$(node.text)};" +end + +struct Mesh <: AbstractTikzElement + xmin::Float64 + xmax::Float64 + ymin::Float64 + ymax::Float64 + props::Dict{String,String} +end + +function Mesh(xmin, xmax, ymin, ymax; step="1.0cm", draw="gray", line_width=0.03, kwargs...) + Mesh(xmin, xmax, ymin, ymax, build_props(; step=step, draw=draw, line_width=line_width, kwargs...)) +end +function command(grid::Mesh) + return "\\draw[$(command(grid.props))] ($(grid.xmin),$(grid.ymin)) grid ($(grid.xmax),$(grid.ymax));" +end + +struct Line <: AbstractTikzElement + src::String + dst::String + controls::Vector{Int} + props::Dict{String,String} +end + +function Line(src, dst; controls=Int[], line_width = "0.03", kwargs...) + Line(string(src), string(dst), controls, build_props(; line_width=line_width, kwargs...)) +end +Line(src::Node, dst::Node; kwargs...) = Line(src.id, dst.id) + +function command(edge::Line) + head = "\\draw[$(command(edge.props))]" + if isempty(edge.controls) + return "$head ($(edge.src)) -- ($(edge.dst));" + else + return "$head ($(edge.src)) .. controls $(join(["($c)" for c in edge.controls], " and ")) .. ($(edge.dst));" + end +end + +struct PlainText <: AbstractTikzElement + x::Float64 + y::Float64 + text::String + props::Dict{String,String} +end +function PlainText(x, y, text; kwargs...) + PlainText(x, y, text, build_props(; kwargs...)) +end +function command(text::PlainText) + "\\node[$(command(text.props))] at ($(text.x), $(text.y)) {$(text.text)};" +end + +function command(node::Dict) # properties + return join(["$k=$v" for (k,v) in node], ", ") +end + +function generate_standalone(header::String, props::Dict, content::String) + return """ +\\documentclass[crop,tikz]{standalone} +$(header) +\\begin{document} +\\begin{tikzpicture}[$(command(props))] +$content +\\end{tikzpicture} +\\end{document} +""" +end +generate_standalone(canvas::Canvas) = generate_standalone(canvas.header, canvas.props, join([[generate_rgbcolor(k,v...) for (k,v) in canvas.colors]..., command.(canvas.contents)...], "\n")) + +function Base.write(io::IO, canvas::Canvas) + write(io, generate_standalone(canvas)) +end + +end \ No newline at end of file diff --git a/src/weighted.jl b/src/weighted.jl index 2d934d6..ca6e123 100644 --- a/src/weighted.jl +++ b/src/weighted.jl @@ -32,7 +32,7 @@ function Base.show(io::IO, x::WeightedCell) end Base.show(io::IO, ::MIME"text/plain", cl::WeightedCell) = Base.show(io, cl) -struct WeightedNode{T,WT} <: Node +struct WeightedNode{T,WT} <: AbstractNode x::T y::T weight::WT diff --git a/test/runtests.jl b/test/runtests.jl index fbb46ae..90f0e13 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -28,3 +28,7 @@ end @testset "weighted" begin include("weighted.jl") end + +@testset "tikz" begin + include("tikz/tikz.jl") +end diff --git a/test/tikz/tikz.jl b/test/tikz/tikz.jl new file mode 100644 index 0000000..182c203 --- /dev/null +++ b/test/tikz/tikz.jl @@ -0,0 +1,30 @@ +using UnitDiskMapping.TikzGraph, Test + +@testset "commands" begin + n = Node(0.2, 0.5) + @test command(n) isa String + m = Node(0.6, 0.5) + @test command(n) isa String + l = Line(m, n) + @test command(n) isa String + b = BoundingBox(0, 10, 0, 10) + @test command(b) isa String + g = Mesh(0, 10, 0, 10) + @test command(g) isa String + s = StringElement("jajaja") + @test command(s) == "jajaja" + s = PlainText(20.0, 3.0, "jajaja") + @test command(s) isa String +end + +@testset "canvas" begin + res = canvas() do c + Node(0.2, 0.5; draw=rgbcolor!(c, 21, 42, 36)) >> c + "jajaja" >> c + end + @test res isa Canvas + @test generate_standalone(res) isa String + write("test.tex", res) + @test isfile("test.tex") + rm("test.tex") +end \ No newline at end of file