Skip to content

Commit 70bf65a

Browse files
committed
Add extension for Plots.jl, and put UnicodePlots.jl on equal footing
Remove the definition for the 3-argument `Base.show()` method when UnicodePlots.jl is loaded. This treats UnicodePlots more like a plotting package than a fancy text output.
1 parent ba97064 commit 70bf65a

File tree

9 files changed

+139
-36
lines changed

9 files changed

+139
-36
lines changed

Project.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "KernelDensityEstimation"
22
uuid = "e46ec5f4-66dc-4371-a668-81bd92d19d7d"
3-
authors = ["Justin Willmert <[email protected]>"]
43
version = "0.8.0"
4+
authors = ["Justin Willmert <[email protected]>"]
55

66
[deps]
77
FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
@@ -12,22 +12,26 @@ PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
1212
[weakdeps]
1313
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
1414
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
15+
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
1516
UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"
1617

1718
[extensions]
1819
KDEDistributionsExt = ["Distributions"]
1920
KDEMakieExt = ["Makie"]
21+
KDERecipesBaseExt = ["RecipesBase"]
2022
KDEUnicodePlotsExt = ["UnicodePlots"]
2123

2224
[compat]
2325
Distributions = "0.25.46"
2426
FFTW = "1.4"
2527
Makie = "0.15,0.16,0.17,0.18,0.19,0.20,0.21,0.22,0.23,0.24"
2628
PrecompileTools = "1.1"
29+
RecipesBase = "1.3"
2730
UnicodePlots = "3"
2831
julia = "1.6"
2932

3033
[extras]
3134
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
3235
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
36+
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
3337
UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"

docs/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ KernelDensity = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b"
88
KernelDensityEstimation = "e46ec5f4-66dc-4371-a668-81bd92d19d7d"
99
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
1010
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
11+
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
1112
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1213
UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"
1314

@@ -19,6 +20,7 @@ Documenter = "1.3"
1920
DocumenterInterLinks = "1"
2021
KernelDensity = "0.6"
2122
Makie = "0.20, 0.21, 0.22, 0.23"
23+
Plots = "1"
2224
julia = "1.6"
2325

2426
[extras]

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ bib = CitationBibliography(joinpath(@__DIR__, "src", "refs.bib");
1616
style = :numeric)
1717
links = InterLinks(
1818
"Julia" => "https://docs.julialang.org/en/v1/",
19+
"UnicodePlots" => "https://juliaplots.org/UnicodePlots.jl/stable/",
1920
)
2021

2122
showcases = map(ff -> joinpath("showcase", ff),

docs/src/extensions.md

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,67 @@ nothing # hide
8989

9090
![](ext_makie.svg)
9191

92+
93+
## [Plots.jl](@id ext-plots)
94+
95+
Plotting the [`UnivariateKDE`](@ref) object is natively supported within the
96+
[`Plots.jl`](https://juliahub.com/ui/Packages/General/Plots)
97+
ecosystem of backends by defining a plot recipe for `RecipesBase.jl`.
98+
99+
The density estimate is interpreted by default as a `:line` series type with
100+
xlabel `"value"` and ylabel `"density"`.
101+
102+
```@example ext_plots
103+
using KernelDensityEstimation: kde, LinearBinning
104+
using Random # hide
105+
Random.seed!(100) # hide
106+
107+
# 500 samples from a Chisq(ν=4) distribution
108+
rv = dropdims(sum(abs2, randn(4, 500), dims=1), dims=1)
109+
nothing # hide
110+
```
111+
112+
```@example ext_plots
113+
using Plots
114+
115+
K = kde(rv; lo = 0.0, boundary = :closedleft)
116+
H = kde(rv; lo = 0.0, boundary = :closedleft,
117+
bwratio = 1.0, method = LinearBinning())
118+
119+
plot(
120+
plot(H, title = "stairs", seriestype = :stepmid),
121+
plot(K, title = "lines", ylabel = nothing),
122+
layout = (1, 2), size = (800, 300),
123+
leftmargin = (2.5, :mm), bottommargin = (3.0, :mm),
124+
link = :all, legend = false
125+
)
126+
127+
savefig("ext_plots.svg") # hide
128+
nothing # hide
129+
```
130+
131+
![](ext_plots.svg)
132+
133+
92134
## [UnicodePlots.jl](@id ext-unicodeplots)
93135

94136
For quick, approximate visualization of a density within the terminal, an extension is provided for the
95137
[`UnicodePlots.jl`](https://juliahub.com/ui/Packages/General/UnicodePlots)
96-
package.
97-
The three-argument `Base.show` method is defined to show the Unicode plot by default, so the distribution will be
98-
previewed at the REPL automatically.
138+
package and extends the `lineplot` (and `lineplot!`) methods.
99139

100140
```@example ext_unicodeplots
101141
using KernelDensityEstimation
102-
using UnicodePlots
103142
using Random # hide
104143
Random.seed!(100) # hide
105144
106145
# 500 samples from a Chisq(ν=4) distribution
107146
rv = dropdims(sum(abs2, randn(4, 500), dims=1), dims=1)
147+
nothing # hide
148+
```
149+
150+
```@example ext_unicodeplots
151+
using UnicodePlots
152+
108153
K = kde(rv; lo = 0.0, boundary = :closedleft)
154+
lineplot(K)
109155
```

docs/src/releasenotes.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ Depth = 2:2
1313

1414
## v0.8.0 — Unreleased
1515

16+
- The [package extension](extensions.md#ext-unicodeplots) for
17+
[`UnicodePlots.jl`](https://juliahub.com/ui/Packages/General/UnicodePlots)
18+
no longer defines the 3-argument [`Base.show`](@extref Base.show-Tuple{IO, Any, Any}) method for the
19+
[`UnivariateKDE`](@ref) type in favor of requiring the user to explicitly plot it in the terminal
20+
with [`UnicodePlots.lineplot`](@extref)
21+
22+
- The [package extension](extensions.md#ext-plots) for
23+
[`RecipesBase.jl`](https://juliahub.com/ui/Packages/General/RecipesBase)
24+
has been added to aid in plotting within the
25+
[`Plots.jl`](https://juliahub.com/ui/Packages/General/Plots)
26+
ecosystem.
27+
1628
- Various changes to the interfaces and type definitions have been made to allow for future support of bivariate (and
1729
possibly multivariate) density estimates.
1830

ext/KDERecipesBaseExt.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module KDERecipesBaseExt
2+
3+
import KernelDensityEstimation: AbstractBinningKDE, UnivariateKDE
4+
import RecipesBase: @recipe
5+
6+
@recipe function f(K::UnivariateKDE)
7+
seriestype --> :line
8+
xlabel --> "value"
9+
ylabel --> "density"
10+
return K.x, K.f
11+
end
12+
13+
end

ext/KDEUnicodePlotsExt.jl

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,14 @@
11
module KDEUnicodePlotsExt
22

3-
import KernelDensityEstimation
4-
const KDE = KernelDensityEstimation
3+
import KernelDensityEstimation: UnivariateKDE
4+
import UnicodePlots: Plot, lineplot, lineplot!
55

6-
using UnicodePlots: lineplot
7-
8-
function Base.show(io::IO, mime::MIME"text/plain", K::KDE.UnivariateKDE{T}) where {T}
9-
print(io, KDE.UnivariateKDE, '{', T, '}', '(')
10-
io′ = IOContext(io, :compact => true)
11-
println(io′, first(K.x), ':', step(K.x), ':', last(K.x), ", [ … ])")
12-
13-
sigdigits = 4
14-
ylim = (0.0, round(maximum(K.f); sigdigits))
15-
ytickwd = textwidth(string(ylim[2]))
16-
17-
ht, wd = displaysize(io)
18-
ht -= 3#=borders=# + 1#=tick label=# + 1#=first line print=# + 2#=repl context=#
19-
wd -= 4#=borders=# + ytickwd
6+
function lineplot(K::UnivariateKDE; kws...)
7+
lineplot(K.x, K.f; kws...)
8+
end
209

21-
pl = lineplot(K...; ylim, margin = 0, height = ht, width = wd)
22-
show(io, mime, pl)
23-
return nothing
10+
function lineplot!(plot::Plot, K::UnivariateKDE; kws...)
11+
lineplot!(plot, K.x, K.f; kws...)
2412
end
2513

2614
end

test/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
55
KernelDensityEstimation = "e46ec5f4-66dc-4371-a668-81bd92d19d7d"
66
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
77
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
8+
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
89
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
910
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1011
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

test/extensions.jl

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,56 @@ end
7676
end
7777
end
7878

79-
@moduletestset "UnicodePlots" begin
80-
import UnicodePlots
8179

82-
io = IOBuffer()
83-
ht, wd = 50, 120
84-
ioc = IOContext(io, :displaysize => (ht, wd))
80+
@moduletestset "Plots" begin
81+
using Plots
82+
83+
rv = rand(1000)
84+
K = kde(rv, lo = 0.0, hi = 1.0, boundary = :closed)
85+
86+
# simply test that invocation is not an error
87+
@test plot(K) isa Plots.Plot
88+
@test plot(K, linetype = :steppre) isa Plots.Plot
89+
90+
# default labels are set
91+
p = plot(K)
92+
@test p[1][:xaxis][:guide] == "value"
93+
@test p[1][:yaxis][:guide] == "density"
94+
end
8595

86-
K = kde(collect(0:0.01:1), bandwidth = 0.5, boundary = :closed)
87-
show(ioc, MIME"text/plain"(), K)
88-
str = String(take!(io))
8996

90-
let S = split(str, '\n')
91-
@test all(length(s) <= wd for s in S)
92-
@test length(S) < ht
97+
@moduletestset "UnicodePlots" begin
98+
using UnicodePlots
99+
100+
K = kde(sqrt.(0:0.01:1), bandwidth = 0.5, boundary = :closed)
101+
102+
function termprint(x)
103+
context = (:displaysize => (50, 120), :color => true)
104+
return sprint(x; context) do io, obj
105+
show(io, MIME"text/plain"(), obj)
106+
end
93107
end
94-
@test any(!isascii, str)
108+
109+
# generate an empty plot (to check that plotting is different from)
110+
empty = termprint(Plot(Float64[], Float64[]; xlim = (0, 1), ylim = (0, 3)))
111+
112+
# plot from scratch
113+
p1 = lineplot(K, color = :green)
114+
str1 = termprint(p1)
115+
@test str1 != empty
116+
@test any(!isascii, str1)
117+
118+
# plot into a canvas
119+
p2 = Plot(Float64[], Float64[]; xlim = (0, 1), ylim = (0, 3))
120+
lineplot!(p2, K, color = :green)
121+
str2 = termprint(p2)
122+
@test str1 == str2
123+
# change color and overplot, resulting in different (color!) plot
124+
lineplot!(p2, K, color = :blue)
125+
str3 = termprint(p2)
126+
@test str3 != str2
127+
# but contents are the same if the color information is stripped away
128+
# pattern based on https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
129+
stripcolor(s) = replace(s, r"\e\[(\d+;)*\d*m" => "")
130+
@test stripcolor(str3) == stripcolor(str2)
95131
end

0 commit comments

Comments
 (0)