@@ -8,6 +8,8 @@ Draw a violin plot.
88- `orientation=:vertical`: orientation of the violins (`:vertical` or `:horizontal`)
99- `width=0.8`: width of the violin
1010- `show_median=true`: show median as midline
11+ - `side=:both`: specify `:left` or `:right` to only plot the violin on one side
12+ - `datalimits`: specify values to trim the `violin`. Can be a `Tuple` or a `Function` (e.g. `datalimits=extrema`)
1113"""
1214@recipe (Violin, x, y) do scene
1315 Theme (;
@@ -21,7 +23,8 @@ Draw a violin plot.
2123 n_dodge = automatic,
2224 x_gap = 0.2 ,
2325 dodge_gap = 0.03 ,
24- trim = false ,
26+ datalimits = (- Inf , Inf ),
27+ max_density = automatic,
2528 strokecolor = :white ,
2629 show_median = false ,
2730 mediancolor = automatic,
3134
3235conversion_trait (x:: Type{<:Violin} ) = SampleBased ()
3336
34- function plot! (plot:: Violin )
35- x, y, width, side, show_median = plot[1 ], plot[2 ], plot[:width ], plot[:side ], plot[:show_median ]
36- npoints, boundary, bandwidth = plot[:npoints ], plot[:boundary ], plot[:bandwidth ]
37- dodge, n_dodge, x_gap, dodge_gap = plot[:dodge ], plot[:n_dodge ], plot[:x_gap ], plot[:dodge_gap ]
37+ getuniquevalue (v, idxs) = v
38+
39+ function getuniquevalue (v:: AbstractVector , idxs)
40+ u = view (v, idxs)
41+ f = first (u)
42+ msg = " Collection must have the same value across all indices"
43+ all (isequal (f), u) || throw (ArgumentError (msg))
44+ return f
45+ end
3846
39- signals = lift (x, y, width, dodge, n_dodge, x_gap, dodge_gap, side, show_median, npoints, boundary, bandwidth) do x, y, width, dodge, n_dodge, x_gap, dodge_gap, vside, show_median, n, bound, bw
47+ function plot! (plot:: Violin )
48+ x, y = plot[1 ], plot[2 ]
49+ args = @extract plot (width, side, color, show_median, npoints, boundary, bandwidth,
50+ datalimits, max_density, dodge, n_dodge, x_gap, dodge_gap)
51+ signals = lift (x, y, args... ) do x, y, width, vside, color, show_median, n, bound, bw, limits, max_density, dodge, n_dodge, x_gap, dodge_gap
4052 x̂, violinwidth = xw_from_dodge (x, width, 1 , x_gap, dodge, n_dodge, dodge_gap)
41- vertices = Vector{Point2f0}[]
42- lines = Pair{Point2f0, Point2f0}[]
43- for (key, idxs) in StructArrays. finduniquesorted (x̂)
53+
54+ # Allow `side` to be either scalar or vector
55+ sides = broadcast (x̂, vside) do _, s
56+ return s == :left ? - 1 : s == :right ? 1 : 0
57+ end
58+
59+ sa = StructArray ((x = x̂, side = sides))
60+
61+ specs = map (StructArrays. finduniquesorted (sa)) do (key, idxs)
4462 v = view (y, idxs)
4563 k = KernelDensity. kde (v;
4664 npoints = n,
4765 (bound === automatic ? NamedTuple () : (boundary = bound,)). .. ,
4866 (bw === automatic ? NamedTuple () : (bandwidth = bw,)). .. ,
4967 )
50- spec = (x = key, kde = k, median = median (v))
51- min, max = extrema_nan (spec. kde. density)
68+ l1, l2 = limits isa Function ? limits (v) : limits
69+ i1, i2 = searchsortedfirst (k. x, l1), searchsortedlast (k. x, l2)
70+ kde = (x = view (k. x, i1: i2), density = view (k. density, i1: i2))
71+ c = getuniquevalue (color, idxs)
72+ return (x = key. x, side = key. side, color = to_color (c), kde = kde, median = median (v))
73+ end
74+
75+ max = if max_density === automatic
76+ maximum (specs) do spec
77+ _, max = extrema_nan (spec. kde. density)
78+ return max
79+ end
80+ else
81+ max_density
82+ end
83+
84+ vertices = Vector{Point2f0}[]
85+ lines = Pair{Point2f0, Point2f0}[]
86+ colors = RGBA{Float32}[]
87+
88+ for spec in specs
5289 scale = 0.5 * violinwidth/ max
5390 xl = reverse (spec. x .- spec. kde. density .* scale)
5491 xr = spec. x .+ spec. kde. density .* scale
5592 yl = reverse (spec. kde. x)
5693 yr = spec. kde. x
5794
58- x_coord, y_coord = if vside == : left
95+ x_coord, y_coord = if spec . side == - 1 # left violin
5996 [spec. x; xl; spec. x], [yl[1 ]; yl; yl[end ]]
60- elseif vside == : right
97+ elseif spec . side == 1 # right violin
6198 [spec. x; xr; spec. x], [yr[1 ]; yr; yr[end ]]
6299 else
63100 [spec. x; xr; spec. x; xl], [yr[1 ]; yr; yl[1 ]; yl]
@@ -72,22 +109,30 @@ function plot!(plot::Violin)
72109 ym₋, ym₊ = spec. kde. density[ip- 1 ], spec. kde. density[ip]
73110 xm₋, xm₊ = spec. kde. x[ip- 1 ], spec. kde. x[ip]
74111 ym = (xm * (ym₊ - ym₋) + xm₊ * ym₋ - xm₋ * ym₊) / (xm₊ - xm₋)
75- median_left = Point2f0 (vside == :right ? spec. x : spec. x - ym * scale, xm)
76- median_right = Point2f0 (vside == :left ? spec. x : spec. x + ym * scale, xm)
112+ median_left = Point2f0 (spec . side == 1 ? spec. x : spec. x - ym * scale, xm)
113+ median_right = Point2f0 (spec . side == - 1 ? spec. x : spec. x + ym * scale, xm)
77114 push! (lines, median_left => median_right)
78115 end
116+
117+ push! (colors, spec. color)
79118 end
80- return vertices, lines
119+
120+ return (vertices = vertices, lines = lines, colors = colors)
81121 end
82- t = copy (Theme (plot))
83- mediancolor = pop! (t, :mediancolor )
84- poly! (plot, t, lift (first, signals))
122+
123+ poly! (
124+ plot,
125+ lift (s -> s. vertices, signals),
126+ color = lift (s -> s. colors, signals),
127+ strokecolor = plot[:strokecolor ],
128+ strokewidth = plot[:strokewidth ],
129+ )
85130 linesegments! (
86131 plot,
87- lift (last , signals),
132+ lift (s -> s . lines , signals),
88133 color = lift (
89134 (mc, sc) -> mc === automatic ? sc : mc,
90- mediancolor,
135+ plot[ : mediancolor] ,
91136 plot[:strokecolor ],
92137 ),
93138 linewidth = plot[:medianlinewidth ],
0 commit comments