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