{"id":325,"date":"2015-11-04T10:28:49","date_gmt":"2015-11-04T15:28:49","guid":{"rendered":"https:\/\/blogs.mathworks.com\/graphics\/?p=325"},"modified":"2015-11-04T10:28:49","modified_gmt":"2015-11-04T15:28:49","slug":"on-the-edge","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/graphics\/2015\/11\/04\/on-the-edge\/","title":{"rendered":"On The Edge"},"content":{"rendered":"<div class=\"content\"><!--introduction--><p><a href=\"https:\/\/blogs.mathworks.com\/graphics\/2015\/09\/17\/what-is-a-contour\/\">In an earlier post<\/a>, we discussed how the contour functions interpolate between values. Another important issue is how the contour functions deal with contour levels which are exactly the same as values in the input data. Some users are often surprised by what happens in this case because there are some subtle issues involved. Let's take a detailed look.<\/p><!--\/introduction--><h3>Contents<\/h3><div><ul><li><a href=\"#912648e4-9b8b-4868-9c71-f2c11b9f5ed8\">Contours<\/a><\/li><li><a href=\"#276b1131-3d99-4de1-b82c-76797a0b4e4f\">Filled Contours<\/a><\/li><\/ul><\/div><p>We'll start with a simple matrix with 3 different values.<\/p><pre class=\"codeinput\">z=zeros(6);\r\nz(2:3,2:3)=-1;\r\nz(4:5,4:5)=1;\r\n<\/pre><p>We'll also want the following helpful function to label the values of our matrix.<\/p><pre class=\"codeinput\">type <span class=\"string\">labelcontourvalues<\/span>\r\n<\/pre><pre class=\"codeoutput\">\r\nfunction labelcontourvalues(z)\r\n    for r=1:size(z,1)\r\n        for c=1:size(z,2)\r\n            v = z(r,c);\r\n            color = 'black';\r\n            if (v &lt; 0)\r\n                color = 'blue';\r\n            elseif (v &gt; 0)\r\n                color = 'red';\r\n            end\r\n            h = text(c,r,num2str(v));\r\n            h.HorizontalAlignment = 'center';\r\n            h.VerticalAlignment = 'middle';\r\n            h.Color = color;\r\n            h.BackgroundColor = 'white';\r\n            h.FontSize = 8;\r\n            h.Margin = eps;\r\n        end\r\n    end\r\nend\r\n\r\n<\/pre><h4>Contours<a name=\"912648e4-9b8b-4868-9c71-f2c11b9f5ed8\"><\/a><\/h4><p>If we create a contour with levels which are in between those values, then it's pretty straightforward. Each contour line separates two areas which are above and below the value of the contour line.<\/p><pre class=\"codeinput\">contour(z,<span class=\"string\">'LevelList'<\/span>,[-.5 .5]);\r\ncolormap([0 0 1;1 0 0]);\r\ncaxis([-1 1]);\r\naxis <span class=\"string\">off<\/span>\r\nlabelcontourvalues(z)\r\ntitle(<span class=\"string\">'LevelList = [-.5 .5]'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/contour_on_the_edge_01.png\" alt=\"\"> <p>And if we move the levels very close to the values, those two contour lines move closer to the points.<\/p><pre class=\"codeinput\">contour(z,<span class=\"string\">'LevelList'<\/span>,[-.9 .9]);\r\naxis <span class=\"string\">off<\/span>\r\nlabelcontourvalues(z)\r\ntitle(<span class=\"string\">'LevelList = [-.9 .9]'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/contour_on_the_edge_02.png\" alt=\"\"> <p>But when the levels are exactly equal to data values, we get a somewhat surprising result.<\/p><pre class=\"codeinput\">contour(z,<span class=\"string\">'LevelList'<\/span>,[-1 1]);\r\naxis <span class=\"string\">off<\/span>\r\nlabelcontourvalues(z)\r\ntitle(<span class=\"string\">'LevelList = [-1 1]'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/contour_on_the_edge_03.png\" alt=\"\"> <p>In this case, contour has drawn a line for the level 1. It goes right through the 1 values, as we'd expect. But it didn't draw a line for the level -1. It seems like drawing the -1 level would make this more symmetric, doesn't it?<\/p><p>And furthermore, if flip the sign, we get a different picture. That also seems surprising, doesn't it?<\/p><pre class=\"codeinput\">cla\r\nminus_z = -z;\r\ncontour(minus_z,<span class=\"string\">'LevelList'<\/span>,[-1 1])\r\naxis <span class=\"string\">off<\/span>\r\nlabelcontourvalues(minus_z);\r\ntitle(<span class=\"string\">'LevelList = [-1 1]'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/contour_on_the_edge_04.png\" alt=\"\"> <p>Well things really aren't symmetric here. We can see that if we add a level at 0.<\/p><pre class=\"codeinput\">contour(z,<span class=\"string\">'LevelList'<\/span>,[-1 0 1]);\r\ncolormap([0 0 1;0 0 0;1 0 0]);\r\ncaxis([-1 1]);\r\naxis <span class=\"string\">off<\/span>\r\nlabelcontourvalues(z)\r\ntitle(<span class=\"string\">'LevelList = [-1 0 1]'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/contour_on_the_edge_05.png\" alt=\"\"> <p>Notice how the 0 level goes through the 0 values which have neighboring -1 values, but it doesn't go through the 0 values which are next to 1 values.<\/p><p>It's hard to come up with a contour algorithm which would really be symmetric in this case. Would it somehow go through the middle of the region that is all 0's? You could try that, but it turns out that there are some nasty surprises down that path because of all of the special cases. You could also try drawing a curve on each side of that region of 0's, but that'd be rather strange too. Some people have proposed that contour should fill the region of 0's, but if you try that, you'll see that it looks pretty ugly.<\/p><p>So contour needs to go around one side of the area that is exactly equal to the level. It does this by following a simple rule. It draws a line which separates regions which are less than the level from regions which are greater than or equal to the level.<\/p><p>This is why it doesn't draw a curve for the -1 level. There are no values which are less than -1, so there is no area which should be separated from the area which is equal to -1.<\/p><h4>Filled Contours<a name=\"276b1131-3d99-4de1-b82c-76797a0b4e4f\"><\/a><\/h4><p>If we use the <a href=\"https:\/\/www.mathworks.com\/help\/matlab\/ref\/contourf.html\">contourf function<\/a>, then we'll get filled polygons instead of lines. If we do, we'll see that we get the same curves for the levels 0 and 1 as we did with contour.<\/p><p>We also get a blue region for the values (v &gt;= -1 &amp; v &lt; 0), a white region for the values (v &gt;= 0 &amp; v &lt; 1), and a red region for the values (v &gt;= 1).<\/p><pre class=\"codeinput\">contourf(z,<span class=\"string\">'LevelList'<\/span>,[-1 0 1]);\r\ncolormap([0 0 1;1 1 1;1 0 0]);\r\ncaxis([-1 1]);\r\naxis <span class=\"string\">off<\/span>\r\nlabelcontourvalues(z)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/contour_on_the_edge_06.png\" alt=\"\"> <p>Notice that it didn't draw a square around the -1 values. This is consistent with what the contour function did. But MATLAB has historically had some inconsistencies between the rules used by the different contour functions. In R2014b we made them all use the same rule.<\/p><p>Well, actually there is still an inconsistency in MATLAB's contour functions; that is if we consider isosurface a contour function.<\/p><p>The <a href=\"https:\/\/www.mathworks.com\/help\/matlab\/ref\/isosurface.html\">isosurface function<\/a> is the 3D analogue of contour. It takes a 3D array, instead of a 2D array, and it draws the surface which separates regions, rather than a curve.<\/p><p>We can recreate our example in 3D and see what isosurface does with it.<\/p><p>If we isosurface at -1\/2 and 1\/2, we get two surfaces, just like the two curves we got when we used those levels for contour.<\/p><pre class=\"codeinput\">z=zeros([6 6 6]);\r\nz(2:3,2:3,2:3)=-1;\r\nz(4:5,4:5,4:5)=1;\r\n\r\ncla\r\nisosurface(z,-.5)\r\nisosurface(z,.5)\r\ntitle(<span class=\"string\">'LevelList = [-.5 .5]'<\/span>)\r\nxlim([1 6])\r\nylim([1 6])\r\nzlim([1 6])\r\ncamlight\r\naxis <span class=\"string\">on<\/span>\r\nview(3)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/contour_on_the_edge_07.png\" alt=\"\"> <p>But if we isosurface at -1 and 1, we only get one. The problem is that it's the blue one, while contour gave us the red one.<\/p><pre class=\"codeinput\">cla\r\nisosurface(z,-1)\r\nisosurface(z,1)\r\ntitle(<span class=\"string\">'LevelList = [-1 1]'<\/span>)\r\ncamlight\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/contour_on_the_edge_08.png\" alt=\"\"> <p>It turns out that isosurface uses the opposite rule from contour. It draws a surface between regions which are less than or equal to the level and regions which are greater than the level.<\/p><p>In early versions of MATLAB, there wasn't much consistency between these functions. We've now got the 2D contour functions consistent with each other, but we haven't yet changed isosurface to match.<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_ec3ae52f7b3e406d971f1a467411bff2() {\r\n        \/\/ Remember the title so we can use it in the new page\r\n        title = document.title;\r\n\r\n        \/\/ Break up these strings so that their presence\r\n        \/\/ in the Javascript doesn't mess up the search for\r\n        \/\/ the MATLAB code.\r\n        t1='ec3ae52f7b3e406d971f1a467411bff2 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' ec3ae52f7b3e406d971f1a467411bff2';\r\n    \r\n        b=document.getElementsByTagName('body')[0];\r\n        i1=b.innerHTML.indexOf(t1)+t1.length;\r\n        i2=b.innerHTML.indexOf(t2);\r\n \r\n        code_string = b.innerHTML.substring(i1, i2);\r\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\r\n\r\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \r\n        \/\/ in the XML parser.\r\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\r\n        \/\/ doesn't go ahead and substitute the less-than character. \r\n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\r\n\r\n        copyright = 'Copyright 2015 The MathWorks, Inc.';\r\n\r\n        w = window.open();\r\n        d = w.document;\r\n        d.write('<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add copyright line at the bottom if specified.\r\n        if (copyright.length > 0) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (copyright.length > 0) {\r\n                d.writeln('% _' + copyright + '_');\r\n            }\r\n        }\r\n\r\n        d.write('<\/pre>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script><p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><br><a href=\"javascript:grabCode_ec3ae52f7b3e406d971f1a467411bff2()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; R2015b<br><\/p><\/div><!--\r\nec3ae52f7b3e406d971f1a467411bff2 ##### SOURCE BEGIN #####\r\n%% On the Edge\r\n% <https:\/\/blogs.mathworks.com\/graphics\/2015\/09\/17\/what-is-a-contour\/ In an earlier post>, \r\n% we discussed how the contour functions interpolate between\r\n% values. Another important issue is how the contour functions deal with\r\n% contour levels which are exactly the same as values in the input data. \r\n% Some users are often surprised by what happens in this case because there\r\n% are some subtle issues involved. Let's take a detailed look.\r\n\r\n%%\r\n% We'll start with a simple matrix with 3 different values.\r\nz=zeros(6);\r\nz(2:3,2:3)=-1;\r\nz(4:5,4:5)=1;\r\n\r\n%%\r\n% We'll also want the following helpful function to label the values of our\r\n% matrix.\r\ntype labelcontourvalues\r\n\r\n%% Contours\r\n% If we create a contour with levels which are in between those values,\r\n% then it's pretty straightforward. Each contour line separates two areas which\r\n% are above and below the value of the contour line.\r\n%\r\ncontour(z,'LevelList',[-.5 .5]);\r\ncolormap([0 0 1;1 0 0]);\r\ncaxis([-1 1]);\r\naxis off\r\nlabelcontourvalues(z)\r\ntitle('LevelList = [-.5 .5]')\r\n\r\n%%\r\n% And if we move the levels very close to the values, those two contour\r\n% lines move closer to the points.\r\n%\r\ncontour(z,'LevelList',[-.9 .9]);\r\naxis off\r\nlabelcontourvalues(z)\r\ntitle('LevelList = [-.9 .9]')\r\n\r\n%%\r\n% But when the levels are exactly equal to data values, we get a somewhat\r\n% surprising result.\r\n%\r\ncontour(z,'LevelList',[-1 1]);\r\naxis off\r\nlabelcontourvalues(z)\r\ntitle('LevelList = [-1 1]')\r\n\r\n%%\r\n% In this case, contour has drawn a line for the level 1. It goes right\r\n% through the 1 values, as we'd expect. But it didn't draw a line for the level -1. \r\n% It seems like drawing the -1 level would make this more symmetric,\r\n% doesn't it?\r\n%\r\n% And furthermore, if flip the sign, we get a different picture. That also seems\r\n% surprising, doesn't it?\r\ncla\r\nminus_z = -z;\r\ncontour(minus_z,'LevelList',[-1 1])\r\naxis off\r\nlabelcontourvalues(minus_z);\r\ntitle('LevelList = [-1 1]')\r\n\r\n%%\r\n% Well things really aren't symmetric here. We can see that if we add a level at 0.\r\n%\r\ncontour(z,'LevelList',[-1 0 1]);\r\ncolormap([0 0 1;0 0 0;1 0 0]);\r\ncaxis([-1 1]);\r\naxis off\r\nlabelcontourvalues(z)\r\ntitle('LevelList = [-1 0 1]')\r\n\r\n%%\r\n% Notice how the 0 level goes through the 0 values which have neighboring \r\n% -1 values, but it doesn't go through the 0 values which are next to 1 values.\r\n%\r\n% It's hard to come up with a contour algorithm which would really be symmetric in\r\n% this case. Would it somehow go through the middle of the region that is all 0's?\r\n% You could try that, but it turns out that there are some nasty surprises\r\n% down that path because of all of the special cases. You could also try\r\n% drawing a curve on each side of that region of 0's, but that'd be rather\r\n% strange too. Some people have proposed that contour should fill the\r\n% region of 0's, but if you try that, you'll see that it looks pretty ugly.\r\n%\r\n% So contour needs to go around one side of the area that is exactly equal\r\n% to the level. It does this by following a simple rule. It draws a line\r\n% which separates regions which are less than the level from regions which\r\n% are greater than or equal to the level.\r\n%\r\n% This is why it doesn't draw a curve for the -1 level. There are no\r\n% values which are less than -1, so there is no area which should be\r\n% separated from the area which is equal to -1. \r\n\r\n%% Filled Contours\r\n% If we use the <https:\/\/www.mathworks.com\/help\/matlab\/ref\/contourf.html contourf function>, then we'll get filled polygons instead of\r\n% lines. If we do, we'll see that we get the same curves for the levels 0\r\n% and 1 as we did with contour. \r\n%\r\n% We also get a blue region for the values (v >= -1 & v < 0), a white region for\r\n% the values (v >= 0 & v < 1), and a red region for the values (v >= 1).\r\n%\r\ncontourf(z,'LevelList',[-1 0 1]);\r\ncolormap([0 0 1;1 1 1;1 0 0]);\r\ncaxis([-1 1]);\r\naxis off\r\nlabelcontourvalues(z)\r\n\r\n%%\r\n% Notice that it didn't draw a square around the -1 values. This is\r\n% consistent with what the contour function did. But MATLAB has historically\r\n% had some inconsistencies between the rules used by the different contour\r\n% functions. In R2014b we made them all use the same rule.\r\n%\r\n% Well, actually there is still an inconsistency in MATLAB's contour\r\n% functions; that is if we consider isosurface a contour function. \r\n%\r\n% The <https:\/\/www.mathworks.com\/help\/matlab\/ref\/isosurface.html isosurface\r\n% function> is the 3D analogue of contour. It takes a 3D array, instead of a\r\n% 2D array, and it draws the surface which separates regions, rather than a\r\n% curve. \r\n%\r\n% We can recreate our example in 3D and see what isosurface does\r\n% with it. \r\n%\r\n% If we isosurface at -1\/2 and 1\/2, we get two surfaces, just like the two\r\n% curves we got when we used those levels for contour.\r\n%\r\nz=zeros([6 6 6]);\r\nz(2:3,2:3,2:3)=-1;\r\nz(4:5,4:5,4:5)=1;\r\n\r\ncla\r\nisosurface(z,-.5)\r\nisosurface(z,.5)\r\ntitle('LevelList = [-.5 .5]')\r\nxlim([1 6])\r\nylim([1 6])\r\nzlim([1 6])\r\ncamlight\r\naxis on\r\nview(3)\r\n\r\n%%\r\n% But if we isosurface at -1 and 1, we only get one. The problem is that\r\n% it's the blue one, while contour gave us the red one.\r\n%\r\ncla\r\nisosurface(z,-1)\r\nisosurface(z,1)\r\ntitle('LevelList = [-1 1]')\r\ncamlight\r\n\r\n%%\r\n% It turns out that isosurface uses the opposite rule from contour. It draws a\r\n% surface between regions which are less than or equal to the level and regions which\r\n% are greater than the level.\r\n%\r\n% In early versions of MATLAB, there wasn't much consistency between these\r\n% functions. We've now got the 2D contour functions consistent with each\r\n% other, but we haven't yet changed isosurface to match.\r\n\r\n##### SOURCE END ##### ec3ae52f7b3e406d971f1a467411bff2\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/graphics\/files\/feature_image\/contour_on_the_edge_thumbnail.png\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><!--introduction--><p><a href=\"https:\/\/blogs.mathworks.com\/graphics\/2015\/09\/17\/what-is-a-contour\/\">In an earlier post<\/a>, we discussed how the contour functions interpolate between values. Another important issue is how the contour functions deal with contour levels which are exactly the same as values in the input data. Some users are often surprised by what happens in this case because there are some subtle issues involved. Let's take a detailed look.... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/graphics\/2015\/11\/04\/on-the-edge\/\">read more >><\/a><\/p>","protected":false},"author":89,"featured_media":328,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[5],"tags":[],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts\/325"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/users\/89"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/comments?post=325"}],"version-history":[{"count":2,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts\/325\/revisions"}],"predecessor-version":[{"id":327,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts\/325\/revisions\/327"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/media\/328"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/media?parent=325"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/categories?post=325"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/tags?post=325"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}