Skip to content

Commit d7feae3

Browse files
Fix GR dashed legend line rendering
1 parent 70f0cd7 commit d7feae3

2 files changed

Lines changed: 106 additions & 12 deletions

File tree

PlotsBase/ext/GRExt.jl

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,8 @@ function gr_polyline(
369369
arrowside = :none,
370370
arrowstyle = :simple,
371371
arrowsize = 1,
372+
linestyle = :solid,
373+
linewidth = 1.0,
372374
)
373375
draw_head = arrowside in (:head, :both)
374376
draw_tail = arrowside in (:tail, :both)
@@ -395,7 +397,11 @@ function gr_polyline(
395397
end
396398
# if we found a start and end, draw the line segment, otherwise we're done
397399
if istart > 0 && iend > 0
398-
func(x[istart:iend], y[istart:iend])
400+
if func === GR.polyline
401+
gr_styled_polyline(x[istart:iend], y[istart:iend], linestyle, linewidth)
402+
else
403+
func(x[istart:iend], y[istart:iend])
404+
end
399405
if draw_head
400406
gr_set_arrowstyle(arrowstyle)
401407
GR.drawarrow(x[iend - 1], y[iend - 1], x[iend], y[iend])
@@ -411,6 +417,59 @@ function gr_polyline(
411417
return
412418
end
413419

420+
function gr_styled_polyline(x, y, style, linewidth)
421+
style in (:dash, :dot, :dashdot, :dashdotdot) || return GR.polyline(x, y)
422+
GR.setlinetype(gr_linetypes.solid)
423+
lscale = max(1, linewidth) / 2
424+
dash = 0.012lscale
425+
gap = 0.008lscale
426+
dot = 0.0025lscale
427+
return if style === :dash
428+
gr_dashed_polyline(x, y, (dash, gap))
429+
elseif style === :dot
430+
gr_dashed_polyline(x, y, (dot, gap))
431+
elseif style === :dashdot
432+
gr_dashed_polyline(x, y, (dash, gap, dot, gap))
433+
else
434+
gr_dashed_polyline(x, y, (dash, gap, dot, gap, dot, gap))
435+
end
436+
end
437+
438+
function gr_dashed_polyline(x, y, pattern)
439+
ipattern = 1
440+
pattern_offset = 0.0
441+
xs = Vector{Float64}(undef, 2)
442+
ys = Vector{Float64}(undef, 2)
443+
for i in 1:(length(x) - 1)
444+
x1, y1 = GR.wctondc(x[i], y[i])
445+
x2, y2 = GR.wctondc(x[i + 1], y[i + 1])
446+
dx, dy = x2 - x1, y2 - y1
447+
segment_length = hypot(dx, dy)
448+
segment_length > 0 || continue
449+
segment_offset = 0.0
450+
while segment_offset < segment_length
451+
pattern_remaining = pattern[ipattern] - pattern_offset
452+
step = min(pattern_remaining, segment_length - segment_offset)
453+
if isodd(ipattern)
454+
t1 = segment_offset / segment_length
455+
t2 = (segment_offset + step) / segment_length
456+
xa, ya = GR.ndctowc(x1 + t1 * dx, y1 + t1 * dy)
457+
xb, yb = GR.ndctowc(x1 + t2 * dx, y1 + t2 * dy)
458+
xs[1], xs[2] = xa, xb
459+
ys[1], ys[2] = ya, yb
460+
GR.polyline(xs, ys)
461+
end
462+
segment_offset += step
463+
pattern_offset += step
464+
if pattern_offset >= pattern[ipattern]
465+
pattern_offset = 0.0
466+
ipattern = ipattern == length(pattern) ? 1 : ipattern + 1
467+
end
468+
end
469+
end
470+
return
471+
end
472+
414473
function gr_polyline3d(x, y, z, func = GR.polyline3d)
415474
iend = 0
416475
n = length(x)
@@ -600,10 +659,11 @@ end
600659
# ---------------------------------------------------------
601660

602661
function gr_set_line(lw, style, c, s) # s can be Subplot or Series
662+
linewidth = get_thickness_scaling(s) * max(0, lw / gr_nominal_size(s))
603663
GR.setlinetype(gr_linetypes[style])
604-
GR.setlinewidth(get_thickness_scaling(s) * max(0, lw / gr_nominal_size(s)))
664+
GR.setlinewidth(linewidth)
605665
gr_set_linecolor(c)
606-
return nothing
666+
return linewidth
607667
end
608668

609669
gr_set_fill(c) = (gr_set_fillcolor(c); GR.setfillintstyle(GR.INTSTYLE_SOLID); nothing)
@@ -1190,7 +1250,7 @@ gr_legend_bbox(xpos, ypos, leg) = GR.drawrect(
11901250
ypos + 0.5leg.dy,
11911251
)
11921252

1193-
const gr_lw_clamp_factor = Ref(5)
1253+
const gr_lw_clamp_factor = Ref(3)
11941254

11951255
function gr_add_legend(sp, leg, viewport_area)
11961256
sp[:legend_position] (:none, :inline) && return
@@ -1244,7 +1304,7 @@ function gr_add_legend(sp, leg, viewport_area)
12441304
ls = get_linestyle(series)
12451305
la = get_linealpha(series)
12461306
clamped_lw = (lfps / 8) * clamp(lw, min_lw, max_lw)
1247-
gr_set_line(clamped_lw, ls, lc, sp) # see github.com/JuliaPlots/Plots.jl/issues/3003
1307+
linewidth = gr_set_line(clamped_lw, ls, lc, sp) # see github.com/JuliaPlots/Plots.jl/issues/3003
12481308
_debug[] && gr_legend_bbox(xpos, ypos, leg)
12491309

12501310
if ((st :shape || series[:fillrange] nothing) && series[:ribbon] nothing)
@@ -1262,16 +1322,21 @@ function gr_add_legend(sp, leg, viewport_area)
12621322
gr_set_transparency(fc, get_fillalpha(series))
12631323
gr_polyline(x, y, GR.fillarea)
12641324
gr_set_transparency(lc, la)
1265-
gr_set_line(clamped_lw, ls, lc, sp)
1266-
st :shape && gr_polyline(x, y)
1325+
linewidth = gr_set_line(clamped_lw, ls, lc, sp)
1326+
st :shape && gr_styled_polyline(x, y, ls, linewidth)
12671327
end
12681328

12691329
max_markersize = Inf
12701330
if st in (:path, :straightline, :path3d)
12711331
max_markersize = leg.base_markersize
12721332
gr_set_transparency(lc, la)
12731333
filled = series[:fillrange] nothing && series[:ribbon] nothing
1274-
GR.polyline(xpos .+ [lft, rgt], ypos .+ (filled ? [top, top] : [0, 0]))
1334+
gr_styled_polyline(
1335+
xpos .+ [lft, rgt],
1336+
ypos .+ (filled ? [top, top] : [0, 0]),
1337+
ls,
1338+
linewidth,
1339+
)
12751340
end
12761341

12771342
if (msh = series[:markershape]) :none
@@ -2033,7 +2098,8 @@ function gr_draw_segments(series, x, y, z, fillrange, clims)
20332098
GR.fillarea(fx, fy)
20342099
end
20352100
(lc = get_linecolor(series, clims, i)) |> gr_set_fillcolor
2036-
gr_set_line(get_linewidth(series, i), get_linestyle(series, i), lc, series)
2101+
ls = get_linestyle(series, i)
2102+
linewidth = gr_set_line(get_linewidth(series, i), ls, lc, series)
20372103
gr_set_transparency(lc, get_linealpha(series, i))
20382104
if is3d
20392105
GR.polyline3d(x[rng], y[rng], z[rng])
@@ -2044,7 +2110,15 @@ function gr_draw_segments(series, x, y, z, fillrange, clims)
20442110
arrowside, arrowstyle, arrowsize =
20452111
arrow.side, arrow.style, (arrow.headlength + arrow.headwidth) / 2
20462112

2047-
gr_polyline(x[rng], y[rng]; arrowside, arrowstyle, arrowsize)
2113+
gr_polyline(
2114+
x[rng],
2115+
y[rng];
2116+
arrowside,
2117+
arrowstyle,
2118+
arrowsize,
2119+
linestyle = ls,
2120+
linewidth,
2121+
)
20482122
end
20492123
end
20502124
return
@@ -2110,9 +2184,10 @@ function gr_draw_shapes(series, clims)
21102184

21112185
# draw the shapes
21122186
lc = get_linecolor(series, clims, i)
2113-
gr_set_line(get_linewidth(series, i), get_linestyle(series, i), lc, series)
2187+
ls = get_linestyle(series, i)
2188+
linewidth = gr_set_line(get_linewidth(series, i), ls, lc, series)
21142189
gr_set_transparency(lc, get_linealpha(series, i))
2115-
GR.polyline(xseg, yseg)
2190+
gr_styled_polyline(xseg, yseg, ls, linewidth)
21162191
end
21172192
end
21182193
return

PlotsBase/test/test_backends.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,25 @@
4444
end
4545
end
4646

47+
@testset "GR - linestyle visual defaults" begin
48+
with(:gr) do
49+
fn = tempname() * ".svg"
50+
plt = plot(
51+
1:4,
52+
[1:4 4:-1:1];
53+
marker = :circle,
54+
linestyle = [:dash :dot],
55+
linewidth = 2,
56+
label = ["dash" "dot"],
57+
)
58+
savefig(plt, fn)
59+
svg_text = read(fn, String)
60+
@test !occursin("stroke-dasharray", svg_text)
61+
@test count(r"stroke:#009af9", svg_text) > 10
62+
@test count(r"stroke:#e26f46", svg_text) > 10
63+
end
64+
end
65+
4766
is_pkgeval() || @testset "PlotlyJS" begin
4867
with(:plotlyjs) do
4968
PlotlyJSExt = Base.get_extension(PlotsBase, :PlotlyJSExt)

0 commit comments

Comments
 (0)