diff --git a/README.md b/README.md
index 4153322..96644c0 100644
--- a/README.md
+++ b/README.md
@@ -38,15 +38,21 @@ You can also play around with the different options, and tune your violin plots
```matlab
grouporder={'England','Sweden','Japan','Italy','Germany','France','USA'};
+color = jet(length(grouporder));
+pos = 3;
vs = Violin({MPG(strcmp(Origin, grouporder{pos}))},...
- position,...
+ pos,...
'HalfViolin','right',...% left, full
'QuartileStyle','shadow',... % boxplot, none
'DataStyle', 'histogram',... % scatter, none
'ShowNotches', false,...
'ShowMean', false,...
'ShowMedian', true,...
- 'ViolinColor', color);
+ 'ViolinColor', {color(pos,:)},...
+ 'Orientation', 'horizontal');
+ylim(pos + [-.5, 0.5])
+yticks(pos)
+yticklabels(grouporder{pos})
```
![example image2](example2.png)
diff --git a/Violin.m b/Violin.m
index ce58885..0a069a8 100644
--- a/Violin.m
+++ b/Violin.m
@@ -1,714 +1,744 @@
-classdef Violin < handle
- % Violin creates violin plots for some data
- % A violin plot is an easy to read substitute for a box plot
- % that replaces the box shape with a kernel density estimate of
- % the data, and optionally overlays the data points itself.
- % It is also possible to provide two sets of data which are supposed
- % to be compared by plotting each column of the two datasets together
- % on each side of the violin.
- %
- % Additional constructor parameters include the width of the
- % plot, the bandwidth of the kernel density estimation, the
- % X-axis position of the violin plot, and the categories.
- %
- % Use violinplot for a
- % boxplot-like wrapper for
- % interactive plotting.
- %
- % See for more information on Violin Plots:
- % J. L. Hintze and R. D. Nelson, "Violin plots: a box
- % plot-density trace synergism," The American Statistician, vol.
- % 52, no. 2, pp. 181-184, 1998.
- %
- % Violin Properties:
- % ViolinColor - Fill color of the violin area and data points.
- % Can be either a matrix nx3 or an array of up to two
- % cells containing nx3 matrices.
- % Defaults to the next default color cycle.
- % ViolinAlpha - Transparency of the violin area and data points.
- % Can be either a single scalar value or an array of
- % up to two cells containing scalar values.
- % Defaults to 0.3.
- % EdgeColor - Color of the violin area outline.
- % Defaults to [0.5 0.5 0.5]
- % BoxColor - Color of the box, whiskers, and the outlines of
- % the median point and the notch indicators.
- % Defaults to [0.5 0.5 0.5]
- % MedianColor - Fill color of the median and notch indicators.
- % Defaults to [1 1 1]
- % ShowData - Whether to show data points.
- % Defaults to true
- % ShowNotches - Whether to show notch indicators.
- % Defaults to false
- % ShowMean - Whether to show mean indicator.
- % Defaults to false
- % ShowBox - Whether to show the box.
- % Defaults to true
- % ShowMedian - Whether to show the median indicator.
- % Defaults to true
- % ShowWhiskers - Whether to show the whiskers
- % Defaults to true
- % HalfViolin - Whether to do a half violin(left, right side) or
- % full. Defaults to full.
- % QuartileStyle - Option on how to display quartiles, with a
- % boxplot, shadow or none. Defaults to boxplot.
- % DataStyle - Defines the style to show the data points. Opts:
- % 'scatter', 'histogram' or 'none'. Default is 'scatter'.
- %
- %
- % Violin Children:
- % ScatterPlot - scatter plot of the data points
- % ScatterPlot2 - scatter second plot of the data points
- % ViolinPlot - fill plot of the kernel density estimate
- % ViolinPlot2 - fill second plot of the kernel density estimate
- % BoxPlot - fill plot of the box between the quartiles
- % WhiskerPlot - line plot between the whisker ends
- % MedianPlot - scatter plot of the median (one point)
- % NotchPlots - scatter plots for the notch indicators
- % MeanPlot - line plot at mean value
-
-
- % Copyright (c) 2016, Bastian Bechtold
- % This code is released under the terms of the BSD 3-clause license
-
- properties (Access=public)
- ScatterPlot % scatter plot of the data points
- ScatterPlot2 % comparison scatter plot of the data points
- ViolinPlot % fill plot of the kernel density estimate
- ViolinPlot2 % comparison fill plot of the kernel density estimate
- BoxPlot % fill plot of the box between the quartiles
- WhiskerPlot % line plot between the whisker ends
- MedianPlot % scatter plot of the median (one point)
- NotchPlots % scatter plots for the notch indicators
- MeanPlot % line plot of the mean (horizontal line)
- HistogramPlot % histogram of the data
- ViolinPlotQ % fill plot of the Quartiles as shadow
- end
-
- properties (Dependent=true)
- ViolinColor % fill color of the violin area and data points
- ViolinAlpha % transparency of the violin area and data points
- MarkerSize % marker size for the data dots
- MedianMarkerSize % marker size for the median dot
- LineWidth % linewidth of the median plot
- EdgeColor % color of the violin area outline
- BoxColor % color of box, whiskers, and median/notch edges
- BoxWidth % width of box between the quartiles in axis space (default 10% of Violin plot width, 0.03)
- MedianColor % fill color of median and notches
- ShowData % whether to show data points
- ShowNotches % whether to show notch indicators
- ShowMean % whether to show mean indicator
- ShowBox % whether to show the box
- ShowMedian % whether to show the median line
- ShowWhiskers % whether to show the whiskers
- HalfViolin % whether to do a half violin(left, right side) or full
- end
-
- methods
- function obj = Violin(data, pos, varargin)
- %Violin plots a violin plot of some data at pos
- % VIOLIN(DATA, POS) plots a violin at x-position POS for
- % a vector of DATA points.
- %
- % VIOLIN(..., 'PARAM1', val1, 'PARAM2', val2, ...)
- % specifies optional name/value pairs:
- % 'Width' Width of the violin in axis space.
- % Defaults to 0.3
- % 'Bandwidth' Bandwidth of the kernel density
- % estimate. Should be between 10% and
- % 40% of the data range.
- % 'ViolinColor' Fill color of the violin area
- % and data points.Can be either a matrix
- % nx3 or an array of up to two cells
- % containing nx3 matrices.
- % 'ViolinAlpha' Transparency of the violin area and data
- % points. Can be either a single scalar
- % value or an array of up to two cells
- % containing scalar values. Defaults to 0.3.
- % 'MarkerSize' Size of the data points, if shown.
- % Defaults to 24
- % 'MedianMarkerSize' Size of the median indicator, if shown.
- % Defaults to 36
- % 'EdgeColor' Color of the violin area outline.
- % Defaults to [0.5 0.5 0.5]
- % 'BoxColor' Color of the box, whiskers, and the
- % outlines of the median point and the
- % notch indicators. Defaults to
- % [0.5 0.5 0.5]
- % 'MedianColor' Fill color of the median and notch
- % indicators. Defaults to [1 1 1]
- % 'ShowData' Whether to show data points.
- % Defaults to true
- % 'ShowNotches' Whether to show notch indicators.
- % Defaults to false
- % 'ShowMean' Whether to show mean indicator.
- % Defaults to false
- % 'ShowBox' Whether to show the box
- % Defaults to true
- % 'ShowMedian' Whether to show the median line
- % Defaults to true
- % 'ShowWhiskers' Whether to show the whiskers
- % Defaults to true
- % 'HalfViolin' Whether to do a half violin(left, right side) or
- % full. Defaults to full.
- % 'QuartileStyle' Option on how to display quartiles, with a
- % boxplot or as a shadow. Defaults to boxplot.
- % 'DataStyle' Defines the style to show the data points. Opts:
- % 'scatter', 'histogram' or 'none'. Default is 'Scatter'.
-
- st = dbstack; % get the calling function for reporting errors
- namefun = st.name;
- args = obj.checkInputs(data, pos, varargin{:});
-
- if length(data)==1
- data2 = [];
- data = data{1};
-
- else
- data2 = data{2};
- data = data{1};
- end
-
- if isempty(args.ViolinColor)
- Release= strsplit(version('-release'), {'a','b'}); %Check release
- if str2num(Release{1})> 2019 || strcmp(version('-release'), '2019b')
- C = colororder;
- else
- C = lines;
- end
-
- if pos > length(C)
- C = lines;
- end
- args.ViolinColor = {repmat(C,ceil(size(data,2)/length(C)),1)};
- end
-
- data = data(not(isnan(data)));
- data2 = data2(not(isnan(data2)));
- if numel(data) == 1
- obj.MedianPlot = scatter(pos, data, 'filled');
- obj.MedianColor = args.MedianColor;
- obj.MedianPlot.MarkerEdgeColor = args.EdgeColor;
- return
- end
-
- hold('on');
-
-
- %% Calculate kernel density estimation for the violin
- [density, value, width] = obj.calcKernelDensity(data, args.Bandwidth, args.Width);
-
- % also calculate the kernel density of the comparison data if
- % provided
- if ~isempty(data2)
- [densityC, valueC, widthC] = obj.calcKernelDensity(data2, args.Bandwidth, args.Width);
- end
-
- %% Plot the data points within the violin area
- if length(density) > 1
- [~, unique_idx] = unique(value);
- jitterstrength = interp1(value(unique_idx), density(unique_idx)*width, data, 'linear','extrap');
- else % all data is identical:
- jitterstrength = density*width;
- end
- if isempty(data2) % if no comparison data
- jitter = 2*(rand(size(data))-0.5); % both sides
- else
- jitter = rand(size(data)); % only right side
- end
- switch args.HalfViolin % this is more modular
- case 'left'
- jitter = -1*(rand(size(data))); %left
- case 'right'
- jitter = 1*(rand(size(data))); %right
- case 'full'
- jitter = 2*(rand(size(data))-0.5);
- end
- % Make scatter plot
- switch args.DataStyle
- case 'scatter'
- if ~isempty(data2)
- jitter = 1*(rand(size(data))); %right
- obj.ScatterPlot = ...
- scatter(pos + jitter.*jitterstrength, data, args.MarkerSize, 'filled');
- % plot the data points within the violin area
- if length(densityC) > 1
- jitterstrength = interp1(valueC, densityC*widthC, data2);
- else % all data is identical:
- jitterstrength = densityC*widthC;
- end
- jitter = -1*rand(size(data2));% left
- obj.ScatterPlot2 = ...
- scatter(pos + jitter.*jitterstrength, data2, args.MarkerSize, 'filled');
- else
- obj.ScatterPlot = ...
- scatter(pos + jitter.*jitterstrength, data, args.MarkerSize, 'filled');
-
- end
- case 'histogram'
- [counts,edges] = histcounts(data, size(unique(data),1));
- switch args.HalfViolin
- case 'right'
- obj.HistogramPlot= plot([pos-((counts')/max(counts))*max(jitterstrength)*2, pos*ones(size(counts,2),1)]',...
- [edges(1:end-1)+max(diff(edges))/2; edges(1:end-1)+max(diff(edges))/2],'-','LineWidth',1, 'Color', 'k');
- case 'left'
- obj.HistogramPlot= plot([pos*ones(size(counts,2),1), pos+((counts')/max(counts))*max(jitterstrength)*2]',...
- [edges(1:end-1)+max(diff(edges))/2; edges(1:end-1)+max(diff(edges))/2],'-','LineWidth',1, 'Color', 'k');
- otherwise
- fprintf([namefun, ' No histogram/bar plot option available for full violins, as it would look overcrowded.\n'])
- end
- case 'none'
- end
-
- %% Plot the violin
- halfViol= ones(1, size(density,2));
- if isempty(data2) % if no comparison data
- switch args.HalfViolin
- case 'right'
- obj.ViolinPlot = ... % plot color will be overwritten later
- fill([pos+density*width halfViol*pos], ...
- [value value(end:-1:1)], [1 1 1],'LineStyle','-');
- case 'left'
- obj.ViolinPlot = ... % plot color will be overwritten later
- fill([halfViol*pos pos-density(end:-1:1)*width], ...
- [value value(end:-1:1)], [1 1 1],'LineStyle','-');
- case 'full'
- obj.ViolinPlot = ... % plot color will be overwritten later
- fill([pos+density*width pos-density(end:-1:1)*width], ...
- [value value(end:-1:1)], [1 1 1],'LineStyle','-');
- end
- else
- % plot right half of the violin
- obj.ViolinPlot = ...
- fill([pos+density*width pos-density(1)*width], ...
- [value value(1)], [1 1 1],'LineStyle','-');
- % plot left half of the violin
- obj.ViolinPlot2 = ...
- fill([pos-densityC(end)*widthC pos-densityC(end:-1:1)*widthC], ...
- [valueC(end) valueC(end:-1:1)], [1 1 1],'LineStyle','-');
- end
-
- %% Plot the quartiles within the violin
- quartiles = quantile(data, [0.25, 0.5, 0.75]);
- flat= [halfViol*pos halfViol*pos];
- switch args.QuartileStyle
- case 'shadow'
- switch args.HalfViolin
- case 'right'
- w = [pos+density*width halfViol*pos];
- h= [value value(end:-1:1)];
- case 'left'
- w = [halfViol*pos pos-density(end:-1:1)*width];
- h= [value value(end:-1:1)];
- case 'full'
- w = [pos+density*width pos-density(end:-1:1)*width];
- h= [value value(end:-1:1)];
- end
- indices = h >= quartiles(1) & h <= quartiles(3);
- obj.ViolinPlotQ = ... % plot color will be overwritten later
- fill(w(indices), ...
- h(indices),'Marker','none', [1 1 1],'LineStyle','-');
- case 'boxplot'
- obj.BoxPlot = ... % plot color will be overwritten later
- fill(pos+[-1,1,1,-1]*args.BoxWidth, ...
- [quartiles(1) quartiles(1) quartiles(3) quartiles(3)], ...
- [1 1 1],'Marker','none','LineStyle','-');
- case 'none'
- end
-
- %% Plot the data mean
- meanValue = mean(data);
- if length(density) > 1
- [~, unique_idx] = unique(value);
- meanDensityWidth = interp1(value(unique_idx), density(unique_idx), meanValue, 'linear','extrap')*width;
- else % all data is identical:
- meanDensityWidth = density*width;
- end
- if meanDensityWidth lowhisker)));
- hiwhisker = quartiles(3) + 1.5*IQR;
- hiwhisker = min(hiwhisker, max(data(data < hiwhisker)));
- if ~isempty(lowhisker) && ~isempty(hiwhisker)
- obj.WhiskerPlot = plot([pos pos], [lowhisker hiwhisker],...
- 'Marker','none','LineStyle','-');
- end
-
- % Median
- obj.MedianPlot = scatter(pos, quartiles(2), args.MedianMarkerSize, [1 1 1], 'filled');
-
- % Notches
- obj.NotchPlots = ...
- scatter(pos, quartiles(2)-1.57*IQR/sqrt(length(data)), ...
- [], [1 1 1], 'filled', '^');
- obj.NotchPlots(2) = ...
- scatter(pos, quartiles(2)+1.57*IQR/sqrt(length(data)), ...
- [], [1 1 1], 'filled', 'v');
-
- %% Set graphical preferences
- obj.EdgeColor = args.EdgeColor;
- obj.MedianPlot.LineWidth = args.LineWidth;
- obj.BoxColor = args.BoxColor;
- obj.BoxWidth = args.BoxWidth;
- obj.MedianColor = args.MedianColor;
- obj.ShowData = args.ShowData;
- obj.ShowNotches = args.ShowNotches;
- obj.ShowMean = args.ShowMean;
- obj.ShowBox = args.ShowBox;
- obj.ShowMedian = args.ShowMedian;
- obj.ShowWhiskers = args.ShowWhiskers;
-
- if not(isempty(args.ViolinColor))
- if size(args.ViolinColor{1},1) > 1
- ViolinColor{1} = args.ViolinColor{1}(pos,:);
- else
- ViolinColor{1} = args.ViolinColor{1};
- end
- if length(args.ViolinColor)==2
- if size(args.ViolinColor{2},1) > 1
- ViolinColor{2} = args.ViolinColor{2}(pos,:);
- else
- ViolinColor{2} = args.ViolinColor{2};
- end
- else
- ViolinColor{2} = ViolinColor{1};
- end
- else
- % defaults
- if args.scpltBool
- ViolinColor{1} = obj.ScatterPlot.CData;
- else
- ViolinColor{1} = [0 0 0];
- end
- ViolinColor{2} = [0 0 0];
- end
- obj.ViolinColor = ViolinColor;
-
-
- if not(isempty(args.ViolinAlpha))
- if length(args.ViolinAlpha{1})>1
- error('Only scalar values are accepted for the alpha color channel');
- else
- ViolinAlpha{1} = args.ViolinAlpha{1};
- end
- if length(args.ViolinAlpha)==2
- if length(args.ViolinAlpha{2})>1
- error('Only scalar values are accepted for the alpha color channel');
- else
- ViolinAlpha{2} = args.ViolinAlpha{2};
- end
- else
- ViolinAlpha{2} = ViolinAlpha{1}/2; % default unless specified
- end
- else
- % default
- ViolinAlpha = {1,1};
- end
- obj.ViolinAlpha = ViolinAlpha;
-
- set(obj.ViolinPlot, 'Marker', 'none', 'LineStyle', '-');
- set(obj.ViolinPlot2, 'Marker', 'none', 'LineStyle', '-');
- end
-
- %% SET METHODS
- function set.EdgeColor(obj, color)
- if ~isempty(obj.ViolinPlot)
- obj.ViolinPlot.EdgeColor = color;
- obj.ViolinPlotQ.EdgeColor = color;
- if ~isempty(obj.ViolinPlot2)
- obj.ViolinPlot2.EdgeColor = color;
- end
- end
- end
-
- function color = get.EdgeColor(obj)
- if ~isempty(obj.ViolinPlot)
- color = obj.ViolinPlot.EdgeColor;
- end
- end
-
-
- function set.MedianColor(obj, color)
- obj.MedianPlot.MarkerFaceColor = color;
- if ~isempty(obj.NotchPlots)
- obj.NotchPlots(1).MarkerFaceColor = color;
- obj.NotchPlots(2).MarkerFaceColor = color;
- end
- end
-
- function color = get.MedianColor(obj)
- color = obj.MedianPlot.MarkerFaceColor;
- end
-
-
- function set.BoxColor(obj, color)
- if ~isempty(obj.BoxPlot)
- obj.BoxPlot.FaceColor = color;
- obj.BoxPlot.EdgeColor = color;
- obj.WhiskerPlot.Color = color;
- obj.MedianPlot.MarkerEdgeColor = color;
- obj.NotchPlots(1).MarkerFaceColor = color;
- obj.NotchPlots(2).MarkerFaceColor = color;
- elseif ~isempty(obj.ViolinPlotQ)
- obj.WhiskerPlot.Color = color;
- obj.MedianPlot.MarkerEdgeColor = color;
- obj.NotchPlots(1).MarkerFaceColor = color;
- obj.NotchPlots(2).MarkerFaceColor = color;
- end
- end
-
- function color = get.BoxColor(obj)
- if ~isempty(obj.BoxPlot)
- color = obj.BoxPlot.FaceColor;
- end
- end
-
-
- function set.BoxWidth(obj,width)
- if ~isempty(obj.BoxPlot)
- pos=mean(obj.BoxPlot.XData);
- obj.BoxPlot.XData=pos+[-1,1,1,-1]*width;
- end
- end
-
- function width = get.BoxWidth(obj)
- width=max(obj.BoxPlot.XData)-min(obj.BoxPlot.XData);
- end
-
-
- function set.ViolinColor(obj, color)
- obj.ViolinPlot.FaceColor = color{1};
- obj.ScatterPlot.MarkerFaceColor = color{1};
- obj.MeanPlot.Color = color{1};
- if ~isempty(obj.ViolinPlot2)
- obj.ViolinPlot2.FaceColor = color{2};
- obj.ScatterPlot2.MarkerFaceColor = color{2};
- end
- if ~isempty(obj.ViolinPlotQ)
- obj.ViolinPlotQ.FaceColor = color{1};
- end
- for idx = 1: size(obj.HistogramPlot,1)
- obj.HistogramPlot(idx).Color = color{1};
- end
- end
-
- function color = get.ViolinColor(obj)
- color{1} = obj.ViolinPlot.FaceColor;
- if ~isempty(obj.ViolinPlot2)
- color{2} = obj.ViolinPlot2.FaceColor;
- end
- end
-
-
- function set.ViolinAlpha(obj, alpha)
- obj.ViolinPlotQ.FaceAlpha = .65;
- obj.ViolinPlot.FaceAlpha = alpha{1};
- obj.ScatterPlot.MarkerFaceAlpha = 1;
- if ~isempty(obj.ViolinPlot2)
- obj.ViolinPlot2.FaceAlpha = alpha{2};
- obj.ScatterPlot2.MarkerFaceAlpha = 1;
- end
- end
-
- function alpha = get.ViolinAlpha(obj)
- alpha{1} = obj.ViolinPlot.FaceAlpha;
- if ~isempty(obj.ViolinPlot2)
- alpha{2} = obj.ViolinPlot2.FaceAlpha;
- end
- end
-
-
- function set.ShowData(obj, yesno)
- if yesno
- obj.ScatterPlot.Visible = 'on';
- for idx = 1: size(obj.HistogramPlot,1)
- obj.HistogramPlot(idx).Visible = 'on';
- end
- else
- obj.ScatterPlot.Visible = 'off';
- for idx = 1: size(obj.HistogramPlot,1)
- obj.HistogramPlot(idx).Visible = 'off';
- end
- end
- if ~isempty(obj.ScatterPlot2)
- obj.ScatterPlot2.Visible = obj.ScatterPlot.Visible;
- end
-
- end
-
- function yesno = get.ShowData(obj)
- if ~isempty(obj.ScatterPlot)
- yesno = strcmp(obj.ScatterPlot.Visible, 'on');
- end
- end
-
-
- function set.ShowNotches(obj, yesno)
- if ~isempty(obj.NotchPlots)
- if yesno
- obj.NotchPlots(1).Visible = 'on';
- obj.NotchPlots(2).Visible = 'on';
- else
- obj.NotchPlots(1).Visible = 'off';
- obj.NotchPlots(2).Visible = 'off';
- end
- end
- end
-
- function yesno = get.ShowNotches(obj)
- if ~isempty(obj.NotchPlots)
- yesno = strcmp(obj.NotchPlots(1).Visible, 'on');
- end
- end
-
-
- function set.ShowMean(obj, yesno)
- if ~isempty(obj.MeanPlot)
- if yesno
- obj.MeanPlot.Visible = 'on';
- else
- obj.MeanPlot.Visible = 'off';
- end
- end
- end
-
- function yesno = get.ShowMean(obj)
- if ~isempty(obj.BoxPlot)
- yesno = strcmp(obj.BoxPlot.Visible, 'on');
- end
- end
-
-
- function set.ShowBox(obj, yesno)
- if ~isempty(obj.BoxPlot)
- if yesno
- obj.BoxPlot.Visible = 'on';
- else
- obj.BoxPlot.Visible = 'off';
- end
- end
- end
-
- function yesno = get.ShowBox(obj)
- if ~isempty(obj.BoxPlot)
- yesno = strcmp(obj.BoxPlot.Visible, 'on');
- end
- end
-
-
- function set.ShowMedian(obj, yesno)
- if ~isempty(obj.MedianPlot)
- if yesno
- obj.MedianPlot.Visible = 'on';
- else
- obj.MedianPlot.Visible = 'off';
- end
- end
- end
-
- function yesno = get.ShowMedian(obj)
- if ~isempty(obj.MedianPlot)
- yesno = strcmp(obj.MedianPlot.Visible, 'on');
- end
- end
-
-
- function set.ShowWhiskers(obj, yesno)
- if ~isempty(obj.WhiskerPlot)
- if yesno
- obj.WhiskerPlot.Visible = 'on';
- else
- obj.WhiskerPlot.Visible = 'off';
- end
- end
- end
-
- function yesno = get.ShowWhiskers(obj)
- if ~isempty(obj.WhiskerPlot)
- yesno = strcmp(obj.WhiskerPlot.Visible, 'on');
- end
- end
-
- end
-
- methods (Access=private)
- function results = checkInputs(~, data, pos, varargin)
- isscalarnumber = @(x) (isnumeric(x) & isscalar(x));
- p = inputParser();
- p.addRequired('Data', @(x)isnumeric(vertcat(x{:})));
- p.addRequired('Pos', isscalarnumber);
- p.addParameter('Width', 0.3, isscalarnumber);
- p.addParameter('Bandwidth', [], isscalarnumber);
- iscolor = @(x) (isnumeric(x) & size(x,2) == 3);
- p.addParameter('ViolinColor', [], @(x)iscolor(vertcat(x{:})));
- p.addParameter('MarkerSize', 24, @isnumeric);
- p.addParameter('MedianMarkerSize', 36, @isnumeric);
- p.addParameter('LineWidth', 0.75, @isnumeric);
- p.addParameter('BoxColor', [0.5 0.5 0.5], iscolor);
- p.addParameter('BoxWidth', 0.01, isscalarnumber);
- p.addParameter('EdgeColor', [0.5 0.5 0.5], iscolor);
- p.addParameter('MedianColor', [1 1 1], iscolor);
- p.addParameter('ViolinAlpha', {0.3,0.15}, @(x)isnumeric(vertcat(x{:})));
- isscalarlogical = @(x) (islogical(x) & isscalar(x));
- p.addParameter('ShowData', true, isscalarlogical);
- p.addParameter('ShowNotches', false, isscalarlogical);
- p.addParameter('ShowMean', false, isscalarlogical);
- p.addParameter('ShowBox', true, isscalarlogical);
- p.addParameter('ShowMedian', true, isscalarlogical);
- p.addParameter('ShowWhiskers', true, isscalarlogical);
- validSides={'full', 'right', 'left'};
- checkSide = @(x) any(validatestring(x, validSides));
- p.addParameter('HalfViolin', 'full', checkSide);
- validQuartileStyles={'boxplot', 'shadow', 'none'};
- checkQuartile = @(x)any(validatestring(x, validQuartileStyles));
- p.addParameter('QuartileStyle', 'boxplot', checkQuartile);
- validDataStyles = {'scatter', 'histogram', 'none'};
- checkStyle = @(x)any(validatestring(x, validDataStyles));
- p.addParameter('DataStyle', 'scatter', checkStyle);
-
- p.parse(data, pos, varargin{:});
- results = p.Results;
- end
- end
-
- methods (Static)
- function [density, value, width] = calcKernelDensity(data, bandwidth, width)
- if isempty(data)
- error('Empty input data');
- end
- [density, value] = ksdensity(data, 'bandwidth', bandwidth);
- density = density(value >= min(data) & value <= max(data));
- value = value(value >= min(data) & value <= max(data));
- value(1) = min(data);
- value(end) = max(data);
- value = [value(1)*(1-1E-5), value, value(end)*(1+1E-5)];
- density = [0, density, 0];
-
- % all data is identical
- if min(data) == max(data)
- density = 1;
- value= mean(value);
- end
-
- width = width/max(density);
- end
- end
-end
-
+classdef Violin < handle
+ % Violin creates violin plots for some data
+ % A violin plot is an easy to read substitute for a box plot
+ % that replaces the box shape with a kernel density estimate of
+ % the data, and optionally overlays the data points itself.
+ % It is also possible to provide two sets of data which are supposed
+ % to be compared by plotting each column of the two datasets together
+ % on each side of the violin.
+ %
+ % Additional constructor parameters include the width of the
+ % plot, the bandwidth of the kernel density estimation, the
+ % X-axis position of the violin plot, and the categories.
+ %
+ % Use violinplot for a
+ % boxplot-like wrapper for
+ % interactive plotting.
+ %
+ % See for more information on Violin Plots:
+ % J. L. Hintze and R. D. Nelson, "Violin plots: a box
+ % plot-density trace synergism," The American Statistician, vol.
+ % 52, no. 2, pp. 181-184, 1998.
+ %
+ % Violin Properties:
+ % ViolinColor - Fill color of the violin area and data points.
+ % Can be either a matrix nx3 or an array of up to two
+ % cells containing nx3 matrices.
+ % Defaults to the next default color cycle.
+ % ViolinAlpha - Transparency of the violin area and data points.
+ % Can be either a single scalar value or an array of
+ % up to two cells containing scalar values.
+ % Defaults to 0.3.
+ % EdgeColor - Color of the violin area outline.
+ % Defaults to [0.5 0.5 0.5]
+ % BoxColor - Color of the box, whiskers, and the outlines of
+ % the median point and the notch indicators.
+ % Defaults to [0.5 0.5 0.5]
+ % MedianColor - Fill color of the median and notch indicators.
+ % Defaults to [1 1 1]
+ % ShowData - Whether to show data points.
+ % Defaults to true
+ % ShowNotches - Whether to show notch indicators.
+ % Defaults to false
+ % ShowMean - Whether to show mean indicator.
+ % Defaults to false
+ % ShowBox - Whether to show the box.
+ % Defaults to true
+ % ShowMedian - Whether to show the median indicator.
+ % Defaults to true
+ % ShowWhiskers - Whether to show the whiskers
+ % Defaults to true
+ % HalfViolin - Whether to do a half violin(left, right side) or
+ % full. Defaults to full.
+ % QuartileStyle - Option on how to display quartiles, with a
+ % boxplot, shadow or none. Defaults to boxplot.
+ % DataStyle - Defines the style to show the data points. Opts:
+ % 'scatter', 'histogram' or 'none'. Default is 'scatter'.
+ % Orientation - Defines the orientation of the violin plot. Opts:
+ % 'vertical', 'horizontal'. Default is 'vertical'.
+ % Parent - The parent axis of the violin plot.
+ %
+ %
+ % Violin Children:
+ % ScatterPlot - scatter plot of the data points
+ % ScatterPlot2 - scatter second plot of the data points
+ % ViolinPlot - fill plot of the kernel density estimate
+ % ViolinPlot2 - fill second plot of the kernel density estimate
+ % BoxPlot - fill plot of the box between the quartiles
+ % WhiskerPlot - line plot between the whisker ends
+ % MedianPlot - scatter plot of the median (one point)
+ % NotchPlots - scatter plots for the notch indicators
+ % MeanPlot - line plot at mean value
+
+
+ % Copyright (c) 2016, Bastian Bechtold
+ % This code is released under the terms of the BSD 3-clause license
+
+ properties (Access=public)
+ ScatterPlot % scatter plot of the data points
+ ScatterPlot2 % comparison scatter plot of the data points
+ ViolinPlot % fill plot of the kernel density estimate
+ ViolinPlot2 % comparison fill plot of the kernel density estimate
+ BoxPlot % fill plot of the box between the quartiles
+ WhiskerPlot % line plot between the whisker ends
+ MedianPlot % scatter plot of the median (one point)
+ NotchPlots % scatter plots for the notch indicators
+ MeanPlot % line plot of the mean (horizontal line)
+ HistogramPlot % histogram of the data
+ ViolinPlotQ % fill plot of the Quartiles as shadow
+ Parent % parent axis
+ end
+
+ properties (SetAccess=protected, GetAccess=public)
+ Orientation % 'horizontal' or 'vertical'
+ end
+
+ properties (Dependent=true)
+ ViolinColor % fill color of the violin area and data points
+ ViolinAlpha % transparency of the violin area and data points
+ MarkerSize % marker size for the data dots
+ MedianMarkerSize % marker size for the median dot
+ LineWidth % linewidth of the median plot
+ EdgeColor % color of the violin area outline
+ BoxColor % color of box, whiskers, and median/notch edges
+ BoxWidth % width of box between the quartiles in axis space (default 10% of Violin plot width, 0.03)
+ MedianColor % fill color of median and notches
+ ShowData % whether to show data points
+ ShowNotches % whether to show notch indicators
+ ShowMean % whether to show mean indicator
+ ShowBox % whether to show the box
+ ShowMedian % whether to show the median line
+ ShowWhiskers % whether to show the whiskers
+ HalfViolin % whether to do a half violin(left, right side) or full
+ end
+
+ methods
+ function obj = Violin(data, pos, varargin)
+ %Violin plots a violin plot of some data at pos
+ % VIOLIN(DATA, POS) plots a violin at x-position POS for
+ % a vector of DATA points.
+ %
+ % VIOLIN(..., 'PARAM1', val1, 'PARAM2', val2, ...)
+ % specifies optional name/value pairs:
+ % 'Width' Width of the violin in axis space.
+ % Defaults to 0.3
+ % 'Bandwidth' Bandwidth of the kernel density
+ % estimate. Should be between 10% and
+ % 40% of the data range.
+ % 'ViolinColor' Fill color of the violin area
+ % and data points.Can be either a matrix
+ % nx3 or an array of up to two cells
+ % containing nx3 matrices.
+ % 'ViolinAlpha' Transparency of the violin area and data
+ % points. Can be either a single scalar
+ % value or an array of up to two cells
+ % containing scalar values. Defaults to 0.3.
+ % 'MarkerSize' Size of the data points, if shown.
+ % Defaults to 24
+ % 'MedianMarkerSize' Size of the median indicator, if shown.
+ % Defaults to 36
+ % 'EdgeColor' Color of the violin area outline.
+ % Defaults to [0.5 0.5 0.5]
+ % 'BoxColor' Color of the box, whiskers, and the
+ % outlines of the median point and the
+ % notch indicators. Defaults to
+ % [0.5 0.5 0.5]
+ % 'MedianColor' Fill color of the median and notch
+ % indicators. Defaults to [1 1 1]
+ % 'ShowData' Whether to show data points.
+ % Defaults to true
+ % 'ShowNotches' Whether to show notch indicators.
+ % Defaults to false
+ % 'ShowMean' Whether to show mean indicator.
+ % Defaults to false
+ % 'ShowBox' Whether to show the box
+ % Defaults to true
+ % 'ShowMedian' Whether to show the median line
+ % Defaults to true
+ % 'ShowWhiskers' Whether to show the whiskers
+ % Defaults to true
+ % 'HalfViolin' Whether to do a half violin(left, right side) or
+ % full. Defaults to full.
+ % 'QuartileStyle' Option on how to display quartiles, with a
+ % boxplot or as a shadow. Defaults to boxplot.
+ % 'DataStyle' Defines the style to show the data points. Opts:
+ % 'scatter', 'histogram' or 'none'. Default is 'Scatter'.
+ % 'Orientation' Defines the orientation of the violin plot. Opts:
+ % 'vertical', 'horizontal'. Default is 'vertical'.
+ % 'Parent' The parent axis of the violin plot.
+
+ st = dbstack; % get the calling function for reporting errors
+ namefun = st.name;
+ args = obj.checkInputs(data, pos, varargin{:});
+ obj.Orientation = args.Orientation;
+ obj.Parent = args.Parent;
+
+ if length(data)==1
+ data2 = [];
+ data = data{1};
+
+ else
+ data2 = data{2};
+ data = data{1};
+ end
+
+ if isempty(args.ViolinColor)
+ Release= strsplit(version('-release'), {'a','b'}); %Check release
+ if str2num(Release{1})> 2019 || strcmp(version('-release'), '2019b')
+ C = colororder;
+ else
+ C = lines;
+ end
+
+ if pos > length(C)
+ C = lines;
+ end
+ args.ViolinColor = {repmat(C,ceil(size(data,2)/length(C)),1)};
+ end
+
+ data = data(not(isnan(data)));
+ data2 = data2(not(isnan(data2)));
+ if numel(data) == 1
+ [x, y] = obj.swapOrientationMaybe(pos, data);
+ obj.MedianPlot = scatter(x, y, 'filled', 'Parent', obj.Parent);
+ obj.MedianColor = args.MedianColor;
+ obj.MedianPlot.MarkerEdgeColor = args.EdgeColor;
+ return
+ end
+
+ hold(obj.Parent, 'on');
+
+
+ %% Calculate kernel density estimation for the violin
+ [density, value, width] = obj.calcKernelDensity(data, args.Bandwidth, args.Width);
+
+ % also calculate the kernel density of the comparison data if
+ % provided
+ if ~isempty(data2)
+ [densityC, valueC, widthC] = obj.calcKernelDensity(data2, args.Bandwidth, args.Width);
+ end
+
+ %% Plot the data points within the violin area
+ if length(density) > 1
+ [~, unique_idx] = unique(value);
+ jitterstrength = interp1(value(unique_idx), density(unique_idx)*width, data, 'linear','extrap');
+ else % all data is identical:
+ jitterstrength = density*width;
+ end
+ if isempty(data2) % if no comparison data
+ jitter = 2*(rand(size(data))-0.5); % both sides
+ else
+ jitter = rand(size(data)); % only right side
+ end
+ switch args.HalfViolin % this is more modular
+ case 'left'
+ jitter = -1*(rand(size(data))); %left
+ case 'right'
+ jitter = 1*(rand(size(data))); %right
+ case 'full'
+ jitter = 2*(rand(size(data))-0.5);
+ end
+ % Make scatter plot
+ switch args.DataStyle
+ case 'scatter'
+ if ~isempty(data2)
+ jitter = 1*(rand(size(data))); %right
+ [x, y] = obj.swapOrientationMaybe(pos + jitter.*jitterstrength, data);
+ obj.ScatterPlot = ...
+ scatter(x, y, args.MarkerSize, 'filled', 'Parent', obj.Parent);
+ % plot the data points within the violin area
+ if length(densityC) > 1
+ jitterstrength = interp1(valueC, densityC*widthC, data2);
+ else % all data is identical:
+ jitterstrength = densityC*widthC;
+ end
+ jitter = -1*rand(size(data2));% left
+ [x, y] = obj.swapOrientationMaybe(pos + jitter.*jitterstrength, data2);
+ obj.ScatterPlot2 = ...
+ scatter(x, y, args.MarkerSize, 'filled', 'Parent', obj.Parent);
+ else
+ [x, y] = obj.swapOrientationMaybe(pos + jitter.*jitterstrength, data);
+ obj.ScatterPlot = ...
+ scatter(x, y, args.MarkerSize, 'filled', 'Parent', obj.Parent);
+
+ end
+ case 'histogram'
+ [counts,edges] = histcounts(data, size(unique(data),1));
+ switch args.HalfViolin
+ case 'right'
+ [x, y] = obj.swapOrientationMaybe([pos-((counts')/max(counts))*max(jitterstrength)*2, pos*ones(size(counts,2),1)]', ...
+ [edges(1:end-1)+max(diff(edges))/2; edges(1:end-1)+max(diff(edges))/2]);
+ obj.HistogramPlot = plot(x,y,'-','LineWidth',1, 'Color', 'k', 'Parent', obj.Parent);
+ case 'left'
+ [x, y] = obj.swapOrientationMaybe([pos*ones(size(counts,2),1), pos+((counts')/max(counts))*max(jitterstrength)*2]', ...
+ [edges(1:end-1)+max(diff(edges))/2; edges(1:end-1)+max(diff(edges))/2]);
+ obj.HistogramPlot = plot(x,y,'-','LineWidth',1, 'Color', 'k', 'Parent', obj.Parent);
+ otherwise
+ fprintf([namefun, ' No histogram/bar plot option available for full violins, as it would look overcrowded.\n'])
+ end
+ case 'none'
+ end
+
+ %% Plot the violin
+ halfViol= ones(1, size(density,2));
+ if isempty(data2) % if no comparison data
+ switch args.HalfViolin
+ case 'right'
+ [x,y ] = obj.swapOrientationMaybe([pos+density*width halfViol*pos], ...
+ [value value(end:-1:1)]);
+ obj.ViolinPlot = fill(x,y, [1 1 1], 'Parent', obj.Parent); % plot color will be overwritten later
+ case 'left'
+ [x,y] = obj.swapOrientationMaybe([halfViol*pos pos-density(end:-1:1)*width], ...
+ [value value(end:-1:1)]);
+ obj.ViolinPlot = fill(x,y, [1 1 1], 'Parent', obj.Parent); % plot color will be overwritten later
+ case 'full'
+ [x, y] = obj.swapOrientationMaybe([pos+density*width pos-density(end:-1:1)*width], ...
+ [value value(end:-1:1)]);
+ obj.ViolinPlot = fill(x,y, [1 1 1], 'Parent', obj.Parent); % plot color will be overwritten later
+ end
+ else
+ % plot right half of the violin
+ [x, y] = obj.swapOrientationMaybe([pos+density*width pos-density(1)*width], [value value(1)]);
+ obj.ViolinPlot = fill(x ,y, [1 1 1], 'Parent', obj.Parent);
+ % plot left half of the violin
+ [x, y] = obj.swapOrientationMaybe([pos-densityC(end)*widthC pos-densityC(end:-1:1)*widthC], ...
+ [valueC(end) valueC(end:-1:1)]);
+ obj.ViolinPlot2 = fill(x, y, [1 1 1], 'Parent', obj.Parent);
+ end
+
+ %% Plot the quartiles within the violin
+ quartiles = quantile(data, [0.25, 0.5, 0.75]);
+ flat= [halfViol*pos halfViol*pos];
+ switch args.QuartileStyle
+ case 'shadow'
+ switch args.HalfViolin
+ case 'right'
+ w = [pos+density*width halfViol*pos];
+ h = [value value(end:-1:1)];
+ case 'left'
+ w = [halfViol*pos pos-density(end:-1:1)*width];
+ h = [value value(end:-1:1)];
+ case 'full'
+ w = [pos+density*width pos-density(end:-1:1)*width];
+ h = [value value(end:-1:1)];
+ end
+ indices = h >= quartiles(1) & h <= quartiles(3);
+ [x, y] = obj.swapOrientationMaybe(w(indices), h(indices));
+ obj.ViolinPlotQ = fill(x, y, [1 1 1], 'Parent', obj.Parent); % plot color will be overwritten later
+ case 'boxplot'
+ [x, y] = obj.swapOrientationMaybe(pos+[-1,1,1,-1]*args.BoxWidth, ...
+ [quartiles(1) quartiles(1) quartiles(3) quartiles(3)]);
+ obj.BoxPlot = fill(x, y, [1 1 1], 'Parent', obj.Parent); % plot color will be overwritten later
+ case 'none'
+ end
+
+ %% Plot the data mean
+ meanValue = mean(data);
+ if length(density) > 1
+ [~, unique_idx] = unique(value);
+ meanDensityWidth = interp1(value(unique_idx), density(unique_idx), meanValue, 'linear','extrap')*width;
+ else % all data is identical:
+ meanDensityWidth = density*width;
+ end
+ if meanDensityWidth lowhisker)));
+ hiwhisker = quartiles(3) + 1.5*IQR;
+ hiwhisker = min(hiwhisker, max(data(data < hiwhisker)));
+ if ~isempty(lowhisker) && ~isempty(hiwhisker)
+ [x, y] = obj.swapOrientationMaybe([pos pos], [lowhisker hiwhisker]);
+ obj.WhiskerPlot = plot(x, y, 'Parent', obj.Parent);
+ end
+
+ % Median
+ [x, y] = obj.swapOrientationMaybe(pos, quartiles(2));
+ obj.MedianPlot = scatter(x, y, ...
+ args.MedianMarkerSize, [1 1 1], 'filled', 'Parent', obj.Parent);
+
+ % Notches
+ [x, y] = obj.swapOrientationMaybe(pos, quartiles(2)-1.57*IQR/sqrt(length(data)));
+ obj.NotchPlots = scatter(x, y, [], [1 1 1], 'filled', '^', 'Parent', obj.Parent);
+ [x, y] = obj.swapOrientationMaybe(pos, quartiles(2)+1.57*IQR/sqrt(length(data)));
+ obj.NotchPlots(2) = scatter(x, y, [], [1 1 1], 'filled', 'v', 'Parent', obj.Parent);
+
+ %% Set graphical preferences
+ obj.EdgeColor = args.EdgeColor;
+ obj.MedianPlot.LineWidth = args.LineWidth;
+ obj.BoxColor = args.BoxColor;
+ obj.BoxWidth = args.BoxWidth;
+ obj.MedianColor = args.MedianColor;
+ obj.ShowData = args.ShowData;
+ obj.ShowNotches = args.ShowNotches;
+ obj.ShowMean = args.ShowMean;
+ obj.ShowBox = args.ShowBox;
+ obj.ShowMedian = args.ShowMedian;
+ obj.ShowWhiskers = args.ShowWhiskers;
+
+ if not(isempty(args.ViolinColor))
+ if size(args.ViolinColor{1},1) > 1
+ ViolinColor{1} = args.ViolinColor{1}(pos,:);
+ else
+ ViolinColor{1} = args.ViolinColor{1};
+ end
+ if length(args.ViolinColor)==2
+ if size(args.ViolinColor{2},1) > 1
+ ViolinColor{2} = args.ViolinColor{2}(pos,:);
+ else
+ ViolinColor{2} = args.ViolinColor{2};
+ end
+ else
+ ViolinColor{2} = ViolinColor{1};
+ end
+ else
+ % defaults
+ if args.scpltBool
+ ViolinColor{1} = obj.ScatterPlot.CData;
+ else
+ ViolinColor{1} = [0 0 0];
+ end
+ ViolinColor{2} = [0 0 0];
+ end
+ obj.ViolinColor = ViolinColor;
+
+
+ if not(isempty(args.ViolinAlpha))
+ if length(args.ViolinAlpha{1})>1
+ error('Only scalar values are accepted for the alpha color channel');
+ else
+ ViolinAlpha{1} = args.ViolinAlpha{1};
+ end
+ if length(args.ViolinAlpha)==2
+ if length(args.ViolinAlpha{2})>1
+ error('Only scalar values are accepted for the alpha color channel');
+ else
+ ViolinAlpha{2} = args.ViolinAlpha{2};
+ end
+ else
+ ViolinAlpha{2} = ViolinAlpha{1}/2; % default unless specified
+ end
+ else
+ % default
+ ViolinAlpha = {1,1};
+ end
+ obj.ViolinAlpha = ViolinAlpha;
+
+
+ set(obj.ViolinPlot, 'Marker', 'none', 'LineStyle', '-');
+ set(obj.ViolinPlot2, 'Marker', 'none', 'LineStyle', '-');
+ end
+
+ %% SET METHODS
+ function set.EdgeColor(obj, color)
+ if ~isempty(obj.ViolinPlot)
+ obj.ViolinPlot.EdgeColor = color;
+ obj.ViolinPlotQ.EdgeColor = color;
+ if ~isempty(obj.ViolinPlot2)
+ obj.ViolinPlot2.EdgeColor = color;
+ end
+ end
+ end
+
+ function color = get.EdgeColor(obj)
+ if ~isempty(obj.ViolinPlot)
+ color = obj.ViolinPlot.EdgeColor;
+ end
+ end
+
+
+ function set.MedianColor(obj, color)
+ obj.MedianPlot.MarkerFaceColor = color;
+ if ~isempty(obj.NotchPlots)
+ obj.NotchPlots(1).MarkerFaceColor = color;
+ obj.NotchPlots(2).MarkerFaceColor = color;
+ end
+ end
+
+ function color = get.MedianColor(obj)
+ color = obj.MedianPlot.MarkerFaceColor;
+ end
+
+
+ function set.BoxColor(obj, color)
+ if ~isempty(obj.BoxPlot)
+ obj.BoxPlot.FaceColor = color;
+ obj.BoxPlot.EdgeColor = color;
+ obj.WhiskerPlot.Color = color;
+ obj.MedianPlot.MarkerEdgeColor = color;
+ obj.NotchPlots(1).MarkerFaceColor = color;
+ obj.NotchPlots(2).MarkerFaceColor = color;
+ elseif ~isempty(obj.ViolinPlotQ)
+ obj.WhiskerPlot.Color = color;
+ obj.MedianPlot.MarkerEdgeColor = color;
+ obj.NotchPlots(1).MarkerFaceColor = color;
+ obj.NotchPlots(2).MarkerFaceColor = color;
+ end
+ end
+
+ function color = get.BoxColor(obj)
+ if ~isempty(obj.BoxPlot)
+ color = obj.BoxPlot.FaceColor;
+ end
+ end
+
+
+ function set.BoxWidth(obj,width)
+ if ~isempty(obj.BoxPlot)
+ pos=mean(obj.BoxPlot.XData);
+ obj.BoxPlot.XData=pos+[-1,1,1,-1]*width;
+ end
+ end
+
+ function width = get.BoxWidth(obj)
+ width=max(obj.BoxPlot.XData)-min(obj.BoxPlot.XData);
+ end
+
+
+ function set.ViolinColor(obj, color)
+ obj.ViolinPlot.FaceColor = color{1};
+ obj.ScatterPlot.MarkerFaceColor = color{1};
+ obj.MeanPlot.Color = color{1};
+ if ~isempty(obj.ViolinPlot2)
+ obj.ViolinPlot2.FaceColor = color{2};
+ obj.ScatterPlot2.MarkerFaceColor = color{2};
+ end
+ if ~isempty(obj.ViolinPlotQ)
+ obj.ViolinPlotQ.FaceColor = color{1};
+ end
+ for idx = 1: size(obj.HistogramPlot,1)
+ obj.HistogramPlot(idx).Color = color{1};
+ end
+ end
+
+ function color = get.ViolinColor(obj)
+ color{1} = obj.ViolinPlot.FaceColor;
+ if ~isempty(obj.ViolinPlot2)
+ color{2} = obj.ViolinPlot2.FaceColor;
+ end
+ end
+
+
+ function set.ViolinAlpha(obj, alpha)
+ obj.ViolinPlotQ.FaceAlpha = .65;
+ obj.ViolinPlot.FaceAlpha = alpha{1};
+ obj.ScatterPlot.MarkerFaceAlpha = 1;
+ if ~isempty(obj.ViolinPlot2)
+ obj.ViolinPlot2.FaceAlpha = alpha{2};
+ obj.ScatterPlot2.MarkerFaceAlpha = 1;
+ end
+ end
+
+ function alpha = get.ViolinAlpha(obj)
+ alpha{1} = obj.ViolinPlot.FaceAlpha;
+ if ~isempty(obj.ViolinPlot2)
+ alpha{2} = obj.ViolinPlot2.FaceAlpha;
+ end
+ end
+
+
+ function set.ShowData(obj, yesno)
+ if yesno
+ obj.ScatterPlot.Visible = 'on';
+ for idx = 1: size(obj.HistogramPlot,1)
+ obj.HistogramPlot(idx).Visible = 'on';
+ end
+ else
+ obj.ScatterPlot.Visible = 'off';
+ for idx = 1: size(obj.HistogramPlot,1)
+ obj.HistogramPlot(idx).Visible = 'off';
+ end
+ end
+ if ~isempty(obj.ScatterPlot2)
+ obj.ScatterPlot2.Visible = obj.ScatterPlot.Visible;
+ end
+
+ end
+
+ function yesno = get.ShowData(obj)
+ if ~isempty(obj.ScatterPlot)
+ yesno = strcmp(obj.ScatterPlot.Visible, 'on');
+ end
+ end
+
+
+ function set.ShowNotches(obj, yesno)
+ if ~isempty(obj.NotchPlots)
+ if yesno
+ obj.NotchPlots(1).Visible = 'on';
+ obj.NotchPlots(2).Visible = 'on';
+ else
+ obj.NotchPlots(1).Visible = 'off';
+ obj.NotchPlots(2).Visible = 'off';
+ end
+ end
+ end
+
+ function yesno = get.ShowNotches(obj)
+ if ~isempty(obj.NotchPlots)
+ yesno = strcmp(obj.NotchPlots(1).Visible, 'on');
+ end
+ end
+
+
+ function set.ShowMean(obj, yesno)
+ if ~isempty(obj.MeanPlot)
+ if yesno
+ obj.MeanPlot.Visible = 'on';
+ else
+ obj.MeanPlot.Visible = 'off';
+ end
+ end
+ end
+
+ function yesno = get.ShowMean(obj)
+ if ~isempty(obj.BoxPlot)
+ yesno = strcmp(obj.BoxPlot.Visible, 'on');
+ end
+ end
+
+
+ function set.ShowBox(obj, yesno)
+ if ~isempty(obj.BoxPlot)
+ if yesno
+ obj.BoxPlot.Visible = 'on';
+ else
+ obj.BoxPlot.Visible = 'off';
+ end
+ end
+ end
+
+ function yesno = get.ShowBox(obj)
+ if ~isempty(obj.BoxPlot)
+ yesno = strcmp(obj.BoxPlot.Visible, 'on');
+ end
+ end
+
+
+ function set.ShowMedian(obj, yesno)
+ if ~isempty(obj.MedianPlot)
+ if yesno
+ obj.MedianPlot.Visible = 'on';
+ else
+ obj.MedianPlot.Visible = 'off';
+ end
+ end
+ end
+
+ function yesno = get.ShowMedian(obj)
+ if ~isempty(obj.MedianPlot)
+ yesno = strcmp(obj.MedianPlot.Visible, 'on');
+ end
+ end
+
+
+ function set.ShowWhiskers(obj, yesno)
+ if ~isempty(obj.WhiskerPlot)
+ if yesno
+ obj.WhiskerPlot.Visible = 'on';
+ else
+ obj.WhiskerPlot.Visible = 'off';
+ end
+ end
+ end
+
+ function yesno = get.ShowWhiskers(obj)
+ if ~isempty(obj.WhiskerPlot)
+ yesno = strcmp(obj.WhiskerPlot.Visible, 'on');
+ end
+ end
+
+ end
+
+ methods (Access=private)
+ function results = checkInputs(~, data, pos, varargin)
+ isscalarnumber = @(x) (isnumeric(x) & isscalar(x));
+ p = inputParser();
+ p.addRequired('Data', @(x)isnumeric(vertcat(x{:})));
+ p.addRequired('Pos', isscalarnumber);
+ p.addParameter('Width', 0.3, isscalarnumber);
+ p.addParameter('Bandwidth', [], isscalarnumber);
+ iscolor = @(x) (isnumeric(x) & size(x,2) == 3);
+ p.addParameter('ViolinColor', [], @(x)iscolor(vertcat(x{:})));
+ p.addParameter('MarkerSize', 24, @isnumeric);
+ p.addParameter('MedianMarkerSize', 36, @isnumeric);
+ p.addParameter('LineWidth', 0.75, @isnumeric);
+ p.addParameter('BoxColor', [0.5 0.5 0.5], iscolor);
+ p.addParameter('BoxWidth', 0.01, isscalarnumber);
+ p.addParameter('EdgeColor', [0.5 0.5 0.5], iscolor);
+ p.addParameter('MedianColor', [1 1 1], iscolor);
+ p.addParameter('ViolinAlpha', {0.3,0.15}, @(x)isnumeric(vertcat(x{:})));
+ isscalarlogical = @(x) (islogical(x) & isscalar(x));
+ p.addParameter('ShowData', true, isscalarlogical);
+ p.addParameter('ShowNotches', false, isscalarlogical);
+ p.addParameter('ShowMean', false, isscalarlogical);
+ p.addParameter('ShowBox', true, isscalarlogical);
+ p.addParameter('ShowMedian', true, isscalarlogical);
+ p.addParameter('ShowWhiskers', true, isscalarlogical);
+ validSides={'full', 'right', 'left'};
+ checkSide = @(x) any(validatestring(x, validSides));
+ p.addParameter('HalfViolin', 'full', checkSide);
+ validQuartileStyles={'boxplot', 'shadow', 'none'};
+ checkQuartile = @(x)any(validatestring(x, validQuartileStyles));
+ p.addParameter('QuartileStyle', 'boxplot', checkQuartile);
+ validDataStyles = {'scatter', 'histogram', 'none'};
+ checkStyle = @(x)any(validatestring(x, validDataStyles));
+ p.addParameter('DataStyle', 'scatter', checkStyle);
+ p.addParameter('Orientation', 'vertical', @(x) ismember(x, {'vertical', 'horizontal'}));
+ p.addParameter('Parent', gca, @(x) isa(x,'matlab.graphics.axis.Axes'));
+
+ p.parse(data, pos, varargin{:});
+ results = p.Results;
+ end
+
+ function [x, y] = swapOrientationMaybe(obj, x, y)
+ %swapOrientationMaybe swaps the two variables x and y
+ % if Violin.Orientation property set to horizontal.
+ % If orientation is vertical, it returns x and y as is.
+ if strcmp(obj.Orientation, 'horizontal')
+ tmp = x;
+ x = y;
+ y = tmp;
+ end
+ end
+ end
+
+ methods (Static)
+ function [density, value, width] = calcKernelDensity(data, bandwidth, width)
+ if isempty(data)
+ error('Empty input data');
+ end
+ [density, value] = ksdensity(data, 'bandwidth', bandwidth);
+ density = density(value >= min(data) & value <= max(data));
+ value = value(value >= min(data) & value <= max(data));
+ value(1) = min(data);
+ value(end) = max(data);
+ value = [value(1)*(1-1E-5), value, value(end)*(1+1E-5)];
+ density = [0, density, 0];
+
+ % all data is identical
+ if min(data) == max(data)
+ density = 1;
+ value= mean(value);
+ end
+
+ width = width/max(density);
+ end
+ end
+end
+
diff --git a/example2.png b/example2.png
index 392203a..3adfb73 100644
Binary files a/example2.png and b/example2.png differ
diff --git a/readme_figures.m b/readme_figures.m
index ef5a933..87ab7dd 100644
--- a/readme_figures.m
+++ b/readme_figures.m
@@ -11,48 +11,53 @@
ylabel('Fuel Economy in MPG');
xlim([0.5, 7.5]);
-set(gcf,"Units","pixels","Position",[100 100 560 420])
+set(gcf,'Units','pixels','Position',[100 100 560 420])
% save results
-exportgraphics(gcf,"example.png")
+exportgraphics(gcf,'example.png')
%% figure 2
close all
clear vs
-C = colormap("jet");
+C = colormap('jet');
grouporder={'England','Sweden','Japan','Italy','Germany','France','USA'};
% England (just a dot)
-vs = Violin({MPG(strcmp(Origin,'England'))},1);
+vs = Violin({MPG(strcmp(Origin,'England'))},1,'Orientation','horizontal');
% Sweden (different Bandwidths)
vs = Violin({MPG(strcmp(Origin,'Sweden'))},2,...
- "HalfViolin","left",...
- "Bandwidth",3);
+ 'HalfViolin','left',...
+ 'Bandwidth',3,...
+ 'Orientation','horizontal');
vs = Violin({MPG(strcmp(Origin,'Sweden'))},2,...
- "HalfViolin","right",...
- "Bandwidth",12);
+ 'HalfViolin','right',...
+ 'Bandwidth',12,...
+ 'Orientation','horizontal');
% Japan (show quartiles)
vs = Violin({MPG(strcmp(Origin,'Japan'))},3,...
'QuartileStyle','shadow',... % boxplot, none
- 'ShowBox',true);
+ 'ShowBox',true,...
+ 'Orientation','horizontal');
% Italy left: standard
vs = Violin({MPG(strcmp(Origin,'Italy'))},4,...
'QuartileStyle','shadow',... % boxplot, none
'ShowBox',true,...
- "HalfViolin","left");
+ 'HalfViolin','left',...
+ 'Orientation','horizontal');
% Italy right: change color, offset
vs = Violin({MPG(strcmp(Origin,'Italy'))-15},4,...
'QuartileStyle','shadow',... % boxplot, none
'ShowBox',true,...
- "HalfViolin","right",...
- "ViolinColor",{C(7,:)});
+ 'HalfViolin','right',...
+ 'ViolinColor',{C(7,:)},...
+ 'Orientation','horizontal');
% Germany left: standard
@@ -60,37 +65,40 @@
'QuartileStyle','none',... % boxplot, none
'DataStyle', 'none',... % scatter, histogram
'ShowMean',true,...
- "HalfViolin","left");
+ 'HalfViolin','left',...
+ 'Orientation','horizontal');
% Germany histogram
vs = Violin({MPG(strcmp(Origin,'Germany'))},5,...
'QuartileStyle','none',... % boxplot, none
'DataStyle', 'histogram',... % scatter, histogram
'ShowMean',true,...
- "HalfViolin","left");
+ 'HalfViolin','left',...
+ 'Orientation','horizontal');
% France histogram
vs = Violin({MPG(strcmp(Origin,'France'))},6,...
'QuartileStyle','none',... % boxplot, none
'DataStyle', 'histogram',... % scatter, histogram
'ShowMean',false,...
- "HalfViolin","right");
+ 'HalfViolin','right',...
+ 'Orientation','horizontal');
% USA histogram and quartiles
vs = Violin({MPG(strcmp(Origin,'USA'))},7,...
'QuartileStyle','shadow',... % boxplot, none
'DataStyle', 'histogram',... % scatter, histogram
'ShowMean',false,...
- "HalfViolin","right");
+ 'HalfViolin','right',...
+ 'Orientation','horizontal');
% set correct labels
-set(gca,xticklabels=grouporder)
+set(gca,yticklabels=grouporder)
-ylabel('Fuel Economy in MPG');
-xlim([0.5, 7.5]);
-set(gca,"XTickLabelRotation",30)
+xlabel('Fuel Economy in MPG');
+ylim([0.5, 7.5]);
-set(gcf,"Units","pixels","Position",[200 200 560 420])
+set(gcf,'Units','pixels','Position',[200 200 560 420])
% save results
-exportgraphics(gcf,"example2.png")
+exportgraphics(gcf,'example2.png')
\ No newline at end of file
diff --git a/test_cases/testviolinplot.m b/test_cases/testviolinplot.m
index 339632e..851d94c 100644
--- a/test_cases/testviolinplot.m
+++ b/test_cases/testviolinplot.m
@@ -50,14 +50,14 @@ function testviolinplot()
% TEST CASE 6
disp('Test 6: Test plotting only right side & histogram plot, with quartiles as boxplot.');
subplot(2,4,6);
-vs5 = violinplot(MPG, Origin, 'QuartileStyle','boxplot', 'HalfViolin','right',...
+vs6 = violinplot(MPG, Origin, 'QuartileStyle','boxplot', 'HalfViolin','right',...
'DataStyle', 'histogram');
plotdetails(6);
% TEST CASE 7
disp('Test 7: Test plotting only left side & histogram plot, and quartiles as shadow.');
subplot(2,4,7);
-vs5 = violinplot(MPG, Origin, 'QuartileStyle','shadow', 'HalfViolin','left',...
+vs7 = violinplot(MPG, Origin, 'QuartileStyle','shadow', 'HalfViolin','left',...
'DataStyle', 'histogram', 'ShowMean', true);
plotdetails(7);
@@ -65,15 +65,41 @@ function testviolinplot()
% TEST CASE 8
disp('Test 8: Same as previous one, just removing the data of half of the violins afterwards.');
subplot(2,4,8);
-vs5 = violinplot([MPG; 5;5;5;5;5], [Origin; 'test';'test';'test';'test';'test'], 'QuartileStyle','shadow', 'HalfViolin','full',...
+vs8 = violinplot([MPG; 5;5;5;5;5], [Origin; 'test';'test';'test';'test';'test'], 'QuartileStyle','shadow', 'HalfViolin','full',...
'DataStyle', 'scatter', 'ShowMean', false);
plotdetails(8);
-for n= 1:round(length(vs5)/2)
- vs5(1,n).ShowData = 0;
+for n= 1:round(length(vs8)/2)
+ vs8(1,n).ShowData = 0;
end
xlim([0, 9]);
-%other test cases could be added here
+% TEST CASE 9
+disp('Test 9: Test parent property.');
+figure
+for i = 1:4
+ ax(i) = subplot(2,2,i);
+end
+vs9 = violinplot(MPG, Origin,'parent',ax(3));
+title(ax(3),sprintf('Test %02.0f \n',9));
+ylabel(ax(3),'Fuel Economy in MPG ');
+xlim(ax(3),[0, 8]);
+grid(ax(3),'minor');
+set(ax(3), 'color', 'none');
+xtickangle(ax(3),-30);
+fprintf('Test %02.0f passed ok! \n ',9);
+
+% TEST CASE 10
+disp('Test 10: Test of axis orientation.');
+vs10 = violinplot(MPG, Origin,'parent',ax(2),'Orientation','horizontal');
+title(ax(2),sprintf('Test %02.0f \n',10));
+xlabel(ax(2),'Fuel Economy in MPG ');
+ylim(ax(2),[0, 8]);
+grid(ax(2),'minor');
+set(ax(2), 'color', 'none');
+ytickangle(ax(2),0);
+fprintf('Test %02.0f passed ok! \n ',10);
+
+%other test cases could be added here
end
function plotdetails(n)
diff --git a/violinplot.m b/violinplot.m
index 63af839..6cbd860 100644
--- a/violinplot.m
+++ b/violinplot.m
@@ -69,6 +69,9 @@
% Defaults to true
% 'GroupOrder' Cell of category names in order to be plotted.
% Defaults to alphabetical ordering
+% 'Orientation' Orientation of the violin plot.
+% Defaults to 'vertical'.
+% 'Parent' The parent axis of the violin plot.
% Copyright (c) 2016, Bastian Bechtold
% This code is released under the terms of the BSD 3-clause license
@@ -138,8 +141,12 @@
thisData = data.(catnames{n});
violins(n) = Violin({thisData}, n, varargin{:});
end
- set(gca, 'XTick', 1:length(catnames), 'XTickLabels', catnames);
- set(gca,'Box','on');
+ if strcmp(violins(1).Orientation,'vertical')
+ set(violins(1).Parent, 'XTick', 1:length(catnames), 'XTickLabels', catnames);
+ else
+ set(violins(1).Parent, 'YTick', 1:length(catnames), 'YTickLabels', catnames);
+ end
+ set(violins(1).Parent,'Box','on');
return
elseif iscell(data) && length(data(:))==2 % cell input
if not(size(data{1},2)==size(data{2},2))
@@ -164,8 +171,12 @@
thisData = data(cats == thisCat);
violins(n) = Violin({thisData}, n, varargin{:});
end
- set(gca, 'XTick', 1:length(catnames), 'XTickLabels', catnames_labels);
- set(gca,'Box','on');
+ if strcmp(violins(1).Orientation,'vertical')
+ set(violins(1).Parent, 'XTick', 1:length(catnames), 'XTickLabels', catnames);
+ else
+ set(violins(1).Parent, 'YTick', 1:length(catnames), 'YTickLabels', catnames);
+ end
+ set(violins(1).Parent,'Box','on');
return
else
data = {data};
@@ -175,19 +186,31 @@
% 1D data, no categories
if not(hascategories) && isvector(data{1})
violins = Violin(data, 1, varargin{:});
- set(gca, 'XTick', 1);
+ if strcmp(violins(1).Orientation,'vertical')
+ set(violins(1).Parent, 'XTick', 1);
+ else
+ set(violins(1).Parent, 'yTick', 1);
+ end
% 2D data with or without categories
elseif ismatrix(data{1})
for n=1:size(data{1}, 2)
thisData = cellfun(@(x)x(:,n),data,'UniformOutput',false);
violins(n) = Violin(thisData, n, varargin{:});
end
- set(gca, 'XTick', 1:size(data{1}, 2));
+ if strcmp(violins(1).Orientation,'vertical')
+ set(violins(1).Parent, 'XTick', 1:size(data{1}, 2));
+ else
+ set(violins(1).Parent, 'YTick', 1:size(data{1}, 2));
+ end
if hascategories && length(cats) == size(data{1}, 2)
- set(gca, 'XTickLabels', cats);
+ if strcmp(violins(1).Orientation,'vertical')
+ set(violins(1).Parent, 'XTickLabels', cats);
+ else
+ set(violins(1).Parent, 'YTickLabels', cats);
+ end
end
end
-set(gca,'Box','on');
+set(violins(1).Parent,'Box','on');
end