4
votes

I have an X Y Z dataset. X and Y are the 2D coordinates and Z is the intensity.

I plot the data using the scatter function:

markerSize=100;
scatter(x,y,markerSize,z,'s','fill');

I use the options 's' and 'fill' to get filled squares.

My problem is the markerSize value corresponds to the area of the marker, and its unit is points (1/72 of one inch).

The marker size is constant, even if I resize the figure plot. So that the gap between the data points increases when I increase the figure size.

What I would like is a constant marker size which is a constant of the axis unit. For instance, the marker size should be 5x5 (5 in X axis and 5 in Y axis).

Thanks for your help.

5
wouldn't it be easier to define a certain constant figure size and set the marker size accordingly? I mean how often are you going to resize your figure window? Open it in the size desired and fix it like that. In this case you could also easily make the markersize dependent from the figuresize - but in advance and not by evaluating any callbacks. (which would be possible though)Robert Seifert
I am actually creating a GUI that will be used by different people on different screen resolution. The plot is defined in the GUI (it does not create another figure). I will give a try to a constant figure and see if it can solve it.Dav

5 Answers

4
votes

You want to make the size of markers proportional to the figure size.

The size of markers is controlled by SizeData parameter of the scattergroup object. The size of figure is stored in Position parameter of the figure object. The difficult part is to interactively resize the marker when the figure size is changed. So you need to use ResizeFcn callback and call setmarkersize function that you define.

function [ ] = setmarkersize( src, evnt )

    % # get position of the figure (pos = [x, y, width, height]) 
    pos = get(src, 'Position'); 
    % # get the scattergroup object 
    h = get(get(src,'children'),'children'); 
    % # resize the marker
    relativesize = 0.5;
    set(h,'SizeData', pos(3)*relativesize); 

end
================================================
% # attach the callback to figure 
f = figure('ResizeFcn', @setmarkersize); 
h = scatter(x,y,markerSize,z,'s','fill');
2
votes

You will have to set the marker size manually according to the actual figure size on screen. Using axes property Position you can convert data units to relative figure units. In the next step this size can be converted to the absolute size in points on screen. With that information you can set the marker size accordingly. In the following code snippet I've set the x/y axis limits and width/height of the axis to identical values, because the square marker area can only be calculated reasonably if the marker width is equal to the marker height.

Set marker size relative to data units

% test data
x = [25*rand(1,10) 2.5];
y = [25*rand(1,10) 2.5];
z = [rand(1,10) 0.5];

% relative marker size in squared normalized figure units
marker_rel = 5;

%% Set relative marker size (approximately)
scatter(x,y,100,z,'s','fill');

% Set identical x/y limits and set axes height=widht, so that markers
% really represent squares in data units
xlim([0 25]);
ylim([0 25]);
set(gca, 'Units', 'Points');
axpos_pt = get(gca, 'Position');
axpos_pt = [axpos_pt(1) axpos_pt(2) min(axpos(3:4)) min(axpos(3:4))];
set(gca, 'Position', axpos_pt);

grid on;
grid minor;

% Set marker size relative to data units
markerSize = (marker_rel * axpos_pt(3) / diff(xlim))^2;
set(get(gca, 'children'), 'sizedata', markerSize);

As it turns out, the displayed marker size is slightly smaller than expected. Obviously, there's some bounding box of unknown (at least to me) size, see here.

An alternative approach is to plot rectangles "manually" as shown in the following code snippet (same test data is used). When the figure is resized, the rectangles are resized as well without any special callback function being needed.

Draw rectangles manually

%% Use rectangles (exact)
figure;
axes;
cmp = colormap;
z_range = max(z) - min(z);
line_style = 'none'; % set to '-' to make the edges visible
for k = 1:length(x)
    x_pos = x(k) - marker_rel/2;
    y_pos = y(k) - marker_rel/2;
    w = marker_rel;
    h = marker_rel;
    color = cmp( round(((z(k) - min(z))/z_range)*(length(cmp) - 1)) + 1, : );
        rectangle('Position', [x_pos y_pos w h], 'FaceColor', color,...
          'LineStyle', line_style);
end
grid on;
grid minor;

The above code produces the desired marker size:

Demonstration of rectangles with desired size and color grading

In general, these are no squares. They can (and will) only be squares if xlim = ylim and absolute height of axis = absolute width of axis. I have shown in my first code snippet how to achieve this.

1
votes

I found an answer on the Matlab central forum which does not use the useful dsxy2figxy function. Here is the link to it (link)

The code is the following:

x = rand(1,50);
y = rand(1,50);
s = 5; %Marker width in units of X
h = scatter(x,y); % Create a scatter plot and return a handle to the 'hggroup' object
%Obtain the axes size (in axpos) in Points
currentunits = get(gca,'Units');
set(gca, 'Units', 'Points');
axpos = get(gca,'Position');
set(gca, 'Units', currentunits);
markerWidth = s/diff(xlim)*axpos(3); % Calculate Marker width in points
set(h, 'SizeData', markerWidth^2)
1
votes

The answer by ysakamoto doesn't work in Matlab 2014, where Mathworks changed figure handles from double to object. The following modification makes it work again:

function [ ] = setmarkersize( src, ~ )
% get position of the figure (pos = [x, y, width, height])
pos = get(src, 'Position');
% marker size
relativesize = 0.01;
% axes is not necessarily the only child of figure (e.g. colorbar may be first)
for i = 1:numel(src.Children)
    if strcmpi(src.Children(i).Type, 'axes')
        % make marker size depend on figure width
        src.Children(i).Children.SizeData = pos(3) * relativesize;
        break
    end
end
end

Also, when creating the figure it will not set the marker size to the correct value until resized. So call setmarkersize explicitly:

f = figure('ResizeFcn', @setmarkersize);
...
setmarkersize(f)
0
votes

Consider abandoning 'markers' and drawing directly on the axis using rectangles or circles. The 'rectangle' command with 'curvature' set to [1 1] is essentially an open circle. Units are in plot units, so each rectangle can be scaled:

rectangle('position', [rectCentX rectCentY widthInPlotUnits, heightInPlotUnits],'curvature',[1 1])