How can I change 'PlotStyle' property of a given boxplot figure? - matlab

Given a .fig file of a Matlab boxplot (i.e. underlying data not available), is it possible to change the PlotStyle attribute (from 'traditional' to 'compact')?

This question is kind of tricky because not like other graphic objects in Matlab, boxplot is a group of lines. As so, all the properties that are set while you create it are inaccessible (and in fact does not exist) after plotting.
One option to deal with that is to create a 'dummy' boxplot, and then alter it to your data. Because boxplot has no simple properties of XData and YData, at least not as we use to them, it takes some work to do that.
Here is a short code to demonstrate that:
% this is just to make a figure for example:
X = normrnd(10,1,100,1);
boxplot(X) % this is the 'Traditional' figure that you load
% you start here, after you load your figure:
bx = findobj('tag','boxplot');
% get the properties of the axes:
axlimx = bx.Parent.XLim;
axlimy = bx.Parent.YLim;
% get all needed properties for plotting compact box plot
txt = bx.Parent.XAxis.TickLabels;
med = get(findobj(bx,'tag','Median'),'YData'); % Median
out = get(findobj(bx,'tag','Outliers'),'YData'); % Outliers
box = get(findobj(bx,'tag','Box'),'YData'); % the Box
whis = cell2mat(get([findobj(bx,'tag','Lower Whisker')...
findobj(bx,'tag','Upper Whisker')],'YData')); % Whisker
minmax = #(R) [min(R(:)) max(R(:))]; % helper function
close all
% Now we closed the original figure, and create a new one for manipulation:
boxplot(normrnd(10,1,100,1),'PlotStyle','Compact');
bxc = findobj('tag','boxplot');
% set the properties of the axes:
bxc.Parent.XLim = axlimx;
bxc.Parent.YLim = axlimy;
% set all properties of the compact box plot:
bxc.Children(1).String = txt;
set(bxc.Children(2),'YData',out) % Outliers
set(bxc.Children(3:4),'YData',med(1)) % MedianInner & MedianOuter
set(bxc.Children(5),'YData',minmax(box)) % the Box
set(bxc.Children(6),'YData',minmax(whis)) % Whisker
Another way to alter the boxplot to 'compact' style, is to change the graphics directly. In this case, we don't create a new dummy figure but work on the loaded figure.
Here is a code for that approach:
% this is just to make a figure for example:
X = normrnd(10,1,100,1);
boxplot(X) % this is the 'Traditional' figure that you load
% you start here, after you load your figure:
bx = findobj('tag','boxplot');
minmax = #(R) [min(R(:)) max(R(:))]; % helper function
% get the whisker limits:
whis = cell2mat(get([findobj(bx,'tag','Lower Whisker')...
findobj(bx,'tag','Upper Whisker')],'YData')); % Whisker
set(findobj(bx,'tag','Upper Whisker'),{'YData','Color','LineStyle'},...
{minmax(whis),'b','-'})
% set the median:
set(findobj(bx,'tag','Median'),{'XData','YData','LineStyle','Marker',...
'Color','MarkerSize','MarkerFaceColor'},...
{1,min(get(findobj(bx,'tag','Median'),'YData')),'none','o','b',6,'auto'});
% set the box:
set(findobj(bx,'tag','Box'),{'XData','YData','LineWidth'},...
{[1 1],minmax(get(findobj(bx,'tag','Box'),'YData')),4});
im_med = copyobj(findobj(bx,'tag','Median'),bx);
im_med.Marker = '.';
% set the outliers:
out = get(findobj(bx,'tag','Outliers'),'YData'); % Outliers
set(findobj(bx,'tag','Outliers'),{'XData','LineStyle','Marker','MarkerEdgeColor',...
'MarkerSize','MarkerFaceColor'},{0.9+0.2*rand(size(out)),'none','o','b',4,'none'});
% rotate x-axis labels:
bx.Parent.XAxis.TickLabelRotation = 90;
% delete all the rest:
delete(findobj(bx,'tag','Lower Whisker'))
delete(findobj(bx,'tag','Lower Adjacent Value'))
delete(findobj(bx,'tag','Upper Adjacent Value'))

Related

How to plot in realtime properly? (from a serialport) Matlab

I am reading data from a serialport using callback terminator:
configureCallback(s,"terminator",#readSerialData)
I want to plot this data in real time. So what I do is to use a global structure of arrays where the gathered data is stored in every new row. There are different arrays because I need to classify different sources of data
I use a function "createfigure" inside the #readSerialData, which plots the array of the corresponding source of incoming data.
However, this becomes extremely laggy, so this must not be the right way to do...
Also, in the graphs I want to see all the arrays plotted in once. But the result is that it only plots the last received array on top of the other plots. So it accumulates lines on top of each other, making the axes illegible
function createfigure2(source,X1,Y1)
% X1: vector of x data
% Y1: vector of y data
% Auto-generated by MATLAB on 21-Jan-2021 13:54:53
% Create figure
figure1 = figure(1);
hold on;
% Create axes
axes1 = axes('Parent',figure1,...
'Position',[0.109498680738786 0.69811320754717 0.800791556728232 0.273584905660377]);
hold(axes1,'on');
% Create plot
plot(X1,Y1,'DisplayName',source,'Parent',axes1,'Marker','*','LineWidth',0.01,...
'LineStyle',':');
end
You can create a plot object (parameter returned when calling the plot function) and send it to the callback itself by setting it as a parameter of "BytesAvailableFcn". Then update the plot directly inside the callback.
function mainFunction()
figure1 = figure();
axes1 = axes('Parent',figure1,...
'Position',[0.109498680738786 0.69811320754717 0.800791556728232 0.273584905660377]);
numParams = 4;
plotObjects = cell(1,numParams);
plotObjects{1} = plot(nan,nan,'r-','parent',axes1);
hold(axes1, 'on');
plotObjects{2} = plot(nan,nan,'g-','parent',axes1);
plotObjects{3} = plot(nan,nan,'b-','parent',axes1);
plotObjects{4} = plot(nan,nan,'k-','parent',axes1);
hold(axes1, 'off');
sp = openSerialPort('COM9', plotObjects);
...
end
function sp = openSerialPort(portName, plotObjects)
% intialize your com port here
sp = serial(portName);
sp.BaudRate = 9600;
...
sp.BytesAvailableFcn = {#plotMyData, plotObjects}
end
function plotMyData(sp, ~, plotObjects)
% read the data from the serial port...
[dataVectX, dataVectY] = get_data_from_serial...
% This considers that dataVectX and Y contains the readings of 4...
% different sensors where dataVectX(1) is the first sensor, dataVectX(2) is...
% the second sensor, and so one.
% update data
for paramID = 1:4
plotObjects{paramID}.XData = dataVectX(paramID);
plotObjects{paramID}.YData = dataVectY(paramID);
end
% if you wanted to append the data instead of replacing it you can either use
% plotObjects{paramID}.XData = [plotObjects{paramID}.XData, newXVector]; % use this if x is a vector
% plotObjects{paramID}.YData = [plotObjects{paramID}.YData, newYVector];
% or
% plotObjects{paramID}.XData(end+1) = dataVectX(paramID); % use this if x is a single value
% plotObjects{paramID}.YData(end+1) = dataVectY(paramID);
drawnow();
end

Get currently selected data point from axes in a figure

I have a MATLAB figure with an axes containing a scatter plot.
Every point on this scatter plot has a signal data array associated with it.
I want to take user input as point selection from scatter plot and plot the corresponding signal data in another axes on the same figure.
Combine a global definition of your second axes with an UpdateFcn on the datacursor. See example below, which generates a sine wave based on a random variable selected.
function getSelectedDataPoint()
% create figure
fig = figure;
% make second axes a global to adress in myupdatefcn
global ax2
% define axes
ax1 = axes('parent',fig,'position',[0.05 0.05 0.9 0.4]);
ax2 = axes('parent',fig,'position',[0.05 0.55 0.9 0.4]);
% Random scatter
scatter(ax1,rand(25,1),rand(25,1),25,'filled')
% Set datacursormode to on
dcm_obj = datacursormode(fig);
datacursormode on
% Specify objective function for clock
set(dcm_obj,'UpdateFcn',#myupdatefcn)
% Define objective function
function text = myupdatefcn(~,obj)
text = sprintf('X: %f \n Y: %f',[obj.Position(1),obj.Position(2)]);
% Find corresponding signal
id = find(and(obj.Position(1) == obj.Target.XData,obj.Position(2) ==
obj.Target.YData));
% Do your thing with the signals
x = 0:0.1:100;
y = sin(obj.Target.XData(id)*x);
% plot on second axes
plot(ax2,x,y)
end
end
I am not sure I entirely understand what you want to do, but you might want to use the "Tag" option available for many objects in MATLAB.
Replace line under "Random scatter" by:
% Random scatter
hold(ax1,'on')
scatTag = cell(1,10);
for i = 1:10
scatTag{i} = scatter(ax1,rand(1,1),rand(1,1),25,'filled');
scatTag{i}.Tag = num2str(i);
end
In the data cursor update function, replace the line "id= find(..."
tagname = obj.Target.Tag;
And modify the signal function to point to your target tag, whatever function that is. In my example, you could do this to define the y-values:
y = sin(str2double(tagname)*x);
It will generate another sine wave, based on the tag.
Hope this helps :)

Matlab - refresh 3D volume (slice) with GUI

I would like to refresh my figure (3D, obtained with the matlab slice command) using a slider (GUI) to update the slice position.
Here is my code to create the figure (it works):
% Create figure
Fig_3dview = figure;
Fig_3dview.Name= 'Microstructure viewer';
% hold on;
% The data to be displayed are within "Volume", a n*m*k matrix
Domain_size = size(Volume)
% Initial position of the slices
% Basically, the 6 faces of the volume
xslice = [1,Domain_size(1)];
yslice = [1,Domain_size(2)];
zslice = [1,Domain_size(3)];
% Display all the slices
imslice = slice(Volume,xslice,yslice,zslice,'parent',Fig_3dview);
% Remove voxel edge
set(imslice,'edgecolor','none')
% Current axis label
ax_3d_slice = gca;
% Set axis equal and tight
axis equal tight
hold off;
The code for the GUI, to control the slice position (it doesn't work):
% Create GUI figure
Fig_3dview_GUI = figure;
Fig_3dview_GUI.Name= 'Microstructure viewer 3D GUI';
% Create slider
h_slice1 = uicontrol('Style','slider','BackgroundColor','w',...
'Unit','pixels','Position',[10,200,250,50],'Parent',Fig_3dview_GUI,...
'Callback',{#slider_slice1_Callback});
% Define function associated with h_slice1
function slider_slice1_Callback(source,eventdata)
% Determine the selected data set.
pos_normalized = source.Value;
position_slice_1= round(pos_normalized*Domain_size(1)); % Get closest integer value
position_slice_1=max(1,position_slice_1); % 0 must be avoided
% Update figure
And here I don't know how to update the figure Fig_3dview with the new position for the slice normal to the first direction.
I would like the new figure looks like:
xslice = [position_slice_1,Domain_size(1)];
imslice = slice(Volume,xslice,yslice,zslice,'parent',Fig_3dview);
I do not want to close the figure and regenerate it, I want to refresh it, so that I get a smooth visualization.
% Update figure
end
Thanks for any advice.
François

Colour the tick lables in a dendrogram to match the cluster colours

How can I individually colour the labels of a dendrogram so that they match the colours of the clusters in MATLAB?
Here is an example desired output generated using the code in my answer below (note the lables are just the 50 charater series 'A':'r'):
If there is a more straightforward way to do this, please do post an answer as I was unable to find the solution to this by googling. If not, the code is below for posterity.
I could not find a definitive answer to this but I managed to piece the following together from a couple of ideas (shown in the comments) I found online. Hopefully this is useful to someone.
I'm assuming that your data you are clustering is in the matrix data and that labels are stored in a cell array called labels:
%% Hierarchical clustering
T = linkage(data,'average','spearman');
D = pdist(data, 'spearman');
leafOrder = optimalleaforder(T, D);
th = 0.726;
H = dendrogram(T, 0,'ReOrder', leafOrder, 'Orientation', 'left', 'ColorThreshold', th);
h = gca;
set(h, 'YTickLabel', labels(leafOrder));
%Changing the colours
%First get a list of the colours of each line object
lineColours = cell2mat(get(H,'Color'));
colourList = unique(lineColours, 'rows');
% For each cluster (i.e. for each unique colour)
for colour = 1:size(colourList,1)
% see http://stackoverflow.com/a/16677119/1011724 for the idea of
% copying the axis
ax = copyobj(gca, gcf);
% see http://undocumentedmatlab.com/blog/customizing-axes-rulers for
% more on YRuler. This might not work on older versions of MATLAB.
yruler = ax.YRuler;
rgb = floor(colourList(colour,:)'*255);
% Make all the datalabels of the new axis the current colour. (We will
% later make those labels that aren't this colour empty.)
yruler.TickLabels.ColorData = uint8([rgb;255]);
% Might not be necessary, but stopped me getting errors
pause(0.1)
% The hard bit is figuring out which line object matches which label
% Note that there might be an easier way if you are willing to alter your dendrogram.m file: http://www.mathworks.com/matlabcentral/newsreader/view_thread/134997
idx = ismember(lineColours, colourList(colour,:), 'rows');
clusterNodes = [T(idx,1);T(idx,2)];
% Cluster nodes greater than the number of data points are none terminal
% nodes and thus not of interest.
[~,c]=find(bsxfun(#eq,clusterNodes(clusterNodes < length(labels)+1),leafOrder))
% Convert to a logical index
idx = ~ismember(1:(size(lineColours,1)+1), c);
n = sum(idx);
% Set the labels we don't want to colour (this iteration) to be empty
% char arrays.
yruler.TickLabels.String(idx) = mat2cell(repmat(char(),n,1),zeros(n,1),0);
end

How to programatically update histogram contents and datatip location? (MATLAB hg2)

I am trying to make an animation where several datasets are being cycled through in a histogram plot, and a datatip follows the highest bar in every frame, as demonstrated below:
                          
Here's a code which achieves the desired result using a bar graph:
%% // Initialization
close all force; clear variables; clc;
%% // Generate some data:
indMax = 20; data = randi(indMax,[5,45]);
%% // Generate the 1st values to plot:
edges = 0.5:1:indMax+0.5;
counts = histcounts(data(1,:),edges);
[~,maxInd] = max(counts);
%% // Create the plot and the datatip:
figure(100); hBar = bar(1:indMax,counts);
hDT = makedatatip(hBar,maxInd); hDT = handle(hDT);
grid on; hold on; grid minor; xlim([0,indMax+1]); ylim([0,10]);
%% // Update the figure and the datatip:
for indFrame = 2:size(data,1)
counts = histcounts(data(indFrame,:),edges);
[~,maxInd] = max(counts);
hBar.YData = counts; %// Update bar heights
hDT.Cursor.DataIndex = maxInd; %// Update datatip location
%// Alternatively to the above line: hDT.Position = [newX newY newZ];
java.lang.Thread.sleep(1000);
drawnow;
end
Note that the datatip is created using a modified version of the makedatatip submission from FEX, as per the comment on the submission page (this is true for the 27/06/2012 version of makedatatip):
a couple of changes need to be made to the code:
***********CHANGE 1*********
line 122 needs to be: pos = [X(index(n)) Y(index(n)) 0];
***********CHANGE 2*********
lines 135-141 should be commented OUT
And also Change 3: line 84 to Z = [];
Since makedatatip attempts to acces the 'XData' and 'YData' properties of the input handle, which are absent in histogram plots, it refuses to work. So my question is:
How can datatips be created and updated programmatically in histogram plots (using matlab-hg2), along with the histogram itself?
Turns out the solution is quite straight-forward, at least when only a single datatip is needed. Here are the required steps:
Replace the bar plot with a histogram:
hHist = histogram(data(1,:),edges);
Create the datatip "manually" instead of using makedatatip:
hDataCursorMgr = datacursormode(ancestor(hHist,'figure'));
hDT = createDatatip(hDataCursorMgr,hHist);
Update the position as needed:
hDT.Cursor.DataIndex = maxInd;
To update the histogram's bar heights, it is not possible to update the 'Values' property directly (since it's read-only), so one must update the 'Data' property (and let MATLAB recompute the bar heights on its own):
hHist.Data = data(indFrame,:);
And everything put together:
%% // Initialization
close all force; clear variables; clc;
%% // Generate some data:
indMax = 20; data = randi(indMax,[5,45]);
%% // Generate the 1st values to plot:
edges = 0.5:1:indMax+0.5;
counts = histcounts(data(1,:),edges);
[~,maxInd] = max(counts);
%% // Create the plot and the datatip:
figure(100); hHist = histogram(data(1,:),edges);
hDataCursorMgr = datacursormode(ancestor(hHist,'figure'));
hDT = createDatatip(hDataCursorMgr,hHist); hDT.Cursor.DataIndex = maxInd;
grid on; hold on; grid minor; xlim([0,indMax+1]); ylim([0,10]);
%% // Update the plot and the datatip:
for indFrame = 2:size(data,1)
[~,maxInd] = max(histcounts(data(indFrame,:),edges));
hHist.Data = data(indFrame,:);
hDT.Cursor.DataIndex = maxInd;
java.lang.Thread.sleep(1000);
drawnow;
end
Which results in:
                              
Some notes \ observations:
Datatips can only be added to supported data types, which currently only consist of double values (i.e. plotting something other than double doesn't let you add datatips to it, apparently). This is true for MATLAB 2015a. See another discussion about it here.
If datatips should contain some LaTeX-formatted strings, this Q&A describes what needs to be done.
The gif animations I used were created using this.
To center the animations in the posts, I used a combination of "alt+0160" and "alt+255".