Skip to content

Commit

Permalink
Add support for roundradius option (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
kimikage authored Sep 7, 2020
1 parent 4e6e882 commit f2ead5f
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 34 deletions.
15 changes: 14 additions & 1 deletion docs/src/other-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ ProfileSVG.view(width=400, height=200)
ProfileSVG.view(g, width=400, height=200) # hide
```

## `roundradius`
The `raoundradius` option specifies the rounding radius of the corners of each
frame in pixels.

```@example ex
ProfileSVG.view(roundradius=6)
ProfileSVG.view(g, roundradius=6) # hide
```

!!! tip
By setting `roundradius` to `0`, the size of the output SVG file can be
reduced slightly.

## `font` and `fontsize`

The `font` option specifies the font family names for texts. This setting is
Expand Down Expand Up @@ -84,7 +97,7 @@ You can specify the default values for options with
You can also reset the settings with [`ProfileSVG.init`](@ref).

```@example ex
ProfileSVG.set_default(StackFrameCategory(), fontsize=8)
ProfileSVG.set_default(StackFrameCategory(), roundradius=0, fontsize=8)
ProfileSVG.view(width=300)
ProfileSVG.view(g, width=300) # hide
```
10 changes: 8 additions & 2 deletions src/ProfileSVG.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct FGConfig
maxframes::Int
width::Float64
height::Float64
roundradius::Float64
font::String
fontsize::Float64
notext::Bool
Expand All @@ -51,14 +52,15 @@ function FGConfig(g::Union{FlameGraph, Nothing} = nothing,
maxframes::Int = default_config.maxframes,
width::Real = default_config.width,
height::Real = default_config.height,
roundradius::Real = default_config.roundradius,
font::AbstractString = default_config.font,
fontsize::Real = default_config.fontsize,
notext::Bool = default_config.notext,
kwargs...)

gopts = graph_options === nothing ? flamegraph_kwargs(kwargs) : graph_options
FGConfig(g, gopts, fcolor,
maxdepth, maxframes, width, height, font, fontsize, notext)
maxdepth, maxframes, width, height, roundradius, font, fontsize, notext)
end


Expand All @@ -75,6 +77,7 @@ function init()
maxframes=2000,
width=960,
height=0,
roundradius=2,
font="inherit",
fontsize=12,
notext=false)
Expand Down Expand Up @@ -115,6 +118,8 @@ View profiling results.
- `height` (default: `0`)
- The height of output SVG image in pixels. If you set the height to `0`, it
will be calculated automatically according to the graph.
- `roundradius` (default: `2`)
- The rounding radius of the corners of each frame in pixels.
- `font` (default: `"inherit"`)
- The font family names for texts. This setting is used as the CSS
`font-family` property, i.e. you can use a comma-separated list.
Expand Down Expand Up @@ -235,6 +240,7 @@ function Base.show(io::IO, ::MIME"image/svg+xml", fg::FGConfig)
x = (first(ndata.span)-1) * xstep + leftmargin
y = height - j * ystep - botmargin
w = length(ndata.span) * xstep
r = fg.roundradius
file = string(sf.file)
m = match(r"[^\\/]+$", file)
if m !== nothing
Expand All @@ -245,7 +251,7 @@ function Base.show(io::IO, ::MIME"image/svg+xml", fg::FGConfig)
basename = file
end
shortinfo = "$(sf.func) in $basename:$(sf.line)"
write_svgflamerect(io, x, y, w, ystep, shortinfo, dirinfo, color)
write_svgflamerect(io, x, y, w, ystep, r, shortinfo, dirinfo, color)

for c in g
flamerects(io, c, j + 1, nextidx)
Expand Down
16 changes: 11 additions & 5 deletions src/svgwriter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ function write_svgheader(io::IO, fig_id, width, height, font, fontsize, notext)
#$fig_id-bg {
fill: $bg_fill;
}
#$fig_id-viewport rect {
#$fig_id-viewport rect, #$fig_id-viewport path {
vector-effect: non-scaling-stroke;
}
#$fig_id-viewport text {
stroke: $fontcolorhex;
stroke-width: 0;
stroke-opacity: $text_stroke_opacity;
}
#$fig_id-viewport rect:hover {
#$fig_id-viewport rect:hover, #$fig_id-viewport path:hover {
stroke: $fontcolorhex;
stroke-width: 1;
}
Expand All @@ -86,16 +86,22 @@ function write_svgheader(io::IO, fig_id, width, height, font, fontsize, notext)
""")
end

function write_svgflamerect(io::IO, xstart, ystart, width, height, shortinfo, dirinfo, color)
function write_svgflamerect(io::IO, xstart, ystart, width, height, roundradius,
shortinfo, dirinfo, color)
x = simplify(xstart)
y = simplify(ystart)
yt = simplify(y + height * 0.75)
w = simplify(simplify(width + xstart) - x)
h = simplify(simplify(height + ystart) - y)
r = simplify(roundradius)
sinfo = escape_html(shortinfo)
dinfo = escape_html(dirinfo)
println(io, """<rect x="$x" y="$y" width="$w" height="$h" """,
"""fill="#$(hex(color))" rx="2" data-dinfo="$dinfo"/>""")
if r > zero(r)
print(io, """<rect x="$x" y="$y" width="$w" height="$h" rx="$r" """)
else
print(io, """<path d="M$x,$(y)v$(h)h$(w)v-$(h)z" """)
end
println(io, """fill="#$(hex(color))" data-dinfo="$dinfo"/>""")
println(io, """<text x="$x" dx="4" y="$yt">$sinfo</text>""")
end

Expand Down
73 changes: 52 additions & 21 deletions src/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
try {
var opts = Object.defineProperty({}, 'passive', {
get: function () {
supportsPassive = true;
return supportsPassive = true;
}
});
window.addEventListener("testPassive", null, opts);
Expand Down Expand Up @@ -46,7 +46,6 @@
var ZOOM_STEP = 1.4;
var VIEWPORT_SCALE = 0.9;
var VIEWPORT_MARGIN_X = 20;
var ROUNDRECT_R = 2;

var formatText = function (fig, text, availableWidth) {
if (availableWidth < 3 * fig.charWidthM) {
Expand Down Expand Up @@ -97,7 +96,13 @@
fig.scaleX = targetScaleX;
fig.scaleY = targetScaleY;

var rects = fig.viewport.selectAll('rect');
var rects = undefined;
var pathrects = undefined;
if (fig.roundradius > 0) {
rects = fig.viewport.selectAll('rect');
} else {
pathrects = fig.viewport.selectAll('path');
}

var scaleViewport = function (step) {
var scaleX = oldScaleX + (targetScaleX - oldScaleX) * step;
Expand All @@ -109,34 +114,52 @@
rMatrix.e = oldE + (targetE - oldE) * step; // TransX
rMatrix.f = oldF + (targetF - oldF) * step; // TransY

rects.forEach(function (r) {
var rect = r.node;
rect.setAttribute('rx', Math.max(0.0, ROUNDRECT_R / scaleX));
rect.setAttribute('ry', Math.max(0.0, ROUNDRECT_R / scaleY));
});
if (rects) {
rects.forEach(function (r) {
var rect = r.node;
rect.setAttribute('rx', Math.max(0.0, fig.roundradius / scaleX));
rect.setAttribute('ry', Math.max(0.0, fig.roundradius / scaleY));
});
}
};

var finish = function () {
scaleViewport(1);
var scaleXt = 1.0 / targetScaleX;
var scaleYt = 1.0 / targetScaleY;
rects.forEach(function (r) {
var rect = r.node;
var rectx = rect.x.baseVal.value;
var recty = rect.y.baseVal.value;
var rectw = rect.width.baseVal.value;
var text = rect.nextElementSibling;
var shortinfo = rect.getAttribute("data-shortinfo");

var updateText = function (text, x, y, w, shortinfo) {
var tMatrix = text.transform.baseVal.getItem(0).matrix;
tMatrix.a = scaleXt;
tMatrix.d = scaleYt;
tMatrix.e = (1.0 - scaleXt) * rectx;
tMatrix.f = (1.0 - scaleYt) * recty;
tMatrix.e = (1.0 - scaleXt) * x;
tMatrix.f = (1.0 - scaleYt) * y;

text.textContent = formatText(fig, shortinfo, rectw / scaleXt);
text.firstChild.nodeValue = formatText(fig, shortinfo, w / scaleXt);
text.style.display = 'inherit';
});
};
if (rects) {
rects.forEach(function (r) {
var rect = r.node;
var x = rect.x.baseVal.value;
var y = rect.y.baseVal.value;
var w = rect.width.baseVal.value;
var shortinfo = rect.getAttribute('data-shortinfo');
updateText(rect.nextElementSibling, x, y, w, shortinfo);
});
}
if (pathrects) {
pathrects.forEach(function (p) {
var path = p.node;
// The API compatibility of path segments is problematic.
var d = path.getAttribute('d');
var values = d.match(/^M\s*([\d.]+)[\s,]+([\d.]+)[^h]+h\s*([\d.]+)/);
var x = Number(values[1]);
var y = Number(values[2]);
var w = Number(values[3]);
var shortinfo = path.getAttribute('data-shortinfo');
updateText(path.nextElementSibling, x, y, w, shortinfo);
});
}
};

if (deltaT != 0) {
Expand Down Expand Up @@ -185,6 +208,12 @@
}
texts = null;

fig.roundradius = 0.0;
var rect = fig.viewport.select('rect');
if (rect) {
fig.roundradius = rect.node.rx.baseVal.value;
}

fig.scaleX = 1.0;
fig.scaleY = 1.0; // prepare for the future
fig.focusX = fig.cx; // center x in the raw (scaleX=1) coordinate space
Expand Down Expand Up @@ -249,7 +278,8 @@
details.previousElementSibling.style.display = 'none';
};

fig.viewport.selectAll('rect').forEach(function (r) {
var rects = fig.viewport.selectAll(fig.roundradius > 0 ? 'rect' : 'path');
rects.forEach(function (r) {
var rect = r.node;
var text = rect.nextElementSibling;
rect.setAttribute('data-shortinfo', unescapeHtml(text.textContent));
Expand All @@ -261,6 +291,7 @@
var transform = svg.node.createSVGTransform();
text.transform.baseVal.initialize(transform); // matrix(1, 0, 0, 1, 0, 0)
});
rects = null;

bg.dblclick(function () {
ProfileSVG.reset(fig);
Expand Down
28 changes: 23 additions & 5 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ end
@test fg.graph_options[:lidict] == lidict
@test fg.width == 123.4
@test fg.height == 0
@test fg.roundradius == 2
@test fg.font == "inherit"
@test fg.fontsize == 12
@test fg.notext == false
Expand All @@ -72,18 +73,20 @@ end
@test fg.graph_options[:lidict] == lidict
@test fg.width == 960
@test fg.height == 0
@test fg.roundradius == 2
@test fg.font == "inherit"
@test fg.fontsize == 12.34
@test fg.notext == false

g = flamegraph(backtraces, lidict=lidict)

fg = ProfileSVG.view(sfc, g, C=true, height=123.4, unknown=true)
fg = ProfileSVG.view(sfc, g, C=true, height=123.4, roundradius=0, unknown=true)
@test FlameGraphs.depth(fg.g) == 4 # `C` option does not affect the graph
@test fg.fcolor isa StackFrameCategory
@test fg.graph_options[:C] == true
@test fg.width == 960
@test fg.height == 123.4
@test fg.roundradius == 0
@test fg.font == "inherit"
@test fg.fontsize == 12
@test fg.notext == false
Expand All @@ -94,6 +97,7 @@ end
@test fg.graph_options[:C] == true
@test fg.width == 960
@test fg.height == 0
@test fg.roundradius == 2
@test fg.font == "serif"
@test fg.fontsize == 12
@test fg.notext == true
Expand All @@ -111,57 +115,68 @@ end
function has_filled_rect(str, color)
occursin(Regex("""<rect[^/]+fill="$color" """), str)
end
function has_filled_path(str, color)
occursin(Regex("""<path[^/]+fill="$color" """), str)
end

ProfileSVG.save(sfc, io, backtraces,
C=true, lidict=lidict, width=123.4, unknown=nothing)
str = String(take!(io))
@test svg_size(str) == ("123.4", "147")
@test has_filled_rect(str, "#FF0000")
@test !has_filled_path(str, "#FF0000")
filename = tempname()
ProfileSVG.save(sfc, filename, backtraces,
C=true, lidict=lidict, width=123.4, unknown=nothing)
str = read(filename, String)
rm(filename)
@test svg_size(str) == ("123.4", "147")
@test has_filled_rect(str, "#FF0000")
@test !has_filled_path(str, "#FF0000")

ProfileSVG.save(io, backtraces,
C=true, lidict=lidict, fontsize=12.34, unknown=missing)
str = String(take!(io))
@test svg_size(str) == ("960", "151")
@test !has_filled_rect(str, "#FF0000")
@test !has_filled_path(str, "#FF0000")
filename = tempname()
ProfileSVG.save(filename, backtraces,
C=true, lidict=lidict, fontsize=12.34, unknown=missing)
str = read(filename, String)
rm(filename)
@test svg_size(str) == ("960", "151")
@test !has_filled_rect(str, "#FF0000")
@test !has_filled_path(str, "#FF0000")


g = flamegraph(backtraces, lidict=lidict)

ProfileSVG.save(sfc, io, g, C=true, height=123.4, unknown=true)
ProfileSVG.save(sfc, io, g, C=true, height=123.4, roundradius=0, unknown=true)
str = String(take!(io))
@test svg_size(str) == ("960", "123.4")
@test has_filled_rect(str, "#FF0000")
@test !has_filled_rect(str, "#FF0000")
@test has_filled_path(str, "#FF0000")
filename = tempname()
ProfileSVG.save(sfc, filename, g, C=true, height=123.4, unknown=true)
ProfileSVG.save(sfc, filename, g, C=true, height=123.4, roundradius=0, unknown=true)
str = read(filename, String)
rm(filename)
@test svg_size(str) == ("960", "123.4")
@test has_filled_rect(str, "#FF0000")
@test !has_filled_rect(str, "#FF0000")
@test has_filled_path(str, "#FF0000")

ProfileSVG.save(io, g, C=true, font="serif", notext=true, unknown=false)
str = String(take!(io))
@test svg_size(str) == ("960", "136")
@test !has_filled_rect(str, "#FF0000")
@test !has_filled_path(str, "#FF0000")
filename = tempname()
ProfileSVG.save(filename, g, C=true, font="serif", notext=true, unknown=false)
str = read(filename, String)
rm(filename)
@test svg_size(str) == ("960", "136")
@test !has_filled_rect(str, "#FF0000")
@test !has_filled_path(str, "#FF0000")
end

@testset "size limitation" begin
Expand Down Expand Up @@ -189,6 +204,7 @@ end
@test fgc.graph_options[:lidict] == lidict
@test fgc.width == 123.4
@test fgc.height == 567.8
@test fgc.roundradius == 2
@test fgc.font == "inherit"
@test fgc.fontsize == 12
@test fgc.notext == false
Expand All @@ -202,6 +218,7 @@ end
@test fgc.graph_options[:lidict] == lidict
@test fgc.width == 123.4
@test fgc.height == 567.8
@test fgc.roundradius == 2
@test fgc.font == "inherit"
@test fgc.fontsize == 9
@test fgc.notext == false
Expand All @@ -215,6 +232,7 @@ end
@test fgc.graph_options[:lidict] == lidict
@test fgc.width == 123.4
@test fgc.height == 0
@test fgc.roundradius == 2
@test fgc.font == "inherit"
@test fgc.fontsize == 12
@test fgc.notext == false
Expand Down

0 comments on commit f2ead5f

Please sign in to comment.