├── LICENSE
├── README.md
├── Violin.m
├── example.png
├── example2.png
├── readme_figures.m
├── test_cases
└── testviolinplot.m
└── violinplot.m
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016 Bastian Bechtold
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 |
5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 |
7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 |
9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 |
11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Violin Plots for Matlab
2 |
3 | A violin plot is an easy to read substitute for a box plot that
4 | replaces the box shape with a kernel density estimate of the data, and
5 | optionally overlays the data points itself. The original boxplot shape
6 | is still included as a grey box/line in the center of the violin.
7 |
8 | Violin plots are a superset of box plots, and give a much richer
9 | understanding of the data distribution, while not taking more space.
10 | You will be able to instantly spot too-sparse data, or multi-modal
11 | distributions, which could go unnoticed in boxplots.
12 |
13 | `violinplot` is meant as a direct substitute for `boxplot` (excluding
14 | named arguments). Additional constructor parameters include the width
15 | of the plot, the bandwidth of the kernel density estimation, and the
16 | X-axis position of the violin plot.
17 |
18 | For more information about violin plots, read "[Violin plots: a box
19 | plot-density trace synergism](http://www.stat.cmu.edu/~rnugent/PCMI2016/papers/ViolinPlots.pdf)"
20 | by J. L. Hintze and R. D. Nelson in The American Statistician, vol.
21 | 52, no. 2, pp. 181-184, 1998 (DOI: 10.2307/2685478).
22 |
23 | For a simple call:
24 |
25 | ```matlab
26 | load carbig MPG Origin
27 | Origin = cellstr(Origin);
28 | figure
29 | vs = violinplot(MPG, Origin);
30 | ylabel('Fuel Economy in MPG');
31 | xlim([0.5, 7.5]);
32 | ```
33 |
34 | 
35 |
36 |
37 | You can also play around with the different options, and tune your violin plots to your liking.
38 |
39 | ```matlab
40 | grouporder={'England','Sweden','Japan','Italy','Germany','France','USA'};
41 | color = jet(length(grouporder));
42 | pos = 3;
43 | vs = Violin({MPG(strcmp(Origin, grouporder{pos}))},...
44 | pos,...
45 | 'HalfViolin','right',...% left, full
46 | 'QuartileStyle','shadow',... % boxplot, none
47 | 'DataStyle', 'histogram',... % scatter, none
48 | 'ShowNotches', false,...
49 | 'ShowMean', false,...
50 | 'ShowMedian', true,...
51 | 'ViolinColor', {color(pos,:)},...
52 | 'Orientation', 'horizontal');
53 | ylim(pos + [-.5, 0.5])
54 | yticks(pos)
55 | yticklabels(grouporder{pos})
56 | ```
57 | 
58 |
59 | ## Citation
60 |
61 | [](https://zenodo.org/badge/latestdoi/60771923)
62 |
63 | If you want to cite this repository, use
64 |
65 | > Bechtold, Bastian, 2016. Violin Plots for Matlab, Github Project
66 | > https://github.com/bastibe/Violinplot-Matlab, DOI: 10.5281/zenodo.4559847
67 |
--------------------------------------------------------------------------------
/Violin.m:
--------------------------------------------------------------------------------
1 | classdef Violin < handle
2 | % Violin creates violin plots for some data
3 | % A violin plot is an easy to read substitute for a box plot
4 | % that replaces the box shape with a kernel density estimate of
5 | % the data, and optionally overlays the data points itself.
6 | % It is also possible to provide two sets of data which are supposed
7 | % to be compared by plotting each column of the two datasets together
8 | % on each side of the violin.
9 | %
10 | % Additional constructor parameters include the width of the
11 | % plot, the bandwidth of the kernel density estimation, the
12 | % X-axis position of the violin plot, and the categories.
13 | %
14 | % Use violinplot for a
15 | % boxplot-like wrapper for
16 | % interactive plotting.
17 | %
18 | % See for more information on Violin Plots:
19 | % J. L. Hintze and R. D. Nelson, "Violin plots: a box
20 | % plot-density trace synergism," The American Statistician, vol.
21 | % 52, no. 2, pp. 181-184, 1998.
22 | %
23 | % Violin Properties:
24 | % ViolinColor - Fill color of the violin area and data points.
25 | % Can be either a matrix nx3 or an array of up to two
26 | % cells containing nx3 matrices.
27 | % Defaults to the next default color cycle.
28 | % ViolinAlpha - Transparency of the violin area and data points.
29 | % Can be either a single scalar value or an array of
30 | % up to two cells containing scalar values.
31 | % Defaults to 0.3.
32 | % EdgeColor - Color of the violin area outline.
33 | % Defaults to [0.5 0.5 0.5]
34 | % BoxColor - Color of the box, whiskers, and the outlines of
35 | % the median point and the notch indicators.
36 | % Defaults to [0.5 0.5 0.5]
37 | % MedianColor - Fill color of the median and notch indicators.
38 | % Defaults to [1 1 1]
39 | % ShowData - Whether to show data points.
40 | % Defaults to true
41 | % ShowNotches - Whether to show notch indicators.
42 | % Defaults to false
43 | % ShowMean - Whether to show mean indicator.
44 | % Defaults to false
45 | % ShowBox - Whether to show the box.
46 | % Defaults to true
47 | % ShowMedian - Whether to show the median indicator.
48 | % Defaults to true
49 | % ShowWhiskers - Whether to show the whiskers
50 | % Defaults to true
51 | % HalfViolin - Whether to do a half violin(left, right side) or
52 | % full. Defaults to full.
53 | % QuartileStyle - Option on how to display quartiles, with a
54 | % boxplot, shadow or none. Defaults to boxplot.
55 | % DataStyle - Defines the style to show the data points. Opts:
56 | % 'scatter', 'histogram' or 'none'. Default is 'scatter'.
57 | % Orientation - Defines the orientation of the violin plot. Opts:
58 | % 'vertical', 'horizontal'. Default is 'vertical'.
59 | % Parent - The parent axis of the violin plot.
60 | %
61 | %
62 | % Violin Children:
63 | % ScatterPlot - scatter plot of the data points
64 | % ScatterPlot2 - scatter second plot of the data points
65 | % ViolinPlot - fill plot of the kernel density estimate
66 | % ViolinPlot2 - fill second plot of the kernel density estimate
67 | % BoxPlot - fill plot of the box between the quartiles
68 | % WhiskerPlot - line plot between the whisker ends
69 | % MedianPlot - scatter plot of the median (one point)
70 | % NotchPlots - scatter plots for the notch indicators
71 | % MeanPlot - line plot at mean value
72 |
73 |
74 | % Copyright (c) 2016, Bastian Bechtold
75 | % This code is released under the terms of the BSD 3-clause license
76 |
77 | properties (Access=public)
78 | ScatterPlot % scatter plot of the data points
79 | ScatterPlot2 % comparison scatter plot of the data points
80 | ViolinPlot % fill plot of the kernel density estimate
81 | ViolinPlot2 % comparison fill plot of the kernel density estimate
82 | BoxPlot % fill plot of the box between the quartiles
83 | WhiskerPlot % line plot between the whisker ends
84 | MedianPlot % scatter plot of the median (one point)
85 | NotchPlots % scatter plots for the notch indicators
86 | MeanPlot % line plot of the mean (horizontal line)
87 | HistogramPlot % histogram of the data
88 | ViolinPlotQ % fill plot of the Quartiles as shadow
89 | Parent % parent axis
90 | end
91 |
92 | properties (SetAccess=protected, GetAccess=public)
93 | Orientation % 'horizontal' or 'vertical'
94 | end
95 |
96 | properties (Dependent=true)
97 | ViolinColor % fill color of the violin area and data points
98 | ViolinAlpha % transparency of the violin area and data points
99 | MarkerSize % marker size for the data dots
100 | MedianMarkerSize % marker size for the median dot
101 | LineWidth % linewidth of the median plot
102 | EdgeColor % color of the violin area outline
103 | BoxColor % color of box, whiskers, and median/notch edges
104 | BoxWidth % width of box between the quartiles in axis space (default 10% of Violin plot width, 0.03)
105 | MedianColor % fill color of median and notches
106 | ShowData % whether to show data points
107 | ShowNotches % whether to show notch indicators
108 | ShowMean % whether to show mean indicator
109 | ShowBox % whether to show the box
110 | ShowMedian % whether to show the median line
111 | ShowWhiskers % whether to show the whiskers
112 | HalfViolin % whether to do a half violin(left, right side) or full
113 | end
114 |
115 | methods
116 | function obj = Violin(data, pos, varargin)
117 | %Violin plots a violin plot of some data at pos
118 | % VIOLIN(DATA, POS) plots a violin at x-position POS for
119 | % a vector of DATA points.
120 | %
121 | % VIOLIN(..., 'PARAM1', val1, 'PARAM2', val2, ...)
122 | % specifies optional name/value pairs:
123 | % 'Width' Width of the violin in axis space.
124 | % Defaults to 0.3
125 | % 'Bandwidth' Bandwidth of the kernel density
126 | % estimate. Should be between 10% and
127 | % 40% of the data range.
128 | % 'ViolinColor' Fill color of the violin area
129 | % and data points.Can be either a matrix
130 | % nx3 or an array of up to two cells
131 | % containing nx3 matrices.
132 | % 'ViolinAlpha' Transparency of the violin area and data
133 | % points. Can be either a single scalar
134 | % value or an array of up to two cells
135 | % containing scalar values. Defaults to 0.3.
136 | % 'MarkerSize' Size of the data points, if shown.
137 | % Defaults to 24
138 | % 'MedianMarkerSize' Size of the median indicator, if shown.
139 | % Defaults to 36
140 | % 'EdgeColor' Color of the violin area outline.
141 | % Defaults to [0.5 0.5 0.5]
142 | % 'BoxColor' Color of the box, whiskers, and the
143 | % outlines of the median point and the
144 | % notch indicators. Defaults to
145 | % [0.5 0.5 0.5]
146 | % 'MedianColor' Fill color of the median and notch
147 | % indicators. Defaults to [1 1 1]
148 | % 'ShowData' Whether to show data points.
149 | % Defaults to true
150 | % 'ShowNotches' Whether to show notch indicators.
151 | % Defaults to false
152 | % 'ShowMean' Whether to show mean indicator.
153 | % Defaults to false
154 | % 'ShowBox' Whether to show the box
155 | % Defaults to true
156 | % 'ShowMedian' Whether to show the median line
157 | % Defaults to true
158 | % 'ShowWhiskers' Whether to show the whiskers
159 | % Defaults to true
160 | % 'HalfViolin' Whether to do a half violin(left, right side) or
161 | % full. Defaults to full.
162 | % 'QuartileStyle' Option on how to display quartiles, with a
163 | % boxplot or as a shadow. Defaults to boxplot.
164 | % 'DataStyle' Defines the style to show the data points. Opts:
165 | % 'scatter', 'histogram' or 'none'. Default is 'Scatter'.
166 | % 'Orientation' Defines the orientation of the violin plot. Opts:
167 | % 'vertical', 'horizontal'. Default is 'vertical'.
168 | % 'Parent' The parent axis of the violin plot.
169 |
170 | st = dbstack; % get the calling function for reporting errors
171 | namefun = st.name;
172 | args = obj.checkInputs(data, pos, varargin{:});
173 | obj.Orientation = args.Orientation;
174 | obj.Parent = args.Parent;
175 |
176 | if length(data)==1
177 | data2 = [];
178 | data = data{1};
179 |
180 | else
181 | data2 = data{2};
182 | data = data{1};
183 | end
184 |
185 | if isempty(args.ViolinColor)
186 | Release= strsplit(version('-release'), {'a','b'}); %Check release
187 | if str2num(Release{1})> 2019 || strcmp(version('-release'), '2019b')
188 | C = colororder;
189 | else
190 | C = lines;
191 | end
192 |
193 | if pos > length(C)
194 | C = lines;
195 | end
196 | args.ViolinColor = {repmat(C,ceil(size(data,2)/length(C)),1)};
197 | end
198 |
199 | data = data(not(isnan(data)));
200 | data2 = data2(not(isnan(data2)));
201 | if numel(data) == 1
202 | [x, y] = obj.swapOrientationMaybe(pos, data);
203 | obj.MedianPlot = scatter(x, y, 'filled', 'Parent', obj.Parent);
204 | obj.MedianColor = args.MedianColor;
205 | obj.MedianPlot.MarkerEdgeColor = args.EdgeColor;
206 | return
207 | end
208 |
209 | hold(obj.Parent, 'on');
210 |
211 |
212 | %% Calculate kernel density estimation for the violin
213 | [density, value, width] = obj.calcKernelDensity(data, args.Bandwidth, args.Width);
214 |
215 | % also calculate the kernel density of the comparison data if
216 | % provided
217 | if ~isempty(data2)
218 | [densityC, valueC, widthC] = obj.calcKernelDensity(data2, args.Bandwidth, args.Width);
219 | end
220 |
221 | %% Plot the data points within the violin area
222 | if length(density) > 1
223 | [~, unique_idx] = unique(value);
224 | jitterstrength = interp1(value(unique_idx), density(unique_idx)*width, data, 'linear','extrap');
225 | else % all data is identical:
226 | jitterstrength = density*width;
227 | end
228 | if isempty(data2) % if no comparison data
229 | jitter = 2*(rand(size(data))-0.5); % both sides
230 | else
231 | jitter = rand(size(data)); % only right side
232 | end
233 | switch args.HalfViolin % this is more modular
234 | case 'left'
235 | jitter = -1*(rand(size(data))); %left
236 | case 'right'
237 | jitter = 1*(rand(size(data))); %right
238 | case 'full'
239 | jitter = 2*(rand(size(data))-0.5);
240 | end
241 | % Make scatter plot
242 | switch args.DataStyle
243 | case 'scatter'
244 | if ~isempty(data2)
245 | jitter = 1*(rand(size(data))); %right
246 | [x, y] = obj.swapOrientationMaybe(pos + jitter.*jitterstrength, data);
247 | obj.ScatterPlot = ...
248 | scatter(x, y, args.MarkerSize, 'filled', 'Parent', obj.Parent);
249 | % plot the data points within the violin area
250 | if length(densityC) > 1
251 | jitterstrength = interp1(valueC, densityC*widthC, data2);
252 | else % all data is identical:
253 | jitterstrength = densityC*widthC;
254 | end
255 | jitter = -1*rand(size(data2));% left
256 | [x, y] = obj.swapOrientationMaybe(pos + jitter.*jitterstrength, data2);
257 | obj.ScatterPlot2 = ...
258 | scatter(x, y, args.MarkerSize, 'filled', 'Parent', obj.Parent);
259 | else
260 | [x, y] = obj.swapOrientationMaybe(pos + jitter.*jitterstrength, data);
261 | obj.ScatterPlot = ...
262 | scatter(x, y, args.MarkerSize, 'filled', 'Parent', obj.Parent);
263 |
264 | end
265 | case 'histogram'
266 | [counts,edges] = histcounts(data, size(unique(data),1));
267 | switch args.HalfViolin
268 | case 'right'
269 | [x, y] = obj.swapOrientationMaybe([pos-((counts')/max(counts))*max(jitterstrength)*2, pos*ones(size(counts,2),1)]', ...
270 | [edges(1:end-1)+max(diff(edges))/2; edges(1:end-1)+max(diff(edges))/2]);
271 | obj.HistogramPlot = plot(x,y,'-','LineWidth',1, 'Color', 'k', 'Parent', obj.Parent);
272 | case 'left'
273 | [x, y] = obj.swapOrientationMaybe([pos*ones(size(counts,2),1), pos+((counts')/max(counts))*max(jitterstrength)*2]', ...
274 | [edges(1:end-1)+max(diff(edges))/2; edges(1:end-1)+max(diff(edges))/2]);
275 | obj.HistogramPlot = plot(x,y,'-','LineWidth',1, 'Color', 'k', 'Parent', obj.Parent);
276 | otherwise
277 | fprintf([namefun, ' No histogram/bar plot option available for full violins, as it would look overcrowded.\n'])
278 | end
279 | case 'none'
280 | end
281 |
282 | %% Plot the violin
283 | halfViol= ones(1, size(density,2));
284 | if isempty(data2) % if no comparison data
285 | switch args.HalfViolin
286 | case 'right'
287 | [x,y ] = obj.swapOrientationMaybe([pos+density*width halfViol*pos], ...
288 | [value value(end:-1:1)]);
289 | obj.ViolinPlot = fill(x,y, [1 1 1], 'Parent', obj.Parent); % plot color will be overwritten later
290 | case 'left'
291 | [x,y] = obj.swapOrientationMaybe([halfViol*pos pos-density(end:-1:1)*width], ...
292 | [value value(end:-1:1)]);
293 | obj.ViolinPlot = fill(x,y, [1 1 1], 'Parent', obj.Parent); % plot color will be overwritten later
294 | case 'full'
295 | [x, y] = obj.swapOrientationMaybe([pos+density*width pos-density(end:-1:1)*width], ...
296 | [value value(end:-1:1)]);
297 | obj.ViolinPlot = fill(x,y, [1 1 1], 'Parent', obj.Parent); % plot color will be overwritten later
298 | end
299 | else
300 | % plot right half of the violin
301 | [x, y] = obj.swapOrientationMaybe([pos+density*width pos-density(1)*width], [value value(1)]);
302 | obj.ViolinPlot = fill(x ,y, [1 1 1], 'Parent', obj.Parent);
303 | % plot left half of the violin
304 | [x, y] = obj.swapOrientationMaybe([pos-densityC(end)*widthC pos-densityC(end:-1:1)*widthC], ...
305 | [valueC(end) valueC(end:-1:1)]);
306 | obj.ViolinPlot2 = fill(x, y, [1 1 1], 'Parent', obj.Parent);
307 | end
308 |
309 | %% Plot the quartiles within the violin
310 | quartiles = quantile(data, [0.25, 0.5, 0.75]);
311 | flat= [halfViol*pos halfViol*pos];
312 | switch args.QuartileStyle
313 | case 'shadow'
314 | switch args.HalfViolin
315 | case 'right'
316 | w = [pos+density*width halfViol*pos];
317 | h = [value value(end:-1:1)];
318 | case 'left'
319 | w = [halfViol*pos pos-density(end:-1:1)*width];
320 | h = [value value(end:-1:1)];
321 | case 'full'
322 | w = [pos+density*width pos-density(end:-1:1)*width];
323 | h = [value value(end:-1:1)];
324 | end
325 | indices = h >= quartiles(1) & h <= quartiles(3);
326 | [x, y] = obj.swapOrientationMaybe(w(indices), h(indices));
327 | obj.ViolinPlotQ = fill(x, y, [1 1 1], 'Parent', obj.Parent); % plot color will be overwritten later
328 | case 'boxplot'
329 | [x, y] = obj.swapOrientationMaybe(pos+[-1,1,1,-1]*args.BoxWidth, ...
330 | [quartiles(1) quartiles(1) quartiles(3) quartiles(3)]);
331 | obj.BoxPlot = fill(x, y, [1 1 1], 'Parent', obj.Parent); % plot color will be overwritten later
332 | case 'none'
333 | end
334 |
335 | %% Plot the data mean
336 | meanValue = mean(data);
337 | if length(density) > 1
338 | [~, unique_idx] = unique(value);
339 | meanDensityWidth = interp1(value(unique_idx), density(unique_idx), meanValue, 'linear','extrap')*width;
340 | else % all data is identical:
341 | meanDensityWidth = density*width;
342 | end
343 | if meanDensityWidth lowhisker)));
363 | hiwhisker = quartiles(3) + 1.5*IQR;
364 | hiwhisker = min(hiwhisker, max(data(data < hiwhisker)));
365 | if ~isempty(lowhisker) && ~isempty(hiwhisker)
366 | [x, y] = obj.swapOrientationMaybe([pos pos], [lowhisker hiwhisker]);
367 | obj.WhiskerPlot = plot(x, y, 'Parent', obj.Parent);
368 | end
369 |
370 | % Median
371 | [x, y] = obj.swapOrientationMaybe(pos, quartiles(2));
372 | obj.MedianPlot = scatter(x, y, ...
373 | args.MedianMarkerSize, [1 1 1], 'filled', 'Parent', obj.Parent);
374 |
375 | % Notches
376 | [x, y] = obj.swapOrientationMaybe(pos, quartiles(2)-1.57*IQR/sqrt(length(data)));
377 | obj.NotchPlots = scatter(x, y, [], [1 1 1], 'filled', '^', 'Parent', obj.Parent);
378 | [x, y] = obj.swapOrientationMaybe(pos, quartiles(2)+1.57*IQR/sqrt(length(data)));
379 | obj.NotchPlots(2) = scatter(x, y, [], [1 1 1], 'filled', 'v', 'Parent', obj.Parent);
380 |
381 | %% Set graphical preferences
382 | obj.EdgeColor = args.EdgeColor;
383 | obj.MedianPlot.LineWidth = args.LineWidth;
384 | obj.BoxColor = args.BoxColor;
385 | obj.BoxWidth = args.BoxWidth;
386 | obj.MedianColor = args.MedianColor;
387 | obj.ShowData = args.ShowData;
388 | obj.ShowNotches = args.ShowNotches;
389 | obj.ShowMean = args.ShowMean;
390 | obj.ShowBox = args.ShowBox;
391 | obj.ShowMedian = args.ShowMedian;
392 | obj.ShowWhiskers = args.ShowWhiskers;
393 |
394 | if not(isempty(args.ViolinColor))
395 | if size(args.ViolinColor{1},1) > 1
396 | ViolinColor{1} = args.ViolinColor{1}(pos,:);
397 | else
398 | ViolinColor{1} = args.ViolinColor{1};
399 | end
400 | if length(args.ViolinColor)==2
401 | if size(args.ViolinColor{2},1) > 1
402 | ViolinColor{2} = args.ViolinColor{2}(pos,:);
403 | else
404 | ViolinColor{2} = args.ViolinColor{2};
405 | end
406 | else
407 | ViolinColor{2} = ViolinColor{1};
408 | end
409 | else
410 | % defaults
411 | if args.scpltBool
412 | ViolinColor{1} = obj.ScatterPlot.CData;
413 | else
414 | ViolinColor{1} = [0 0 0];
415 | end
416 | ViolinColor{2} = [0 0 0];
417 | end
418 | obj.ViolinColor = ViolinColor;
419 |
420 |
421 | if not(isempty(args.ViolinAlpha))
422 | if length(args.ViolinAlpha{1})>1
423 | error('Only scalar values are accepted for the alpha color channel');
424 | else
425 | ViolinAlpha{1} = args.ViolinAlpha{1};
426 | end
427 | if length(args.ViolinAlpha)==2
428 | if length(args.ViolinAlpha{2})>1
429 | error('Only scalar values are accepted for the alpha color channel');
430 | else
431 | ViolinAlpha{2} = args.ViolinAlpha{2};
432 | end
433 | else
434 | ViolinAlpha{2} = ViolinAlpha{1}/2; % default unless specified
435 | end
436 | else
437 | % default
438 | ViolinAlpha = {1,1};
439 | end
440 | obj.ViolinAlpha = ViolinAlpha;
441 |
442 |
443 | set(obj.ViolinPlot, 'Marker', 'none', 'LineStyle', '-');
444 | set(obj.ViolinPlot2, 'Marker', 'none', 'LineStyle', '-');
445 | end
446 |
447 | %% SET METHODS
448 | function set.EdgeColor(obj, color)
449 | if ~isempty(obj.ViolinPlot)
450 | obj.ViolinPlot.EdgeColor = color;
451 | obj.ViolinPlotQ.EdgeColor = color;
452 | if ~isempty(obj.ViolinPlot2)
453 | obj.ViolinPlot2.EdgeColor = color;
454 | end
455 | end
456 | end
457 |
458 | function color = get.EdgeColor(obj)
459 | if ~isempty(obj.ViolinPlot)
460 | color = obj.ViolinPlot.EdgeColor;
461 | end
462 | end
463 |
464 |
465 | function set.MedianColor(obj, color)
466 | obj.MedianPlot.MarkerFaceColor = color;
467 | if ~isempty(obj.NotchPlots)
468 | obj.NotchPlots(1).MarkerFaceColor = color;
469 | obj.NotchPlots(2).MarkerFaceColor = color;
470 | end
471 | end
472 |
473 | function color = get.MedianColor(obj)
474 | color = obj.MedianPlot.MarkerFaceColor;
475 | end
476 |
477 |
478 | function set.BoxColor(obj, color)
479 | if ~isempty(obj.BoxPlot)
480 | obj.BoxPlot.FaceColor = color;
481 | obj.BoxPlot.EdgeColor = color;
482 | obj.WhiskerPlot.Color = color;
483 | obj.MedianPlot.MarkerEdgeColor = color;
484 | obj.NotchPlots(1).MarkerFaceColor = color;
485 | obj.NotchPlots(2).MarkerFaceColor = color;
486 | elseif ~isempty(obj.ViolinPlotQ)
487 | obj.WhiskerPlot.Color = color;
488 | obj.MedianPlot.MarkerEdgeColor = color;
489 | obj.NotchPlots(1).MarkerFaceColor = color;
490 | obj.NotchPlots(2).MarkerFaceColor = color;
491 | end
492 | end
493 |
494 | function color = get.BoxColor(obj)
495 | if ~isempty(obj.BoxPlot)
496 | color = obj.BoxPlot.FaceColor;
497 | end
498 | end
499 |
500 |
501 | function set.BoxWidth(obj,width)
502 | if ~isempty(obj.BoxPlot)
503 | pos=mean(obj.BoxPlot.XData);
504 | obj.BoxPlot.XData=pos+[-1,1,1,-1]*width;
505 | end
506 | end
507 |
508 | function width = get.BoxWidth(obj)
509 | width=max(obj.BoxPlot.XData)-min(obj.BoxPlot.XData);
510 | end
511 |
512 |
513 | function set.ViolinColor(obj, color)
514 | obj.ViolinPlot.FaceColor = color{1};
515 | obj.ScatterPlot.MarkerFaceColor = color{1};
516 | obj.MeanPlot.Color = color{1};
517 | if ~isempty(obj.ViolinPlot2)
518 | obj.ViolinPlot2.FaceColor = color{2};
519 | obj.ScatterPlot2.MarkerFaceColor = color{2};
520 | end
521 | if ~isempty(obj.ViolinPlotQ)
522 | obj.ViolinPlotQ.FaceColor = color{1};
523 | end
524 | for idx = 1: size(obj.HistogramPlot,1)
525 | obj.HistogramPlot(idx).Color = color{1};
526 | end
527 | end
528 |
529 | function color = get.ViolinColor(obj)
530 | color{1} = obj.ViolinPlot.FaceColor;
531 | if ~isempty(obj.ViolinPlot2)
532 | color{2} = obj.ViolinPlot2.FaceColor;
533 | end
534 | end
535 |
536 |
537 | function set.ViolinAlpha(obj, alpha)
538 | obj.ViolinPlotQ.FaceAlpha = .65;
539 | obj.ViolinPlot.FaceAlpha = alpha{1};
540 | obj.ScatterPlot.MarkerFaceAlpha = 1;
541 | if ~isempty(obj.ViolinPlot2)
542 | obj.ViolinPlot2.FaceAlpha = alpha{2};
543 | obj.ScatterPlot2.MarkerFaceAlpha = 1;
544 | end
545 | end
546 |
547 | function alpha = get.ViolinAlpha(obj)
548 | alpha{1} = obj.ViolinPlot.FaceAlpha;
549 | if ~isempty(obj.ViolinPlot2)
550 | alpha{2} = obj.ViolinPlot2.FaceAlpha;
551 | end
552 | end
553 |
554 |
555 | function set.ShowData(obj, yesno)
556 | if yesno
557 | obj.ScatterPlot.Visible = 'on';
558 | for idx = 1: size(obj.HistogramPlot,1)
559 | obj.HistogramPlot(idx).Visible = 'on';
560 | end
561 | else
562 | obj.ScatterPlot.Visible = 'off';
563 | for idx = 1: size(obj.HistogramPlot,1)
564 | obj.HistogramPlot(idx).Visible = 'off';
565 | end
566 | end
567 | if ~isempty(obj.ScatterPlot2)
568 | obj.ScatterPlot2.Visible = obj.ScatterPlot.Visible;
569 | end
570 |
571 | end
572 |
573 | function yesno = get.ShowData(obj)
574 | if ~isempty(obj.ScatterPlot)
575 | yesno = strcmp(obj.ScatterPlot.Visible, 'on');
576 | end
577 | end
578 |
579 |
580 | function set.ShowNotches(obj, yesno)
581 | if ~isempty(obj.NotchPlots)
582 | if yesno
583 | obj.NotchPlots(1).Visible = 'on';
584 | obj.NotchPlots(2).Visible = 'on';
585 | else
586 | obj.NotchPlots(1).Visible = 'off';
587 | obj.NotchPlots(2).Visible = 'off';
588 | end
589 | end
590 | end
591 |
592 | function yesno = get.ShowNotches(obj)
593 | if ~isempty(obj.NotchPlots)
594 | yesno = strcmp(obj.NotchPlots(1).Visible, 'on');
595 | end
596 | end
597 |
598 |
599 | function set.ShowMean(obj, yesno)
600 | if ~isempty(obj.MeanPlot)
601 | if yesno
602 | obj.MeanPlot.Visible = 'on';
603 | else
604 | obj.MeanPlot.Visible = 'off';
605 | end
606 | end
607 | end
608 |
609 | function yesno = get.ShowMean(obj)
610 | if ~isempty(obj.BoxPlot)
611 | yesno = strcmp(obj.BoxPlot.Visible, 'on');
612 | end
613 | end
614 |
615 |
616 | function set.ShowBox(obj, yesno)
617 | if ~isempty(obj.BoxPlot)
618 | if yesno
619 | obj.BoxPlot.Visible = 'on';
620 | else
621 | obj.BoxPlot.Visible = 'off';
622 | end
623 | end
624 | end
625 |
626 | function yesno = get.ShowBox(obj)
627 | if ~isempty(obj.BoxPlot)
628 | yesno = strcmp(obj.BoxPlot.Visible, 'on');
629 | end
630 | end
631 |
632 |
633 | function set.ShowMedian(obj, yesno)
634 | if ~isempty(obj.MedianPlot)
635 | if yesno
636 | obj.MedianPlot.Visible = 'on';
637 | else
638 | obj.MedianPlot.Visible = 'off';
639 | end
640 | end
641 | end
642 |
643 | function yesno = get.ShowMedian(obj)
644 | if ~isempty(obj.MedianPlot)
645 | yesno = strcmp(obj.MedianPlot.Visible, 'on');
646 | end
647 | end
648 |
649 |
650 | function set.ShowWhiskers(obj, yesno)
651 | if ~isempty(obj.WhiskerPlot)
652 | if yesno
653 | obj.WhiskerPlot.Visible = 'on';
654 | else
655 | obj.WhiskerPlot.Visible = 'off';
656 | end
657 | end
658 | end
659 |
660 | function yesno = get.ShowWhiskers(obj)
661 | if ~isempty(obj.WhiskerPlot)
662 | yesno = strcmp(obj.WhiskerPlot.Visible, 'on');
663 | end
664 | end
665 |
666 | end
667 |
668 | methods (Access=private)
669 | function results = checkInputs(~, data, pos, varargin)
670 | isscalarnumber = @(x) (isnumeric(x) & isscalar(x));
671 | p = inputParser();
672 | p.addRequired('Data', @(x)isnumeric(vertcat(x{:})));
673 | p.addRequired('Pos', isscalarnumber);
674 | p.addParameter('Width', 0.3, isscalarnumber);
675 | p.addParameter('Bandwidth', [], isscalarnumber);
676 | iscolor = @(x) (isnumeric(x) & size(x,2) == 3);
677 | p.addParameter('ViolinColor', [], @(x)iscolor(vertcat(x{:})));
678 | p.addParameter('MarkerSize', 24, @isnumeric);
679 | p.addParameter('MedianMarkerSize', 36, @isnumeric);
680 | p.addParameter('LineWidth', 0.75, @isnumeric);
681 | p.addParameter('BoxColor', [0.5 0.5 0.5], iscolor);
682 | p.addParameter('BoxWidth', 0.01, isscalarnumber);
683 | p.addParameter('EdgeColor', [0.5 0.5 0.5], iscolor);
684 | p.addParameter('MedianColor', [1 1 1], iscolor);
685 | p.addParameter('ViolinAlpha', {0.3,0.15}, @(x)isnumeric(vertcat(x{:})));
686 | isscalarlogical = @(x) (islogical(x) & isscalar(x));
687 | p.addParameter('ShowData', true, isscalarlogical);
688 | p.addParameter('ShowNotches', false, isscalarlogical);
689 | p.addParameter('ShowMean', false, isscalarlogical);
690 | p.addParameter('ShowBox', true, isscalarlogical);
691 | p.addParameter('ShowMedian', true, isscalarlogical);
692 | p.addParameter('ShowWhiskers', true, isscalarlogical);
693 | validSides={'full', 'right', 'left'};
694 | checkSide = @(x) any(validatestring(x, validSides));
695 | p.addParameter('HalfViolin', 'full', checkSide);
696 | validQuartileStyles={'boxplot', 'shadow', 'none'};
697 | checkQuartile = @(x)any(validatestring(x, validQuartileStyles));
698 | p.addParameter('QuartileStyle', 'boxplot', checkQuartile);
699 | validDataStyles = {'scatter', 'histogram', 'none'};
700 | checkStyle = @(x)any(validatestring(x, validDataStyles));
701 | p.addParameter('DataStyle', 'scatter', checkStyle);
702 | p.addParameter('Orientation', 'vertical', @(x) ismember(x, {'vertical', 'horizontal'}));
703 | p.addParameter('Parent', gca, @(x) isa(x,'matlab.graphics.axis.Axes'));
704 |
705 | p.parse(data, pos, varargin{:});
706 | results = p.Results;
707 | end
708 |
709 | function [x, y] = swapOrientationMaybe(obj, x, y)
710 | %swapOrientationMaybe swaps the two variables x and y
711 | % if Violin.Orientation property set to horizontal.
712 | % If orientation is vertical, it returns x and y as is.
713 | if strcmp(obj.Orientation, 'horizontal')
714 | tmp = x;
715 | x = y;
716 | y = tmp;
717 | end
718 | end
719 | end
720 |
721 | methods (Static)
722 | function [density, value, width] = calcKernelDensity(data, bandwidth, width)
723 | if isempty(data)
724 | error('Empty input data');
725 | end
726 | [density, value] = ksdensity(data, 'bandwidth', bandwidth);
727 | density = density(value >= min(data) & value <= max(data));
728 | value = value(value >= min(data) & value <= max(data));
729 | value(1) = min(data);
730 | value(end) = max(data);
731 | value = [value(1)*(1-1E-5), value, value(end)*(1+1E-5)];
732 | density = [0, density, 0];
733 |
734 | % all data is identical
735 | if min(data) == max(data)
736 | density = 1;
737 | value= mean(value);
738 | end
739 |
740 | width = width/max(density);
741 | end
742 | end
743 | end
744 |
745 |
--------------------------------------------------------------------------------
/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bastibe/Violinplot-Matlab/ada518615b4a229ea6f444df4796b1387aeaaf42/example.png
--------------------------------------------------------------------------------
/example2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bastibe/Violinplot-Matlab/ada518615b4a229ea6f444df4796b1387aeaaf42/example2.png
--------------------------------------------------------------------------------
/readme_figures.m:
--------------------------------------------------------------------------------
1 | clearvars
2 | close all
3 | clc
4 |
5 | %% figure 1
6 |
7 | load carbig MPG Origin
8 | Origin = cellstr(Origin);
9 | figure
10 | vs = violinplot(MPG, Origin);
11 | ylabel('Fuel Economy in MPG');
12 | xlim([0.5, 7.5]);
13 |
14 | set(gcf,'Units','pixels','Position',[100 100 560 420])
15 |
16 | % save results
17 | exportgraphics(gcf,'example.png')
18 |
19 | %% figure 2
20 | close all
21 | clear vs
22 |
23 | C = colormap('jet');
24 |
25 | grouporder={'England','Sweden','Japan','Italy','Germany','France','USA'};
26 |
27 | % England (just a dot)
28 | vs = Violin({MPG(strcmp(Origin,'England'))},1,'Orientation','horizontal');
29 |
30 | % Sweden (different Bandwidths)
31 | vs = Violin({MPG(strcmp(Origin,'Sweden'))},2,...
32 | 'HalfViolin','left',...
33 | 'Bandwidth',3,...
34 | 'Orientation','horizontal');
35 |
36 | vs = Violin({MPG(strcmp(Origin,'Sweden'))},2,...
37 | 'HalfViolin','right',...
38 | 'Bandwidth',12,...
39 | 'Orientation','horizontal');
40 |
41 | % Japan (show quartiles)
42 | vs = Violin({MPG(strcmp(Origin,'Japan'))},3,...
43 | 'QuartileStyle','shadow',... % boxplot, none
44 | 'ShowBox',true,...
45 | 'Orientation','horizontal');
46 |
47 | % Italy left: standard
48 | vs = Violin({MPG(strcmp(Origin,'Italy'))},4,...
49 | 'QuartileStyle','shadow',... % boxplot, none
50 | 'ShowBox',true,...
51 | 'HalfViolin','left',...
52 | 'Orientation','horizontal');
53 |
54 | % Italy right: change color, offset
55 | vs = Violin({MPG(strcmp(Origin,'Italy'))-15},4,...
56 | 'QuartileStyle','shadow',... % boxplot, none
57 | 'ShowBox',true,...
58 | 'HalfViolin','right',...
59 | 'ViolinColor',{C(7,:)},...
60 | 'Orientation','horizontal');
61 |
62 |
63 | % Germany left: standard
64 | vs = Violin({MPG(strcmp(Origin,'Germany'))},5,...
65 | 'QuartileStyle','none',... % boxplot, none
66 | 'DataStyle', 'none',... % scatter, histogram
67 | 'ShowMean',true,...
68 | 'HalfViolin','left',...
69 | 'Orientation','horizontal');
70 |
71 | % Germany histogram
72 | vs = Violin({MPG(strcmp(Origin,'Germany'))},5,...
73 | 'QuartileStyle','none',... % boxplot, none
74 | 'DataStyle', 'histogram',... % scatter, histogram
75 | 'ShowMean',true,...
76 | 'HalfViolin','left',...
77 | 'Orientation','horizontal');
78 |
79 | % France histogram
80 | vs = Violin({MPG(strcmp(Origin,'France'))},6,...
81 | 'QuartileStyle','none',... % boxplot, none
82 | 'DataStyle', 'histogram',... % scatter, histogram
83 | 'ShowMean',false,...
84 | 'HalfViolin','right',...
85 | 'Orientation','horizontal');
86 |
87 | % USA histogram and quartiles
88 | vs = Violin({MPG(strcmp(Origin,'USA'))},7,...
89 | 'QuartileStyle','shadow',... % boxplot, none
90 | 'DataStyle', 'histogram',... % scatter, histogram
91 | 'ShowMean',false,...
92 | 'HalfViolin','right',...
93 | 'Orientation','horizontal');
94 |
95 | % set correct labels
96 | set(gca,yticklabels=grouporder)
97 |
98 | xlabel('Fuel Economy in MPG');
99 | ylim([0.5, 7.5]);
100 |
101 | set(gcf,'Units','pixels','Position',[200 200 560 420])
102 |
103 | % save results
104 | exportgraphics(gcf,'example2.png')
--------------------------------------------------------------------------------
/test_cases/testviolinplot.m:
--------------------------------------------------------------------------------
1 | function testviolinplot()
2 | figure();
3 | % One could use tiled layout for better plotting them but it would be
4 | % incompatible with older versions
5 | subplot(2,4,1);
6 |
7 | % TEST CASE 1
8 | disp('Test 1: Violin plot default options');
9 | load carbig MPG Origin
10 | Origin = cellstr(Origin);
11 | vs = violinplot(MPG, Origin);
12 | plotdetails(1);
13 |
14 | % TEST CASE 2
15 | disp('Test 2: Test the plot ordering option');
16 | grouporder={'USA','Sweden','Japan','Italy','Germany','France','England'};
17 |
18 | subplot(2,4,2);
19 | vs2 = violinplot(MPG,Origin,'GroupOrder',grouporder);
20 | plotdetails(2);
21 |
22 | % TEST CASE 3
23 | disp('Test 3: Test the numeric input construction mode');
24 | subplot(2,4,3);
25 | cats = categorical(Origin);
26 | catnames = (unique(cats)); % this ignores categories without any data
27 | catnames_labels = {};
28 | thisData = NaN(length(MPG),length(catnames));
29 | for n = 1:length(catnames)
30 | thisCat = catnames(n);
31 | catnames_labels{n} = char(thisCat);
32 | thisData(1:length(MPG(cats == thisCat)),n) = MPG(cats == thisCat);
33 | end
34 | vs3 = violinplot(thisData,catnames_labels);
35 | plotdetails(3);
36 |
37 | % TEST CASE 4
38 | disp('Test 4: Test two sided violin plots. Japan is being compared.');
39 | subplot(2,4,4);
40 | C = colororder;
41 | vs4 = violinplot({thisData,repmat(thisData(:,5),1,7)},catnames_labels,'ViolinColor',{C,C(5,:)},'ViolinAlpha',{0.3 0.3}, 'ShowMean', true, 'MarkerSize',8);
42 | plotdetails(4);
43 |
44 | % TEST CASE 5
45 | disp('Test 5: Test shadow for quartiles.');
46 | subplot(2,4,5);
47 | vs5 = violinplot(MPG, Origin, 'QuartileStyle','shadow');
48 | plotdetails(5);
49 |
50 | % TEST CASE 6
51 | disp('Test 6: Test plotting only right side & histogram plot, with quartiles as boxplot.');
52 | subplot(2,4,6);
53 | vs6 = violinplot(MPG, Origin, 'QuartileStyle','boxplot', 'HalfViolin','right',...
54 | 'DataStyle', 'histogram');
55 | plotdetails(6);
56 |
57 | % TEST CASE 7
58 | disp('Test 7: Test plotting only left side & histogram plot, and quartiles as shadow.');
59 | subplot(2,4,7);
60 | vs7 = violinplot(MPG, Origin, 'QuartileStyle','shadow', 'HalfViolin','left',...
61 | 'DataStyle', 'histogram', 'ShowMean', true);
62 | plotdetails(7);
63 |
64 |
65 | % TEST CASE 8
66 | disp('Test 8: Same as previous one, just removing the data of half of the violins afterwards.');
67 | subplot(2,4,8);
68 | vs8 = violinplot([MPG; 5;5;5;5;5], [Origin; 'test';'test';'test';'test';'test'], 'QuartileStyle','shadow', 'HalfViolin','full',...
69 | 'DataStyle', 'scatter', 'ShowMean', false);
70 | plotdetails(8);
71 | for n= 1:round(length(vs8)/2)
72 | vs8(1,n).ShowData = 0;
73 | end
74 | xlim([0, 9]);
75 |
76 | % TEST CASE 9
77 | disp('Test 9: Test parent property.');
78 | figure
79 | for i = 1:4
80 | ax(i) = subplot(2,2,i);
81 | end
82 | vs9 = violinplot(MPG, Origin,'parent',ax(3));
83 | title(ax(3),sprintf('Test %02.0f \n',9));
84 | ylabel(ax(3),'Fuel Economy in MPG ');
85 | xlim(ax(3),[0, 8]);
86 | grid(ax(3),'minor');
87 | set(ax(3), 'color', 'none');
88 | xtickangle(ax(3),-30);
89 | fprintf('Test %02.0f passed ok! \n ',9);
90 |
91 | % TEST CASE 10
92 | disp('Test 10: Test of axis orientation.');
93 | vs10 = violinplot(MPG, Origin,'parent',ax(2),'Orientation','horizontal');
94 | title(ax(2),sprintf('Test %02.0f \n',10));
95 | xlabel(ax(2),'Fuel Economy in MPG ');
96 | ylim(ax(2),[0, 8]);
97 | grid(ax(2),'minor');
98 | set(ax(2), 'color', 'none');
99 | ytickangle(ax(2),0);
100 | fprintf('Test %02.0f passed ok! \n ',10);
101 |
102 | %other test cases could be added here
103 | end
104 |
105 | function plotdetails(n)
106 | title(sprintf('Test %02.0f \n',n));
107 | ylabel('Fuel Economy in MPG ');
108 | xlim([0, 8]); grid minor;
109 | set(gca, 'color', 'none');
110 | xtickangle(-30);
111 | fprintf('Test %02.0f passed ok! \n ',n);
112 | end
113 |
114 |
--------------------------------------------------------------------------------
/violinplot.m:
--------------------------------------------------------------------------------
1 | function violins = violinplot(data, cats, varargin)
2 | %Violinplots plots violin plots of some data and categories
3 | % VIOLINPLOT(DATA) plots a violin of a double vector DATA
4 | %
5 | % VIOLINPLOT(DATAMATRIX) plots violins for each column in
6 | % DATAMATRIX.
7 | %
8 | % VIOLINPLOT(DATAMATRIX, CATEGORYNAMES) plots violins for each
9 | % column in DATAMATRIX and labels them according to the names in the
10 | % cell-of-strings CATEGORYNAMES.
11 | %
12 | % In the cases above DATA and DATAMATRIX can be a vector or a matrix,
13 | % respectively, either as is or wrapped in a cell.
14 | % To produce violins which have one distribution on one half and another
15 | % one on the other half, DATA and DATAMATRIX have to be cell arrays
16 | % with two elements, each containing a vector or a matrix. The number of
17 | % columns of the two data sets has to be the same.
18 | %
19 | % VIOLINPLOT(DATA, CATEGORIES) where double vector DATA and vector
20 | % CATEGORIES are of equal length; plots violins for each category in
21 | % DATA.
22 | %
23 | % VIOLINPLOT(TABLE), VIOLINPLOT(STRUCT), VIOLINPLOT(DATASET)
24 | % plots violins for each column in TABLE, each field in STRUCT, and
25 | % each variable in DATASET. The violins are labeled according to
26 | % the table/dataset variable name or the struct field name.
27 | %
28 | % violins = VIOLINPLOT(...) returns an object array of
29 | % Violin objects.
30 | %
31 | % VIOLINPLOT(..., 'PARAM1', val1, 'PARAM2', val2, ...)
32 | % specifies optional name/value pairs for all violins:
33 | % 'Width' Width of the violin in axis space.
34 | % Defaults to 0.3
35 | % 'Bandwidth' Bandwidth of the kernel density estimate.
36 | % Should be between 10% and 40% of the data range.
37 | % 'ViolinColor' Fill color of the violin area and data points. Accepts
38 | % 1x3 color vector or nx3 color vector where n = num
39 | % groups. In case of two data sets being compared it can
40 | % be an array of up to two cells containing nx3
41 | % matrices.
42 | % Defaults to the next default color cycle.
43 | % 'ViolinAlpha' Transparency of the violin area and data points.
44 | % Can be either a single scalar value or an array of
45 | % up to two cells containing scalar values.
46 | % Defaults to 0.3.
47 | % 'MarkerSize' Size of the data points, if shown.
48 | % Defaults to 24
49 | % 'MedianMarkerSize' Size of the median indicator, if shown.
50 | % Defaults to 36
51 | % 'EdgeColor' Color of the violin area outline.
52 | % Defaults to [0.5 0.5 0.5]
53 | % 'BoxColor' Color of the box, whiskers, and the outlines of
54 | % the median point and the notch indicators.
55 | % Defaults to [0.5 0.5 0.5]
56 | % 'MedianColor' Fill color of the median and notch indicators.
57 | % Defaults to [1 1 1]
58 | % 'ShowData' Whether to show data points.
59 | % Defaults to true
60 | % 'ShowNotches' Whether to show notch indicators.
61 | % Defaults to false
62 | % 'ShowMean' Whether to show mean indicator
63 | % Defaults to false
64 | % 'ShowBox' Whether to show the box.
65 | % Defaults to true
66 | % 'ShowMedian' Whether to show the median indicator.
67 | % Defaults to true
68 | % 'ShowWhiskers' Whether to show the whiskers
69 | % Defaults to true
70 | % 'GroupOrder' Cell of category names in order to be plotted.
71 | % Defaults to alphabetical ordering
72 | % 'Orientation' Orientation of the violin plot.
73 | % Defaults to 'vertical'.
74 | % 'Parent' The parent axis of the violin plot.
75 |
76 | % Copyright (c) 2016, Bastian Bechtold
77 | % This code is released under the terms of the BSD 3-clause license
78 |
79 | hascategories = exist('cats','var') && not(isempty(cats));
80 |
81 | %parse the optional grouporder argument
82 | %if it exists parse the categories order
83 | % but also delete it from the arguments passed to Violin
84 | grouporder = {};
85 | idx=find(strcmp(varargin, 'GroupOrder'));
86 | if ~isempty(idx) && numel(varargin)>idx
87 | if iscell(varargin{idx+1})
88 | grouporder = varargin{idx+1};
89 | varargin(idx:idx+1)=[];
90 | else
91 | error('Second argument of ''GroupOrder'' optional arg must be a cell of category names')
92 | end
93 | end
94 |
95 | % check and correct the structure of ViolinColor input
96 | idx=find(strcmp(varargin, 'ViolinColor'));
97 | if ~isempty(idx) && iscell(varargin{idx+1})
98 | if length(varargin{idx+1}(:))>2
99 | error('ViolinColor input can be at most a two element cell array');
100 | end
101 | elseif ~isempty(idx) && isnumeric(varargin{idx+1})
102 | varargin{idx+1} = varargin(idx+1);
103 | end
104 |
105 | % check and correct the structure of ViolinAlpha input
106 | idx=find(strcmp(varargin, 'ViolinAlpha'));
107 | if ~isempty(idx) && iscell(varargin{idx+1})
108 | if length(varargin{idx+1}(:))>2
109 | error('ViolinAlpha input can be at most a two element cell array');
110 | end
111 | elseif ~isempty(idx) && isnumeric(varargin{idx+1})
112 | varargin{idx+1} = varargin(idx+1);
113 | end
114 |
115 | % tabular data
116 | if isa(data, 'dataset') || isstruct(data) || istable(data)
117 | if isa(data, 'dataset')
118 | colnames = data.Properties.VarNames;
119 | elseif istable(data)
120 | colnames = data.Properties.VariableNames;
121 | elseif isstruct(data)
122 | colnames = fieldnames(data);
123 | end
124 | catnames = {};
125 | if isempty(grouporder)
126 | for n=1:length(colnames)
127 | if isnumeric(data.(colnames{n}))
128 | catnames = [catnames colnames{n}]; %#ok<*AGROW>
129 | end
130 | end
131 | catnames = sort(catnames);
132 | else
133 | for n=1:length(grouporder)
134 | if isnumeric(data.(grouporder{n}))
135 | catnames = [catnames grouporder{n}];
136 | end
137 | end
138 | end
139 |
140 | for n=1:length(catnames)
141 | thisData = data.(catnames{n});
142 | violins(n) = Violin({thisData}, n, varargin{:});
143 | end
144 | if strcmp(violins(1).Orientation,'vertical')
145 | set(violins(1).Parent, 'XTick', 1:length(catnames), 'XTickLabels', catnames);
146 | else
147 | set(violins(1).Parent, 'YTick', 1:length(catnames), 'YTickLabels', catnames);
148 | end
149 | set(violins(1).Parent,'Box','on');
150 | return
151 | elseif iscell(data) && length(data(:))==2 % cell input
152 | if not(size(data{1},2)==size(data{2},2))
153 | error('The two input data matrices have to have the same number of columns');
154 | end
155 | elseif iscell(data) && length(data(:))>2 % cell input
156 | error('Up to two datasets can be compared');
157 | elseif isnumeric(data) % numeric input
158 | % 1D data, one category for each data point
159 | if hascategories && numel(data) == numel(cats)
160 | if isempty(grouporder)
161 | cats = categorical(cats);
162 | else
163 | cats = categorical(cats, grouporder);
164 | end
165 |
166 | catnames = (unique(cats)); % this ignores categories without any data
167 | catnames_labels = {};
168 | for n = 1:length(catnames)
169 | thisCat = catnames(n);
170 | catnames_labels{n} = char(thisCat);
171 | thisData = data(cats == thisCat);
172 | violins(n) = Violin({thisData}, n, varargin{:});
173 | end
174 | if strcmp(violins(1).Orientation,'vertical')
175 | set(violins(1).Parent, 'XTick', 1:length(catnames), 'XTickLabels', catnames);
176 | else
177 | set(violins(1).Parent, 'YTick', 1:length(catnames), 'YTickLabels', catnames);
178 | end
179 | set(violins(1).Parent,'Box','on');
180 | return
181 | else
182 | data = {data};
183 | end
184 | end
185 |
186 | % 1D data, no categories
187 | if not(hascategories) && isvector(data{1})
188 | violins = Violin(data, 1, varargin{:});
189 | if strcmp(violins(1).Orientation,'vertical')
190 | set(violins(1).Parent, 'XTick', 1);
191 | else
192 | set(violins(1).Parent, 'yTick', 1);
193 | end
194 | % 2D data with or without categories
195 | elseif ismatrix(data{1})
196 | for n=1:size(data{1}, 2)
197 | thisData = cellfun(@(x)x(:,n),data,'UniformOutput',false);
198 | violins(n) = Violin(thisData, n, varargin{:});
199 | end
200 | if strcmp(violins(1).Orientation,'vertical')
201 | set(violins(1).Parent, 'XTick', 1:size(data{1}, 2));
202 | else
203 | set(violins(1).Parent, 'YTick', 1:size(data{1}, 2));
204 | end
205 | if hascategories && length(cats) == size(data{1}, 2)
206 | if strcmp(violins(1).Orientation,'vertical')
207 | set(violins(1).Parent, 'XTickLabels', cats);
208 | else
209 | set(violins(1).Parent, 'YTickLabels', cats);
210 | end
211 | end
212 | end
213 |
214 | set(violins(1).Parent,'Box','on');
215 |
216 | end
217 |
--------------------------------------------------------------------------------