diff --git a/CHANGELOG.md b/CHANGELOG.md index 43b5ef6..6b2e8f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog -## [1.19.0] - 2026-XX-XX +## [1.19.1] - 2026-04-23 +- several small fixes in `UnicodePlots` extension. +- fix in slice plotting: use `simplexgrid` constructor for the resulting grid to ensure a consistent grid. + +## [1.19.0] - 2026-04-23 - `UnicodePlots` does support multiplots if the package `Term` is loaded. - Add colorbar for cell regions in 2D `gridplot!` with `Py[thon]Plot` diff --git a/Project.toml b/Project.toml index 5b53cb2..09a170e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "GridVisualize" uuid = "5eed8a63-0fb0-45eb-886d-8d5a387d12b8" -version = "1.19.0" +version = "1.19.1" authors = ["Juergen Fuhrmann ", "Patrick Jaap "] [deps] diff --git a/ext/GridVisualizeUnicodePlotsExt.jl b/ext/GridVisualizeUnicodePlotsExt.jl index 133538a..35f14b1 100644 --- a/ext/GridVisualizeUnicodePlotsExt.jl +++ b/ext/GridVisualizeUnicodePlotsExt.jl @@ -6,7 +6,8 @@ Extension module for UnicodePlots.jl module GridVisualizeUnicodePlotsExt import GridVisualize: initialize!, gridplot!, scalarplot!, vectorplot!, bregion_cmap, region_cmap, reveal, streamplot! -using GridVisualize: UnicodePlotsType, GridVisualizer, SubVisualizer, vectorsample, quiverdata +using GridVisualize: UnicodePlotsType, GridVisualizer, SubVisualizer, vectorsample, quiverdata, isolevels +using GridVisualizeTools: marching_triangles using UnicodePlots: UnicodePlots using ExtendableGrids: Coordinates, simplexgrid, ON_CELLS, ON_FACES, ON_EDGES, CellNodes, FaceNodes, BFaceNodes, CellGeometries, CellRegions, BFaceRegions, num_cells, num_nodes, local_celledgenodes, num_bfaceregions, num_cellregions, num_targets, interpolate! using Colors: Colors, RGB, RGBA, red, green, blue @@ -22,7 +23,7 @@ function reveal(p::GridVisualizer, ::Type{UnicodePlotsType}) if layout == (1, 1) display(subplots[1][:figure]) else - if !isdefined(Main, :Term) + if :Term ∉ Symbol.(Base.loaded_modules_array()) @warn "A GridVisualizer with multiple UnicodePlots requires 'Term.jl' to be loaded: add Term.jl to your environment." else figures = [subplot[:figure] for subplot in subplots if haskey(subplot, :figure)] @@ -61,89 +62,89 @@ function gridplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{2}}, grid) # find bounding box coords = grid[Coordinates] - ex = extrema(view(coords, 1, :)) - ey = extrema(view(coords, 2, :)) - - # line color for interior edges - if typeof(ctx[:color]) <: RGB - color = ( - Int(round(ctx[:color].r * 255)), - Int(round(ctx[:color].g * 255)), - Int(round(ctx[:color].b * 255)), - ) + xlimits = ctx[:xlimits] + ylimits = ctx[:ylimits] + if xlimits[1] < xlimits[2] + ex = xlimits else - color = ctx[:color] + ex = extrema(view(coords, 1, :)) + end + if ylimits[1] < ylimits[2] + ey = ylimits + else + ey = extrema(view(coords, 2, :)) end + # line color for interior edges + edge_color = UnicodePlots.ansi_color(:normal) + # determine resolution (divided by 10, to reduce pixel count in the terminal) layout = ctx[:layout] - resolution = ctx[:size] ./ 12 ./ (layout[2], layout[1]) - legend_space = 0 - aspect = ctx[:aspect] * resolution[1] / (resolution[1] + legend_space) - - if (true) # auto scale feature, do we want this? - wx = ex[2] - ex[1] - wy = ey[2] - ey[1] - rescale = wx / wy * (resolution[1] / (2 * resolution[2])) - if rescale > 1 - resolution = (resolution[1] * aspect, Int(ceil(resolution[2] / rescale))) - else - resolution = (Int(ceil(resolution[1] * aspect / rescale)), resolution[2]) - end - end + resolution = ctx[:size] ./ (48, 24) ./ (layout[2], layout[1]) + aspect = ctx[:aspect] + + # rescale resolution + wx = ex[2] - ex[1] + wy = ey[2] - ey[1] + rescale = 2 * wx / wy * (resolution[2] / (resolution[1])) / aspect + resolution = (resolution[1] * rescale, resolution[2]) # we need an integer resolution resolution = @. Int(round(resolution)) + # ensure that legend fits + ncellregions = num_cellregions(grid) + nbregions = num_bfaceregions(grid) + resolution = (resolution[1], max(resolution[2], 5 + ncellregions + nbregions)) + # create UnicodePlots.Canvas - padding = 0 #0.1 * max(ex[2] - ex[1], ey[2] - ey[1]) - ex = (ex[1] - 2 * padding, ex[2] + 0.5 * padding) - ey = (ey[1] - padding, ey[2] + padding) CanvasType = UnicodePlots.BrailleCanvas # should this be a changeable parameter ? canvas = CanvasType( - resolution[2], resolution[1] + legend_space, # number of rows and columns (characters) + resolution[2], resolution[1], # number of rows and columns (characters) origin_y = ey[1], origin_x = ex[1], # position in virtual space - height = (ey[2] - ey[1]) / (resolution[1] / (resolution[1] + legend_space)), width = ex[2] - ex[1]; blend = false + height = (ey[2] - ey[1]), width = ex[2] - ex[1]; blend = false ) - ## plot all edges in the grid - plot_based = ctx[:cellwise] ? ON_CELLS : ON_FACES - if plot_based in [ON_FACES, ON_EDGES] - # plot all edges via FaceNodes - facenodes = grid[FaceNodes] - nfaces = size(facenodes, 2) - for j in 1:nfaces - UnicodePlots.lines!( - canvas, - coords[1, facenodes[1, j]], coords[2, facenodes[1, j]], # from - coords[1, facenodes[2, j]], coords[2, facenodes[2, j]]; # to - color = color - ) - end - elseif plot_based == ON_CELLS - # plot all edges via CellNodes and local_celledgenodes - cellnodes = grid[CellNodes] - cellgeoms = grid[CellGeometries] - ncells = num_cells(grid) - for j in 1:ncells - cen = local_celledgenodes(cellgeoms[j]) - for k in 1:size(cen, 2) + linewidth = ctx[:linewidth] + if linewidth > 0 + ## plot all edges in the grid + plot_based = ctx[:cellwise] ? ON_CELLS : ON_FACES + if plot_based in [ON_FACES, ON_EDGES] + # plot all edges via FaceNodes + facenodes = grid[FaceNodes] + nfaces = size(facenodes, 2) + for j in 1:nfaces UnicodePlots.lines!( canvas, - coords[1, cellnodes[cen[1, k], j]], coords[2, cellnodes[cen[1, k], j]], - coords[1, cellnodes[cen[2, k], j]], coords[2, cellnodes[cen[2, k], j]]; - color = color + coords[1, facenodes[1, j]], coords[2, facenodes[1, j]], # from + coords[1, facenodes[2, j]], coords[2, facenodes[2, j]]; # to + color = edge_color ) end + elseif plot_based == ON_CELLS + # plot all edges via CellNodes and local_celledgenodes + cellnodes = grid[CellNodes] + cellgeoms = grid[CellGeometries] + ncells = num_cells(grid) + for j in 1:ncells + cen = local_celledgenodes(cellgeoms[j]) + for k in 1:size(cen, 2) + UnicodePlots.lines!( + canvas, + coords[1, cellnodes[cen[1, k], j]], coords[2, cellnodes[cen[1, k], j]], + coords[1, cellnodes[cen[2, k], j]], coords[2, cellnodes[cen[2, k], j]]; + color = color + ) + end + end end end # color cell midpoints with cell regions color cellregions = grid[CellRegions] - ncellregions = num_cellregions(grid) cmap = region_cmap(max(2, ncellregions)) ctx[:cmap] = cmap - colors = [ + cell_colors = [ ( Int(round(cmap[i].r * 255)), Int(round(cmap[i].g * 255)), @@ -154,6 +155,7 @@ function gridplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{2}}, grid) cellgeoms = grid[CellGeometries] ncells = num_cells(grid) midpoint = [0.0, 0.0] + markersize = ctx[:markersize] for j in 1:ncells fill!(midpoint, 0.0) nvertices = num_targets(cellnodes, j) @@ -162,15 +164,34 @@ function gridplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{2}}, grid) end midpoint ./= nvertices r = cellregions[j] - UnicodePlots.points!( - canvas, - midpoint[1], midpoint[2]; - color = colors[r] - ) + if markersize > 0 + if markersize < 4 + UnicodePlots.points!( + canvas, + midpoint[1], midpoint[2]; + color = cell_colors[r] + ) + else + if markersize < 6 + character = "•" + elseif markersize < 8 + character = "●" + else + character = "⬤" + end + UnicodePlots.annotate!( + canvas, + midpoint[1], midpoint[2], + character, + UnicodePlots.ansi_color(cell_colors[r]), + false + ) + end + end + end # plot boundary faces with bregion_cmap colors - nbregions = num_bfaceregions(grid) bcmap = bregion_cmap(nbregions) ctx[:bcmap] = bcmap bcolors = [ @@ -195,20 +216,21 @@ function gridplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{2}}, grid) plt = UnicodePlots.Plot(canvas; title = ctx[:title], border = ctx[:border]) - y0 = region_legend!(plt, " cell", 1, []) - y0 = region_legend!(plt, "regions", 2, colors) + y0 = 0 + if markersize > 0 + y0 = region_legend!(plt, " cell", 1, []) + y0 = region_legend!(plt, "regions", 2, cell_colors) + end region_legend!(plt, " bface", y0 + 2, []) region_legend!(plt, "regions", y0 + 3, bcolors) # corner coordinates - ex = extrema(view(coords, 1, :)) - ey = extrema(view(coords, 2, :)) - UnicodePlots.label!(plt, :bl, string(Float16(ex[1])), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) - UnicodePlots.label!(plt, :b, "x") - UnicodePlots.label!(plt, :br, string(Float16(ex[2])), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) - UnicodePlots.label!(plt, :l, 1, string(Float16(ey[2])), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) - UnicodePlots.label!(plt, :l, round(Int, (resolution[2] + 1) / 2), "y") - UnicodePlots.label!(plt, :l, resolution[2], string(Float16(ey[1])), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) + UnicodePlots.label!(plt, :b, ctx[:xlabel]) + UnicodePlots.label!(plt, :bl, UnicodePlots.nice_repr(ex[1], plt), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) + UnicodePlots.label!(plt, :br, UnicodePlots.nice_repr(ex[2], plt), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) + UnicodePlots.label!(plt, :l, 1, UnicodePlots.nice_repr(ey[2], plt), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) + UnicodePlots.label!(plt, :l, round(Int, (resolution[2] + 1) / 2), ctx[:ylabel]) + UnicodePlots.label!(plt, :l, resolution[2], UnicodePlots.nice_repr(ey[1], plt), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) # plot ctx[:figure] = plt @@ -218,36 +240,26 @@ end function gridplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{1}}, grid) UnicodePlots = ctx[:Plotter] - text_color = UnicodePlots.ansi_color(:normal) # find bounding box + xlimits = ctx[:xlimits] coords = grid[Coordinates] - ex = extrema(view(coords, 1, :)) - - # line color for interior edges - if typeof(ctx[:color]) <: RGB - color = ( - Int(round(ctx[:color].r * 255)), - Int(round(ctx[:color].g * 255)), - Int(round(ctx[:color].b * 255)), - ) + if xlimits[1] < xlimits[2] + ex = xlimits else - color = ctx[:color] + ex = extrema(view(coords, 1, :)) end # determine resolution (divided by 5, to reduce pixel count in the terminal) ncellregions = num_cellregions(grid) nbregions = num_bfaceregions(grid) layout = ctx[:layout] - resolution = (Int(round(ctx[:size][1] / 6 / layout[2])), max(7, 5 + ncellregions + nbregions)) + resolution = (Int(round(ctx[:size][1] / 12 / layout[2])), max(7, 5 + ncellregions + nbregions)) # create UnicodePlots.Canvas - legend_space = 0 #5 - padding = 0 #0.05 * (ex[2] - ex[1]) - ex = (ex[1] - padding, ex[2] + padding) CanvasType = UnicodePlots.BrailleCanvas # should this be a changeable parameter ? canvas = CanvasType( - resolution[2], resolution[1] + legend_space, # number of rows and columns (characters) + resolution[2], resolution[1], # number of rows and columns (characters) origin_y = 0, origin_x = ex[1], # position in virtual space height = 1, width = ex[2] - ex[1]; blend = false ) @@ -267,6 +279,12 @@ function gridplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{1}}, grid) cellgeoms = grid[CellGeometries] ncells = num_cells(grid) nnodes = num_nodes(grid) + text_color = UnicodePlots.ansi_color(:normal) + if nnodes < resolution[1] / 2 + for j in 1:nnodes + UnicodePlots.annotate!(canvas, coords[1, j], 0.5, "•", text_color, false) + end + end for j in 1:ncells cen = local_celledgenodes(cellgeoms[j]) r = cellregions[j] @@ -279,9 +297,6 @@ function gridplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{1}}, grid) ) end end - for j in 1:nnodes - UnicodePlots.annotate!(canvas, coords[1, j], 0.5, "•", text_color, false) - end # plot boundary nodes with bregion_cmap colors bcmap = bregion_cmap(nbregions) @@ -299,7 +314,7 @@ function gridplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{1}}, grid) for j in 1:nbfaces red, green, blue = UInt32.(bcolors[bfaceregions[j]]) uint_color = (red << 16) | (green << 8) | blue - UnicodePlots.annotate!(canvas, coords[1, bfacenodes[1, j]], 0.5, "•", UInt32(uint_color), false) + UnicodePlots.annotate!(canvas, coords[1, bfacenodes[1, j]], 0.5, "•", uint_color, false) end plt = UnicodePlots.Plot(canvas; title = ctx[:title], border = ctx[:border]) @@ -311,9 +326,8 @@ function gridplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{1}}, grid) # corner coordinates - ex = extrema(view(coords, 1, :)) UnicodePlots.label!(plt, :bl, string(Float16(ex[1])), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) - UnicodePlots.label!(plt, :b, "x") + UnicodePlots.label!(plt, :b, ctx[:xlabel]) UnicodePlots.label!(plt, :br, string(Float16(ex[2])), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) # plot @@ -334,9 +348,9 @@ function scalarplot!( nfuncs = length(funcs) layout = ctx[:layout] - resolution = @. Int(round(ctx[:size] ./ 6 ./ (layout[2], layout[1] * 2))) # reduce pixel count in the terminal (size is then compatible to other plots) - ylim = ctx[:limits] + resolution = @. Int(round(ctx[:size] ./ 12 ./ (layout[2], 2 * layout[1]))) # reduce pixel count in the terminal (size is then compatible to other plots) + ylim = ctx[:limits] if ylim[1] > ylim[2] # try to find limits automatically ylim = (minimum([minimum(func) for func in funcs]), maximum([maximum(func) for func in funcs])) @@ -364,7 +378,17 @@ function scalarplot!( yscale == :log && (yscale = :log10) yscale == :symlog && (yscale = x -> sign(x) * (log10(1 + abs(x)))) - color = UnicodePlots.ansi_color(Symbol(ctx[:color])) + if typeof(ctx[:color]) <: String || typeof(ctx[:color]) <: Symbol + color = UnicodePlots.ansi_color(Symbol(ctx[:color])) + elseif typeof(ctx[:color]) <: RGB + color = ( + Int(round(ctx[:color].r * 255)), + Int(round(ctx[:color].g * 255)), + Int(round(ctx[:color].b * 255)), + ) + else + color = ctx[:color] + end for ifunc in 1:nfuncs func = funcs[ifunc] @@ -381,6 +405,7 @@ function scalarplot!( xscale, yscale, xlabel = String(ctx[:xlabel]), + ylabel = ctx[:ylabel], name, height = resolution[2], width = resolution[1], @@ -398,6 +423,7 @@ function scalarplot!( ) end end + ctx[:figure] = plt return reveal(ctx, TP) @@ -415,7 +441,7 @@ function scalarplot!( func = funcs[1] layout = ctx[:layout] - resolution = ctx[:size] ./ 6 ./ (layout[2], layout[1]) # reduce pixel count in the terminal + resolution = ctx[:size] ./ 12 ./ (layout[2], layout[1]) # reduce pixel count in the terminal ylim = ctx[:limits] colormap = ctx[:colormap] @@ -428,16 +454,13 @@ function scalarplot!( ex = extrema(view(coords, 1, :)) ey = extrema(view(coords, 2, :)) - if (true) # auto scale feature, do we want this? - wx = ex[2] - ex[1] - wy = ey[2] - ey[1] - rescale = wx / wy * (resolution[1] / (resolution[2])) - if rescale > 1 - resolution = (resolution[1], resolution[2] / rescale) - else - resolution = (resolution[1] / rescale, resolution[2]) - end - end + aspect = ctx[:aspect] + + # rescale resolution + wx = ex[2] - ex[1] + wy = ey[2] - ey[1] + rescale = wx / wy * (resolution[2] / (resolution[1])) / aspect + resolution = (resolution[1] * rescale, resolution[2]) # we need an integer resolution resolution = @. Int(round(resolution)) @@ -450,10 +473,13 @@ function scalarplot!( I = zeros(Float64, num_nodes(xgrid_plot)) interpolate!(I, xgrid_plot, func, grids[1]; eps = 1.0e-14, not_in_domain_value = NaN, trybrute = true) - ctx[:figure] = UnicodePlots.heatmap( + # adjust colormap to desired number of colorlevels + colormap = get(colorschemes[ctx[:colormap]], range(0.0, 1.0, length = max(2, 1 + ctx[:colorlevels]))) + + plt = UnicodePlots.heatmap( reshape(I, (resolution[1], resolution[2]))', - xlabel = "x", - ylabel = "y", + xlabel = ctx[:xlabel], + ylabel = ctx[:ylabel], xfact = (ex[2] - ex[1]) / (resolution[1] - 1), yfact = (ey[2] - ey[1]) / (resolution[2] - 1), xoffset = ex[1], @@ -467,39 +493,110 @@ function scalarplot!( border = ctx[:border] ) + # isolines (only when given as an array) + if typeof(ctx[:levels]) <: AbstractArray + levels, ~, ~ = isolevels(ctx, funcs) + points = marching_triangles(grids[1], func, levels; gridscale = 1.0) + if typeof(ctx[:color]) <: String || typeof(ctx[:color]) <: Symbol + color = UnicodePlots.ansi_color(Symbol(ctx[:color])) + elseif typeof(ctx[:color]) <: RGB + color = ( + Int(round(ctx[:color].r * 255)), + Int(round(ctx[:color].g * 255)), + Int(round(ctx[:color].b * 255)), + ) + else + color = ctx[:color] + end + for j in 1:2:(length(points) - 1) + UnicodePlots.lineplot!(plt, [points[j][1], points[j + 1][1]], [points[j][2], points[j + 1][2]], color = color) + end + end + + ctx[:figure] = plt + return reveal(ctx, TP) end scalarplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{3}}, grids, parentgrid, funcs) = @warn "3D scalarplot is not implemented for the UnicodePlots backend" + +# unicode arrows for vector plot +arrows_verythin = ['↙', '↓', '↘', '→', '↗', '↑', '↖', '←'] +arrows_thin = ['🡯', '🡫', '🡮', '🡪', '🡭', '🡩', '🡬', '🡨'] +arrows_medium = ['🡷', '🡳', '🡶', '🡲', '🡵', '🡱', '🡴', '🡰'] +arrows_thick = ['🡿', '🡻', '🡾', '🡺', '🡽', '🡹', '🡼', '🡸'] +arrows_verythick = ['🢇', '🢃', '🢆', '🢂', '🢅', '🢁', '🢄', '🢀'] + +# helper function that selects the right arrow for a given vector direction and norm +function select_arrow(angle, norm, scale) + if norm * scale < 1.0e-2 + return '•' # use a dot for very small vectors + end + if angle > -7 * π / 8 && angle <= -5 * π / 8 + a = 1 + elseif angle > -5 * π / 8 && angle <= -3 * π / 8 + a = 2 + elseif angle > -3 * π / 8 && angle <= -π / 8 + a = 3 + elseif angle > -π / 8 && angle <= π / 8 + a = 4 + elseif angle > π / 8 && angle <= 3 * π / 8 + a = 5 + elseif angle > 3 * π / 8 && angle <= 5 * π / 8 + a = 6 + elseif angle > 5 * π / 8 && angle <= 7 * π / 8 + a = 7 + else + a = 8 + end + if norm * scale <= 0.2 + return arrows_verythin[a] + elseif norm * scale <= 0.4 + return arrows_thin[a] + elseif norm * scale <= 0.6 + return arrows_medium[a] + elseif norm * scale <= 0.8 + return arrows_thick[a] + else + return arrows_verythick[a] + end +end + + function vectorplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{2}}, grid, func) layout = ctx[:layout] - resolution = ctx[:size] ./ 12 ./ (layout[2], layout[1]) # reduce pixel count in the terminal + resolution = ctx[:size] ./ (48, 24) ./ (layout[2], layout[1]) # find bounding box coords = grid[Coordinates] - ex = extrema(view(coords, 1, :)) - ey = extrema(view(coords, 2, :)) + xlimits = ctx[:xlimits] + ylimits = ctx[:ylimits] + if xlimits[1] < xlimits[2] + ex = xlimits + else + ex = extrema(view(coords, 1, :)) + end + if ylimits[1] < ylimits[2] + ey = ylimits + else + ey = extrema(view(coords, 2, :)) + end - aspect = ctx[:aspect] * resolution[1] / resolution[1] + aspect = ctx[:aspect] - if (true) # auto scale feature, do we want this? - wx = ex[2] - ex[1] - wy = ey[2] - ey[1] - rescale = wx / wy * (resolution[1] / (2 * resolution[2])) - if rescale > 1 - resolution = (resolution[1] * aspect, Int(ceil(resolution[2] / rescale))) - else - resolution = (Int(ceil(resolution[1] * aspect / rescale)), resolution[2]) - end - end + # rescale resolution + wx = ex[2] - ex[1] + wy = ey[2] - ey[1] + rescale = 2 * wx / wy * (resolution[2] / (resolution[1])) / aspect + resolution = (resolution[1] * rescale, resolution[2]) # we need an integer resolution resolution = @. Int(round(resolution)) # query vector field raster points - rc, rv = vectorsample(grid, func; gridscale = ctx[:gridscale], rasterpoints = ((resolution[1] - 1) / 2, 2 * (resolution[2] - 1)), offset = ctx[:offset]) + rc, rv = vectorsample(grid, func; gridscale = ctx[:gridscale], rasterpoints = ((resolution[1] - 1) / 2, resolution[2] - 1), offset = ctx[:offset], xlimits = ex, ylimits = ey) qc, qv = quiverdata(rc, rv; vscale = ctx[:vscale], vnormalize = ctx[:vnormalize], vconstant = ctx[:vconstant]) # construct canvas @@ -511,18 +608,8 @@ function vectorplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{2}}, grid, func ) # plot arrows - scale = minimum(resolution) / maximum(ctx[:rasterpoints]) / 300 narrows = size(qv, 2) vscale = ctx[:vscale] # vscale steers arrow thickness - if vscale <= 0.25 - arrows = ['↙', '↓', '↘', '→', '↗', '↑', '↖', '←'] - elseif vscale <= 0.5 - arrows = ['🡯', '🡫', '🡮', '🡪', '🡭', '🡩', '🡬', '🡨'] - elseif vscale <= 1 - arrows = ['🡷', '🡳', '🡶', '🡲', '🡵', '🡱', '🡴', '🡰'] - else - arrows = ['🢇', '🢃', '🢆', '🢂', '🢅', '🢁', '🢄', '🢀'] - end maxnorm = maximum(sqrt.(sum(qv .^ 2, dims = 1))) colormap = colorschemes[ctx[:colormap]] for a in 1:narrows @@ -531,32 +618,23 @@ function vectorplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{2}}, grid, func anorm = sqrt(qv[1, a]^2 + qv[2, a]^2) scale = anorm / maxnorm uint_color = UnicodePlots.ansi_color(colormap[scale]) - if angle > -7 * π / 8 && angle <= -5 * π / 8 - char = arrows[1] - elseif angle > -5 * π / 8 && angle <= -3 * π / 8 - char = arrows[2] - elseif angle > -3 * π / 8 && angle <= -π / 8 - char = arrows[3] - elseif angle > -π / 8 && angle <= π / 8 - char = arrows[4] - elseif angle > π / 8 && angle <= 3 * π / 8 - char = arrows[5] - elseif angle > 3 * π / 8 && angle <= 5 * π / 8 - char = arrows[6] - elseif angle > 5 * π / 8 && angle <= 7 * π / 8 - char = arrows[7] - else - char = arrows[8] - end - if scale < 1.0e-2 - char = '•' # use a dot for very small vectors - end + char = select_arrow(angle, anorm / maxnorm, vscale) UnicodePlots.annotate!(canvas, qc[1, a], qc[2, a], char, uint_color, false) end # generate plot - plt = UnicodePlots.Plot(canvas; title = ctx[:title], border = ctx[:border]) + plt = UnicodePlots.Plot( + canvas; title = ctx[:title], border = ctx[:border], + xfact = (ex[2] - ex[1]) / (resolution[1] - 1), + yfact = (ey[2] - ey[1]) / (resolution[2] - 1), + xlabel = ctx[:xlabel], + ylabel = ctx[:ylabel], + xoffset = ex[1], + yoffset = ey[1], + compact_labels = false, + labels = true + ) # add colormap plt.cmap.bar = ctx[:colorbar] == :none ? false : true @@ -566,12 +644,10 @@ function vectorplot!(ctx, TP::Type{UnicodePlotsType}, ::Type{Val{2}}, grid, func # corner coordinates ex = extrema(view(coords, 1, :)) ey = extrema(view(coords, 2, :)) - UnicodePlots.label!(plt, :bl, string(Float16(ex[1])), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) - UnicodePlots.label!(plt, :b, "x") - UnicodePlots.label!(plt, :br, string(Float16(ex[2])), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) - UnicodePlots.label!(plt, :l, 1, string(Float16(ey[2])), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) - UnicodePlots.label!(plt, :l, round(Int, (resolution[2] + 1) / 2), "y") - UnicodePlots.label!(plt, :l, resolution[2], string(Float16(ey[1])), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) + UnicodePlots.label!(plt, :bl, UnicodePlots.nice_repr(ex[1], plt), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) + UnicodePlots.label!(plt, :br, UnicodePlots.nice_repr(ex[2], plt), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) + UnicodePlots.label!(plt, :l, 1, UnicodePlots.nice_repr(ey[2], plt), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) + UnicodePlots.label!(plt, :l, resolution[2], UnicodePlots.nice_repr(ey[1], plt), UnicodePlots.ansi_color(UnicodePlots.BORDER_COLOR[])) ctx[:figure] = plt diff --git a/src/common.jl b/src/common.jl index 4bfa21c..0d0a949 100644 --- a/src/common.jl +++ b/src/common.jl @@ -257,7 +257,7 @@ function vectorsample( tol = reltol * extent # point spacing - spacing = [extent / rasterpoints[i] for i in 1:dim] + spacing = [(cminmax[i][2] - cminmax[i][1]) / rasterpoints[i] for i in 1:dim] # index range ijkmax = ones(Int, 3) diff --git a/src/slice_plots.jl b/src/slice_plots.jl index 4360142..82c5d0c 100644 --- a/src/slice_plots.jl +++ b/src/slice_plots.jl @@ -214,9 +214,6 @@ function slice_plot!(ctx, ::Type{Val{3}}, grid, values) # get new data from marching_tetrahedra new_coords, new_triangles, new_values = GridVisualize.marching_tetrahedra(grid, values, [plane], []) - # construct new 2D grid - grid_2d = ExtendableGrid{Float64, Int32}() - a::Float64, b::Float64, c::Float64, _ = plane # transformation matrix @@ -242,23 +239,25 @@ function slice_plot!(ctx, ::Type{Val{3}}, grid, values) rotation_matrix = compute_3d_z_rotation_matrix([a, b, c]) end - grid_2d[Coordinates] = Matrix{Float64}(undef, 2, length(new_coords)) + coords = Matrix{Float64}(undef, 2, length(new_coords)) for (ip, p) in enumerate(new_coords) # to obtain the projected coordinates, we can simply use the transpose of the rotation matrix - @views grid_2d[Coordinates][:, ip] .= (rotation_matrix'p)[1:2] + @views coords[:, ip] .= (rotation_matrix'p)[1:2] end if (a, b, c) ∉ [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)] # adjust the coordinates s.t. the minimal coordinate is zero - @views grid_2d[Coordinates][1, :] .-= minimum(grid_2d[Coordinates][1, :]) - @views grid_2d[Coordinates][2, :] .-= minimum(grid_2d[Coordinates][2, :]) + @views coords[1, :] .-= minimum(coords[1, :]) + @views coords[2, :] .-= minimum(coords[2, :]) end - grid_2d[CellNodes] = Matrix{Int32}(undef, 3, length(new_triangles)) + cellnodes = Matrix{Int32}(undef, 3, length(new_triangles)) for (it, t) in enumerate(new_triangles) - @views grid_2d[CellNodes][:, it] .= t + @views cellnodes[:, it] .= t end + # construct new 2D grid + grid_2d = simplexgrid(coords, cellnodes) return scalarplot!(ctx, grid_2d, new_values) end