- Undocumented Matlab - https://undocumentedmatlab.com -

Customizing contour plots

Posted By Yair Altman On November 18, 2015 | 12 Comments

One of my clients asked me last week whether it is possible to access and customize individual contour lines and labels in HG2 (Matlab’s new graphics system, R2014+). Today’s post will discuss how this could indeed be done.

Matlab contour plot
Matlab contour plot
In HG1 (R2014a and earlier), contour handles were simple hggroup objects that incorporated text and patch child handles. The contour labels, lines and fill patches could easily be accessed via these child handles (contour lines and fills use the same patch object: the lines are simply the patch edges; fills are their faces). The lines could then be customized, the label strings changed, and the patch faces (fills) recolored:

[X,Y,Z] = peaks;
[C,hContour] = contour(X,Y,Z,20, 'ShowText','on');
hChildren = get(hContour, 'Children');
set(hChildren(1), 'String','Yair', 'Color','b');  % 1st text (contour label)
set(hChildren(end), 'EdgeColor',[0,1,1]);         % last patch (contour line)

The problem is that in HG2 (R2014b onward), contour (and its sibling functions, contourf etc.) return a graphic object that has no accessible children. In other words, hContour.Children returns an empty array:

>> hContour.Children
ans =
  0x0 empty GraphicsPlaceholder array.
>> allchild(hContour)
ans =
  0x0 empty GraphicsPlaceholder array.
>> isempty(hContour.Children)
ans =
     1

So how then can we access the internal contour patches and labels?

HG2’s contour object’s hidden properties

Skipping several fruitless dead-ends, it turns out that in HG2 the text labels, lines and fills are stored in undocumented hidden properties called TextPrims, EdgePrims and (surprise, surprise) FacePrims, which hold corresponding arrays of matlab.graphics.primitive.world.Text, matlab.graphics.primitive.world.LineStrip and matlab.graphics.primitive.world.TriangleStrip object handles (the drawnow part is also apparently very important, otherwise you might get errors due to the Prim objects not being ready by the time the code is reached):

>> drawnow;  % very important!
>> hContour.TextPrims  % row array of Text objects
ans =
  1x41 Text array:
  Columns 1 through 14
    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text
  Columns 15 through 28
    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text
  Columns 29 through 41
    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text    Text
>> hContour.EdgePrims  % column array of LineStrip objects
ans =
  20x1 LineStrip array:
  ineStrip
  LineStrip
  LineStrip
  ...
>> hContour.FacePrims  % column array of TriangleStrip objects (empty if no fill)
ans =
  0x0 empty TriangleStrip array.

We can now access and customize the individual contour lines, labels and fills:

hContour.TextPrims(4).String = 'Dani';
hContour.TextPrims(7).Visible = 'off';
hContour.TextPrims(9).VertexData = single([-1.3; 0.5; 0]);  % Label location in data units
hContour.EdgePrims(2).ColorData = uint8([0;255;255;255]);  % opaque cyan
hContour.EdgePrims(5).Visible = 'off';

Note that the LineStrip objects here are the same as those used for the axes Axles, which I described [1] a few months ago. Any customization that we could do to the axle LineStrips can also be applied to contour LineStrips, and vice versa.
For example, to achieve the appearance of a topographic map [2], we might want to modify some contour lines to use dotted LineStyle and other lines to appear bold by having larger LineWidth. Similarly, we may wish to hide some labels (by setting their Visible property to ‘off’) and make other labels bold (by setting their Font.Weight property to ‘bold’). There are really numerous customization possibilities here.
Here is a listing of the standard (non-hidden) properties exposed by these objects:

>> get(hContour.TextPrims(1))
        BackgroundColor: []
              ColorData: []
              EdgeColor: []
                   Font: [1x1 matlab.graphics.general.Font]
          FontSmoothing: 'on'
       HandleVisibility: 'on'
                HitTest: 'off'
    HorizontalAlignment: 'center'
            Interpreter: 'none'
                  Layer: 'middle'
              LineStyle: 'solid'
              LineWidth: 1
                 Margin: 1
                 Parent: [1x1 Contour]
          PickableParts: 'visible'
               Rotation: 7.24591082075548
                 String: '-5.1541'
          StringBinding: 'copy'
             VertexData: [3x1 single]
      VerticalAlignment: 'middle'
                Visible: 'on'
>> get(hContour.EdgePrims(1))
          AlignVertexCenters: 'off'
             AmbientStrength: 0.3
                ColorBinding: 'object'
                   ColorData: [4x1 uint8]
                   ColorType: 'truecolor'
             DiffuseStrength: 0.6
            HandleVisibility: 'on'
                     HitTest: 'off'
                       Layer: 'middle'
                     LineCap: 'none'
                    LineJoin: 'round'
                   LineStyle: 'solid'
                   LineWidth: 0.5
               NormalBinding: 'none'
                  NormalData: []
                      Parent: [1x1 Contour]
               PickableParts: 'visible'
    SpecularColorReflectance: 1
            SpecularExponent: 10
            SpecularStrength: 0.9
                   StripData: [1 18]
                     Texture: [0x0 GraphicsPlaceholder]
                  VertexData: [3x17 single]
               VertexIndices: []
                     Visible: 'on'
       WideLineRenderingHint: 'software'
>> get(hContour.FacePrims(1))
             AmbientStrength: 0.3
             BackFaceCulling: 'none'
                ColorBinding: 'object'
                   ColorData: [4x1 uint8]
                   ColorType: 'truecolor'
             DiffuseStrength: 0.6
            HandleVisibility: 'on'
                     HitTest: 'off'
                       Layer: 'middle'
               NormalBinding: 'none'
                  NormalData: []
                      Parent: [1x1 Contour]
               PickableParts: 'visible'
    SpecularColorReflectance: 1
            SpecularExponent: 10
            SpecularStrength: 0.9
                   StripData: [1 4 13 16 33 37 41 44 51 54 61 64 71 74 87 91 94 103]
                     Texture: [0x0 GraphicsPlaceholder]
            TwoSidedLighting: 'off'
                  VertexData: [3x102 single]
               VertexIndices: []
                     Visible: 'on'

But how did I know these properties existed? The easiest way in this case would be to use my getundoc utility [3], but we could also use my uiinspect utility [4] or even the plain-ol’ struct function [5].
p.s. – there’s an alternative way, using the Java bean adapter that is associated with each Matlab graphics object: java(hContour). Specifically, this object apparent has the public method browseableChildren(java(hContour)) which returns the list of all children (in our case, 41 text labels [bean adapters], 20 lines, and a single object holding a ListOfPointsHighlight that corresponds to the regular hidden SelectionHandle property). However, I generally dislike working with the bean adapters, especially when there’s a much “cleaner” way to get these objects, in this case using the regular EdgePrims, FacePrims, TextPrims and SelectionHandle properties. Readers who are interested in Matlab internals can explore the bean adapters using a combination of my getundoc [3] and uiinspect [4] utilities.
So far for the easy part. Now for some more challenging questions:

Customizing the color

First, can we modify the contour fill to have a semi- (or fully-) transparent fill color? – indeed we can:

[~, hContour] = contourf(peaks(20), 10);
drawnow;  % this is important, to ensure that FacePrims is ready in the next line!
hFills = hContour.FacePrims;  % array of TriangleStrip objects
[hFills.ColorType] = deal('truecoloralpha');  % default = 'truecolor'
for idx = 1 : numel(hFills)
   hFills(idx).ColorData(4) = 150;   % default=255
end

Contour plot in HG2, with and without transparency
Contour plot in HG2, with and without transparency

Similar transparency effects can also be applied to the LineStrip and Text objects. A discussion of the various combinations of acceptable color properties can be found here [1].

Mouse clicks

Next, how can we set a custom context-menu for individual labels and contour lines?
Unfortunately, Text, LineStrip and TriangleStrip objects do not posses a ButtonDownFcn or UIContextMenu property, not even hidden. I tried searching in the internal/undocumented properties, but nothing came up.

Mouse click solution #1

So the next logical step would be to trap the mouse-click event at the contour object level. We cannot simply click the contour and check the clicked object because that would just give us the hContour object handle rather than the individual Text or LineStrip. So the idea would be to set hContour.HitTest='off', in the hope that the mouse click would be registered on the graphic object directly beneath the mouse cursor, namely the label or contour line. It turns out that the labels’ and lines’ HitTest property is ‘off’ by default, so, we also need to set them all to ‘on’:

hContour.HitTest = 'off';
[hContour.TextPrims.HitTest] = deal('on');
[hContour.EdgePrims.HitTest] = deal('on');
[hContour.FacePrims.HitTest] = deal('on');
hContour.ButtonDownFcn = @(h,e)disp(struct(e));

This seemed simple enough, but failed spectacularly: it turns out that because hContour.HitTest='off', mouse clicks are not registered on this objects, and on the other hand we cannot set the ButtonDownFcn on the primitive objects because they don’t have a ButtonDownFcn property!
Who said life is easy?
One workaround is to set the figure’s WindowButtonDownFcn property:

set(gcf, 'WindowButtonDownFcn', @myMouseClickCallback);

Now, inside your myMouseClickCallback function you can check the clicked object. We could use the undocumented builtin hittest(hFig) function [6] to see which object was clicked. Alternatively, we could use the callback eventData‘s undocumented HitObject/HitPrimitive properties (this variant does not require the HitTest property modifications above):

function myMouseClickCallback(hFig, eventData)
   hitPrimitive = hittest(hFig);  % undocumented function
   hitObject    = eventData.HitObject;     % undocumented property => returns a Contour object (=hContour)
   hitPrimitive = eventData.HitPrimitive;  % undocumented property => returns a Text or LineStrip object
   hitPoint     = eventData.Point;         % undocumented property => returns [x,y] pixels from figure's bottom-left corner
   if strcmpi(hFig.SelectionType,'alt')  % right-click
      if isa(hitPrimitive, 'matlab.graphics.primitive.world.Text')  % label
         displayTextContextMenu(hitPrimitive, hitPoint)
      elseif isa(hitPrimitive, 'matlab.graphics.primitive.world.LineStrip')  % contour line
         displayLineContextMenu(hitPrimitive, hitPoint)
      elseif isa(hitPrimitive, 'matlab.graphics.primitive.world.TriangleStrip')  % contour fill
         displayFillContextMenu(hitPrimitive, hitPoint)
      else
         ...
      end
   end
end
Mouse click solution #2

A totally different solution is to keep the default hContour.HitTest='on' (and the primitives’ as ‘off’) and simply query the contour object’s ButtonDownFcn callback’s eventData‘s undocumented Primitive property:

hContour.ButtonDownFcn = @myMouseClickCallback;

And in the callback function:

function myMouseClickCallback(hContour, eventData)
   hitPrimitive = eventData.Primitive;  % undocumented property => returns a Text or LineStrip object
   hitPoint     = eventData.IntersectionPoint;  % [x,y,z] in data units
   hFig = ancestor(hContour, 'figure');
   if strcmpi(hFig.SelectionType,'alt')  % right-click
      if isa(hitPrimitive, 'matlab.graphics.primitive.world.Text')  % label
         displayTextContextMenu(hitPrimitive, hitPoint)
      elseif isa(hitPrimitive, 'matlab.graphics.primitive.world.LineStrip')  % contour line
         displayLineContextMenu(hitPrimitive, hitPoint)
      elseif isa(hitPrimitive, 'matlab.graphics.primitive.world.TriangleStrip')  % contour fill
         displayFillContextMenu(hitPrimitive, hitPoint)
      else
         ...
      end
   end
end

This article [7] should be a good start in how to code the displayTextContextMenu etc. functions to display a context menu.

Customizations reset

Finally, there are apparently numerous things that cause our customized labels and lines to reset to their default appearance: resizing, updating contour properties etc. To update the labels in all these cases in one place, simply listen to the undocumented MarkedClean event [8]:

addlistener(hContour, 'MarkedClean', @updateLabels);

Where updateLabels is a function were you set all the new labels.

Prediction about forward compatibility

I am marking this article as “High risk of breaking in future Matlab versions [9]“, not because of the basic functionality (being important enough I don’t presume it will go away anytime soon) but because of the property names: TextPrims, EdgePrims and FacePrims don’t seem to be very user-friendly property names. So far MathWorks has been very diligent in making its object properties have meaningful names, and so I assume that when the time comes to expose these properties, they will be renamed (perhaps to TextHandles, EdgeHandles and FaceHandles, or perhaps LabelHandles, LineHandles and FillHandles). For this reason, even if you find out in some future Matlab release that TextPrims, EdgePrims and FacePrims don’t exist, perhaps they still exist and simply have different names.
Addendum November 11, 2017: The TextPrims, EdgePrims and FacePrims properties have still not changed their names and functionality. I explained a nice use for them in a followup post [10], explaining how we can modify the contour labels to have different font sizes and the same colors as their corresponding contour lines.

Categories: Handle graphics, Hidden property, High risk of breaking in future versions, Stock Matlab function


12 Comments (Open | Close)

12 Comments To "Customizing contour plots"

#1 Comment By Eric Sampson On November 19, 2015 @ 09:49

Very nice post Yair, thanks.

#2 Comment By Will On November 25, 2015 @ 05:44

The primitives don’t have a ButtonDownFcn property but they do have a Hit event. It was you who [17]! Adding an event listener directly to those primitives (and of course switching their HitTest property to ‘on’) seems like a much more straightforward solution than the other two…

#3 Comment By Yair Altman On November 25, 2015 @ 05:52

Thanks – I completely forgot about this alternative. There are many ways to skin a cat…

#4 Comment By Diaa Abidou On December 19, 2015 @ 13:53

Hello,

Thanks for sharing this useful piece of information.

However, if I have a filled-contour plot and I would like to hide certain level of some value. For example, to hide levels of temperature equal to or greater than some upper limit for better demonstration. How can I do that ?

Many thanks

#5 Comment By Yair Altman On December 19, 2015 @ 14:02

@Diaa – as the post explains, you have direct access to all the separate contour labels and lines – you can hide/delete those lines and labels which your program’s logic decides should not be displayed.

#6 Comment By Alban Flachot On June 8, 2016 @ 17:53

Hello !

Thank you very much for sharing all this. I may have still a related problem I hope you could help me solve…

I have a mountain-like 3D surface plot and a value as a treshold. When I cut the 3D plot a the treshold level using contourf, it plots separeted surfaces.
For illustration, here is a simple example:

z = peaks(20);
z = z/max(z(:));
[c,h] = contourf(z,[0.4 1]);

gives a figure with 3 delimited areas. I would like to display these areas in different colors, say yellow, green and blue. This seemed possible in the previous versions of matlab using, h.Children, but I can’t see how I could obtain the same result now that internal patches are not defined anymore. Do you perhaps know the answer ?

I am sorry if this is trivial with all the pieces of information you shared already, I am a bit new to matlab and still could not figure this thing out.

Thanks in advance !

#7 Comment By Chunyu Liu On July 11, 2016 @ 18:46

Hi,
Thank you so much for your fruitful share about this.
But I have a problem with saving the figure.
For example, I want to change face color for different contour level.
Once I change it, It show up immediately. However, When I save the figure, the changing goes away.
It just save the original defaulted color.

#8 Comment By Yair Altman On July 11, 2016 @ 19:52

@Chunyu – when you save the figure, many of the undocumented customizations are reset by Matlab.

If you are saving into a *.fig file format, then you can add your customizations to the figure’s CreateFcn callback (see [18] and [19]).

If you are saving to EPS, PDF or some image format then try using the [20] or [21] utilities.

#9 Comment By Austin Spencer On April 9, 2019 @ 21:31

As of at least MATLAB R2018b, the names of some undocumented contour properties have changed. The edges are now listed under EdgeLoopPrims and are of type LineLoop. EdgePrims is still present, but is empty. LineLoop has ColorData and ColorType properties, so the same customization is still possible.

#10 Comment By Austin Spencer On April 11, 2019 @ 17:26

An update/correction after some additional exploration: EdgePrims is still used for contour lines that do not form closed loops, as is the case when a contour line intersects the edge of the dataset. Therefore, in order to set the properties of all contours consistently, you must loop over all EdgePrims and EdgeLoopPrims.

#11 Comment By JCFL On March 2, 2021 @ 12:17

I was trying the above trick to change the transparency of contour lines and wondering why not all the lines have changed — then I found your post. Thanks and your post saved me.

#12 Comment By turhv On December 31, 2020 @ 19:49

very nice! work perfectly to me in MATLAB 2019a.
thanks!!!


Article printed from Undocumented Matlab: https://undocumentedmatlab.com

URL to article: https://undocumentedmatlab.com/articles/customizing-contour-plots

URLs in this post:

[1] which I described: http://undocumentedmatlab.com/blog/customizing-axes-rulers#Axle

[2] topographic map: https://en.wikipedia.org/wiki/Topographic_map

[3] getundoc utility: http://undocumentedmatlab.com/blog/getundoc-get-undocumented-object-properties

[4] uiinspect utility: http://undocumentedmatlab.com/blog/uiinspect

[5] struct function: http://undocumentedmatlab.com/blog/accessing-private-object-properties

[6] hittest(hFig) function: https://www.mathworks.com/matlabcentral/newsreader/view_thread/239883#613421

[7] This article: http://undocumentedmatlab.com/blog/adding-context-menu-to-uitree

[8] MarkedClean event: http://undocumentedmatlab.com/blog/undocumented-hg2-graphics-events

[9] High risk of breaking in future Matlab versions: http://undocumentedmatlab.com/blog/category/presumed-future-risk/high-risk-of-breaking-in-future-versions

[10] a followup post: http://undocumentedmatlab.com/blog/customizing-contour-plots-part2

[11] Customizing contour plots part 2 : https://undocumentedmatlab.com/articles/customizing-contour-plots-part2

[12] Customizing contour plots part 2 : https://undocumentedmatlab.com/articles/customizing-contour-plots-part-2

[13] Customizing histogram plots : https://undocumentedmatlab.com/articles/customizing-histogram-plots

[14] Accessing hidden HG2 plot functionality : https://undocumentedmatlab.com/articles/hidden-hg2-plot-functionality

[15] Customizing axes rulers : https://undocumentedmatlab.com/articles/customizing-axes-rulers

[16] Customizing axes part 2 : https://undocumentedmatlab.com/articles/customizing-axes-part-2

[17] : https://undocumentedmatlab.com/blog/undocumented-hg2-graphics-events

[18] : https://undocumentedmatlab.com/blog/figure-toolbar-components#comment-13890

[19] : https://undocumentedmatlab.com/blog/axes-looseinset-property#comment-327641

[20] : https://undocumentedmatlab.com/blog/export_fig

[21] : https://undocumentedmatlab.com/blog/screencapture-utility

Copyright © Yair Altman - Undocumented Matlab. All rights reserved.