diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc0f781..46e6bba2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ See https://github.com/JuliaGraphics/Cairo.jl/pull/357. ### Added +- `markcells()` and `getcells()` + ### Changed - fixes for `drawpath(p, f)` to do the Bezier curve truncation better diff --git a/docs/src/howto/images.md b/docs/src/howto/images.md index 860533b7..338c15cb 100644 --- a/docs/src/howto/images.md +++ b/docs/src/howto/images.md @@ -454,14 +454,14 @@ fontsize(15) sethue("white") setline(1) transform([0 1 1 0 0 0]) -cells = Table(10, 10, 512 / 10, 512 / 10, Point(512 / 2, 512 / 2)) -for (pos, n) in cells +t = Table(10, 10, 512 / 10, 512 / 10, Point(512 / 2, 512 / 2)) +for (pos, n) in t text(string(n), pos, halign = :center, valign = :middle) box(pos, 512 / 10, 512 / 10, :stroke) end setline(5) -highlightcells(cells, collect(1:100)[[2, 4, 35, 69]], :stroke, - color = colorant"blue") +sethue("blue") +markcells(t, getcells(t, [2, 4, 35, 69])) M ``` diff --git a/docs/src/howto/tables-grids.md b/docs/src/howto/tables-grids.md index 689de0c8..86213c00 100644 --- a/docs/src/howto/tables-grids.md +++ b/docs/src/howto/tables-grids.md @@ -241,6 +241,34 @@ nothing # hide ![array table](../assets/figures/arraytable.svg) +## Selecting and highlighting cells + +With `getcells()` and `markcells()` you can mark particular cells of Tables and Tilers. By default the `markcells()` function draws a box around each cell. You can choose the `:fill` action, or supply a four-argument function that adds graphics relevant to the cell's position, width, height, and number. + +```@example +using Luxor # hide +@drawsvg begin +background("antiquewhite") +t = Tiler(600, 300, 12, 12) + +sethue("purple") +markcells(t, getcells(t, [1, 6:12, 24, 24:6:96, 144])) + +sethue("red") +setopacity(0.5) +markcells(t, getcells(t, 10:80), action=:fill) + +setcolor("black") +fontsize(12) +markcells(t, getcells(t, 1:144), func = (pt, w, h, n) -> begin + sethue("white") + circle(pt, h/2, :fill) + sethue("black") + text(string(n), pt, halign=:center, valign=:middle) +end) +end 600 300 +``` + ## Grids You might also find a use for a grid. Luxor provides a simple grid utility. Grids are lazy: they'll supply the next point on the grid when you ask for it. diff --git a/src/Luxor.jl b/src/Luxor.jl index 6ed82037..35ec3014 100644 --- a/src/Luxor.jl +++ b/src/Luxor.jl @@ -49,6 +49,7 @@ include("basics.jl") include("Turtle.jl") include("shapes.jl") include("BoundingBox.jl") +include("Table.jl") include("polygons.jl") include("triangles.jl") include("hexagons.jl") @@ -65,7 +66,6 @@ include("animate.jl") include("bars.jl") include("bezierpath.jl") include("mesh.jl") -include("Table.jl") include("Boxmaptile.jl") include("noise.jl") include("deprecations.jl") @@ -137,7 +137,7 @@ export Drawing, translationmatrix, cairotojuliamatrix, juliatocairomatrix, getrotation, getscale, gettranslation, setmode, getmode, GridHex, GridRect, nextgridpoint, Table, - highlightcells, readpng, readsvg, placeimage, placeeps, + highlightcells, getcells, markcells, readpng, readsvg, placeimage, placeeps, svgstring, julialogo, juliacircles, barchart, mesh, setmesh, add_mesh_patch, mask, # animation diff --git a/src/Table.jl b/src/Table.jl index 72769636..048bdaac 100644 --- a/src/Table.jl +++ b/src/Table.jl @@ -282,18 +282,7 @@ end box(t::Table, cellnumber::Int; action = :none, vertices = false) = box(t, cellnumber, action, vertices = vertices) -""" - highlightcells(t::Table, cellnumbers, action::Symbol=:stroke; - color::Colorant=colorant"red", - offset = 0) - -Highlight (draw or fill) one or more cells of table `t`. `cellnumbers` is a range, -array, or an array of row/column tuples. - - highlightcells(t, 1:10, :fill, color=colorant"blue") - highlightcells(t, vcat(1:5, 150), :stroke, color=colorant"magenta") - highlightcells(t, [(4, 5), (3, 6)]) -""" +# superseded, will be deprecated function highlightcells(t::Table, cellnumbers, action::Symbol = :stroke; color::Colorant = colorant"red", offset = 0) diff --git a/src/tiles-grids.jl b/src/tiles-grids.jl index 4206b669..0ca6177b 100644 --- a/src/tiles-grids.jl +++ b/src/tiles-grids.jl @@ -106,6 +106,11 @@ function Base.getindex(pt::Tiler, i::Int) return (Point(xcoord, ycoord), i) end +function Base.getindex(t::Tiler, r::Int, c::Int) + n = ((r - 1) * t.ncols) + c + return first(t[n]) +end + function Base.size(pt::Tiler) return (pt.nrows, pt.ncols) end @@ -387,3 +392,162 @@ Return the Bounding Box enclosing the tile at row `r` column `c`. function BoundingBox(t::Tiler, r, c) return BoundingBox(t, ((r - 1) * t.ncols) + c) end + +""" + markcells(t::Union{Tiler, Table}, cells; + action = :stroke, + func = nothing) + +Mark the cells of Tiler/Table `t` in the `cells`. + +By default a box is drawn around the cell using the current settings and filled or stroked according to `action`. + +You can supply a function for the `func` keyword that can do anything you want. The function should accept four arguments, `position`, `width`, `height`, and `number`. + +## Example + +This code draws 30 circles in 5 rows and 6 columns, numbered and +colored sequentially in four colors. `getcells()` obtains a selection of the cells from the Tiler. + +```julia +@draw begin + fontsize(30) + #t = Table(5, 6, 60, 60) + t = Tiler(400, 400, 5, 6) + markcells(t, getcells(t, 1:30), + func = (pos, w, h, n) -> begin + sethue(["red", "green", "blue", "purple"][mod1(n, end)]) + circle(pos, w / 2, :fill) + sethue("white") + text(string(n), pos, halign = :center, valign = :middle) + end) +end +``` +""" +function markcells(t::Union{Tiler,Table}, cells; + action = :stroke, + func = nothing) + @layer begin + for cell in cells + pos, n = cell + row, col = (1 + div(n - 1, t.ncols)), mod1(n, t.ncols) + if t isa Table + w = t.colwidths[col] + h = t.rowheights[row] + else + w = t.tilewidth + h = t.tileheight + end + if isnothing(func) + # just draw a box with current settings + box(pos, w, h, action) + else + func(pos, w, h, n) + end + end + end +end + +""" + getcells(t, rows, columns) + +Get the Tiler or Table cells in `t` corresponding to `rows` and `columns`. + +- `t` is a Tiler or Table + +- `rows` is an array or range of row numbers +- `cols` is an array or range of column numbers + +Use `:` for "all rows" or "all columns". + +Returns an array of Tuples, where each Tuple is `(Point, Number)`. + +`markcells()` can use the result of this function to mark selected cells. + + ## Example + +```julia +@draw begin + chessboard = Tiler(600, 600, 8, 8) + # odd rows odd columns + s = getcells(chessboard, 1:2:12, 1:2:12) + markcells(chessboard, s, action = :fill) + # even rows even columns + s = getcells(chessboard, 2:2:12, 2:2:12) + markcells(chessboard, s, action = :fill) +end +``` +""" +function getcells(t::Union{Table,Tiler}, rows, columns) + result = Tuple[] + for r in rows + r < 1 && continue + r > t.nrows && continue + for c in columns + c < 1 && continue + c > t.ncols && continue + n = ((r - 1) * t.ncols) + c + push!(result, (t[r, c], n)) + end + end + return result +end + +getcells(t::Table, ::Colon, ::Colon) = getcells(t, 1:(t.nrows), 1:(t.ncols)) +getcells(t::Table, ::Colon, columns) = getcells(t, 1:(t.nrows), columns) +getcells(t::Table, rows, ::Colon) = getcells(t, rows, 1:(t.ncols)) + +getcells(t::Tiler, ::Colon, ::Colon) = getcells(t, 1:(t.nrows), 1:(t.ncols)) +getcells(t::Tiler, ::Colon, columns) = getcells(t, 1:(t.nrows), columns) +getcells(t::Tiler, rows, ::Colon) = getcells(t, rows, 1:(t.ncols)) + +""" + getcells(t, n::T) where T <: AbstractRange + +Get the Tiler/Table cells with numbers in range `n`. + +Returns an array of Tuples, where each Tuple is `(Point, Number)`. +""" +function getcells(t, n::T) where {T<:AbstractRange} + result = Tuple[] + for i in n + append!(result, getcells(t, i)) + end + return result +end + +""" + getcells(t, a::T) where T <: AbstractArray + +Get the Tiler/Table cells with numbers in array `a`. + +Returns an array of Tuples, where each Tuple is `(Point, Number)`. +""" +function getcells(t, a::T) where {T<:AbstractArray} + result = Tuple[] + for i in a + append!(result, getcells(t, i)) + end + return result +end + +""" + getcells(t, n::Int) + +Get the cell `n` in Tiler or Table `t`. + +Returns an array of Tuples, where each Tuple is `(Point, Number)`. + +!!! note + + Luxor Tables and Tilers are numbered by row then column, rather than the usual Julia column-major numbering: + +``` + 1 2 3 4 5 + 6 7 8 9 10 +11 12 13 14 15 +16 17 18 19 20 +``` +""" +getcells(t::Table, n::Int) = [(t[n], n)] # array of tuples +getcells(t::Tiler, n::Int) = [(t[n])] # array of tuples diff --git a/test/getcell-test.jl b/test/getcell-test.jl new file mode 100644 index 00000000..004316aa --- /dev/null +++ b/test/getcell-test.jl @@ -0,0 +1,126 @@ +using Luxor +using Colors +using Test + +function getcelltest(fname) + Drawing(1200, 1200, fname) + origin() + a = [1, 2, 4] + b = 2:3 + R = 6 + k = 7 + fontsize(30) + panes = Tiler(1200, 1200, 3, 3) + @layer begin + translate(first(panes[1])) + t = Tiler(260, 260, R, 4) + for (pos, n) in t + sethue(HSB(36n, 0.7, 0.8)) + box(t, n, :fill) + sethue("purple") + box(t, n, :stroke) + end + s = getcells(t, a, b) + for is in s + pt, n = is + sethue("white") + circle(pt, 20, :fill) + end + end + @layer begin + translate(first(panes[2])) + t = Table(R, 4, 40, 40) + for (pos, n) in t + sethue(HSB(36n, 0.7, 0.8)) + box(t, n, :fill) + sethue("purple") + box(t, n, :stroke) + end + s = getcells(t, a, b) + for is in s + pt, n = is + sethue("white") + circle(pt, 20, :fill) + end + end + + @layer begin + translate(first(panes[3])) + t = Tiler(260, 260, R, 4) + for (pos, n) in t + sethue(HSB(36n, 0.7, 0.8)) + box(t, n, :fill) + sethue("purple") + box(t, n, :stroke) + end + s = getcells(t, k) + for is in s + pt, n = is + sethue("white") + circle(pt, 20, :fill) + sethue("black") + text(string(k), pt, halign=:center, valign=:middle) + end + end + + @layer begin + translate(first(panes[4])) + t = Tiler(250, 250, R, 4) + t = Table(R, 4, 40, 40) + for (pos, n) in t + sethue(HSB(36n, 0.7, 0.8)) + box(t, n, :fill) + sethue("purple") + box(t, n, :stroke) + end + s = getcells(t, 22:-3:7) + markcells(t, s, + action=:fill, + func=(pos, w, h, n) -> begin + sethue("white") + circle(pos, h / 2, :fill) + sethue("black") + text(string(n), pos, halign=:center, valign=:middle) + end + ) + end + + @layer begin + translate(first(panes[5])) + t = Tiler(400, 400, 5, 6) + markcells(t, getcells(t, 1:30), + func=(pos, w, h, n) -> begin + sethue(["red", "green", "blue", "purple"][mod1(n, end)]) + circle(pos, w / 2, :fill) + sethue("white") + text(string(n), pos, halign=:center, valign=:middle) + end) + end + + @layer begin + translate(first(panes[6])) + t = Tiler(300, 300, 4, 5) + t = Table(4, 5, 50, 60) + for (pos, n) in t + sethue(HSB(36n, 0.7, 0.8)) + box(t, n, :fill) + sethue("purple") + box(t, n, :stroke) + text(string(n), pos, halign=:center, valign=:middle) + end + markcells(t, getcells(t, 1:2:10), + func=(pos, w, h, n) -> begin + isodd(n) ? sethue("red") : sethue("green") + circle(pos, w / 2, :fill) + sethue("white") + text(string(n), pos, halign=:center, valign=:middle) + end) + + end + @test finish() == true +end + +fname = "getcell.png" +getcelltest(fname) +println("getcell test: output in $(fname)") +println("...finished getcell tests: output in $(fname)") diff --git a/test/runtests.jl b/test/runtests.jl index 78533771..962ed3dc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -146,6 +146,7 @@ function run_all_tests() @testset "tilerstables" begin include("pagetiler-test.jl") include("table-tests.jl") + include("getcell-test.jl") end @testset "matrix" begin include("matrix-tests.jl")