13

I'd like to create climographs (climate diagrams) with pgfplots, but these graphs have some special rules (defined by Walter and Lieth about 50 years ago) for axis scaling which I have absolutely no idea how to realize with pgfplots, because the second y axis scaling is allowed to change inbetween and the scalings of both y axis are coupled to each other. Examples below.

Rules: The left y axis (temperature T) scales in steps of 10°C starting at zero if no temperature below zero exists, else it also extends in steps of 10°C below zero. This is still the easy part (I can do that), now for the hard part: The right y axis (amount of rain) scales with two times the value of T up to 300mm. But if rain above 300mm exists it scales in 200mm steps starting at 100mm. The right axis never goes below zero.

My question is: What are the commands I have to use to dynamically change the axis scaling depending on the input values and to couple the axis to each other? {x,y}{min,max}, tick and friends are certainly not enough. I know the basics of plotting with pgfplots, but I have absolutely no idea where to start such a dynamical scaling approach or how much of this I have to calculate myself (with own code) and how much pgfplots can do for me. For example, I obviously ned to parse the input data somehow to find maximum values, but I have no idea how to do that in pgfplots.

To have something to work with, here is the data from the first example:

#bombay.txt
#M T/°C N/mm
 1 23.9    3
 2 23.9    3
 3 26.1    3
 4 28.1    2
 5 29.7   18
 6 28.9  485
 7 27.2  617
 8 27.0  340
 9 27.0  264
10 28.1   64
11 27.2   13
12 25.6    3

And this is how for I got code-wise, but now I don't know how to proceed (colours, line styles, grid, etc. don't matter for now, I'm able to do that on my own, only the axis scaling/coupling is the problem):

\documentclass{standalone}
\usepackage{pgfplots} \pgfplotsset{compat=newest}
\usepackage{siunitx}
\begin{document}
\begin{tikzpicture}
  \def\filename{bombay.txt}
  \def\monthnames{{"J","F","Mar","A","May","Jun","Jul","A","S","O","N","D"}}
  \begin{axis}[
        ylabel={Temperature in \si{\celsius}},
        tick pos=left,
        xticklabel={\pgfmathparse{\monthnames[Mod(\tick-1,12)]}\pgfmathresult},
        xmin=1, xmax=12,
        ymin=0, ytick={0,10,20,30,40,50}]
    \addplot+[red, mark=none] table [x index=0, y index=1] {\filename};
  \end{axis}
    \begin{axis}[
        ylabel={Rain in \si{\milli\metre}},
        axis y line*=right,
        hide x axis,
        xmin=1, xmax=12, ymin=0]
    \addplot+[blue, mark=none] table [x index=0, y index=2] {\filename};
  \end{axis}
\end{tikzpicture}
\end{document}

Ex 1: Scaling of rain axis changes (because > 300mm), only T > 0 enter image description here

Example 2: Rain axis does not change scaling because it is always < 300mm. T < 0. enter image description here

  • Scaling values in one axis sounds like an application of section "Symbolic Coordinates and User Transformations" in the pgfplots manual, in particular: y coord trafo. This would allow you to check if something extends 100. What happens with the values? Seems like a linear scale down starting at 100, right? – Christian Feuersänger Mar 8 '14 at 14:07
  • If I get it correctly, the "coupling" between left and right axis is something like "if temperature >0, then use ymin=0 for both else make sure that the left starts at the correct ymin without generating tick labels"? In your example, temp min =-10 seems to require mm min = -20 combined with ytickmin=0 ... right? – Christian Feuersänger Mar 8 '14 at 14:10
  • I know that you are currently not interested in formatting, colors, etc. But as soon as you arrive at it, you may want to read about key set layers and read the documentation of the fillbetween library which has been released recently with pgfplots 1.10 (it allows to fill parts between plots). – Christian Feuersänger Mar 8 '14 at 14:11
  • Thanks for the comments. 1) Yes, a linear scaledown at 100 by factor ten ("from 20 to 200 per tick"). 2) yes, correct. 3) Nice, thanks, will have a look at it. :) – Foo Bar Mar 8 '14 at 15:45
7

What you need is possible with a style set up which

  • configures the non-linear transformation for the rain axis
  • computes suitable (coupled) axis limits for both y axes
  • computes suitable (coupled) units for both y axes
  • requires manual input to decide where both axes start/end. From what I understand from your use-case, this input consists of the the min temperature and the max rain level (the other limit is always implicitly bound).

I arrived at the following advanced prototype:

\documentclass{standalone}
\usepackage{pgfplots} 
\pgfplotsset{compat=1.9}

  % The following \pgfplotsset macro defines a *manual* wheather
  % visualization system which couples temperature and rain axis.
  \pgfplotsset{
     rain trafo/.style={
        y coord trafo/.code={%
            \pgfmathparse{##1 > 100 ? 100 + 0.1*(##1 - 100) : ##1}%
        },%
        y coord inv trafo/.code={%
            \pgfmathparse{##1 > 100 ? 100 + 10*(##1 - 100) : ##1}%
        },%
        %
        % configure the allowed tick positions for the rain
        % (UNTRANSFORMED!):
        ytick={0,20,...,100,300,500,...,1000},
     },
     %
     /wheather/temp min/.initial=,
     /wheather/rain max/.initial=,
     %
     % USAGE: \pgfkeys{/wheather/set up={temp min=<min temperature>, rain max=<max rain axis value>}
     % It will set up two styles "temp axis" and "rain axis". These
     % styles will contain suitable axis limits and unit scales.
     /wheather/set up/.code={%
        \pgfkeysalso{/wheather/.cd,#1}%
        %
        % these values are supposed to be given:
        \pgfkeysgetvalue{/wheather/rain max}\rainMax
        \pgfkeysgetvalue{/wheather/temp min}\temperatureMin
        %
        % make sure they are given!
        \ifx\rainMax\empty
            \PackageError{wheather}{Please provide 'rain max=MAX RAIN VALUE IN AXIS'}{}%
        \fi
        \ifx\temperatureMin\empty
            \PackageError{wheather}{Please provide 'temp min=MIN TEMPERATURE VALUE IN AXIS'}{}%
        \fi
        %
        % now, compute the MISSING values. These are rain min and
        % temperature max....
        \begingroup
        % do this in a group such that 'rain trafo' is only applied
        % within the group...
        % 
        % store it - just in case 'y coord trafo' is empty.
        \let\pgfmathresult=\rainMax
        %
        \pgfplotsset{rain trafo,y coord trafo={\rainMax}}%
        \global\let\rainMaxTransformed=\pgfmathresult
        \pgfmathparse{\pgfmathresult /2}%
        \global\let\temperatureMax=\pgfmathresult
        \endgroup
        %
        \pgfmathparse{2*(\pgfkeysvalueof{/wheather/temp min})}%
        \let\rainMin=\pgfmathresult
        %
        % now we want to compute a suitable axis scale for both axes.
        % To this end, we scale them such that they fit into the value
        % of  \axisdefaultheight :
        \pgfmathparse{\axisdefaultheight/(\temperatureMax-\temperatureMin)}%
        \let\temperatureUnit=\pgfmathresult
        %
        \pgfmathparse{\axisdefaultheight/(\rainMaxTransformed-\rainMin)}%
        \let\rainUnit=\pgfmathresult
        %
        % OK, compute the result:
        \pgfplotsset{%
            set layers,
            %
            % this is to be used for the temperature axis:
             temp axis/.style={
                y=\temperatureUnit,
                ymin=\temperatureMin,
                ymax=\temperatureMax,
                %
                % configure the allowed tick positions for a temperature:
                ytick={-40,-30,-20,...,140},
                % ... but only show the *label* for "small"
                % temperatures:
                yticklabel={%
                    \ifdim\tick pt<45pt 
                        \pgfmathprintnumber\tick
                    \fi
                },
                ylabel={Temperature},
                %tick pos=left,% seems to fail for 'axis x line=middle'!?
                %
                % configure the x axis:
                axis x line*=middle,
                xmin=1, xmax=12,
                xtick={1,2,...,12},
                xticklabels={J,F,M,A,M,J,J,A,S,O,N,D},
                x tick label as interval,
                grid=major,
             },
             %
             % ... and this is to be used for the rain axis:
             rain axis/.style={
                y=\rainUnit,
                ymin=\rainMin,
                ymax=\rainMax,
                % this range will be overwritten by 'rain trafo' if it
                % is active:
                ytick={0,20,...,400},
                rain trafo,
                yticklabel style={/pgf/number format/precision=0},
                ylabel={Rain},
                axis y line*=right,
                hide x axis,
                xmin=1, xmax=12,
             },
        }%
     },
  }

\begin{filecontents}{bombay.txt}
#bombay.txt
#M T/°C N/mm
 1 23.9    3
 2 23.9    3
 3 26.1    3
 4 28.1    2
 5 29.7   18
 6 28.9  485
 7 27.2  617
 8 27.0  340
 9 27.0  264
10 28.1   64
11 27.2   13
12 25.6    3
\end{filecontents}

\begin{filecontents}{moskow.txt}
#M T/°C N/mm
 1 -10.9    30
 2 -12.9    23
 3 -6.1    30
 4 3.1    40
 5 6.7   50
 6 15.9  60
 7 20.2  61
 8 22.0  50
 9 17.0  45
10 10.1   20
11 3.2   22
12 -9.6    25
\end{filecontents}

\begin{document}
\begin{tikzpicture}

  \pgfplotsset{/wheather/set up={temp min=0, rain max=800}}

  \def\filename{bombay.txt}

  \begin{axis}[
        temp axis,
    ]
    \addplot+[red, mark=none] table [x index=0, y index=1] {\filename};
  \end{axis}
    \begin{axis}[
        rain axis,
    ]
    \addplot+[blue, mark=none] table [x index=0, y index=2] {\filename};
  \end{axis}
\end{tikzpicture}

\begin{tikzpicture}

  \pgfplotsset{
     *clear* the nonlinear rain trafo. We do not want it here:
     rain trafo/.style=,
     /wheather/set up={temp min=-20, rain max=160},
  }

  \def\filename{moskow.txt}

  \begin{axis}[
        temp axis,
    ]
    \addplot+[red, mark=none] table [x index=0, y index=1] {\filename};
  \end{axis}
    \begin{axis}[
        rain axis,
    ]
    \addplot+[blue, mark=none] table [x index=0, y index=2] {\filename};
  \end{axis}
\end{tikzpicture}
\end{document}

enter image description here

I believe it is already quite good, although it has a couple of items left - mostly formatting and the fill paths.

And there is one open issue which is actually a "missing feature" in pgfplots: as soon as one writes axis x line=middle, pgfplots does not show the first and last tick label. Very unfortunate for this application. I will take that on the todo list for pgfplots. Perhaps you should undo axis x line=middle to fix this. Please note that displaying x ticks as interval means that you have n-1 tick labels for n tick positions. In short: you might need to replicate the data point for january somehow.

  • Could you eventually extend your script so that the script makes the are between temperature curve and rain curve yellow, if the temperature curve is over the rain curve. It should also mark everything under the rain curve dark blue if it's over 100mm. If the rain curve is over the temperature curve, the are in between should get blue stripes. (I'd like to re-make some of the diagrams on wikipedia and that would amke it MUCH easier and the output more consistent – Martin Thoma Sep 8 '15 at 19:21
  • 1
    Such things can be done using the fillbetween library -- to some extend. I have some older feature requests in which the fillbetween library should automatically understand "larger" and "smaller". Currently, it requires manual output. But you may want to read its manual; I will think about a solution for the automatic approach (might require a new version of the software, though). – Christian Feuersänger Sep 9 '15 at 17:22
  • 1
    update Adding hide obscured x ticks=false, to the list of temp axis/.style={...} will show the missing tick labels, i.e. addresses the "missing feature" mentioned in the text. – Christian Feuersänger Sep 9 '15 at 17:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.