19

I am generating a bunch of plots automatically using pgfplots. For some datasets the span of data in one axis (y) is relatively small (relative to the absolute value) and therefore pgfplots when printing the numbers in that axis outputs a ridiculous repetition of numbers (see figure, -1.41 repeated 4 times).

repeated numbers in axis

Of course one option is to increase the formatting precision for the numbers, adding digits. The question is, Can the precision be set automatically by some option in the axis options?

This can lead to an unlimited number of necessary digits, which leads to a second question is there an easy way to automatically choose a reference base value and "add" it to the scaling (e.g. $\cdot 10^8 - 10^7$ instead of $\cdot 10^7$ in the example).

MWE:

\documentclass[]{article}
\usepackage[]{pgfplots}
\begin{document}
\begin{tikzpicture}
\begin{axis}
\addplot coordinates {
 ( 10, -14136746 )
 ( 72.421875, -14136749 )
 ( 166.054688, -14136829 )
 ( 228.476562, -14137018 )
 ( 290.898438, -14137366 )
};
\end{axis}
\end{tikzpicture}
\end{document}
alfC
  • 14,350
  • While not being a (La)TeX solution, you could use Python's matplotlib and export to pgf. It allows solutions to both of your questions/issues (adding a fixed value and significant digits). I use this approach for all my plots combined with a makefile, which automatically reruns the Python-scripts on changes. – Skillmon Aug 08 '17 at 18:45

3 Answers3

9

This answer should address question 1.

The precision could be set as axis option by means of /pgf/number format/.cd,fixed,precision=<some value>.

Here are some examples:

\documentclass[]{article}
\usepackage{pgfplots}
\begin{document}
\centering
\foreach \precision in {4,5,6,7}
{
\begin{tikzpicture}
\begin{axis}[/pgf/number format/.cd,fixed,precision=\precision,
title={Precision=\precision}
]
\addplot coordinates {
 ( 10, -14136746 )
 ( 72.421875, -14136749 )
 ( 166.054688, -14136829 )
 ( 228.476562, -14137018 )
 ( 290.898438, -14137366 )
};
\end{axis}
\end{tikzpicture}
}
\end{document}

Result:

enter image description here

3

I might have a solution, although the current code is very ugly, because I'm not very familiar with macro's, calculating and storing values.

The code works as follows. It looks at the amount of digits in the largest y value of the data set (\nmax). Then it calculates the amount of digits of: \ymax - \ymin (ndiff). Then the amount of significant digits is given by: \nmax - \ndiff.

Code

\documentclass[]{article}
\usepackage{pgfplots}
\pgfplotsset{compat=1.13}
\usepackage{pgfplotstable}
\usepackage{filecontents}
\usepackage[nomessages]{fp}
\usepackage{xparse}

% From https://tex.stackexchange.com/questions/197844/rounding-a-number-to-its-hundred
\ExplSyntaxOn
\DeclareExpandableDocumentCommand{\ceil}{m}
{
    \fp_eval:n { ceil ( #1 ) }
}
\ExplSyntaxOff

\newcommand{\logten}[1]
{
    \FPeval\res{ ln( abs(#1) ) / ln( 10 ) }
}

% From Jake: https://tex.stackexchange.com/questions/24910/find-a-extremal-value-in-external-data-file-with-pgfplot
\newcommand{\findmax}[1]{
    \pgfplotsforeachungrouped \table in {#1} {%
        \pgfplotstablevertcat{\concatenated}{\table}%
    }%
    \pgfplotstablesort[sort key={1},sort cmp={float >}]{\sorted}{\concatenated}%
    \pgfplotstablegetelem{0}{1}\of{\sorted}%
    \let\ymax=\pgfplotsretval%
}
\newcommand{\findmin}[1]{
    \pgfplotsforeachungrouped \table in {#1} {%
        \pgfplotstablevertcat{\concatenated}{\table}%
    }%
    \pgfplotstablesort[sort key={1},sort cmp={float <}]{\sorted}{\concatenated}%
    \pgfplotstablegetelem{0}{1}\of{\sorted}%
    \let\ymin=\pgfplotsretval%
}

\begin{filecontents}{dataA.dat}
    10          -14135746
    72.421875   -14136100
    166.054688  -14136829
    228.476562  -14137018
    290.898438  -14137701
\end{filecontents}

\begin{filecontents}{dataB.dat}
    10          -14136846
    72.421875   -14136949
    166.054688  -14136829
    228.476562  -14136718
    290.898438  -14136866
\end{filecontents}

\begin{document}

\findmax{dataA.dat}
\findmin{dataA.dat}

% number of digits in \ymax
\logten{\ymax}
\def\nmax{\ceil{\res}}
\FPeval{\nmax}{(nmax) + 1}

% Number of digits in the difference
\FPeval{\diff}{(\ymax) - (\ymin)}%   
\logten{\diff}
\def\ndiff{\ceil{\res}}

% Calculate the precision
\FPeval\precision{clip(nmax - ndiff)}

\begin{tikzpicture}
    \begin{axis}[
        y tick label style={
            /pgf/number format/.cd,
            fixed,
            zerofill,
            precision=\precision,
            /tikz/.cd,},
        title={dataA.dat, Precision=\precision}]

        \addplot table[] {dataA.dat};
    \end{axis}
\end{tikzpicture}
\end{document}

Result for both data sets

dataA dataB

Bugs

The code works for one data set, but if I run the function \findmin or \findmax on a second data set in the same document, it sometimes returns an old \ymin or \ymax.

Roald
  • 1,245
3

This is just a cleaned version of Roald's version (expl3-side). I have made up one command that calculates the precision for the file (to be used before the file is included).

\documentclass{article}
\usepackage{pgfplots}
\pgfplotsset{compat=1.15}
\usepackage{pgfplotstable}
\usepackage{filecontents}
\usepackage{xparse}

\ExplSyntaxOn
\fp_new:N \l__roald_ymax_fp
\fp_new:N \l__roald_ymin_fp
\fp_new:N \l__roald_diff_fp
\fp_new:N \l__roald_nmax_fp
\fp_new:N \l__roald_precision_fp

\cs_generate_variant:Nn \fp_set:Nn { NV }

% From Jake (adapted): https://tex.stackexchange.com/questions/24910/find-a-extremal-value-in-external-data-file-with-pgfplot
\DeclareDocumentCommand { \precisionforfile } { m }
  {
    % max
    \pgfplotsforeachungrouped \table in {#1} {
        \pgfplotstablevertcat{\concatenated}{\table}
    }
    \pgfplotstablesort[sort~key={1},sort~cmp={float~>}]{\sorted}{\concatenated}
    \pgfplotstablegetelem{0}{1}\of{\sorted}
    \fp_set:NV \l__roald_ymax_fp \pgfplotsretval
    % min
    \pgfplotsforeachungrouped \table in {#1} {
        \pgfplotstablevertcat{\concatenated}{\table}
    }
    \pgfplotstablesort[sort~key={1},sort~cmp={float~<}]{\sorted}{\concatenated}
    \pgfplotstablegetelem{0}{1}\of{\sorted}
    \fp_set:NV \l__roald_ymin_fp \pgfplotsretval
    % calc
    \fp_set:Nn \l__roald_nmax_fp { ceil ( ln ( abs( \l__roald_ymax_fp ) ) / ln ( 10 ) ) + 1 }
    % Number of digits in the difference
    \fp_set:Nn \l__roald_diff_fp { ceil ( ln ( abs( \l__roald_ymax_fp - \l__roald_ymin_fp ) ) / ln ( 10 ) ) }
    % Calculate the precision
    \fp_set:Nn \l__roald_precision_fp { \l__roald_nmax_fp - \l__roald_diff_fp }
    \def\precision{\fp_to_int:N \l__roald_precision_fp}
  }
\ExplSyntaxOff

\begin{filecontents}{dataA.dat}
    10          -14135746
    72.421875   -14136100
    166.054688  -14136829
    228.476562  -14137018
    290.898438  -14137701
\end{filecontents}

\begin{filecontents}{dataB.dat}
    10          -14136846
    72.421875   -14136949
    166.054688  -14136829
    228.476562  -14136718
    290.898438  -14136866
\end{filecontents}

\begin{document}

\precisionforfile{dataA.dat}

\begin{tikzpicture}
    \begin{axis}[
        y tick label style={
            /pgf/number format/.cd,
            fixed,
            zerofill,
            precision=\precision,
            /tikz/.cd,},
        title={dataA.dat, Precision=\precision}]

        \addplot table[] {dataA.dat};
    \end{axis}
\end{tikzpicture}
\end{document}

Update: The new version works with coordinate input.

\documentclass{article}
\usepackage{pgfplots}
\pgfplotsset{compat=1.15}
\usepackage{pgfplotstable}
\usepackage{filecontents}
\usepackage{xparse}

\ExplSyntaxOn
\fp_new:N \l__roald_ymax_fp
\fp_new:N \l__roald_ymin_fp
\fp_new:N \l__roald_nmax_fp

\fp_new:N \l_roald_precision_fp

\cs_generate_variant:Nn \regex_split:nnN { nxN }
\cs_generate_variant:Nn \fp_set:Nn { Nx }

\DeclareDocumentCommand { \precisionforcoord } { m }
  {
    \fp_zero:N \l__roald_ymin_fp
    \fp_zero:N \l__roald_ymax_fp
    \regex_split:nxN { \( } { #1 } \l_tmpa_seq
    \seq_remove_all:Nn \l_tmpa_seq { ~ }
    \seq_map_inline:Nn \l_tmpa_seq
      {
        \tl_set:Nn \l_tmpa_tl { ##1 }
        \tl_replace_all:Nnn \l_tmpa_tl { ) } { }
        \regex_split:nxN { \, } { \l_tmpa_tl } \l_tmpb_seq
        \fp_set:Nx \l_tmpa_fp { \seq_item:Nn \l_tmpb_seq { 2 } }
        \fp_compare:nT { \l__roald_ymin_fp = 0 } { \fp_gset_eq:NN \l__roald_ymin_fp \l_tmpa_fp }
        \fp_compare:nT { \l__roald_ymax_fp = 0 } { \fp_gset_eq:NN \l__roald_ymax_fp \l_tmpa_fp }
        \fp_compare:nT { \l_tmpa_fp > \l__roald_ymax_fp } { \fp_gset_eq:NN \l__roald_ymax_fp \l_tmpa_fp }
        \fp_compare:nT { \l_tmpa_fp < \l__roald_ymin_fp } { \fp_gset_eq:NN \l__roald_ymin_fp \l_tmpa_fp }
      }
    \fp_set:Nn \l__roald_nmax_fp { ceil ( ln ( abs( \l__roald_ymax_fp ) ) / ln ( 10 ) ) + 1 }
    \fp_set:Nn \l_roald_precision_fp { \l__roald_nmax_fp - ( ceil ( ln ( abs( \l__roald_ymax_fp - \l__roald_ymin_fp ) ) / ln ( 10 ) ) ) }
    \def\precision{\fp_to_int:N \l_roald_precision_fp}
  }
\ExplSyntaxOff

\def\coordinatesa{
    (10,          -14135746)
    (72.421875,   -14136100)
    (166.054688,  -14136829)
    (228.476562,  -14137018)
    (290.898438,  -14137701)
}

\def\coordinatesb{
    (10,          -14136846)
    (72.421875,   -14136949)
    (166.054688,  -14136829)
    (228.476562,  -14136718)
    (290.898438,  -14136866)
}

\begin{document}
\precisionforcoord{\coordinatesa}
\begin{tikzpicture}
    \begin{axis}[
        y tick label style={
            /pgf/number format/.cd,
            fixed,
            zerofill,
            precision=\precision,
            /tikz/.cd,},
        title={A, Precision=\precision}]
        \addplot coordinates \coordinatesa;
    \end{axis}
\end{tikzpicture}
\vskip2em
\precisionforcoord{\coordinatesb}
\begin{tikzpicture}
    \begin{axis}[
        y tick label style={
            /pgf/number format/.cd,
            fixed,
            zerofill,
            precision=\precision,
            /tikz/.cd,},
        title={B, Precision=\precision}]
        \addplot coordinates \coordinatesb;
    \end{axis}
\end{tikzpicture}
\end{document}

For the new version you will need an up-to-date expl3 installation (TL17). If you're on an older distro (e.g. TL16) updated to the frozen state you can change \pgfplotsset{compat=1.15} to \pgfplotsset{compat=1.14} and include a \usepackage{l3regex}. Note: The latter package will probably be removed from distributions in the future.


Update 2: I've just introduced a nearly on-the-fly command which replaces your addplot. The only problem is that it includes the axis environment (you cannot add another plot there), because the y labels have to be adjusted as options there.

\documentclass{article}
\usepackage{pgfplots}
\pgfplotsset{compat=1.15}
\usepackage{pgfplotstable}
\usepackage{filecontents}
\usepackage{xparse}

\ExplSyntaxOn
\fp_new:N \l__roald_ymax_fp
\fp_new:N \l__roald_ymin_fp
\fp_new:N \l__roald_nmax_fp

\fp_new:N \l_roald_precision_fp

\cs_generate_variant:Nn \regex_split:nnN { nxN }
\cs_generate_variant:Nn \fp_set:Nn { Nx }

\DeclareDocumentCommand { \precisionforcoord } { m }
  {
    \fp_zero:N \l__roald_ymin_fp
    \fp_zero:N \l__roald_ymax_fp
    \regex_split:nxN { \( } { #1 } \l_tmpa_seq
    \seq_remove_all:Nn \l_tmpa_seq { ~ }
    \seq_map_inline:Nn \l_tmpa_seq
      {
        \tl_set:Nn \l_tmpa_tl { ##1 }
        \tl_replace_all:Nnn \l_tmpa_tl { ) } { }
        \regex_split:nxN { \, } { \l_tmpa_tl } \l_tmpb_seq
        \fp_set:Nx \l_tmpa_fp { \seq_item:Nn \l_tmpb_seq { 2 } }
        \fp_compare:nT { \l__roald_ymin_fp = 0 } { \fp_gset_eq:NN \l__roald_ymin_fp \l_tmpa_fp }
        \fp_compare:nT { \l__roald_ymax_fp = 0 } { \fp_gset_eq:NN \l__roald_ymax_fp \l_tmpa_fp }
        \fp_compare:nT { \l_tmpa_fp > \l__roald_ymax_fp } { \fp_gset_eq:NN \l__roald_ymax_fp \l_tmpa_fp }
        \fp_compare:nT { \l_tmpa_fp < \l__roald_ymin_fp } { \fp_gset_eq:NN \l__roald_ymin_fp \l_tmpa_fp }
      }
    \fp_set:Nn \l__roald_nmax_fp { ceil ( ln ( abs( \l__roald_ymax_fp ) ) / ln ( 10 ) ) + 1 }
    \fp_set:Nn \l_roald_precision_fp { \l__roald_nmax_fp - ( ceil ( ln ( abs( \l__roald_ymax_fp - \l__roald_ymin_fp ) ) / ln ( 10 ) ) ) }
    \def\precision{\fp_to_int:N \l_roald_precision_fp}
  }
\DeclareDocumentCommand { \axisplot } { O{} m }
  {
    \precisionforcoord{#2}
    \begin{axis}[y~tick~label~style={
            /pgf/number~format/.cd,
            fixed,
            zerofill,
            precision=\precision,
            /tikz/.cd,},#1]
      \addplot coordinates #2;
    \end{axis}
  }
\ExplSyntaxOff

\def\coordinatesa{
    (10,          -14135746)
    (72.421875,   -14136100)
    (166.054688,  -14136829)
    (228.476562,  -14137018)
    (290.898438,  -14137701)
}

\def\coordinatesb{
    (10,          -14136846)
    (72.421875,   -14136949)
    (166.054688,  -14136829)
    (228.476562,  -14136718)
    (290.898438,  -14136866)
}

\begin{document}
\begin{tikzpicture}
\axisplot[title={A, Precision=\precision}]{\coordinatesa}
\end{tikzpicture}
\vskip1em
\begin{tikzpicture}
\axisplot[title={B, Precision=\precision}]{\coordinatesb}
\end{tikzpicture}
\end{document}

precision

TeXnician
  • 33,589
  • One might even omit the definitions of \l__roald_ymax_fp and the minimum version (the pgfplotsretval could be used). – TeXnician Aug 05 '17 at 15:18
  • Would this work for files only ? or can it work for inline tables. (Like the example in the question) – alfC Aug 05 '17 at 18:39
  • @alfC I've updated with a version that works for coordinates only. – TeXnician Aug 06 '17 at 09:01
  • I am getting an error LaTeX error: "kernel/command-not-defined" ! ! Control sequence \regex_split:nnN undefined. with pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016). I get the same error with lualatex. – alfC Aug 09 '17 at 06:31
  • 1
    @alfC If your TL16 is fully updated (frozen state) then you need to change the pgfplots compat setting to 1.14 and have a \usepackage{l3regex}. – TeXnician Aug 09 '17 at 06:36
  • I had to use \pgfplotsset{compat=1.13} \usepackage{l3regex} in my TeXLive (from updated Fedora 26). I think it worth using the most compatible version. – alfC Aug 09 '17 at 06:39
  • @alfC Did it work then? It should give you the adapted precisions eitherway. – TeXnician Aug 09 '17 at 06:41
  • yes it works very nicely. I mean "posting the most compatible version" (not "using the most compatible version"). Do you think there is way to do this automatically, without predefining the data before the plot? (do it on the fly with the current dataset.) – alfC Aug 09 '17 at 06:41
  • 1
    @alfC I've introduced a new command, but it is not ideal. A real on-the-fly version is probably impossible as you need to adjust the parental axis environment. – TeXnician Aug 09 '17 at 06:54