pgf-tikz / pgfplots

pgfplots - A TeX package to draw normal and/or logarithmic plots directly in TeX in two and three dimensions with a user-friendly interface and pgfplotstable - a TeX package to round and format numerical tables. Examples in manuals and/or on web site.
http://pgfplots.sourceforge.net/
187 stars 33 forks source link

Unexpected behavior when using `colorbar as legend` #465

Open BobYue-01 opened 11 months ago

BobYue-01 commented 11 months ago

When using the colorbar as legend key, the ticks on colorbar seem to be incorrect: from the third onwards, they are all the same as the second.

The error is already visible in the official manual (1.18.1, page 293). 1.18.1 Manual, page 293

To make sure that I understood correctly what the key does, I checked version 1.16, which shows the expected result in the manual (1.16, page 290). 1.16 Manual, page 290

muzimuzhi commented 11 months ago

With the help of git bisect, commit d2fbb2a (fixed SIMPLE scientific notation in tick arguments, 2019-05-30) is the first bad commit.

\documentclass{article}
\usepackage{pgfplots}
\pgfplotsset{compat=newest}

\begin{document}
\pgfplotscolorbardrawstandalone[
    colorbar as legend,
    colorbar style={
        ticklabel style={font=\tiny},
    },
    colormap access=const,
    colormap={CM}{
        of colormap={
            viridis,
            target pos={
                0,200,300,350,375,
                400,700,800,850,1000
            },
            sample for=const,
        },
    },
]
\end{document}
muzimuzhi commented 11 months ago

This is caused by interactions between the new extended uniform-loop-list optimization for \pgfplotsforeachungrouped, fpu library, and \pgfplotsarrayselect.

If /pgf/fpu is on, \pgfplotsforeachungrouped \x in {<uniform list>} {<body>} will set \x as a number in fpu representation like 1Y2.0e0] (+2.0 * 10^0). With commit https://github.com/pgf-tikz/pgfplots/commit/d2fbb2a5c2af41c892541a9cd64d82ad60ab6774, the form(s) of <uniform list> was extended from 0,1,...,5 to also recognize 0,...,5. This hurt the use of

\pgfplotsforeachungrouped \x in {0,...,\pgfplots@colorcount} {%
  \pgfplotsarrayselect{\x}\of\positions\to\pgfplotsretval
  % ...
}

in colorbar drawing, since \pgfplotsarrayselect\x\of\<list>\to\<return> requires that \x is an integer assignable to a TeX count. https://github.com/pgf-tikz/pgfplots/blob/a1fe88f619859de5f798ab4c095aeea40f6fad59/tex/generic/pgfplots/pgfplots.code.tex#L1367-L1376 https://github.com/pgf-tikz/pgfplots/blob/a1fe88f619859de5f798ab4c095aeea40f6fad59/tex/generic/pgfplots/liststructure/pgfplotsarray.code.tex#L207-L225

A quick and very specific workaround

diff --git a/tex/generic/pgfplots/pgfplots.code.tex b/tex/generic/pgfplots/pgfplots.code.tex
index 0b4486b..b72bfa6 100644
--- a/tex/generic/pgfplots/pgfplots.code.tex
+++ b/tex/generic/pgfplots/pgfplots.code.tex
@@ -1368,6 +1368,9 @@
            % variable:
            \def\pgfplots@loc@TMPc{}%
            \pgfplotsforeachungrouped \x in {0,...,\pgfplots@colorcount} {%
+               % ensure \x is assignable to a TeX count,
+               % because \pgfplotsarrayselect will assign \x to a TeX count)
+               \pgfmathfloattoint{\x}\let\x=\pgfmathresult
                \pgfplotsarrayselect{\x}\of\positions\to\pgfplotsretval
                \edef\pgfplots@loc@TMPa{\noexpand\pgfmathprintnumber{\pgfplotsretval}}
                \t@pgfplots@toka=\expandafter{\pgfplots@loc@TMPc}%

I believe a thorough solution is to adapt \pgfplotsforeachungrouped (more specifically, \pgfplotsforeachungroupeduniform@ and the ones it calls) to make it's optimization for uniform-loop-list work with fpu.

\documentclass{article}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}

\usepackage{xpatch}
\makeatletter
% patch /pgfplots/colorbar as legend/.style={...}
\expandafter\xpatchcmd\csname pgfk@/pgfplots/colorbar as legend/.@cmd\endcsname
  {%
    \pgfplotsarrayselect{\x}\of\positions\to\pgfplotsretval
  }{%
    \pgfmathfloattoint{\x}\let\x=\pgfmathresult
    \pgfplotsarrayselect{\x}\of\positions\to\pgfplotsretval
  }{}{\PatchFailed}
\expandafter\xpatchcmd\csname pgfk@/pgfplots/colorbar as legend/.@body\endcsname
  {%
    \pgfplotsarrayselect{\x}\of\positions\to\pgfplotsretval
  }{%
    \pgfmathfloattoint{\x}\let\x=\pgfmathresult
    \pgfplotsarrayselect{\x}\of\positions\to\pgfplotsretval
  }{}{\PatchFailed}
\makeatother

\begin{document}
\pgfplotscolorbardrawstandalone[
    colorbar as legend,
    colorbar style={
        ticklabel style={font=\tiny},
    },
    colormap access=const,
    colormap={CM}{
        of colormap={
            viridis,
            target pos={
                0,200,300,350,375,
                400,700,800,850,1000
            },
            sample for=const,
        },
    },
]

{
  \pgfset{fpu=false}
  \foreach \x in {1,...,5} {\x, }\par
  \pgfplotsforeachungrouped \x in {1,2,...,5} {\x, }\par
  \pgfplotsforeachungrouped \x in {1,...,5} {\x, }

  \pgfset{fpu=true}
  \foreach \x in {1,...,5} {\x, }\par
  \pgfplotsforeachungrouped \x in {1,2,...,5} {\x, }\par % \x in fpu forms
  \pgfplotsforeachungrouped \x in {1,...,5} {\x, }       % \x in fpu forms
}
\end{document}

image

muzimuzhi commented 11 months ago

If /pgf/fpu is on, \pgfplotsforeachungrouped \x in {<uniform list>} {<body>} will set \x as a number in fpu representation like 1Y2.0e0] (+2.0 * 10^0). [...] I believe a thorough solution is to adapt \pgfplotsforeachungrouped (more specifically, \pgfplotsforeachungroupeduniform@ and the ones it calls) to make it's optimization for uniform-loop-list work with fpu.

It seems this behavior (for <uniform list> with form a,b,...,n) was on purpose, as it was introduced 14 years ago, in commit 063bebf on 2008-12-06. Hence the diff shown in my previous comment is on the right direction.

Alternatively, add a guard at the beginning of \pgfplotsarrayselect? Not necessary. Most uses of \pgfplotsarrayselect don't need such a guard, and there's already a similar fp-to-int guard added in commit 4214bc0 (improved the 'patch table' feature, 2010-06-22).

https://github.com/pgf-tikz/pgfplots/blob/a1fe88f619859de5f798ab4c095aeea40f6fad59/tex/generic/pgfplots/pgfplotsmeshplothandler.code.tex#L2516-L2521

muzimuzhi commented 11 months ago

Another option: The \pgfplotsforeachungrouped \x in {0,...,\pgfplots@colorcount} {<loop body>} is so simple that it can be replaced by \pgfutil@loop ... \pgfutil@repeat.

diff --git a/tex/generic/pgfplots/pgfplots.code.tex b/tex/generic/pgfplots/pgfplots.code.tex
index 0b4486b..ba213d0 100644
--- a/tex/generic/pgfplots/pgfplots.code.tex
+++ b/tex/generic/pgfplots/pgfplots.code.tex
@@ -1367,13 +1367,16 @@
            % accumulate a list of formatted positions into this
            % variable:
            \def\pgfplots@loc@TMPc{}%
-           \pgfplotsforeachungrouped \x in {0,...,\pgfplots@colorcount} {%
+           \def\x{0}% or, is \c@pgf@counta safe here?
+           \pgfutil@loop
+           \unless\ifnum\x>\pgfplots@colorcount
                \pgfplotsarrayselect{\x}\of\positions\to\pgfplotsretval
-               \edef\pgfplots@loc@TMPa{\noexpand\pgfmathprintnumber{\pgfplotsretval}}
+               \edef\pgfplots@loc@TMPa{\noexpand\pgfmathprintnumber{\pgfplotsretval}}%
                \t@pgfplots@toka=\expandafter{\pgfplots@loc@TMPc}%
                \t@pgfplots@tokb=\expandafter{\pgfplots@loc@TMPa}%
                \edef\pgfplots@loc@TMPc{\the\t@pgfplots@toka \the\t@pgfplots@tokb,}%
-           }%
+               \edef\x{\the\numexpr\x+1}%
+           \pgfutil@repeat
            \t@pgfplots@tokb=\expandafter{\pgfplots@loc@TMPc}%
            \xdef\pgfplots@glob@TMPa{%
                \noexpand\pgfplotsset{%