diff --git a/README.md b/README.md new file mode 100644 index 0000000..d7ee2ed --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# MATLAB contour labels along a curve + +The placement of contour labels by MATLAB's `clabel` can be very +frustrating. Some control can be gained with `'LabelSpacing'`, but +`clabel` still has a mind of its own. The user can take full control +with `'manual'` mode, but that doesn't work well in terms of +automation and consistency. + +This tool, `clabel_along`, allows the user to specify a curve along +which to place the contour labels. + +![Example clabel_along result](example.png) + +## Example Use + + [c, h] = contour( peaks ); + clabel_along( c, [1 37], [40 1] ); + clabel_along( c, [25 25], [37.5 30] ); + clabel_along( c, [27.5 27.5], [27 25] ); + +## Documentation + +H = clabel_along( C, X, Y ) labels all contours in C, placing labels +at the intersections between the contours and the curve in X, Y. +Labels are rotated to align with the contour. + +H = clabel_along( C, X, Y, V ) as above, but only labels the contours +with levels contained in vector V. If V is empty (default), then all +contours are labeled. + +H = clabel_along( C, X, Y, V, ROTATE ) as above, but ROTATE is a +boolean flag to enable label rotation. If ROTATE is TRUE (default), +then the labels are rotated. The rotation angle is based on the slope +of the contour at the intersection point and is corrected for the +aspect ratio of the data and the plot. + +If the data or plot aspect ratios change significantly after the call +to clabel_along, the labels may not look properly aligned. + +The graphics handles for all of the labels are returned in H. + +## Dependencies + +`clabel_along` requires Douglas Schwarz's +[intersections](https://www.mathworks.com/matlabcentral/fileexchange/11837) +routine to calculate the intersectinos between the curve and the contours. + +## License + +This software is Copyright (c) Rob McDonald 2021 and is released under the terms specified in the [license](license.txt). + diff --git a/clabel_along.m b/clabel_along.m new file mode 100644 index 0000000..c816089 --- /dev/null +++ b/clabel_along.m @@ -0,0 +1,120 @@ +function h = clabel_along( c, x, y, varargin ) +%CLABEL_ALONG Label contours along a curve +% H = CLABEL_ALONG( C, X, Y ) labels all contours in C, placing labels +% at the intersections between the contours and the curve in X, Y. +% Labels are rotated to align with the contour. +% +% H = CLABEL_ALONG( C, X, Y, V ) as above, but only labels the contours +% with levels contained in vector V. If V is empty (default), then all +% contours are labeled. +% +% H = CLABEL_ALONG( C, X, Y, V, ROTATE ) as above, but ROTATE is a +% boolean flag to enable label rotation. If ROTATE is TRUE (default), +% then the labels are rotated. The rotation angle is based on the slope +% of the contour at the intersection point and is corrected for the +% aspect ratio of the data and the plot. +% +% If the data or plot aspect ratios change significantly after the call +% to CLABEL_ALONG, the labels may not look properly aligned. +% +% The graphics handles for all of the labels are returned in H. +% +% CLABEL_ALONG requires Douglas Schwarz's INTERSECTIONS routine to +% calculate the intersectinos between the curve and the contours. It is +% available from the Matlab File Exchange +% https://www.mathworks.com/matlabcentral/fileexchange/11837 +% +% Example: +% [c, h] = contour( peaks ); +% CLABEL_ALONG( c, [1 37], [40 1] ); +% CLABEL_ALONG( c, [25 25], [37.5 30] ); +% CLABEL_ALONG( c, [27.5 27.5], [27 25] ); +% +% See also CONTOUR, CONTOURF, CONTOURC, CLABEL, INTERSECTIONS, DASPECT, PBASPECT. + +% Rob McDonald +% rob.a.mcdonald@gmail.com +% 25 March 2021 v. 1.0 -- Original version. + +if ( ~exist( 'intersections', 'file' ) ) + error( 'clabel_along:intersections_not_found',... + 'clabel_along could not find intersections.\nIt is available from the MATLAB file exchange:\nhttps://www.mathworks.com/matlabcentral/fileexchange/11837' ); +end + +% Handle values of contours to label. Empty for all (default). +if ( nargin < 4 ) + v = []; +else + v = varargin{1}; +end + +% Handle rotation disabling flag. True to rotate (default). +if ( nargin < 5 ) + rotate = true; +else + rotate = varargin{2}; +end + +% Grab aspect ratios to correct rotation +% Data aspect ratio +da = daspect(); +ard = da(2) / da(1); +% Plot aspect ratio +pa = pbaspect(); +arp = pa(2) / pa(1); + +% Initialize handle array +h = []; + +% Loop over contours in c array +nlimit = size( c , 2 ); +icont = 1; +while( icont < nlimit ) + + % Pull out contour level and number of points in this contour line + level = c( 1, icont ); + n = c( 2, icont ); + + % Only proceed for empty v (all) or contour in v + if ( isempty( v ) || any( level == v ) ) + + % Pick off contour points + xc = c( 1, icont+1:icont+n ); + yc = c( 2, icont+1:icont+n ); + + % Calculate the intersection points between the contours and the + % guide curve using Douglas Schwarz's routine + % https://www.mathworks.com/matlabcentral/fileexchange/11837 + [ xint, yint, iout ] = intersections( xc, yc, x, y ); + + % Loop over all intersections + nint = length( xint ); + for iint = 1:nint + th = 0; + if ( rotate ) + % Calculate text rotation angle based on curve slope and + % plot and data aspect ratios. + iprev = floor( iout( iint ) ); + if ( iprev == length(xc) ) + iprev = iprev - 1; + end + inext = iprev + 1; + + % Calculate aspect ratio adjusted slope + dx = ( xc( inext ) - xc( iprev ) ); + dy = ( yc( inext ) - yc( iprev ) ) * ard * arp; + + % Calculate angle + th = atan( dy / dx ) * 180.0 / pi; + end + + hh = text( xint(iint), yint(iint), num2str( level ), 'rotation', th, 'HorizontalAlignment', 'center', 'VerticalAlignment', 'middle' ); + + % Append text handle + h = [h hh]; + end + end + + % Increment to next contour line + icont = icont + n + 1; +end diff --git a/example.png b/example.png new file mode 100644 index 0000000..a840147 Binary files /dev/null and b/example.png differ