1######################################################################## 2## 3## Copyright (C) 2007-2021 The Octave Project Developers 4## 5## See the file COPYRIGHT.md in the top-level directory of this 6## distribution or <https://octave.org/copyright/>. 7## 8## This file is part of Octave. 9## 10## Octave is free software: you can redistribute it and/or modify it 11## under the terms of the GNU General Public License as published by 12## the Free Software Foundation, either version 3 of the License, or 13## (at your option) any later version. 14## 15## Octave is distributed in the hope that it will be useful, but 16## WITHOUT ANY WARRANTY; without even the implied warranty of 17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18## GNU General Public License for more details. 19## 20## You should have received a copy of the GNU General Public License 21## along with Octave; see the file COPYING. If not, see 22## <https://www.gnu.org/licenses/>. 23## 24######################################################################## 25 26## -*- texinfo -*- 27## @deftypefn {} {} plotyy (@var{x1}, @var{y1}, @var{x2}, @var{y2}) 28## @deftypefnx {} {} plotyy (@dots{}, @var{fun}) 29## @deftypefnx {} {} plotyy (@dots{}, @var{fun1}, @var{fun2}) 30## @deftypefnx {} {} plotyy (@var{hax}, @dots{}) 31## @deftypefnx {} {[@var{ax}, @var{h1}, @var{h2}] =} plotyy (@dots{}) 32## Plot two sets of data with independent y-axes and a common x-axis. 33## 34## The arguments @var{x1} and @var{y1} define the arguments for the first plot 35## and @var{x1} and @var{y2} for the second. 36## 37## By default the arguments are evaluated with 38## @code{feval (@@plot, @var{x}, @var{y})}. However the type of plot can be 39## modified with the @var{fun} argument, in which case the plots are 40## generated by @code{feval (@var{fun}, @var{x}, @var{y})}. @var{fun} can be 41## a function handle, an inline function, or a string of a function name. 42## 43## The function to use for each of the plots can be independently defined 44## with @var{fun1} and @var{fun2}. 45## 46## If the first argument @var{hax} is an axes handle, then it defines 47## the principal axes in which to plot the @var{x1} and @var{y1} data. 48## 49## The return value @var{ax} is a vector with the axes handles of the two 50## y-axes. @var{h1} and @var{h2} are handles to the objects generated by the 51## plot commands. 52## 53## @example 54## @group 55## x = 0:0.1:2*pi; 56## y1 = sin (x); 57## y2 = exp (x - 1); 58## ax = plotyy (x, y1, x - 1, y2, @@plot, @@semilogy); 59## xlabel ("X"); 60## ylabel (ax(1), "Axis 1"); 61## ylabel (ax(2), "Axis 2"); 62## @end group 63## @end example 64## 65## When using @code{plotyy} in conjunction with @code{subplot} make sure to 66## call @code{subplot} first and pass the resulting axes handle to 67## @code{plotyy}. Do not call @code{subplot} with any of the axes handles 68## returned by @code{plotyy} or the other axes will be removed. 69## 70## @seealso{plot, subplot} 71## @end deftypefn 72 73function [ax, h1, h2] = plotyy (varargin) 74 75 [hax, varargin] = __plt_get_axis_arg__ ("plotyy", varargin{:}); 76 77 nargin = numel (varargin); 78 if (nargin < 4 || nargin > 6) 79 print_usage (); 80 endif 81 82 oldfig = []; 83 if (! isempty (hax)) 84 oldfig = get (0, "currentfigure"); 85 endif 86 unwind_protect 87 hax = newplot (hax); 88 89 ## FIXME: Second conditional test shouldn't be required. 90 ## 'cla reset' needs to delete user properties like __plotyy_axes__. 91 if (isprop (hax, "__plotyy_axes__") 92 && isaxes (get (hax, "__plotyy_axes__")) == [true; true]) 93 hax = get (hax, "__plotyy_axes__"); 94 else 95 hax = [hax; axes("nextplot", get (hax(1), "nextplot"), ... 96 "parent", get(hax(1), "parent"))]; 97 endif 98 99 [axtmp, h1tmp, h2tmp] = __plotyy__ (hax, varargin{:}); 100 101 set (gcf, "currentaxes", hax(1)); 102 103 unwind_protect_cleanup 104 if (! isempty (oldfig)) 105 set (0, "currentfigure", oldfig); 106 endif 107 end_unwind_protect 108 109 if (nargout > 0) 110 ax = axtmp; 111 h1 = h1tmp; 112 h2 = h2tmp; 113 endif 114 115endfunction 116 117function [ax, h1, h2] = __plotyy__ (ax, x1, y1, x2, y2, fun1 = @plot, fun2) 118 119 if (nargin < 7) 120 fun2 = fun1; 121 endif 122 123 xlim = [min([x1(:); x2(:)]), max([x1(:); x2(:)])]; 124 125 axes (ax(1)); 126 127 h1 = feval (fun1, x1, y1); 128 129 set (ax(1), "xlim", xlim); 130 if (isscalar (h1)) 131 ## Coloring y-axis only makes sense if plot contains exactly one line 132 set (ax(1), "ycolor", getcolor (h1)); 133 endif 134 135 set (gcf (), "nextplot", "add"); 136 137 axes (ax(2)); 138 139 colors = get (ax(1), "colororder"); 140 set (ax(2), "colororder", circshift (colors, -numel (h1), 1)); 141 142 if (strcmp (get (ax(1), "__autopos_tag__"), "subplot")) 143 set (ax(2), "__autopos_tag__", "subplot"); 144 elseif (strcmp (graphics_toolkit (), "gnuplot")) 145 set (ax, "activepositionproperty", "position"); 146 else 147 set (ax, "activepositionproperty", "outerposition"); 148 endif 149 150 ## Don't replace axis which has colororder property already modified 151 if (strcmp (get (ax(1), "nextplot"), "replace")) 152 set (ax(2), "nextplot", "replacechildren"); 153 endif 154 h2 = feval (fun2, x2, y2); 155 156 set (ax(2), "yaxislocation", "right", "color", "none", "box", "off", 157 "xlim", xlim); 158 if (isscalar (h2)) 159 ## Coloring y-axis only makes sense if plot contains exactly one line 160 set (ax(2), "ycolor", getcolor (h2)); 161 endif 162 163 set (ax(2), "units", get (ax(1), "units")); 164 if (strcmp (get(ax(1), "activepositionproperty"), "position")) 165 set (ax(2), "position", get (ax(1), "position")); 166 else 167 set (ax(2), {"outerposition", "looseinset"}, 168 get (ax(1), {"outerposition", "looseinset"})); 169 endif 170 171 ## Restore nextplot value by copying value from axis #1 172 set (ax(2), "nextplot", get (ax(1), "nextplot")); 173 174 ## Add invisible text objects that when destroyed, 175 ## also remove the other axis 176 t1 = text (0, 0, "", "parent", ax(1), "tag", "plotyy", 177 "visible", "off", "handlevisibility", "off", 178 "xliminclude", "off", "yliminclude", "off", 179 "zliminclude", "off"); 180 181 t2 = text (0, 0, "", "parent", ax(2), "tag", "plotyy", 182 "visible", "off", "handlevisibility", "off", 183 "xliminclude", "off", "yliminclude", "off", 184 "zliminclude", "off"); 185 186 set (t1, "deletefcn", {@deleteplotyy, ax(2), t2}); 187 set (t2, "deletefcn", {@deleteplotyy, ax(1), t1}); 188 189 ## Add cross-listeners so a change in one axes' attributes updates the other. 190 props = {"units", "looseinset", "position", "xlim", "view", ... 191 "plotboxaspectratio", "plotboxaspectratiomode", "nextplot"}; 192 193 for ii = 1:numel (props) 194 addlistener (ax(1), props{ii}, {@update_prop, ax(2), props{ii}}); 195 addlistener (ax(2), props{ii}, {@update_prop, ax(1), props{ii}}); 196 endfor 197 198 ## Store the axes handles for the sister axes. 199 if (! isprop (ax(1), "__plotyy_axes__")) 200 addproperty ("__plotyy_axes__", ax(1), "data"); 201 set (ax(1), "__plotyy_axes__", ax); 202 else 203 set (ax(1), "__plotyy_axes__", ax); 204 endif 205 if (! isprop (ax(2), "__plotyy_axes__")) 206 addproperty ("__plotyy_axes__", ax(2), "data"); 207 set (ax(2), "__plotyy_axes__", ax); 208 else 209 set (ax(2), "__plotyy_axes__", ax); 210 endif 211 212endfunction 213 214function deleteplotyy (h, ~, ax2, t2) 215 if (isaxes (ax2)) 216 set (t2, "deletefcn", []); 217 delete (ax2); 218 endif 219endfunction 220 221function update_nextplot (h, ~, ax2) 222 persistent recursion = false; 223 224 if (! recursion) 225 unwind_protect 226 recursion = true; 227 set (ax2, "nextplot", get (h, "nextplot")); 228 unwind_protect_cleanup 229 recursion = false; 230 end_unwind_protect 231 endif 232 233endfunction 234 235function update_prop (h, ~, ax2, prop) 236 persistent recursion = false; 237 ## Don't allow recursion 238 if (! recursion && all (ishghandle ([h, ax2]))) 239 unwind_protect 240 recursion = true; 241 val = get (h, prop); 242 if (strcmpi (prop, "position") || strcmpi (prop, "outerposition")) 243 ## Save/restore "positionconstraint" 244 constraint = get (ax2, "activepositionproperty"); 245 set (ax2, prop, get (h, prop), "activepositionproperty", constraint); 246 else 247 set (ax2, prop, get (h, prop)); 248 endif 249 unwind_protect_cleanup 250 recursion = false; 251 end_unwind_protect 252 endif 253 254endfunction 255 256function color = getcolor (ax) 257 258 obj = get (ax); 259 if (isfield (obj, "color")) 260 color = obj.color; 261 elseif (isfield (obj, "facecolor") && ! ischar (obj.facecolor)) 262 color = obj.facecolor; 263 elseif (isfield (obj, "edgecolor") && ! ischar (obj.edgecolor)) 264 color = obj.edgecolor; 265 else 266 color = [0, 0, 0]; 267 endif 268 269endfunction 270 271 272%!demo 273%! clf; 274%! x = 0:0.1:2*pi; 275%! y1 = sin (x); 276%! y2 = exp (x - 1); 277%! ax = plotyy (x,y1, x-1,y2, @plot, @semilogy); 278%! xlabel ("X"); 279%! ylabel (ax(1), "Axis 1"); 280%! ylabel (ax(2), "Axis 2"); 281%! colororder = get (gca, "ColorOrder"); 282%! lcolor = colororder(1,:); 283%! rcolor = colororder(2,:); 284%! text (0.5, 0.5, "Left Axis", ... 285%! "color", lcolor, "horizontalalignment", "center", "parent", ax(1)); 286%! text (4.5, 80, "Right Axis", ... 287%! "color", rcolor, "horizontalalignment", "center", "parent", ax(2)); 288%! title ({"plotyy() example"; "left axis uses @plot, right axis uses @semilogy"}); 289 290%!demo 291%! clf; 292%! colormap ("default"); 293%! x = linspace (-1, 1, 201); 294%! subplot (2,2,1); 295%! plotyy (x,sin(pi*x), x,10*cos(pi*x)); 296%! title ("plotyy() in subplot"); 297%! subplot (2,2,2); 298%! surf (peaks (25)); 299%! subplot (2,2,3); 300%! contour (peaks (25)); 301%! subplot (2,2,4); 302%! plotyy (x,10*sin(2*pi*x), x,cos(2*pi*x)); 303%! title ("plotyy() in subplot"); 304%! axis square; 305