Controlling plot data-tips

Plot data tips are a great visualization aid for Matlab plots. They enable users to interactively click on a plot location and see a tool-tip that contains the clicked location's coordinates. The displayed tooltip text is even customizable using documented properties of the datacursormode object.

plot data tips
plot data tips

A client has recently asked me to automatically display an attached data-tip to the last data point of a plotted time series of values. The idea was to immediately see what the latest value of the data series is.
Unfortunately, the official documentation clearly says that:

You place data tips only by clicking data objects on graphs. You cannot place them programmatically (by executing code to position a data cursor).

Well, this has never stopped us before, has it?

Creating new data tips

Under the hood, data tips use a data-cursor mode, which shares many similarities in behavior and programming code with the other plot modes (zoom, pan, data-brushing, etc.). At any one time, only a single such mode can be active in any figure window (this is a known limitation of the design). The code itself it actually quite complex and handles numerous edge-cases. Understanding it by simply reading the code (under %matlabroot%\toolbox\matlab\graphics\) is actually pretty difficult. A much easier way to understand the programming flow is to liberally distribute breakpoints (start in datacursormode.m) and interactively activate the functionality, then debug the code step-by-step.
Luckily, it turns out that the code to create a new data-tip is actually quite simple: first get the data-cursor mode object, then create a new data tip using the mode's createDatatip() method, update some data-tip properties and finally update the data-tip's position:

                  % First plot the data                  hLine =                  plot                  (xdata, ydata);                  % First get the figure's data-cursor mode, activate it, and set some of its properties                  cursorMode = datacursormode(                  gcf                  );                  set                  (cursorMode,                  'enable','on',                  'UpdateFcn',@setDataTipTxt);                  % Note: the optional @setDataTipTxt is used to customize the data-tip's content text                  % Note: the following code was adapted from %matlabroot%\toolbox\matlab\graphics\datacursormode.m                  % Create a new data tip                  hTarget = handle(hLine); hDatatip = cursorMode.createDatatip                  (hTarget);                  % Create a copy of the context menu for the datatip:                  set                  (hDatatip,'UIContextMenu',get                  (cursorMode,'UIContextMenu'                  )                  );                  set                  (hDatatip,'HandleVisibility','off'                  );                  set                  (hDatatip,'Host',hTarget);                  set                  (hDatatip,'ViewStyle','datatip'                  );                  % Set the data-tip orientation to top-right rather than auto                  set                  (hDatatip,'OrientationMode','manual'                  );                  set                  (hDatatip,'Orientation','top-right'                  );                  % Update the datatip marker appearance                  set                  (hDatatip,                  'MarkerSize',5,                  'MarkerFaceColor','none',                  ...                  'MarkerEdgeColor','k',                  'Marker','o',                  'HitTest','off'                  );                  % Move the datatip to the right-most data vertex point                  position =                  [xdata(                  end                  ),ydata(                  end                  ),1; xdata(                  end                  ),ydata(                  end                  ),-1                  ]; update(hDatatip, position);

% First plot the data hLine = plot(xdata, ydata); % First get the figure's data-cursor mode, activate it, and set some of its properties cursorMode = datacursormode(gcf); set(cursorMode, 'enable','on', 'UpdateFcn',@setDataTipTxt); % Note: the optional @setDataTipTxt is used to customize the data-tip's content text % Note: the following code was adapted from %matlabroot%\toolbox\matlab\graphics\datacursormode.m % Create a new data tip hTarget = handle(hLine); hDatatip = cursorMode.createDatatip(hTarget); % Create a copy of the context menu for the datatip: set(hDatatip,'UIContextMenu',get(cursorMode,'UIContextMenu')); set(hDatatip,'HandleVisibility','off'); set(hDatatip,'Host',hTarget); set(hDatatip,'ViewStyle','datatip'); % Set the data-tip orientation to top-right rather than auto set(hDatatip,'OrientationMode','manual'); set(hDatatip,'Orientation','top-right'); % Update the datatip marker appearance set(hDatatip, 'MarkerSize',5, 'MarkerFaceColor','none', ... 'MarkerEdgeColor','k', 'Marker','o', 'HitTest','off'); % Move the datatip to the right-most data vertex point position = [xdata(end),ydata(end),1; xdata(end),ydata(end),-1]; update(hDatatip, position);

Note: If you don't like messing with the code, consider using Tim Farajian's MakeDataTip utility, which basically does all this behind the scenes. It is much easier to use as a stand-alone utility, although it does not give you the flexiblility with all the data-tip properties as in the code above.

Updating an existing data tip

To modify the appearance of a data-tip, we first need to get access to the hDatatip object that we created earlier, either programmatically, or interactively (or both). Since we can access pre-stored handles only of programmatically-created (not interactively-created) data-tips, we need to use a different method. There are actually two ways to do this:
The basic way is to search the relevant axes for objects that have Tag='DataTipMarker'. For each data-tip, we will get two such handles: one for the marker (Type='line') and the other for the text box tooltip (Type='text'). We can use these to update (for example) the marker size, color and style; and the text's font, border and colors.
A better way is to access the graphics.datatip object itself. This can be done using two hidden properties of the datacursormode object:

                  % Get the list of all data-tips in the current figure                  >> cursorMode = datacursormode(                  gcf                  )                  cursorMode = 	graphics.datacursormanager                  >> cursorMode.DataCursors                  ans                  = 	graphics.datatip:                  2-by-1                  >> cursorMode.CurrentDataCursor                  ans                  = 	graphics.datatip                  >> cursorMode.CurrentDataCursor.get                  Annotation:                  [1x1 hg.Annotation                  ]                  DisplayName:                  ''                  HitTestArea:                  'off'                  BeingDeleted:                  'off'                  ButtonDownFcn:                  [                  ]                  Children:                  [2x1                  double                  ]                  Clipping:                  'on'                  CreateFcn:                  [                  ]                  DeleteFcn:                  [                  ]                  BusyAction:                  'queue'                  HandleVisibility:                  'off'                  HitTest:                  'off'                  Interruptible:                  'on'                  Parent:                  492.005493164063                  SelectionHighlight:                  'on'                  Tag:                  ''                  Type:                  'hggroup'                  UserData:                  [                  ]                  Selected:                  'off'                  FontAngle:                  'normal'                  FontName:                  'Helvetica'                  FontSize:                  8                  FontUnits:                  'points'                  FontWeight:                  'normal'                  EdgeColor:                  [                  0.8                  0.8                  0.8                  ]                  BackgroundColor:                  [                  1                  1                  0.933333333333333                  ]                  TextColor:                  [                  0                  0                  0                  ]                  Marker:                  'o'                  MarkerSize:                  5                  MarkerEdgeColor:                  'k'                  MarkerFaceColor:                  'none'                  MarkerEraseMode:                  'normal'                  Draggable:                  'on'                  String:                  {                  'Date: 01/09/11'                  'Value: 573.24'                  }                  Visible:                  'on'                  StringFcn:                  [                  ]                  UpdateFcn:                  [                  ]                  UIContextMenu:                  [1x1                  uicontextmenu                  ]                  Host:                  [1x1 graph2d.lineseries                  ]                  Interpolate:                  'off'                

% Get the list of all data-tips in the current figure >> cursorMode = datacursormode(gcf) cursorMode = graphics.datacursormanager >> cursorMode.DataCursors ans = graphics.datatip: 2-by-1 >> cursorMode.CurrentDataCursor ans = graphics.datatip >> cursorMode.CurrentDataCursor.get Annotation: [1x1 hg.Annotation] DisplayName: '' HitTestArea: 'off' BeingDeleted: 'off' ButtonDownFcn: [] Children: [2x1 double] Clipping: 'on' CreateFcn: [] DeleteFcn: [] BusyAction: 'queue' HandleVisibility: 'off' HitTest: 'off' Interruptible: 'on' Parent: 492.005493164063 SelectionHighlight: 'on' Tag: '' Type: 'hggroup' UserData: [] Selected: 'off' FontAngle: 'normal' FontName: 'Helvetica' FontSize: 8 FontUnits: 'points' FontWeight: 'normal' EdgeColor: [0.8 0.8 0.8] BackgroundColor: [1 1 0.933333333333333] TextColor: [0 0 0] Marker: 'o' MarkerSize: 5 MarkerEdgeColor: 'k' MarkerFaceColor: 'none' MarkerEraseMode: 'normal' Draggable: 'on' String: {'Date: 01/09/11' 'Value: 573.24'} Visible: 'on' StringFcn: [] UpdateFcn: [] UIContextMenu: [1x1 uicontextmenu] Host: [1x1 graph2d.lineseries] Interpolate: 'off'

We can see that the returned graphics.datatip object includes properties of both the text-box and the marker, making it easy to modify. Moreover, we can use its aforementioned update method to move the datatip to a different plot position (see example in the code above). In addition, we can also use the self-explanatory getCursorInfo(), getaxes(), makeCurrent(), movetofront() methods, and a few others.

Cursor mode and data-tip properties

The graphics.datacursormanager and the graphics.datatip objects have several public properties that we can use:

>> cursorMode.get                  Enable:                  'off'                  SnapToDataVertex:                  'on'                  DisplayStyle:                  'datatip'                  UpdateFcn: @setDataTipTxt                  Figure:                  [1x1                  figure                  ]                  >> cursorMode.CurrentDataCursor.get                  Annotation:                  [1x1 hg.Annotation                  ]                  DisplayName:                  ''                  HitTestArea:                  'off'                  ...                  % See the list above                

>> cursorMode.get Enable: 'off' SnapToDataVertex: 'on' DisplayStyle: 'datatip' UpdateFcn: @setDataTipTxt Figure: [1x1 figure] >> cursorMode.CurrentDataCursor.get Annotation: [1x1 hg.Annotation] DisplayName: '' HitTestArea: 'off' ... % See the list above

Both these objects have plenty of additional hidden properties. You can inspect them using my uiinspect utility. Here is a brief list for reference (R2011b):
graphics.datacursormanager:

  • CurrentDataCursor
  • DataCursors
  • Debug
  • DefaultExportVarName
  • DefaultPanelPosition
  • EnableAxesStacking
  • EnableZStacking
  • ExternalListeners
  • HiddenUpdateFcn
  • NewDataCursorOnClick
  • OriginalRenderer
  • OriginalRendererMode
  • PanelDatatipHandle
  • PanelHandle
  • PanelTextHandle
  • UIContextMenu
  • UIState
  • ZStackMinimum

graphics.datatip:

  • ALimInclude
  • ApplicationData
  • Behavior
  • CLimInclude
  • DataCursorHandle
  • DataManagerHandle
  • Debug
  • DoThrowStartDragEvent
  • EmptyArgUpdateFcn
  • EnableAxesStacking
  • EnableZStacking
  • EraseMode
  • EventObject
  • ExternalListenerHandles
  • HelpTopicKey
  • HostAxes
  • HostListenerHandles
  • IncludeRenderer
  • Invalid
  • IsDeserializing
  • MarkerHandle
  • MarkerHandleButtonDownFcn
  • Orientation
  • OrientationMode
  • OrientationPropertyListener
  • OriginalDoubleBufferState
  • PixelBounds
  • PointsOffset
  • Position
  • SelfListenerHandles
  • Serializable
  • TextBoxHandle
  • TextBoxHandleButtonDownFcn
  • Version
  • ViewStyle
  • XLimInclude
  • YLimInclude
  • ZLimInclude
  • ZStackMinimum
  • uistate

As can be seen, if we really want, we can always use the MarkerHandle or TextBoxHandle directly.

Deleting data tips

To delete a specific data-tip, simply call the cursor mode's removeDataCursor() method; to delete all data-tips, call its removeAllDataCursors() method:

                  % Delete the current data-tip                  cursorMode.removeDataCursor                  (cursorMode.CurrentDataCursor                  )                  % Delete all data-tips                  cursorMode.removeAllDataCursors                  (                  )                

% Delete the current data-tip cursorMode.removeDataCursor(cursorMode.CurrentDataCursor) % Delete all data-tips cursorMode.removeAllDataCursors()

Have you used plot data-tips in some nifty way? If so, please share your experience in a comment below.
p.s. – did you notice that Java was not mentioned anywhere above? Mode managers use pure-Matlab functionality.

Print Print
Leave a Reply
HTML tags such as <b> or <i> are accepted.
Wrap code fragments inside <pre lang="matlab"> tags, like this:
<pre lang="matlab">
a = magic(3);
disp(sum(a))
</pre>
I reserve the right to edit/delete comments (read the site policies).
Not all comments will be answered. You can always email me (altmany at gmail) for private consulting.