I lo... read more >>

]]>The dodecahedron is a particularly interesting polyhedron. It's full of interesting five-fold symmetries. Let's take a look at a couple of them.... read more >>

]]>The dodecahedron is a particularly interesting polyhedron. It's full of interesting five-fold symmetries. Let's take a look at a couple of them.

First we'll need the vertices. There's an interesting pattern to them.

p = (1+sqrt(5))/2; q = 1/p; verts = [-1 -1 -1; ... -1 -1 1; ... 1 -1 1; ... 1 -1 -1; ... -q 0 -p; ... -p -q 0; ... 0 -p q; ... -p q 0; ... 0 -p -q; ... q 0 -p; ... -q 0 p; ... 0 p q; ... p -q 0; ... 0 p -q; ... p q 0; ... q 0 p; ... -1 1 1; ... -1 1 -1; ... 1 1 -1; ... 1 1 1];

We can draw them with scatter3.

grey = [.25 .25 .25]; scatter3(verts(:,1),verts(:,2),verts(:,3),'filled','MarkerFaceColor',grey) axis vis3d grid off box on

And we can draw the edges like so:

```
s = [1 1 1 2 2 2 3 3 3 4 4 4 5 5 6 7 8 8 10 11 11 12 12 12 13 14 14 15 15 16];
e = [5 6 9 6 7 11 7 13 16 9 10 13 10 18 8 9 17 18 19 16 17 14 17 20 15 18 19 19 20 20];
n = nan(1,30);
lx = [verts(s',1)'; verts(e',1)'; n];
ly = [verts(s',2)'; verts(e',2)'; n];
lz = [verts(s',3)'; verts(e',3)'; n];
l = line(lx(:),ly(:),lz(:),'Color',grey);
```

If you look at the first 4 vertices and the last 4 vertices, you might recognize that there's a cube hiding inside the dodecahedron. Let's draw it.

cols = lines(7); faces = [ 4 19 20 3; ... 19 18 17 20; ... 18 1 2 17; ... 1 4 3 2; ... 2 3 20 17; ... 18 19 4 1]; cube1 = patch('Faces',faces,'Vertices',verts,'FaceColor',cols(1,:));

Since everything in a dodecahedron comes in multiples of 5, lets make 4 more copies of that cube.

g2 = hgtransform; cube2 = patch('Parent',g2,'Faces',faces,'Vertices',verts,'FaceColor',cols(2,:)); g3 = hgtransform; cube3 = patch('Parent',g3,'Faces',faces,'Vertices',verts,'FaceColor',cols(3,:)); g4 = hgtransform; cube4 = patch('Parent',g4,'Faces',faces,'Vertices',verts,'FaceColor',cols(4,:)); g5 = hgtransform; cube5 = patch('Parent',g5,'Faces',faces,'Vertices',verts,'FaceColor',cols(5,:)); set([cube1 cube2 cube3 cube4 cube5],'SpecularStrength',0,'AmbientStrength',.4,'EdgeColor','none'); xlim([-p p]) ylim([-p p]) zlim([-p p]) camlight right

The cube has 4 diagonals which define axes of rotational symmetry. What if we start rotating each of those four extra cubes around one of those axes?

rotax = [1 1 1; ... -1 1 -1; ... -1 -1 1; ... 1 -1 -1]; for ang=linspace(0,2*pi,250) g2.Matrix = makehgtform('axisrotate',rotax(1,:),ang); g3.Matrix = makehgtform('axisrotate',rotax(2,:),ang); g4.Matrix = makehgtform('axisrotate',rotax(3,:),ang); g5.Matrix = makehgtform('axisrotate',rotax(4,:),ang); drawnow end

Something interesting happened at a particular angle in there. Did you see it? All of the vertices of the cubes land on vertices of the dodecahedron. It happens when the angle reaches pi/4.

ang = pi/4; g2.Matrix = makehgtform('axisrotate',rotax(1,:),ang); g3.Matrix = makehgtform('axisrotate',rotax(2,:),ang); g4.Matrix = makehgtform('axisrotate',rotax(3,:),ang); g5.Matrix = makehgtform('axisrotate',rotax(4,:),ang);

It can be a little easier to see some of the symmetry patterns if we make all of the cubes the same color and add some different edges.

set(findobj(gca,'Type','patch'),'FaceColor',cols(5,:)) s = [1 1 1 1 2 2 2 2 2 3 3 3 3 3 4 4 4 5 5 5 5 6 6 6 6 6 7 ... 7 7 8 8 8 9 10 10 10 10 11 11 12 12 12 12 13 13 13 14 14 14 15 16 17 17 18 19]; e = [2 7 8 18 3 8 9 16 17 4 9 11 15 20 7 15 19 6 8 14 19 7 9 11 17 18 11 ... 13 16 11 12 14 13 13 14 15 18 12 20 15 16 18 19 16 19 20 15 17 20 16 17 18 20 19 20]; n = nan(1,55); lx2 = [verts(s',1)'; verts(e',1)'; n]; ly2 = [verts(s',2)'; verts(e',2)'; n]; lz2 = [verts(s',3)'; verts(e',3)'; n]; l.XData = lx2(:); l.YData = ly2(:); l.ZData = lz2(:); l.LineWidth = 3;

There are also tetrahedra hiding inside the dodecahedron. Let's draw one of them.

delete(findobj(gca,'Type','patch')) l.XData = lx(:); l.YData = ly(:); l.ZData = lz(:); l.LineWidth = .5; faces = [2 5 12; ... 2 5 13; ... 2 12 13; ... 5 12 13]; tet1 = patch('Faces',faces,'Vertices',verts); tet1.FaceColor = cols(1,:);

Again, we'll make 5 copies of this.

g2 = hgtransform; tet2 = patch('Parent',g2,'Faces',faces,'Vertices',verts,'FaceColor',cols(2,:)); g3 = hgtransform; tet3 = patch('Parent',g3,'Faces',faces,'Vertices',verts,'FaceColor',cols(3,:)); g4 = hgtransform; tet4 = patch('Parent',g4,'Faces',faces,'Vertices',verts,'FaceColor',cols(4,:)); g5 = hgtransform; tet5 = patch('Parent',g5,'Faces',faces,'Vertices',verts,'FaceColor',cols(5,:)); set([tet1 tet2 tet3 tet4 tet5],'SpecularStrength',0,'AmbientStrength',.4,'EdgeColor','none');

And we'll spin these around four different axes. In this case, the axes of rotation are each through the midpoint of an edge which touches one of the vertices of the tetrahedron.

r = 1+p; rotax = [-1, -r, p; ... -p, 1, -r; ... -1, r, p; ... r, -p, -1]; for ang=linspace(0,2*pi,250) g2.Matrix = makehgtform('axisrotate',rotax(1,:),ang); g3.Matrix = makehgtform('axisrotate',rotax(2,:),ang); g4.Matrix = makehgtform('axisrotate',rotax(3,:),ang); g5.Matrix = makehgtform('axisrotate',rotax(4,:),ang); drawnow end

This one's a bit harder to follow, but there is one particularly interesting angle in this case too. When the angle of rotation reaches pi, all of the vertices of the tetrahedra land on vertices of the dodecahedron.

ang = pi; g2.Matrix = makehgtform('axisrotate',rotax(1,:),ang); g3.Matrix = makehgtform('axisrotate',rotax(2,:),ang); g4.Matrix = makehgtform('axisrotate',rotax(3,:),ang); g5.Matrix = makehgtform('axisrotate',rotax(4,:),ang);

The pattern can be a little hard to see at first, but if we look straight down on one of the faces of the dodecahedron, it jumps out at us.

view(0,60)

If we combine the tetrahedra, we get an interesting shape that was first described by Edmund Hess in 1876, who also described that compound of the five cubes.

set(findobj(gca,'Type','patch'),'FaceColor',cols(5,:)) axis off delete(findobj(gca,'Type','line')) delete(findobj(gca,'Type','scatter'))

One of the unusual features of this shape is that it is an enantiomorph of its dual. Enantiomorphs are shapes which are mirror images of each other. There's another object which is a mirror image of this one. Coxeter called this one the "dextro" version. Here's the other one, which is known as the "laevo" version.

first_ax = gca; first_ax.Position = [.05 .05 .4 .8]; title(first_ax,'Dextro') second_ax = copyobj(first_ax,first_ax.Parent); second_ax.Position = [.55 .05 .4 .8]; delete(findobj(second_ax,'Type','patch')) faces = [8 9 19; 8 16 9; 8 19 16; 9 16 19; ... 5 7 15; 5 17 7; 5 15 17; 7 17 15; ... 3 6 12; 3 10 6; 3 12 10; 6 10 12; ... 1 11 13; 1 14 11; 1 13 14; 11 14 13; ... 2 4 18; 2 20 4; 2 18 20; 4 20 18]; hlaevo = patch('Faces',faces,'Vertices',verts, ... 'FaceColor',cols(5,:),'EdgeColor','none', ... 'SpecularStrength',0,'AmbientStrength',.4, ... 'Parent',second_ax); title(second_ax,'Laevo')

The dextro and laevo versions look similar, but you can't rotate one of them in such a way as to make it match the other one.

If we combine a dextro and a laevo in the same axes, we get another interesting object, but that's a story for another day.

```
hlaevo.Parent = first_ax;
delete(second_ax)
first_ax.Position = [0 0 1 1];
title(first_ax,'')
```

I've always found these 5-way symmetries fascinating. What do you think?

Get
the MATLAB code

Published with MATLAB® R2016a

Another new feature that I really like in R2016a is the upgraded fplot function and all of the new members of the fplot family.... read more >>

]]>Another new feature that I really like in R2016a is the upgraded fplot function and all of the new members of the fplot family.

The fplot function has been around for a long time. The basic idea is that you pass it a function which takes X coordinates as inputs and returns Y coordinates as outputs. Then fplot will use that function to draw a curve.

fplot(@(x) sin(x))

This is really useful for getting a "quick feel" for the shape of a function, or a family of functions.

fplot(@(x) besselj(x,1),[0 2*pi]) hold on fplot(@(x) besselj(x,2),[0 2*pi]) fplot(@(x) besselj(x,3),[0 2*pi]) fplot(@(x) besselj(x,4),[0 2*pi]) fplot(@(x) besselj(x,5),[0 2*pi]) hold off legend show

In addition to functions, if you have the Symbolic Math Toolbox, fplot can also accept symbolic variables now. This gives it even more power. For example, I can reproduce that plot with a single call to fplot by calling besselj with a symbolic variable for the domain and a vector for the order.

syms x fplot(besselj(x,1:5),[0,2*pi]) legend show

The new version of fplot has a lot of nice refinements, such as the nice legend entries in those last two examples.

When we first showed the new fplot to Cleve, he gave it one of his favorite functions.

$$tan(sin(x)) + sin(tan(x))$$

This is what the previous version of fplot did with that.

And here's the R2016a version.

fplot(@(x) tan(sin(x)) + sin(tan(x)))

As you can see, it does a better of resolving the details in those tricky bits. And it labeled the asymptotes for use, although it missed the one at $-\pi/2$. The reason Cleve likes this function is that it's a bit of a torture test!

And we can get even more detail if we zoom in.

xlim(pi/2 + [-.2 .2])

Another big enhancement is that fplot can now do parametric curves as well as plotting Y as a function of X. To do this, we just pass it two functions. The first function takes the parameter value as input and returns X coordinates. The second function takes the parameter value as input and returns Y coordinates.

For example, I can use it to recreate the cubic Bézier curve from this blog post. This is a bit simpler than the way I did it in that post. Especially if you're not familiar with things like Kronecker tensor products. Note, you'll need the placelabel function I wrote in that earlier blog post.

clf pt1 = [ 5;-10]; pt2 = [18; 18]; pt3 = [38; -5]; pt4 = [45; 15]; placelabel(pt1,'pt_1'); placelabel(pt2,'pt_2'); placelabel(pt3,'pt_3'); placelabel(pt4,'pt_4'); xlim([0 50]) axis equal hold on cubic_bezier = @(t,a,b,c,d) a* (1-t).^3 ... + 3*b*t .*(1-t).^2 ... + 3*c*t.^2.*(1-t) ... + d*t.^3 ; fplot(@(t)cubic_bezier(t,pt1(1),pt2(1),pt3(1),pt4(1)), ... @(t)cubic_bezier(t,pt1(2),pt2(2),pt3(2),pt4(2)), ... [0 1]) hold off

But one of the coolest features of the new version of fplot is hiding at the bottom of the doc page. If you look down there, you'll see this:

That's right, there's now a whole family of fplot-like functions!

Let's take a look at some of these new ones.

First there's fplot3. It's used for 3D parametric curves, like the ones I wrote about in these earlier posts on this blog (link1, link2). We just pass it 3 functions, like this:

fplot3(@(t) 3*cos(t)+cos(10*t).*cos(t), ... @(t) 3*sin(t)+cos(10*t).*sin(t), ... @(t) sin(10*t)) daspect([1 1 1])

Here's another of my favorite 3D parametric curves. It's commonly known as the baseball curve.

```
a = .4;
xfcn = @(t)sin(pi/2-(pi/2-a)*cos(t)).*cos(t/2+a*sin(2*t));
yfcn = @(t)sin(pi/2-(pi/2-a)*cos(t)).*sin(t/2+a*sin(2*t));
zfcn = @(t)cos(pi/2-(pi/2-a)*cos(t));
fplot3(xfcn,yfcn,zfcn,[0 4*pi])
axis equal
```

There's also the new fsurf function. This means that instead of calling peaks to generate arrays of data ...

[x,y,z] = peaks;

... and then passing that data to surf, ...

```
surf(x,y,z)
xlim([-3 3])
ylim([-3 3])
title('surf(peaks)')
```

I can just call fsurf with a handle to the peaks function.

```
fsurf(@(x,y) peaks(x,y),[-3 3 -3 3])
title('fsurf(@(x,y) peaks(x,y))')
```

This results in a very similar picture, but look what happens when we zoom in.

It's regenerating the mesh on the fly during the zoom. With the surf command, we'd only get the resolution of our original call to peaks.

This makes the fplot family really useful for exploring a function with pan and zoom.

All of the functions I've used so far have been simple enough to write as anonymous functions. But sometimes you'll want to use more complex functions. For example, I've turned the parametric equation for the Klein bottle into the following set of functions:

```
function x = klein_xfcn(u,v)
mask1 = u<pi;
mask2 = ~mask1;
r = klein_rfcn(u);
x = zeros(size(u));
x(mask1) = 6*cos(u(mask1)).*(1+sin(u(mask1))) + r(mask1).*cos(u(mask1)).*cos(v(mask1));
x(mask2) = 6*cos(u(mask2)).*(1+sin(u(mask2))) + r(mask2).*cos(v(mask2)+pi);
```

```
function y = klein_yfcn(u,v)
mask1 = u<pi;
mask2 = ~mask1;
r = klein_rfcn(u);
y = zeros(size(u));
y(mask1) = 16*sin(u(mask1)) + r(mask1).*sin(u(mask1)).*cos(v(mask1));
y(mask2) = 16*sin(u(mask2));
```

```
function z = klein_zfcn(u,v)
r = klein_rfcn(u);
z = r.*sin(v);
```

```
function r = klein_rfcn(u)
r = 4*(1-cos(u)/2);
```

And now I can use fsurf to make a Klein bottle.

h = fsurf(@klein_xfcn,@klein_yfcn,@klein_zfcn,[0 2*pi 0 2*pi]); camlight axis equal title('Klein Bottle')

And we can get a better idea of its shape by making it partially transparent.

h.EdgeColor = 'none'; h.FaceColor = [.929 .694 .125]; h.FaceAlpha = .5; set(gca,'Projection','perspective')

There are a lot of goodies to play with in these functions, aren't there? And we haven't even talked about fmesh and fcontour.

Get
the MATLAB code

Published with MATLAB® R2016a

One of the features I love in R2016a is the new yyaxis function. This is a new way to create plots which have two independent Y's, like the older plotyy function. Let's take a look at how yyaxis works, and why I think it's cool.... read more >>

]]>One of the features I love in R2016a is the new yyaxis function. This is a new way to create plots which have two independent Y's, like the older plotyy function. Let's take a look at how yyaxis works, and why I think it's cool.

First we'll need some data. This type of chart is really useful for looking for relationships between data which has wildly different values. For example, I'm interested in the relationship between the price of gas and how much people drive. The Federal Reserve Bank of St. Louis maintains a great collection of historical records of all sorts of economic data like this. I can get the price of what they call "U.S. Gulf Coast, Regular" from this page, and load it into MATLAB using readtable.

tgas = readtable('FRED_DGASUSGULF.xls','Range','A11:B7777'); tgas.observation_date = datetime(tgas.observation_date,'InputFormat','MM/dd/yyyy');

In the same way, I can get Vehicle Miles Traveled from this page.

tnadjmiles = readtable('FRED_TRFVOLUSM227NFWA.xls','Range','A11:B563'); tnadjmiles.observation_date = datetime(tnadjmiles.observation_date,'InputFormat','MM/dd/yyyy');

Now we're ready to start making our plot. If you're used to plotyy, then you would probably think that we need to collect all of our data together and pass it into a special plotting command. But that's not how yyaxis works. Instead, I use the yyaxis function to "select a side of the axes".

```
yyaxis left
```

Notice that when I did that, I got a YAxis on each side. A blue one on the left, and a red one on the right.

Now I can use any of the regular MATLAB charting commands to insert a chart into that "side" of the axes. Here I'll use plot and ylabel to create a line chart from my gas prices with units labeled on the YAxis. Notice that the plot is colored to match the YAxis on the left.

```
plot(tgas.observation_date,tgas.DGASUSGULF)
ylabel('Dollars per Gallon')
```

Now we can call yyaxis again to switch to the other side, and plot and label my mileage. Notice that this new plot gets the red color of the YAxis on the right.

yyaxis right plot(tnadjmiles.observation_date,tnadjmiles.TRFVOLUSM227NFWA) ylabel('Millions of Miles')

We now have the basic chart I wanted, but we have more historical data for the right side than we do for the left side. That doesn't help us look for correlations, so I want to trim it off. To do that, I can switch back to the left side. If I look in the Children property of the Axes, I'll find the plot of gas prices. I can get the limits of its XData and use that as the XLim of the axes. %

yyaxis left h = get(gca,'Children'); xlim([min(h.XData), max(h.XData)])

Notice that the call to XLim affects both sides of the Axes. That's because there's only one XAxis.

ax = gca; ax.XAxis

ans = NumericRuler with properties: Limits: [725525 736396] Scale: 'linear' Exponent: 0 TickValues: [1x10 double] TickLabelFormat: '%g' Use GET to show all properties

But if we look at YAxis, we'll see that it's an array of 2.

ax.YAxis

ans = 2x1 NumericRuler array: NumericRuler NumericRuler

This makes it easy to customize all of the tick formats. For example, I'd really like to have the same number of decimal places on all of the Y ticks on the left.

```
ax.YAxis(1).TickLabelFormat = '%.2f';
```

And comma separated integers on the right.

```
ax.YAxis(2).TickLabelFormat = '%,d';
ax.YAxis(2).Exponent = 0;
```

One problem with the mileage data is that it has a lot of seasonal variation. That's because Americans generally drive more in the summer months than they do in the winter. This can make it hard to compare to the gas price data, especially if we zoom in to look at details.

xlim([datenum(datetime(2006,1,1)), datenum(datetime(2011,1,1))])

We could perform a low-pass filter on the data, but the Federal Reserve Bank has already done that for us, so we can just download an additional dataset.

tmiles = readtable('FRED_TRFVOLUSM227SFWA.xls','Range','A11:B563'); tmiles.observation_date = datetime(tmiles.observation_date,'Format','MM/dd/yyyy');

Now we want to add this to our Axes. With plotyy, that would be a little tricky. But with yyaxis, I can just switch to the right side, use hold, and plot the new data.

yyaxis right hold on plot(tmiles.observation_date,tmiles.TRFVOLUSM227SFWA) hold off xlim([datenum(datetime(2006,1,1)), datenum(datetime(2011,1,1))])

Notice that this new curve is the same red color, but it gets a new line style so we can tell the two plots apart.

legend('Gas price','Miles traveled','Miles/seasonally adjusted')

If I delete the unadjusted mileage data and zoom out, we can see that the Fed's adjusted data only goes back to 2000.

delete(ax.Children(2)) h = ax.Children(1); xlim([min(h.XData), max(h.XData)]) h.LineStyle = '-'; legend hide

It does look like there's a relationship between the two, but it's important to remember that there was a major recession in this same period. Perhaps that had more effect on the miles traveled than the price of gas did. Let's go back to the FRED data and download the Civilian Unemployment Rate from this page.

unrate = readtable('FRED_UNRATE.xls','Range','A11:B829'); unrate.observation_date = datetime(unrate.observation_date,'InputFormat','MM/dd/yyyy');

And then we'll go to the left side and replace gas prices with unemployment rate.

yyaxis left plot(unrate.observation_date,unrate.UNRATE) ylabel('Unemployment Rate') ax.YAxis(1).TickLabelFormat = '%.1f\%'; xlim([min(h.XData), max(h.XData)])

That pairing looks there might be a relationship, doesn't it? At this point, we're going to want to start statistical analysis of the relationship, but I'm going to skip that. I just wanted to show you how handy the new yyaxis function is for comparing datasets like this.

There's another important difference in yyaxis that I can't really show in a blog post. When I pan an Axes that was created by plotyy, everything pans in lockstep. When I pan an Axes that was created by yyaxis, the XLim of the two sides move together, but only the YLim of the "current" side changes. I found this feature very useful for looking for correlations between these datasets, but we'd really like to hear what you think of the change.

Get
the MATLAB code

Published with MATLAB® R2016a

NOAA maintains a number of buoys that collect weather data, and they publish the data from them on their website.... read more >>

]]>NOAA maintains a number of buoys that collect weather data, and they publish the data from them on their website.

There are four of these buoys just outside Boston harbor. They are the red squares on this map.

For example, the data for buoy 44013, which is the bottom one on that map, is available at this URL.

I downloaded the data for 2015, and I can read it into MATLAB like this:

t=readtable('44013c2015.txt','Format','%d%d%d%d%f%f%f%f%f%f','HeaderLines',2); t.Properties.VariableNames = {'YY','MM','DD','hh','mm','WDIR','WSPD','GDR','GST','GTIME'};

We can see that there are 52,460 rows in this table. That's one measurement every ten minutes for the entire year.

size(t) t(3805:3810,:)

ans = 52460 10 ans = YY MM DD hh mm WDIR WSPD GDR GST GTIME ____ __ __ __ __ ____ ____ ___ ____ _____ 2015 1 27 9 0 19 20.7 999 99 9999 2015 1 27 9 10 17 22.3 999 99 9999 2015 1 27 9 20 18 21.6 999 99 9999 2015 1 27 9 30 17 21.5 999 99 9999 2015 1 27 9 40 14 20.9 999 99 9999 2015 1 27 9 50 13 21.1 16 28.8 946

I'm going to convert the date information into datetime to make it easier to work with.

timestamp = datetime(t.YY,t.MM,t.DD,t.hh,t.mm,0);

Now I can plot the wind direction and speed using the new polarplot command that was just introduced in R2016a. Note that polarplot wants angles in radians, but we have degrees. That means that we'll want to use the deg2rad function.

h = polarplot(deg2rad(t.WDIR),t.WSPD);

This looks a lot like the old polar function that has been in MATLAB for years, but there are some important differences. It created a line object, but if you look closely you can see that it's not quite like the line object you're used to. Notice that instead of XData and YData properties, it has properties named ThetaData and RData. When you use the older polar function, if you want to get the data from the line object, you need to convert it back to theta and r yourself by calling cart2pol. With polarplot, the line object takes care of that for you.

h

h = Line with properties: Color: [0 0.4470 0.7410] LineStyle: '-' LineWidth: 0.5000 Marker: 'none' MarkerSize: 6 MarkerFaceColor: 'none' ThetaData: [1x52460 double] RData: [1x52460 double] ZData: [1x0 double] Use GET to show all properties

And take a look at the axes it created.

ax = gca

ax = PolarAxes with properties: ThetaLim: [0 360] RLim: [0 25] ThetaAxisUnits: 'degrees' ThetaDir: 'counterclockwise' ThetaZeroLocation: 'right' Use GET to show all properties

This is a new PolarAxes, and it has a bunch of properties which are useful here. For example, since the wind direction is in compass points, I want 0 to be north, the values to proceed in a clockwise direction, and the values displayed in degrees. I can do that by just setting these 3 properties.

ax.ThetaAxisUnits = 'degrees'; ax.ThetaZeroLocation = 'top'; ax.ThetaDir = 'clockwise';

We can even switch over to compass points if we'd like:

ax.ThetaTick=0:45:360; ax.ThetaTickLabels={'N','NE','E','SE','S','SW','W','NW','N'};

ax.ThetaTick=0:22.5:360; ax.ThetaTickLabels={'N','NNE','NE','ENE','E','ESE','SE','SSE','S','SSW','SW','WSW','W','WNW','NW','NNW','N'};

And I can customize the ThetaAxis or RAxis, just the way I'm used to customizing the XAxis or YAxis on a "regular" Axes object.

```
ax.RAxisLocation = 90;
ax.RAxis.Color = [1/2 1/2 1/2];
ax.RAxis.Label.String = 'Wind Speed';
```

But that plot is really not very useful, is it? What if we looked at just one day? I'm going to pick January 27th, because we had a really big storm that day.

mask = timestamp>=datetime(2015,1,27) & timestamp<datetime(2015,1,28); h.ThetaData = deg2rad(t.WDIR(mask)); h.RData = t.WSPD(mask);

You can see we had 20 knot winds coming out of the northeast. We call that type of storm a nor'easter in Boston, and they're usually the dangerous ones. This one dropped 22 inches of snow on us.

text(3*pi/16,21,'Nor''easter','FontSize',10,'FontWeight','bold','Color',[3/8 3/8 3/8])

That was the start of a really awful stretch of record breaking snow last winter. Here's what the next couple of weeks looked like.

hold on for d=datetime(2015,1,28)+days(1:13) mask = timestamp>=d & timestamp<(d+days(1)); polarplot(deg2rad(t.WDIR(mask)),t.WSPD(mask)); end

Now I'd like to animate the plot and see the entire year. I could just set the ThetaData and RData in a loop, but I'd like my animation to look a bit smoother.

To make the animation look smooth, I'm going to draw 6 days at a time, and fade the older days out. I can do the fade by creating 6 line objects.

cla m = 'o'; th = 0:pi/4:2*pi; r = ones(size(th)); h = [polarplot(th,r ,m), polarplot(th,r+1,m), polarplot(th,r+2,m), ... polarplot(th,r+3,m), polarplot(th,r+4,m), polarplot(th,r+5,m)];

Now I'll set the colors so they fade between white for my oldest data and blue for my newest.

for i=1:6 s = (i-1)/5; set(h(i),'Color',hsv2rgb(.5661,s,1)); end

And I'll lock the RLim, so the plot doesn't bounce around during the animation.

ax.RLim = [0, max(t.WSPD)];

Now I'm ready to animate.

For each day, I copy to data from each line object to the next older one, and then add the data for the new day.

for d=datetime(2015,1,1):datetime(2015,12,31) % Roll the old data down the line for i=1:5 h(i).ThetaData = h(i+1).ThetaData; h(i).RData = h(i+1).RData; end % Add the current day's data mask = timestamp>=d & timestamp<(d+days(1)); h(end).ThetaData = deg2rad(t.WDIR(mask)); h(end).RData = t.WSPD(mask); % Update the title title(datestr(d)) % Tell the graphics system to draw drawnow end

I've been enjoying the new polarplot function, but I should mention that not all of the graphics commands work with PolarAxes yet. You're basically limited to lines and text for the moment. If you need to put other things in your polar plots, the existing polar, pol2cart, and cart2pol functions will be sticking around, so you can continue to do things the old way.

And speaking of cool new features in R2016a, be sure to check out the new Live Editor. I've uploaded some of my favorite blog posts to the File Exchange as Live Scripts. Here's a link so you can download them and try them out.

Get
the MATLAB code

Published with MATLAB® R2016a

Recently I heard from a MATLAB user who was trying to draw tubes along a curve using this blog post I wrote a while back. Unfortunately her curve was a bit more complex than the ones I used in that post. That approach of sweeping a 2D shape along a... read more >>

]]>Recently I heard from a MATLAB user who was trying to draw tubes along a curve using this blog post I wrote a while back. Unfortunately her curve was a bit more complex than the ones I used in that post. That approach of sweeping a 2D shape along a curve has a number of interesting edge cases that you'll encounter when your curves are complex.

In particular, her curve actually had a "fork" in it. This is a particularly tough case for the sweep approach. You basically need to sweep each of the two halves of the fork, and then slice off the parts of one sweep that are inside the other one. The math for this gets rather tricky.

Luckily there's another approach to this problem. This one is fairly compute intensive, but it's quite a bit simpler to implement. It's called signed distance fields. Let's take a look at how you would solve this problem using an SDF.

First we'll need some sample data. Here's one I made. I have 4 vertices.

verts = [-1 0 0; ... 1/2 0 0; ... 2 3/4 0; ... 2 -3/4 0]; radius = .5;

And I have 3 line segments which connect the four vertices.

segments = [1 2; 2 3; 2 4];

Let's draw the segments.

for i=1:size(segments,1) line(verts(segments(i,:),1),verts(segments(i,:),2),verts(segments(i,:),3)) end box on daspect([1 1 1]) view(3)

So we want a tube-like surface which follows those line segments, and smoothly blends between two which fork off to the right.

The first thing we'll do is build a 3D grid over the space we're interested in. The bigger you make the grid, the better your surface will be, but the amount of memory you’re using will climb quickly.

[y,x,z] = ndgrid(linspace(-1.25,1.25,90),linspace(-1,2.25,90),linspace(-0.75,0.75,45));

And now we'll create a 4th array to hold our signed distance field.

d = zeros(size(z));

Now we loop over all of the grid points. At each point in the grid, we'll loop over all of the line segments and calculate the distance from the grid point to the closest point on the line segment. We'll keep the smallest distance and save it in d.

for i=1:numel(z) p = [x(i),y(i),z(i)]; % Intialize our distance to inf. closest = inf; % Loop over all of the line segments. for j=1:size(segments,1) q1 = verts(segments(j,1),:); q2 = verts(segments(j,2),:); % For line segment q1 + t*(q2-q1), compute the value of t where % distance to p is minimized. Clamp to [0 1] so we don't go off % the end of the line segment. invlen2 = 1/dot(q2-q1,q2-q1); t = max(0,min(1,-dot(q1-p,q2-q1)*invlen2)); v = q1 + t*(q2-q1); % Is that the smallest we've seen for this grid element? closest = min(closest, norm(v-p)-radius); end % Insert the distance into the array. d(i) = closest; end

Notice that the value I saved into the array d was the distance from that grid point to a line segment, minus the radius of the surface at that point on the line segment. That means that anywhere in our grid that d is less than 0 is inside the tube, and anywhere that d is greater than 0 is outside the tube. As we saw in this post about implicit surfaces, this is a job for the isosurface function.

isosurface(x,y,z,d,0) camlight

And we can see that this surface lines up nicely with our original model, and does a nice job of blending that fork.

Let's delete the lines and change the view so we can see inside the fork. See how tidy that intersection is? That would be hard to do with the other approach.

Also notice how it rounds off the ends. We can see inside because I made the grid pretty tight. If my grid had been a bit larger, then each of the three ends would be capped by a hemisphere. This is characteristic of the signed distance fields approach.

delete(findobj(gca,'Type','line')) view([41 18])

But what if we had a different radius at each vertex, and wanted the tube to interpolate between the radii at the end of each segment?

Let's say that we wanted the following radii at each vertex.

radii = [.5 .45 .125 .25]; cla for i=1:size(segments,1) line(verts(segments(i,:),1),verts(segments(i,:),2),verts(segments(i,:),3)) end [xs,ys,zs] = sphere; for i=1:size(verts,1) surface(verts(i,1)+radii(i)*xs, ... verts(i,2)+radii(i)*ys, ... verts(i,3)+radii(i)*zs, ... 'FaceColor','yellow','EdgeColor','none'); end camlight

This is a little trickier, but it's basically just a matter of changing our distance function. We can no longer use the simple formula for the distance between a point and the centerline.

I found a distance function that will do the job in this paper:

Fast distance computation between a point and cylinders, cones, line swept spheres and cone-spheres |

Aurelien Barbier and Eric Galin |

LIRIS - CNRS |

Universite Claude Bernard Lyon 1 |

69622 Villeurbanne Cedex, France |

The authors call this primitive with a centerline and a radius at each end the "cone-sphere" primitive.

This distance equation is more complex, so I'll precompute some terms to help with performance.

nsegments = size(segments,1); invlen2 = zeros(1,nsegments); trange = zeros(nsegments,2); adj_radii = zeros(nsegments,2); for i=1:nsegments q1 = verts(segments(i,1),:); q2 = verts(segments(i,2),:); len2 = dot(q2-q1,q2-q1); invlen2(i) = 1/len2; r1 = radii(segments(i,1)); r2 = radii(segments(i,2)); delta = r1-r2; s = sqrt(len2 - delta^2); dl = delta * invlen2(i); sl = s * sqrt(invlen2(i)); trange(i,1) = r1 * dl; trange(i,2) = 1 + r2 * dl; adj_radii(i,1) = r1 * sl; adj_radii(i,2) = r2 * sl; end

Now we can repeat our loop with this new distance formula.

for i=1:numel(z) p = [x(i),y(i),z(i)]; closest = inf; for j=1:size(segments,1) q1 = verts(segments(j,1),:); q2 = verts(segments(j,2),:); r1 = radii(segments(j,1)); r2 = radii(segments(j,2)); t = -dot(q1-p,q2-q1)*invlen2(j); if t<trange(j,1) v = q1; r = r1; elseif t>trange(j,2) v = q2; r = r2; else v = q1 + t*(q2-q1); adj_t = (t-trange(j,1)) / (trange(j,2)-trange(j,1)); r = adj_radii(j,1) + adj_t*(adj_radii(j,2)-adj_radii(j,1)); end closest = min(closest, norm(v-p)-r); end d(i) = closest; end

And then we can use isosurface, just like we did before.

isosurface(x,y,z,d,0)

See how the surface lines up with the spheres? Now we'll delete them to get a better look.

delete(findobj(gca,'Type','line')) delete(findobj(gca,'Type','surface'))

Here's another isosurface I made from these 6 edges of a tetrahedron using this technique.

verts = randn(4,3); segments = nchoosek(1:4,2); radii = 5/8 * rand(1,4);

That took quite a while to compute, but it would have been awful tricky to draw it with the sweep approach.

Whenever you want to wrap a surface around a complex shape, then signed distance functions are a technique that you should consider.

Get
the MATLAB code

Published with MATLAB® R2015b

One type of question that I'm often asked is about how to use various visualization techniques with what is sometimes called "scatter data". This is data that is a list of individual measurements. These measurements often have locations associated with them, but that's not enough for many visualization techniques. These... read more >>

]]>One type of question that I'm often asked is about how to use various visualization techniques with what is sometimes called "scatter data". This is data that is a list of individual measurements. These measurements often have locations associated with them, but that's not enough for many visualization techniques. These techniques also want grid information. This is information about how the measurements connect to each other. Let's take a look at a couple of ways in which we can get this grid information.

First we'll need some example data. Here I have 250 measurements. Each one has a 2D location and a value.

```
npts = 250;
rng default
x = 2*randn(npts,1);
y = 2*randn(npts,1);
v = sin(x) .* sin(y);
```

Scatter data, which is sometimes known as column data, gets its name from the fact that the scatter plot is the one visualization technique which is really designed for this type of data.

```
scatter(x,y,36,v,'filled')
colorbar
xlim([-2*pi 2*pi])
ylim([-2*pi 2*pi])
```

You can sort of see the pattern of v in that picture. If you squint hard enough. But it'd be a lot nicer if we had colors in between the dots. The pcolor function can draw that kind of picture, but it needs a particular type of input known as a "structured grid". We can create this type of grid by using the meshgrid function.

[xg,yg] = meshgrid(linspace(-2*pi,2*pi,125)); size(xg) size(yg)

ans = 125 125 ans = 125 125

But that's just the grid. That's not enough. We also need to get our values onto the grid. One good way to do this is with scatteredInterpolant.

F = scatteredInterpolant(x,y,v); vg = F(xg,yg); h = pcolor(xg,yg,vg);

We don't really want to see the grid here, so I'll turn it off.

```
h.EdgeColor = 'none';
```

And add a colorbar so we can see what values we're getting.

colorbar

That looks pretty good, but not great. For one thing, the range of values is way too large. We can't possibly have sin(x)*sin(y) reaching values less than -2, can we?

caxis([-1 1])

And while the middle of the picture is pretty good, look at that big yellow blob in the upper left and the big blue blob on the bottom. Where did those come from?

Both of these issues are because out at the edges of the grid scatteredInterpolant is extrapolating past the values we gave it, rather than interpolating between them. Extrapolation is almost always a fairly dicey operation. To do it successfully, you really need to know a lot about the data you're working with, and use a lot of care in choosing your extrapolation function.

Often it's better to just discard the parts of the grid which require extrapolation. That's actually pretty easy to do. We just need to insert nans into the values at those points on the grid. We can find those points on the grid by using convhull and inpolygon.

```
p = convhull(x,y);
xp = x(p);
yp = y(p);
inmask = inpolygon(xg(:),yg(:), xp,yp);
vg(~inmask) = nan;
h = pcolor(xg,yg,vg);
h.EdgeColor = 'none';
xlim([-2*pi 2*pi])
ylim([-2*pi 2*pi])
caxis([-1 1])
colorbar
```

I could use either the boundary function or the alphaShape function instead of convhull here.

As you can see, boundary will be more aggressive about removing portions of the grid which are far away from any sample points. It also has a parameter which lets you adjust that.

```
b = boundary(x,y);
inmask = inpolygon(xg(:),yg(:), x(b),y(b));
vg = F(xg,yg);
vg(~inmask) = nan;
h = pcolor(xg,yg,vg);
h.EdgeColor = 'none';
xlim([-2*pi 2*pi])
ylim([-2*pi 2*pi])
caxis([-1 1])
colorbar
```

Another thing that is very important when you're gridding scatter data is choosing the right grid resolution. The scatteredInterpolant is going to work best when the resolution of the grid is close to the spacing of your original sample points. A grid that's too fine usually isn't a big problem (except for the amount of memory you consume), but if your grid is much coarser than the spacing of the input points, then you may see aliasing artifacts. That's because the scatteredInterpolant doesn't really low-pass filter for you. This issue can become quite challenging when the spacing between your sample points varies widely. A good grid resolution for one area of your data might not be appropriate for another area.

But the "structured grid" isn't the only type of grid available to us. There's also what's known as an "unstructured grid". In a structured grid, we just lay out the values in a 2D array, and know that each one is connected to its immediate neighbors. In an unstructured mesh, we'll keep our columns of data values, and add an extra variable that lists how they're connected.

BTW, I've always thought this naming convention was a little silly because the whole point of an unstructured grid is that you have to spell out the structure in detail. The word "unstructured" doesn't seem like a good description of that.

The simplest type of unstructured grid is a "triangle mesh". In a triangle mesh, we have Nx3 array which is a list of triangles which each connect three of the original points. One easy way to create a triangle mesh is to use the delaunayTriangulation function.

dt = delaunayTriangulation(x,y);

Once we have our triangle mesh, we can draw it using the trisurf function.

```
h = trisurf(dt.ConnectivityList,x,y,zeros(npts,1),v);
h.FaceColor = 'interp';
view(2)
xlim([-2*pi 2*pi])
ylim([-2*pi 2*pi])
caxis([-1 1])
colorbar
```

Again, I'll hide the grid.

```
h.EdgeColor = 'none';
```

There are some advantages to each of these two approaches.

The triangle mesh is nice and compact, and it often has fewer gridding artifacts. In particular, it naturally handles that case where the spacing between the points varies a lot by placing more triangles where the data is sampled more finely.

On the other hand, MATLAB has more visualization techniques which work on a structured grid. For example, I could use one of the contour functions on the structured grid, but MATLAB doesn't come with a contour function that will work on triangle meshes, although you can find some on the File Exchange.

These two examples are just an introduction to the powerful tools that MATLAB provides for gridding scatter data. If you find one of them useful, you might want to explore the different options that scatteredInterpolant accepts, and some of the other functions that are available. And you might also want to start a conversation on MATLAB Answers. There are a number of people there who have experience with gridding different types of data.

Get
the MATLAB code

Published with MATLAB® R2015b

Last time, when I was talking about permutohedra, we saw how truncated octahedra fill 3D space with no gaps. There are a number of shapes with this property, and they have the lovely old family name convex uniform honeycomb. There's another interesting family that is related to the honeycombs. They're... read more >>

]]>Last time, when I was talking about permutohedra, we saw how truncated octahedra fill 3D space with no gaps. There are a number of shapes with this property, and they have the lovely old family name convex uniform honeycomb. There's another interesting family that is related to the honeycombs. They're called apeirohedra or "infinite skew polyhedra". You make the apeirohedra by removing some of the faces of a honeycomb. This gives you an infinite set of faces that divide 3D space into two halves. These two halves pass through each other in very interesting and complicated ways.

Today I'm going to take a look at the simplest of these. It's called the mucube. It's basically an infinite stack of cubes with half of the faces removed. I can draw a tiny piece of it like this:

x = []; y = []; z = []; for i=1:5 for j=1:5 for k=1:5 c = zeros(1,4); if mod(i+j,2) x(end+1,:) = [i i+1 i+1 i]; y(end+1,:) = [j j j+1 j+1]; z(end+1,:) = [k k k k]; end if mod(j+k,2) x(end+1,:) = [i i i i]; y(end+1,:) = [j j+1 j+1 j]; z(end+1,:) = [k k k+1 k+1]; end if mod(k+i,2) x(end+1,:) = [i i i+1 i+1]; y(end+1,:) = [j j j j]; z(end+1,:) = [k k+1 k+1 k]; end end end end c = zeros(size(z')); patch(x',y',z',c,'FaceColor',[.929 .694 .125]) view(3)

But it's very hard to figure out what we're seeing here, isn't it? Let's try again with a different colors for different faces.

cla ax = gca; cols = ax.ColorOrder; colormap(ax,cols); x = []; y = []; z = []; fc = []; for i=1:5 for j=1:5 for k=1:5 c = zeros(1,4); if mod(i+j,2) x(end+1,:) = [i i+1 i+1 i]; y(end+1,:) = [j j j+1 j+1]; z(end+1,:) = [k k k k]; fc(1,end+1,:) = cols(1+mod(k,2),:); end if mod(j+k,2) x(end+1,:) = [i i i i]; y(end+1,:) = [j j+1 j+1 j]; z(end+1,:) = [k k k+1 k+1]; fc(1,end+1,:) = cols(3+mod(i,2),:); end if mod(k+i,2) x(end+1,:) = [i i i+1 i+1]; y(end+1,:) = [j j j j]; z(end+1,:) = [k k+1 k+1 k]; fc(1,end+1,:) = cols(5+mod(j,2),:); end end end end patch(x',y',z',fc)

That helps a little, but not much. The problem is that we're trying to stand outside the mucube and look in. We really can't do that because the mucube is infinite. To really understand it, we're going to have to go inside.

Let's make a bigger one, and go in.

cla x = []; y = []; z = []; fc = []; for i=1:25 for j=1:25 for k=1:25 c = zeros(1,4); if mod(i+j,2) x(end+1,:) = [i i+1 i+1 i]; y(end+1,:) = [j j j+1 j+1]; z(end+1,:) = [k k k k]; fc(1,end+1,:) = cols(1+mod(k,2),:); end if mod(j+k,2) x(end+1,:) = [i i i i]; y(end+1,:) = [j j+1 j+1 j]; z(end+1,:) = [k k k+1 k+1]; fc(1,end+1,:) = cols(3+mod(i,2),:); end if mod(k+i,2) x(end+1,:) = [i i i+1 i+1]; y(end+1,:) = [j j j j]; z(end+1,:) = [k k+1 k+1 k]; fc(1,end+1,:) = cols(5+mod(j,2),:); end end end end patch(x',y',z',fc) axis off equal view(3)

We need to do a few things to move the camera into the mucube.

We need to set the Projection to perspective.

I'm also going to set the CameraViewAngle because the default of 7 degrees would give us a very narrow view.

We'll also turn Clipping off and let the picture fill the entire figure.

Once I've done all that, I can move the CameraPosition into the mucube, and set the CameraTarget to be a short ways away in the direction we want to look.

ax.Projection = 'perspective'; ax.CameraViewAngle = 30; ax.Clipping = 'off'; position = [14.5 14.5 14.5]; ax.CameraPosition = position; ax.CameraTarget = position + [10 0 0];

That's more like it!

What we see is that we're inside a space with infinite corridors going off in three orthogonal directions. In between these corridors are pillars which go in three orthogonal directions.

Inside those pillars, there's another infinite space of corridors. From the point of view of someone in that space, we're inside one of the pillars. These two infinite half-spaces are separated by the faces of the mucube, and it is not possible to pass from one to the other without going through a face.

Now let's try moving around our half-space.

We'll go in a circle around the pillar to our left.

nsteps = 150; center = position - [2 0 0]; radius = 2.25; for ang=linspace(0,2*pi,nsteps) ax.CameraPosition = center + radius*[cos(ang) sin(ang) 0]; ax.CameraTarget = ax.CameraPosition + 10*[-sin(ang) cos(ang) 0]; drawnow end

Because of the symmetry of the mucube, we could just as easily go in a circle around any other pillar. Let's try going around the pillar below us.

center = position - [0 2 0]; for ang=linspace(0,2*pi,nsteps) v1 = [0 cos(ang) sin(ang)]; v2 = [0 -sin(ang) cos(ang)]; ax.CameraPosition = center + radius*v1; ax.CameraTarget = ax.CameraPosition + 10*v2; drawnow end

Wait a minute! That didn't look right, did it? Half way through things suddenly started going backwards. What happened?

It wasn't actually going backwards. What happened is that when we got half way around the circle, our camera suddenly flipped upside down. That's because of a property on the axes named CameraUpVector. The CameraUpVector is one of those properties that you don't need to think about very often. That's because, when the CameraUpVectorMode is 'auto', MATLAB Graphics will chose a value for the CameraUpVector automatically. It usually does a good job.

But this is a case where it doesn't do what we want. Once we're below the pillar and looking backwards, MATLAB Graphics decides that it'd be better to rotate the camera upside down. That might be a good choice for that CameraPosition and CameraTarget, but we know more. We know the entire path we're taking, and we don't want the camera to flip upside down in the middle.

We'll need to take control of our CameraUpVector.

for ang=linspace(0,2*pi,nsteps) v1 = [0 cos(ang) sin(ang)]; v2 = [0 -sin(ang) cos(ang)]; ax.CameraPosition = center + radius*v1; ax.CameraTarget = ax.CameraPosition + 10*v2; ax.CameraUpVector = v1; drawnow end

That looks better!

Can you make animations that fly around different paths through the mucube? For complex paths, computing the CameraUpVector gets a bit tricky. You might want to try some of the techniques I used in the posts about creating ribbons and tubes from parametric curves (link1, link2). What you're actually doing when you compute the CameraUpVector is establishing something called a Frenet frame on your curve.

Can you do the same thing for the apeirohedron that corresponds to our infinite stack of truncated octahedra? That's known as the muoctahedron, and you make it by removing all of the quadrilateral faces from the stack of octahedra, leaving just the hexagons.

Here's some code to get you started:

n = 3; np = factorial(3); p1 = perms(1:3); edges = []; for i=1:np for j=(i+1):np diff = p1(j,:) - p1(i,:); if isscalar(find(diff==-1)) && isscalar(find(diff==1)) && (n-2)==length(find(diff==0)) edges(end+1,:) = [i,j]; end end end g = graph(edges(:,1),edges(:,2)); f = dfsearch(g,1)'; p1(:,4) = ones(np,1); pts = p1 + repmat([-2 -2 -2 0],[np 1]); xrefl = [-1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 0]; yrefl = [ 1 0 0 0; 0 -1 0 0; 0 0 1 0; 0 0 0 0]; zrefl = [ 1 0 0 0; 0 1 0 0; 0 0 -1 0; 0 0 0 0]; verts = []; faces = []; fcolors = []; cols = lines(7); for i=1:7 for j=1:7 for k=1:7 mat = eye(4); cindex = 0; if mod(i,2) mat = mat*xrefl; cindex = cindex + 1; end if mod(j,2) mat = mat*yrefl; cindex = cindex + 2; end if mod(k,2) mat = mat*zrefl; cindex = cindex + 4; end ptmp = pts*mat + repmat([2*i 2*j 2*k 0],[np 1]); faces(end+1,:) = f+size(verts,1); verts = [verts; ptmp(:,1:3)]; cindex = 1+mod(cindex,size(cols,1)); fcolors(end+1,:) = cols(cindex,:); end end end clf patch('Vertices',verts,'Faces',faces,'FaceVertexCData',fcolors,'FaceColor','flat') view(-60,15)

Get
the MATLAB code

Published with MATLAB® R2015b

In earlier posts we've looked at tiling quadrilaterals and pentagons. So what about hexagons? I'm sure you've seen tilings of regular hexagons. They're popular in floor tiles and game tiles. Even bees know about this one. But there's one interesting feature of this tiling that you might not be familiar... read more >>

]]>In earlier posts we've looked at tiling quadrilaterals and pentagons. So what about hexagons? I'm sure you've seen tilings of regular hexagons. They're popular in floor tiles and game tiles. Even bees know about this one. But there's one interesting feature of this tiling that you might not be familiar with. To see this feature, we need to start in a very different place.

We're going to start by making all of the permutations of the sequence [1 2 3]. As you probably know, for n items, there are n! permutations. So there are 6 different ways we can arrange these 3 numbers.

n = 3; np = factorial(n); pts = perms(1:n)

pts = 3 2 1 3 1 2 2 3 1 2 1 3 1 2 3 1 3 2

Now we have an array with 6 rows and 3 columns. Since we have 3 columns, I'm just going to go ahead and use them as coordinates in 3D space. That gives me 6 points that I can draw with the stem3 function.

```
stem3(pts(:,1),pts(:,2),pts(:,3),'filled')
```

These points are all arranged on the edges of a box that goes from 1 to 3 along each axis.

```
lx = [1 3 3 1 1 nan 1 3 3 1 1 nan 1 1 nan 3 3 nan 3 3 nan 1 1 nan];
ly = [1 1 3 3 1 nan 1 1 3 3 1 nan 1 1 nan 1 1 nan 3 3 nan 3 3 nan];
lz = [1 1 1 1 1 nan 3 3 3 3 3 nan 1 3 nan 1 3 nan 1 3 nan 1 3 nan];
line(lx,ly,lz,'Color',[.75 .75 .75])
xlim([0.5 3.5])
ylim([0.5 3.5])
zlim([0.5 3.5])
```

Let's add some labels so we can see what we've got.

xlabel X ylabel Y zlabel Z nodelabels = cellstr(arrayfun(@(x)sprintf('%d',x),pts)); for i=1:np text(pts(i,1),pts(i,2),pts(i,3)+.1,nodelabels{i,1}, ... 'HorizontalAlignment','center', ... 'VerticalAlignment','baseline', ... 'FontSize',16); end

Now I want to connect each pair of points that are on the same face of the cube. One way to do that is like this:

edges = []; for i=1:np for j=(i+1):np diff = pts(j,:) - pts(i,:); if isscalar(find(diff==-1)) && isscalar(find(diff==1)) && (n-2)==length(find(diff==0)) edges(end+1,:) = [i j]; end end end g = graph(edges(:,1),edges(:,2)); o = dfsearch(g,1); f = [o; 1]; line(pts(f,1),pts(f,2),pts(f,3)) f = o';

You can probably already see that these form a hexagon. Let's emphasize that by creating a patch from them, and rotating around to get a better view.

cla cols = lines(7); patch('Faces',f,'Vertices',pts,'FaceColor',cols(1,:)) view([120 30])

And if we stacked an infinite number of boxes and intersected them all with that same plane, we would get a number of different hexagons, tiled in the familiar pattern.

cla vec1 = pts(f(5),:) - pts(f(3),:); vec2 = pts(f(5),:) - pts(f(1),:); p = gobjects(0); for i=-4:4 for j=-4:4 cindex = 1+mod(2*mod(i,2) + 1*mod(j,2),7); tmp_pts = pts + repmat(i*vec1,[np 1]) + repmat(j*vec2,[np 1]); p(end+1) = patch('Faces',f,'Vertices',tmp_pts,'FaceColor',cols(cindex,:)); end end xlim([-2 6]) ylim([-2 6]) zlim([-2 6])

And because all of these patches line in a plane, we can project them into 2D using a pair of basis vectors which are tangent to the plane.

uvec = [1, 0, 0]; vvec = [0, sqrt(2)/2, -sqrt(2)/2]; for i=1:numel(p) v3 = p(i).Vertices; x = sum(v3 .* repmat(uvec,[np 1]),2); y = sum(v3 .* repmat(vvec,[np 1]),2); p(i).Vertices = [x, y, zeros(np,1)]; end

And now we have the familiar 2D tiling of regular hexagons.

```
view(2)
axis off
```

That's kind of an odd way to think about hexagonal tilings, isn't it? But it's actually useful because of one surprising fact. And that is that this trick works in any dimension!

If we did this in 2D, we'd get a line connecting the points [1 2] and [2 1]. What if we did it in 4D? Let's give it a try, and see what we get.

This time we'll get an array that is 24x4 because 4! is 24.

n = 4; np = factorial(n); pts = perms(1:n)

pts = 4 3 2 1 4 3 1 2 4 2 3 1 4 2 1 3 4 1 2 3 4 1 3 2 3 4 2 1 3 4 1 2 3 2 4 1 3 2 1 4 3 1 2 4 3 1 4 2 2 3 4 1 2 3 1 4 2 4 3 1 2 4 1 3 2 1 4 3 2 1 3 4 1 3 2 4 1 3 4 2 1 2 3 4 1 2 4 3 1 4 2 3 1 4 3 2

If we use each row as a point, we have 4 dimensions. This makes things a bit more abstract. Let's start by drawing the points and their connections as an undirected graph.

nodelabels = cellstr(arrayfun(@(x)sprintf('%d',x),pts)); edges = []; for i=1:np for j=(i+1):np diff = pts(j,:) - pts(i,:); if isscalar(find(diff==-1)) && isscalar(find(diff==1)) && (n-2)==length(find(diff==0)) edges(end+1,:) = [i j]; end end end g = graph(edges(:,1),edges(:,2),[],nodelabels); plot(g)

We can see that there appears to be a polyhedron hiding in there.

What if we project this shape down into 3 dimensions? To do this, we'll need three 4D vectors which are normal to each other, and normal to the hyperplane. I happen to have a set right here.

u = [sqrt(2)/2, -sqrt(2)/2, 0, 0]; v = [sqrt(6)/6, sqrt(6)/6, -sqrt(2/3), 0]; w = [sqrt(12)/12, sqrt(12)/12, sqrt(12)/12, -sqrt(3)/2];

Now we can use the same technique I used to project that plane of hexagons down from 3D to 2D.

x = sum(pts .* repmat(u,[np 1]),2); y = sum(pts .* repmat(v,[np 1]),2); z = sum(pts .* repmat(w,[np 1]),2); scatter3(x,y,z,'filled'); axis equal

And then we can get the faces of the shape by using convhull.

faces = convhull(x,y,z); hold on trisurf(faces,x,y,z,'FaceColor',cols(3,:),'EdgeColor','none') axis vis3d camlight right

And we can recognize this as a truncated octahedron.

And just as we saw in the 2D case, these octahedra will tile 3D space with no gaps.

This tiling is known as the bitruncated cubic honeycomb.

And as I said, you can do this same thing in any dimension to generate a set of objects which tile the next dimension down. For example, if you do this with n=5, you'll get a 4D shape called the great prismatodecachoron. This family of shapes go by the curious name of permutohedron. Aren't they an interesting family?

Get
the MATLAB code

Published with MATLAB® R2015b

I recently answered a question on MATLAB Answers about how patch interpolates color data. This is a question I get a lot because it's a bit more complicated than you might expect. Let's take a close look at what it can and can't do.... read more >>

]]>I recently answered a question on MATLAB Answers about how patch interpolates color data. This is a question I get a lot because it's a bit more complicated than you might expect. Let's take a close look at what it can and can't do.

First we'll need a patch with FaceColor='interp'. I'll start with this simple polygon and use the X coordinate as the color data.

ang = 0:pi/4.5:6; x = cos(ang); y = sin(ang); c = x; patch('XData',x,'YData',y,'CData',c,'FaceColor','interp') colorbar

That’s simple, isn’t it? We can work out what color each pixel should be by inserting the X coordinate into the colormap. But the Patch object is actually doing something a little subtler here. It’s actually creating a set of triangles which subdivide the interior of the polygon and then handing those off to the graphics system and asking it to figure out the color of each pixel in each of the triangles. The graphics system does that using linear interpolation. That works in this case, because the function that generates the colors is linear, and it can be exactly represented by linear interpolation across a set of triangles.

But if the color data can't be represented as a linear function, then we’re going to see some artifacts.

Consider what happens if we make the color values the square of the X coordinates.

cla c = x.^2; patch('XData',x,'YData',y,'CData',c,'FaceColor','interp')

That looks pretty good, but if you look closely, you'll see some glitches. For example, the dark blue is to the right of the 0. That seems strange, doesn't it? We can see other issues if we switch to a busier colormap.

colormap(lines(7))

Because we're using X for the colors, those lines should all be vertical. They shouldn't have those bends on the right side.

What you're seeing is that patch has created 7 triangles to cover the interior of the polygon. Near those "glitches", there are two little triangles and one big triangle which are each doing linear interpolation between the point on the right and the points at the top and bottom. But the two small triangles can "see" the points at X=0.766, while the big triangle does't "see" those points. Because the color values at those points don't lie on a linear function through the other 3 points, the small triangles perform a different interpolation from the large one.

There is no set of 7 triangles which is going to be able to get you the “right” colors in this case. The only way to do this correctly is to subdivide the interior of the polygon until your elements are small enough that a linear approximation is good enough. The patch object doesn't know how to do this automatically.

Of course that's an awfully simple polygon. What happens if we use something more complex?

cla x = [1 2 2 3 3 1 1 4 4 1]; y = [1.5 1.5 3 3 1 1 0 0 4 4]; c = x; patch('XData',x,'YData',y,'CData',c,'FaceColor','interp') xlim([.5 4.5]) ylim([-.5 4.5])

That still looks good when we're using the X coordinates as the color data. And it actually works fine for any linear combination of the X & Y coordinates.

cla c = x + y; patch('XData',x,'YData',y,'CData',c,'FaceColor','interp')

But as we've seen, it doesn't work for color data which follows a non-linear function.

cla c = cos(x) .* cos(y); caxis auto patch('XData',x,'YData',y,'CData',c,'FaceColor','interp')

As I said earlier, to handle this case well, we will need to divide the polygon in many small facets, rather than 7 large triangles. We call this operation "meshing".

There are a couple of ways to do this. Here's one of the simpler ones. First we use meshgrid to create a fine mesh of quadrilaterals that covers the original polygon.

[x2, y2] = meshgrid(linspace(min(x),max(x),25), linspace(min(y),max(y),25));

Next we use inpolygon to create a mask of the points in the mesh which are outside of the polygon.

mask = ~inpolygon(x2,y2,x,y);

Now we create our color array from the high-res coordinates, and put nans in all of the locations which are outside the polygon.

c = cos(x2) .* cos(y2); c(mask) = nan;

Then we can use surf2patch to convert this mesh of quads into a form that patch can handle.

fv = surf2patch(x2,y2,zeros(size(c)),c);

Finally we create our patch.

patch(fv,'FaceColor','interp','EdgeColor','none')

That has divided the polygon into a bunch of little quadrilaterals, as we can see here:

set(findobj(gca,'Type','patch'),'EdgeColor',[.75 .75 .75])

That works for this polygon because it is all 90 degree corners, but it isn't going to work for the more general case. One option which is more flexible is to use something like delaunayTriangulation, as Damian Sheehy explained in this post on Loren's blog.

But if you have the Partial Differential Equation Toolbox then there's another way to do it. That toolbox comes with good meshing tools because creating a mesh that accurately represents a non-linear function is very important when you're solving partial differential equations.

I'm not going to go into how you do this in any great detail, but creating a good triangle mesh looks something like this. First we need to setup a 2D PDE model.

pdem = createpde(2);

Next we give it our geometry. Its format for representing geometry is a little different from the patch object.

geom = [2; 10; x'; y']; gd = decsg(geom); geometryFromEdges(pdem,gd);

And now we can call generateMesh to perform the meshing. The Hmax argument to generateMesh controls how finely our polygon gets subdivided.

```
mesh = generateMesh(pdem,'Hmax',.2);
```

The resulting mesh is a set of triangles which cover the interior of the polygon. We can see them using triplot.

triplot(mesh.Elements',mesh.Nodes(1,:)',mesh.Nodes(2,:)') xlim([.5 4.5]) ylim([-.5 4.5])

Now that we have a mesh, we can use that to create the patch that we wanted.

cla c = cos(mesh.Nodes(1,:)) .* cos(mesh.Nodes(2,:)); patch('Faces',mesh.Elements','Vertices',mesh.Nodes','FaceVertexCData',c', ... 'FaceColor','interp','EdgeColor','none') patch('XData',x,'YData',y,'FaceColor','none')

So that's how patch interpolates color data. Coordinate data is actually handled exactly the same way. If I make the Z coordinate a linear function of the X and Y coordinates, then complex polygons are drawn correctly.

cla c = x + y; z = c; patch('XData',x,'YData',y,'ZData',z,'CData',c,'FaceColor','interp') view([-20 50])

But if the Z coordinates are not a linear combination of the X & Y coordinates, then the results get pretty messy.

cla c = cos(x) .* cos(y); z = c; patch('XData',x,'YData',y,'ZData',z,'CData',c,'FaceColor','interp') view([-20 50])

But the same meshing approaches we used for color data work for geometric data.

cla c = cos(mesh.Nodes(1,:)) .* cos(mesh.Nodes(2,:)); z = c; patch('Faces',mesh.Elements','Vertices',[mesh.Nodes', c'],'FaceVertexCData',c','FaceColor','interp','EdgeColor','none') view([-20 50])

I hope that gives you a good idea of what patch can and can't do for interpolating data across a polygon, and also some idea of where to turn when patch can't do what you need.

Get
the MATLAB code

Published with MATLAB® R2015b