diff --git a/tex/generic/pgf/frontendlayer/tikz/libraries/datavisualization/tikzlibrarydatavisualization.code.tex b/tex/generic/pgf/frontendlayer/tikz/libraries/datavisualization/tikzlibrarydatavisualization.code.tex index 04dae1e6b..6d7d7b908 100644 --- a/tex/generic/pgf/frontendlayer/tikz/libraries/datavisualization/tikzlibrarydatavisualization.code.tex +++ b/tex/generic/pgf/frontendlayer/tikz/libraries/datavisualization/tikzlibrarydatavisualization.code.tex @@ -1538,6 +1538,7 @@ smooth cycle/.style={@set={\pgfplothandlerclosedcurve}{default label in legend closed path}}, straight line/.style={@set={\pgfplothandlerlineto}{default label in legend path}}, straight cycle/.style={@set={\pgfplothandlerpolygon}{default label in legend closed path}}, + smooth monotonic line/.style={@set={\pgfplothandlermonotonic}{default label in legend path}}, polygon/.style={straight cycle},% alias gap line/.style={@set={\pgfplothandlergaplineto}{default label in legend path}}, gap cycle/.style={@set={\pgfplothandlergapcycle}{gap circular label in legend line}} diff --git a/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex b/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex index d9b1743b9..14f5d2248 100644 --- a/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex +++ b/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex @@ -1232,6 +1232,7 @@ % Plot options \tikzoption{smooth}[]{\let\tikz@plot@handler=\pgfplothandlercurveto}% \tikzoption{smooth cycle}[]{\let\tikz@plot@handler=\pgfplothandlerclosedcurve}% +\tikzoption{smooth monotonic}[]{\let\tikz@plot@handler=\pgfplothandlermonotonic}% \tikzoption{sharp plot}[]{\let\tikz@plot@handler\pgfplothandlerlineto}% \tikzoption{sharp cycle}[]{\let\tikz@plot@handler\pgfplothandlerpolygon}% diff --git a/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex b/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex index d0b9a388e..3b9031629 100644 --- a/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex +++ b/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex @@ -269,6 +269,135 @@ }% +% This handler converts each plot stream command into a curveto +% command, except for the first, which is converted to the previously +% specified action. The curveto control points are computed and adjusted +% so that the resulting curve respects the monotonicity of the points. +% If a point is a local extremum of the point set, it will also be +% a local extremum of the generated curve. +% This handler treats the x and y direction differently, and expects +% x values to be monotonic. +% See https://en.wikipedia.org/wiki/Monotone_cubic_interpolation for an +% explanation of the algorithm. The last part uses \pgfpathcurveto +% instead of using Hermitte base functions. +\pgfdeclareplothandler{\pgfplothandlermonotonic}{}{% + point macro=\pgf@plot@monotonic@dopointone, + jump macro=\pgf@plot@monotonic@dojump, + end macro=\pgf@plot@monotonic@doend +}% + +\def\pgf@plot@monotonic@dojump{% + \pgf@plot@monotonic@doend + \global\pgf@plot@startedfalse + \global\let\pgf@plotstreampoint\pgf@plot@monotonic@dopointone +}% + +\def\pgf@plot@monotonic@dopointone#1{% + \pgf@process{#1}% + \pgf@plot@first@action{\pgfqpoint{\pgf@x}{\pgf@y}}% + \xdef\pgf@plot@monotonic@pointone{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}% + \global\let\pgf@plotstreampoint=\pgf@plot@monotonic@dopointtwo +}% + +\def\pgf@plot@monotonic@dopointtwo#1{% + \pgf@process{#1}% + \xdef\pgf@plot@monotonic@pointtwo{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}% + % compute vector: + \pgf@xa=\pgf@x% + \pgf@ya=\pgf@y% + \pgf@process{\pgf@plot@monotonic@pointone}% + \advance\pgf@xa by-\pgf@x + \advance\pgf@ya by-\pgf@y + % save left delta + \xdef\pgf@plot@monotonic@deltaleft{\pgf@sys@tonumber\pgf@xa}% + % compute slope + \pgfmathdivide@{\the\pgf@ya}{\the\pgf@xa}% + \global\let\pgf@plot@monotonic@rateleft\pgfmathresult + % init tangent one + \global\let\pgf@plot@monotonic@slopeone\pgf@plot@monotonic@rateleft + % prepare for next step + \global\let\pgf@plotstreampoint=\pgf@plot@monotonic@dopointthree + \global\pgf@plot@startedtrue% +}% + +\def\pgf@plot@monotonic@dopointthree#1{% + \pgf@process{#1}% + \xdef\pgf@plot@monotonic@pointthree{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}% + % compute vector: + \pgf@xa=\pgf@x% + \pgf@ya=\pgf@y% + \pgf@process{\pgf@plot@monotonic@pointtwo}% + \advance\pgf@xa by-\pgf@x + \advance\pgf@ya by-\pgf@y + % save right delta + \xdef\pgf@plot@monotonic@deltaright{\pgf@sys@tonumber\pgf@xa}% + % compute slope + \pgfmathdivide@{\the\pgf@ya}{\the\pgf@xa}% + \global\let\pgf@plot@monotonic@rateright\pgfmathresult + % compute tangent slope + \pgfmathadd@{\pgf@plot@monotonic@rateleft}{\pgf@plot@monotonic@rateright}% + \pgf@xa=\pgfmathresult pt\relax + \pgf@xa=0.5\pgf@xa + % Fix slopes & draw curve + \pgf@plot@monotoniccurveto@fixdraw + % Prepare next: + \global\let\pgf@plot@monotonic@slopeone\pgf@plot@monotonic@slopetwo + \global\let\pgf@plot@monotonic@pointone\pgf@plot@monotonic@pointtwo + \global\let\pgf@plot@monotonic@pointtwo\pgf@plot@monotonic@pointthree + \global\let\pgf@plot@monotonic@deltaleft\pgf@plot@monotonic@deltaright + \global\let\pgf@plot@monotonic@rateleft\pgf@plot@monotonic@rateright +}% + +\def\pgf@plot@monotoniccurveto@fixdraw{% + % fix extremums + % \pgf@xa is slope two + \pgf@ya=\pgf@plot@monotonic@rateleft pt\relax + \pgf@yb=\pgf@plot@monotonic@rateright\pgf@ya + \ifdim\pgf@yb<0.0001pt\relax + \pgf@xa=0pt\relax + \else + % fix motonicity + \pgf@ya=3\pgf@ya + \ifdim\pgf@xa>0pt\relax + \ifdim\pgf@xa>\pgf@ya \pgf@xa=\pgf@ya\fi + \else + \ifdim\pgf@xa<\pgf@ya \pgf@xa=\pgf@ya\fi + \fi + \pgf@ya=\pgf@plot@monotonic@rateright pt\relax + \pgf@ya=3\pgf@ya + \ifdim\pgf@xa>0pt\relax + \ifdim\pgf@xa>\pgf@ya \pgf@xa=\pgf@ya\fi + \else + \ifdim\pgf@xa<\pgf@ya \pgf@xa=\pgf@ya\fi + \fi + \fi + \xdef\pgf@plot@monotonic@slopetwo{\pgf@sys@tonumber\pgf@xa}% + % compute points for left bezier + % (\pgf@x,\pgf@y) is point two + \pgf@ya=\pgf@plot@monotonic@deltaleft pt\relax + \pgf@ya=0.333333\pgf@ya + \advance \pgf@x by -\pgf@ya + \advance \pgf@y by -\pgf@plot@monotonic@slopetwo\pgf@ya + \xdef\pgf@plot@monotonic@supporttwo{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}% + \pgf@process{\pgf@plot@monotonic@pointone}% + \advance \pgf@x by \pgf@ya + \advance \pgf@y by \pgf@plot@monotonic@slopeone\pgf@ya + \xdef\pgf@plot@monotonic@supportone{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}% + % draw left curve: + \pgfpathcurveto{\pgf@plot@monotonic@supportone}{\pgf@plot@monotonic@supporttwo}{\pgf@plot@monotonic@pointtwo}% +}% + +\def\pgf@plot@monotonic@doend{% + \ifpgf@plot@started + % fixdraw needs (\pgf@x,\pgf@y) as point two + % and \pgf@xa as slope two + \pgf@process{\pgf@plot@monotonic@pointtwo}% + \pgf@xa=\pgf@plot@monotonic@rateleft pt\relax + \pgf@plot@monotoniccurveto@fixdraw + \fi +}% + +