├── 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 | ![example image](./example.png) 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 | ![example image2](example2.png) 58 | 59 | ## Citation 60 | 61 | [![DOI](https://zenodo.org/badge/60771923.svg)](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 | --------------------------------------------------------------------------------