3

I am looking for a robust way to 3D plot particles for which I have the 3D coordinates and radii:

 x=[0 1 1 0 0 1 1 0]';
 y=[0 0 1 1 0 0 1 1]';
 z=[0 0 0 0 1 1 1 1]';
 radius=[0.1 0.5 0.1 1 1 0.4 0.6 0.2]';

I tried using:

 scatter3(x,y,z,4/3*pi.*radius.^3,'filled')

Question: How to plot the particles in a way that guaranties the conservation of the relative size and position (and so when the window size is modified, particles see their size adjusted accordingly with the axis)?

Basically, I would like to get the following graph (which I obtained using Ovito, from a .xyz (text) file I generated (it contains [x;y;z;radius])) in MATLAB, with the possibility to adjust the size of the graph window and still get the proper axis scales with respect to the apparent size of the particles):

3D plot of the particles, the color and the size of which represent the radius. Note that the size of the particles is computed that's to the radius.

gnovice
  • 125,304
  • 15
  • 256
  • 359
LeChat
  • 466
  • 3
  • 18
  • 1) From `scatter3`'s doc, it is also area. 2) It can be done via the figure's `SizeChangedFunction`. But how would you define _relative size_ of the marks? Would it be relative to the figure's height? Width? Area? – Luis Mendo Sep 29 '17 at 21:06
  • By _relative size_ I mean that the marker size is set accordingly with respect to the size of the window, the axis... – LeChat Oct 02 '17 at 20:10

2 Answers2

2

The fourth argument to scatter3 defines the marker area in units of square points (1 point = 1/72 inch), which is relative to the figure/screen size and therefore not related to the axes data units. If you want to define the radius of your particles relative to the positional data (such that a particle with radius 1 at the origin will span [-1 1] in the x, y, and z directions) then scatter3 isn't going to work.

One option is to plot each particle as a sphere using surf, as illustrated here. Here's how you can do this, using your sample data:

% Data and unit sphere surface coordinates:
x = [0 1 1 0 0 1 1 0].';
y = [0 0 1 1 0 0 1 1].';
z = [0 0 0 0 1 1 1 1].';
radius = [0.1 0.5 0.1 1 1 0.4 0.6 0.2].';
[xS, yS, zS] = sphere;

% Plot spheres:
for pt = 1:numel(x)
  surf(x(pt)+xS.*radius(pt), ...  % Shift and scale x data
       y(pt)+yS.*radius(pt), ...  % Shift and scale y data
       z(pt)+zS.*radius(pt), ...  % Shift and scale z data
       'EdgeColor', 'none');
  hold on;
end

% Modify figure and axes:
axis equal
set(gcf, 'Color', 'k');
set(gca, 'Color', 'k', 'Box', 'on', 'BoxStyle', 'full', ...
    'XColor', 'w', 'YColor', 'w', 'ZColor', 'w', 'GridColor', 'none');

And here's the plot this produces:

enter image description here

Notice that the surfaces are colored by default based on height. If you want to color them differently, you can modify the CData and FaceColor properties of the surfaces. For example, you can give each surface a flat color based on its radius value (which will be mapped to the current colormap) by modifying the surf call above to this:

surf(x(pt)+xS.*radius(pt), ...
     y(pt)+yS.*radius(pt), ...
     z(pt)+zS.*radius(pt), ...
     radius(pt).*ones(size(xS)), ...
     'FaceColor', 'flat', 'EdgeColor', 'none');
gnovice
  • 125,304
  • 15
  • 256
  • 359
  • oh ok. Thank you. It is just that if one has a huge number of particles to plot, a for loop is very heavy... (cpu, ram, and GPU wise...) – LeChat Nov 01 '17 at 21:32
  • Yeah, but if you actually need to faithfully represent the particle size relative to the axes data scaling, this is probably how you'll have to do it. You could reduce the computational workload some by making your spheres have fewer facets (for example, use `... = sphere(10);`). – gnovice Nov 01 '17 at 21:49
  • Actually, to reduce the load in memory, one can also add to `...= sphere(10);` the option `'FaceColor','interp'` in the plot of each sphere. However, how to impose that the value of FaceColor relies on the value of the radius of the corresponding sphere? Thanks a lot. – LeChat Nov 19 '17 at 23:28
0

Ok found it. Thanks to @gnovice also (thank you mate). In the following, the color of each particle is chosen according to its radius.

packing. Colors reflect the radius. Here is the code:

figure
[xS, yS, zS] = sphere;
cmap=parula(length(x));
radius_sort = sort(radius);
% Plot spheres:
for pt = 1:numel(x)
    cmap_ind = find(radius_sort == radius(pt));
  hs=surf(x(pt)+xS.*radius(pt), ...  % Shift and scale x data
       y(pt)+yS.*radius(pt), ...  % Shift and scale y data
       z(pt)+zS.*radius(pt), ...  % Shift and scale z data
       'EdgeColor', 'none');
       set(hs,'FaceColor',cmap(cmap_ind(1),:))
  hold on;
end

One can also add the following commands to tune the axis and figure colors:

% Modify figure and axes:
axis equal
set(gcf, 'Color', 'k');
set(gca, 'Color', 'k', 'Box', 'on', 'BoxStyle', 'full', ...
    'XColor', 'w', 'YColor', 'w', 'ZColor', 'w', 'GridColor', 'none');
LeChat
  • 466
  • 3
  • 18
  • I added a slightly easier way to color the surfaces to the end of my answer, which maps `CData` to the colormap instead of using fixed RGB triples. This would allow you to change the colors simply by changing the colormap. – gnovice Nov 20 '17 at 20:26