%--------------------------------------------
%
% Package pgfplots
%
% Provides a user-friendly interface to create function plots (normal
% plots, semi-logplots and double-logplots).
%
% It is based on Till Tantau's PGF package.
%
% Copyright 2013 by Christian Feuersaenger
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program. If not, see .
%
%--------------------------------------------
%
% This library adds support for a "soft clip" decoration. It applies
% clipping to an input path, but rather than simply instructing the
% display driver to clip the path, it computes a new clip path from
% the input.
%
% This library is (currently) on top of tikz.
\pgfutil@IfUndefined{pgfplotsset}{%
\pgferror{Please load pgfplots before pgfplots.fillbetween.}%
\endinput
}{}%
\usetikzlibrary{intersections}
\pgfplots@iffileexists{pgflibraryfillbetween.code.tex}{%
\usepgflibrary{fillbetween}
}{%
\pgfplotsusecompatibilityfile{pgflibraryfillbetween.code.tex}%
}%
\pgfutil@IfUndefined{pgfintersectiongetsolutiontimes}{%
\pgfplotsusecompatibilityfile{pgflibraryintersections.code.tex}%
}{}%
\pgfkeys{
% soft clip={(axis cs:0,0) rectangle (axis cs:1,1)}
% or
% soft clip={A}
% where 'A' is defined using 'name path=A' somewhere
/pgf/decoration/soft clip path/.code={%
% FIXME : I would rather NOT evaluate path arguments in this
% context! Who knows how people set keys!? But alas, we cannot
% evaluate late because the CM is reset while working on
% decorations ... (see below)
\tikzlibsoftclip@setkey{\tikzlibsoftclip@setkey@assign}#1\pgf@stop
},
/pgf/decoration/every soft clipped path/.style={},%
}
% #1 a macro containing a soft path
\def\tikzlibsoftclip@setkey@assign#1{%
\let\pgf@decoration@soft@clip=#1%
}%
\tikzlibsoftclip@setkey@assign{\pgfutil@empty}%
\pgfdeclaredecoration{soft clip}{replace}{
\state{replace}[width=\pgfdecoratedpathlength,
persistent precomputation={%
% This here is an earlier draft... but alas, the
% transformation matrix has been reset in this context,
% and we cannot define the clip path dynamically here.
% I left it to document that.
%
%\pgfkeysgetvalue{/pgf/decoration/soft clip path}\pgf@temp
%\def\tikz@marshal{\tikzlibsoftclip@setkey{\tikzlibsoftclip@setkey@assign}}%
%\expandafter\tikz@marshal\pgf@temp\pgf@stop
}
]{%
\ifx\pgf@decoration@soft@clip\pgfutil@empty
\pgfplotsthrow{invalid argument}
{\pgf@decoration@soft@clip}%
{The mandatory argument 'soft clip path=(A) rectangle (B)' has not been set}%
\pgfeov
\else
\tikzset{/pgf/decoration/every soft clipped path}%
\pgfpathcomputesoftclippath{\pgfdecoratedpath}{\pgf@decoration@soft@clip}%
\pgfsetpathandBB{\pgfretval}%
\fi
}
}
% ---------------------------------------------------------------------------------
%
% SOFT-CLIPPING.
%
% "softclip" means to get rid of those parts of a path which are
% outside of a clip path.
%
% An example is to trim at the beginning and/or end of a
% path as part of fill-between ("poor-mans-clipping").
%
% The difference to "real" clipping is that it is applied to the
% path, not to the viewer -- the path can still be drawn with any
% decorations, line widths, etc.
%
% Another difference is that this feature is (considerably) less sophisticated.
%
% #1: the input path
% #2: the clip path (if it is empty, no clipping will be applied)
% OUTPUT:
% \pgfretval is #1 with modifications
\def\pgfpathcomputesoftclippath#1#2{%
\ifx#2\pgfutil@empty
\let\pgfretval=#1%
\else
\ifx#1\pgfutil@empty
\let\pgfretval=#1%
\else
\pgfpathcomputesoftclippath@{#1}{#2}%
\fi
\fi
}
\def\pgfpathcomputesoftclippath@#1#2{%
\begingroup
\pgfprocessround{#1}{#1}%
\pgfprocessround{#2}{#2}%
%
%
\pgfpathcomputesoftclippath@is@first@outside@of@path{#1}{#2}%
\let\b@pgffill@is@outside@clip=\pgfretval
%
%\message{computing soft clip path for ^^J\meaning#1 and ^^J\meaning#2^^J first point of input is outside of clip path=\b@pgffill@is@outside@clip^^J}%
%
% FIXME : it might be that I need to sort them ... !? but
% other tests indicate that I should not!?
%\pgfintersectionsortbyfirstpath
\pgf@intersect@sortfalse
\pgfintersectionofpaths%
{%
\pgfsetpath#1%
}%
{%
\pgfsetpath#2%
}%
%
%\message{... num intersections = \pgfintersectionsolutions^^J}%
%
\ifnum\pgfintersectionsolutions=0 %
\if1\b@pgffill@is@outside@clip
% entire path is outside of the clipped area.
\let\pgfretval=\pgfutil@empty
\else
\let\pgfretval=#1%
\fi
\else
% split the first involved path into the
% segments induced by the intersection points:
\pgfcomputeintersectionsegments{1}%
\let\pgfpathfilled@a@segments=\pgfretval
%
% Now, create a new path which contains only those
% segments which are INSIDE of the clip path.
%
% I assume that I can rely on "even/odd" matching: if the
% first is inside, the second is outside, the third
% inside, etc.
\pgfapplistnewempty{pgfretval@tmp}%
\def\c@pgfpathfilled@counter{0}%
\pgfmathloop
\ifnum\c@pgfpathfilled@counter<\pgfpathfilled@a@segments\relax
\if0\b@pgffill@is@outside@clip
\expandafter\let\expandafter\pgf@loc@path@a\csname pgf@intersect@path@split@a@\c@pgfpathfilled@counter\endcsname
\expandafter\pgfapplistpushback\pgf@loc@path@a\to{pgfretval@tmp}%
\fi
\pgfpathfillbetween@negate\b@pgffill@is@outside@clip
%
\pgfutil@advancestringcounter\c@pgfpathfilled@counter
\repeatpgfmathloop
\pgfapplistlet\pgfretval={pgfretval@tmp}%
\fi
%
\global\let\pgf@glob@TMPa=\pgfretval%
\endgroup
\let\pgfretval=\pgf@glob@TMPa
}
% #1: input path (non-empty)
% #2: soft clip path
% Defines "\def\pgfretval{1}" if (the first point of #1 is outside or on the path #2)
% Defines "\def\pgfretval{0}" if (the first point of #1 is inside of the path #2)
\def\pgfpathcomputesoftclippath@is@first@outside@of@path#1#2{%
% APPROACH: shoot a line starting at the first coordinate of the
% first path through "the middle of #2". Then make an even/odd
% check on the number of intersections.
%
% FIXME : for now, I only support (x,y) rectangle (X,Y) anyway --
% optimize for that case!? This here might be too complex...
\begingroup
\expandafter\pgfpathfillbetween@get@first@coord#1\pgf@stop
\let\pgfpathfilled@a@firstcoord=\pgfretval%
%
% Get some point "in the middle of #2":
\pgfpathcomputesoftclippath@accum@pseudo@mean#2%
\edef\pgfpathfilled@b@center{\noexpand\pgfqpoint\pgfretval}%
%
% We have to shoot *through* #2, not just into the middle of #2.
% Consequently, we need to know how big #2 is:
\pgfpathcomputesoftclippath@is@first@outside@of@path@getBB#2%
\let\pgf@size@hint=\pgfretval
%
% Now, compute a target point such that our shot goes through it:
\pgfqpointscale{%
\pgf@size@hint
}{%
\pgfpointnormalised{%
\pgfpointdiff%
{\expandafter\pgfqpoint\pgfpathfilled@a@firstcoord}%
{\pgfpathfilled@b@center}%
}%
}%
% collect intermediate results as \pgf@xa/\pgf@ya are overwritten:
\edef\pgf@direction@vector{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}%
\pgfpointadd{\pgfpathfilled@b@center}{\pgf@direction@vector}%
\edef\pgfpathfilled@shoot@line@trg{{\the\pgf@x}{\the\pgf@y}}%
%
% this here is our path!
\edef\pgf@direction@path{%
\noexpand\pgfsyssoftpath@movetotoken\pgfpathfilled@a@firstcoord
\noexpand\pgfsyssoftpath@linetotoken\pgfpathfilled@shoot@line@trg
}%
%
% ... ok, compute intersections. This should be fairly fast as
% our second path has just 1 segment, i.e. it will be O(N) where N
% is the number of segments in #2.
\pgf@intersect@sortfalse
\pgfintersectionofpaths%
{%
\pgfsetpath#2%
}%
{%
\pgfsetpath\pgf@direction@path%
}%
%
\ifnum\pgfintersectionsolutions=0 %
% this must not happen! We have taken great care such that we
% *have* at least 1 intersection points!
\pgferror{Illegal state encountered: the computation of a softpath failed. The failure occurred while computing whether the first input coordinate is inside of the clip path (found no intersections)}%
\fi
%
\pgfpathcomputesoftclippath@if@is@first@on@boundary{%
% AH! The first point is ON the boundary of the clip path.
% This is equivalent to the condition that the first
% intersection point equals the first point.
%
% In this case, we say that it is OUTSIDE of the clip path!
%
% This is actually related to
% 1. the fact that the input path is splitted into segments
% induced by the intersection points in order to apply
% soft-clipping.
% 2. the fact that the clipping is based on an even/odd
% matching (in/out).
%
% In that setup, we want to skip the segment until the first
% intersection point - after all, it is "empty" anyway.
%
% To this end, we have to say that this first (empty) segment
% is OUTSIDE:
\def\pgfretval{1}%
}{%
\ifodd\pgfintersectionsolutions\relax%
\def\pgfretval{0}%
\else
\def\pgfretval{1}%
\fi
}%
\pgfmath@smuggleone\pgfretval
\endgroup
}
\def\pgfpathcomputesoftclippath@if@is@first@on@boundary#1#2{%
\pgfpathcomputesoftclippath@compute@infty@norm{%
\pgfpointdiff%
{\expandafter\pgfqpoint\pgfpathfilled@a@firstcoord}%
{\pgfpointintersectionsolution{1}}%
}%
\ifdim\pgf@x<\pgfintersectiontolerance\relax
% YES: the first point IS on the boundary: the first
% intersection solution == first point.
#1%
\else
% No, it is NOT equal to the first intersection solution - and
% thus NOT on the boundary.
#2%
\fi
}%
% Defines \pgf@x to be the infty norm of vector #1
\def\pgfpathcomputesoftclippath@compute@infty@norm#1{%
\pgf@process{#1}%
\ifdim\pgf@x<0sp \global\pgf@x=-\pgf@x\fi
\ifdim\pgf@y<0sp \global\pgf@y=-\pgf@y\fi
\ifdim\pgf@x<\pgf@y
\global\pgf@x=\pgf@y
\fi
}
% Defines \pgfretval to be a *scalar* "size indicator" (1-norm) of the bounding
% box of #1.
%
% #1: a macro containing a softpath.
\def\pgfpathcomputesoftclippath@is@first@outside@of@path@getBB#1{%
\begingroup
\pgf@getpathsizes\pgf@interrupt@pathsizes
% we only need the path size here:
\pgf@relevantforpicturesizefalse
%
% FIXME : CODE CLEANUP NEEDED
\def\pgfsetpathBB@protocol@lastmoveto##1##2{}%
\expandafter\pgfsetpath@loop#1\pgf@stop
\pgfpointdiff
{\pgfqpoint\pgf@pathminx\pgf@pathminy}%
{\pgfqpoint\pgf@pathmaxx\pgf@pathmaxy}%
% compute |v|_1 = x + y (both components are non-negative anyway):
\pgf@xa=\pgf@x
\advance\pgf@xa by\pgf@y
\xdef\pgf@glob@TMPa{\pgf@sys@tonumber\pgf@xa}%
\pgf@setpathsizes\pgf@interrupt@pathsizes
\endgroup
\let\pgfretval=\pgf@glob@TMPa
}%
% Defines \pgfretval to be of a "pseudo" mean of path #1.
%
% Here, "pseudo" refers to the fact that the mean will only be
% accumulated over the "first couple of coordinates" to avoid numeric
% overflows in TeX's math engine.
\def\pgfpathcomputesoftclippath@accum@pseudo@mean#1{%
\begingroup
\let\pgfsyssoftpath@movetotoken\pgfpathcomputesoftclippath@accum@pseudo@mean@
\let\pgfsyssoftpath@linetotoken\pgfpathcomputesoftclippath@accum@pseudo@mean@
\let\pgfsyssoftpath@closepathtoken\pgfpathcomputesoftclippath@accum@pseudo@mean@
\let\pgfsyssoftpath@curvetotoken\pgfpathcomputesoftclippath@accum@pseudo@mean@
\let\pgfsyssoftpath@curvetosupportatoken\pgfpathcomputesoftclippath@accum@pseudo@mean@relax
\let\pgfsyssoftpath@curvetosupportbtoken\pgfpathcomputesoftclippath@accum@pseudo@mean@relax
\c@pgf@countc=0 %
\pgf@xa=0pt %
\pgf@ya=0pt %
#1%
\divide\pgf@xa by\c@pgf@countc
\divide\pgf@ya by\c@pgf@countc
\edef\pgfretval{{\the\pgf@xa}{\the\pgf@ya}}%
\pgfmath@smuggleone\pgfretval
\endgroup
}%
\def\pgfpathcomputesoftclippath@accum@pseudo@mean@relax#1#2{}
\def\pgfpathcomputesoftclippath@accum@pseudo@mean@#1#2{%
\ifnum\c@pgf@countc<4
% avoid overflows. 4 must be sufficient for now.
\advance\pgf@xa by#1\relax
\advance\pgf@ya by#2\relax
\advance\c@pgf@countc by1 %
\fi
}
% #1: of the form '{}{}'
% #2: of the form '{}{}'
\def\pgfpathfillbetween@check@x@less@than#1#2{%
\edef\pgf@temp{#1#2}%
\expandafter\pgfpathfillbetween@check@x@less@than@\pgf@temp
}%
% #1: x1
% #2: y1
% #3: x2
% #4: y2
\def\pgfpathfillbetween@check@x@less@than@#1#2#3#4{%
\ifdim#1>#3\relax
% <=
\def\pgfretval{0}%
\else
\def\pgfretval{1}%
\fi
}%
\def\pgfpathfillbetween@negate#1{%
\if0#1%
\def#1{1}%
\else
\def#1{0}%
\fi
}
\pgfkeys{
/tikz/soft clip assign/name/.code={\tikzgetnamedpath{#1}},
/tikz/soft clip assign/path/.code={\tikzlibsoftclip@setkey@@#1\pgf@stop},
}
%
% \tikzlibsoftclip@setkey{<\macro>} \pgf@stop
%
% OR
%
% \tikzlibsoftclip@setkey{<\macro>} () rectangle ()\pgf@stop
%
% #1: a macro which is called with the resulting clip path as argument #1.
\def\tikzlibsoftclip@setkey#1#2\pgf@stop{%
\pgfutil@in@{=}{#2}%
\ifpgfutil@in@
\pgfqkeys{/tikz/soft clip assign}{#2}%
\else
\def\pgf@temp{#2}%
\pgfplots@command@to@string\pgf@temp\pgf@temp
%
\tikzifisnamedpath{\pgf@temp}{%
\pgfkeysalso{/tikz/soft clip assign/name={#2}}%
}{%
\pgfkeysalso{/tikz/soft clip assign/path={#2}}%
}%
\fi
#1{\pgfretval}%
}
\def\tikzlibsoftclip@setkey@@{%
\edef\tikzlibsoftclip@setkey@@reset{\noexpand\tikz@expandcount=\the\tikz@expandcount\relax}%
\tikz@expandcount=100 %
%
\tikz@scan@one@point\tikzlibsoftclip@setkey@bb@scan@a
}%
\def\tikzlibsoftclip@setkey@bb@scan@a#1{%
\def\tikzlibsoftclip@setkey@bb@a{#1}%
\pgfutil@ifnextchar r{%
\tikzlibsoftclip@setkey@bb@scan@rectangle
}{%
\tikzlibsoftclip@setkey@@error
}%
}%
\def\tikzlibsoftclip@setkey@bb@scan@rectangle rectangle{%
\tikz@scan@one@point\tikzlibsoftclip@setkey@bb@scan@b
}%
\def\tikz@gobble@until@stop#1\pgf@stop{}%
\def\tikzlibsoftclip@setkey@bb@scan@b#1{%
\def\tikzlibsoftclip@setkey@bb@b{#1}%
\pgfutil@ifnextchar \pgf@stop{%
\tikzlibsoftclip@setkey@@reset
\tikzlibsoftclip@setkey@@activate
\tikz@gobble@until@stop
}{%
\tikzlibsoftclip@setkey@@error
}%
}%
\def\tikzlibsoftclip@setkey@@error#1\pgf@stop{%
\def\pgfplots@loc@TMPa{#1}%
\pgfplots@command@to@string\pgfplots@loc@TMPa\pgfplots@loc@TMPb
\pgfplotsthrow{invalid argument}
{\pgfplots@loc@TMPa}%
{fill between: the argument of 'soft clip' has an unexpected format near '\pgfplots@loc@TMPb'; expected '() rectangle ()'}%
\pgfeov
}%
% INPUT:
% - two PGF points \tikzlibsoftclip@setkey@bb@a and \tikzlibsoftclip@setkey@bb@b.
%
% POSTCONDITION: \pgfretval contains the resulting path.
\def\tikzlibsoftclip@setkey@@activate{%
% Expand points to {}{} ...
\pgf@process{\tikzlibsoftclip@setkey@bb@a}%
\edef\tikzlibsoftclip@setkey@bb@a{{\the\pgf@x}{\the\pgf@y}}%
\pgf@process{\tikzlibsoftclip@setkey@bb@b}%
\edef\tikzlibsoftclip@setkey@bb@b{{\the\pgf@x}{\the\pgf@y}}%
%
\pgfinterruptpath
\pgf@relevantforpicturesizefalse%
\expandafter\pgfqpoint\tikzlibsoftclip@setkey@bb@a
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y
\expandafter\pgfqpoint\tikzlibsoftclip@setkey@bb@b
\pgf@xb=\pgf@x
\pgf@yb=\pgf@y
%
\pgfpathmoveto{\pgfqpoint{\pgf@xa}{\pgf@ya}}%
\pgfpathlineto{\pgfqpoint{\pgf@xa}{\pgf@yb}}%
\pgfpathlineto{\pgfqpoint{\pgf@xb}{\pgf@yb}}%
\pgfpathlineto{\pgfqpoint{\pgf@xb}{\pgf@ya}}%
\pgfpathclose
\pgfgetpath\pgfplots@loc@TMPa
\global\let\pgfplots@glob@TMPa=\pgfplots@loc@TMPa
\endpgfinterruptpath
%
\let\pgfretval=\pgfplots@glob@TMPa
}%
% ---------------------------------------------------------------------------------
% Executes #2 if #1 is a named path and #3 otherwise.
\def\tikzifisnamedpath#1#2#3{%
\pgfutil@IfUndefined{tikz@intersect@path@name@#1}{%
\def\tikz@next{#3}%
}{%
\def\tikz@next{#2}%
}%
\tikz@next
}%