% Author and maintainer: SuFan (GitHub: Tape5try)
% Contact: 3546236610@qq.com
% Copyright (c) 2026 SuFan. MIT License; see LICENSE.
\NeedsTeXFormat{LaTeX2e}[2020/10/01]
\ProvidesPackage{gradbars}[2026/10/03 v0.0.3 Inline data bars for LaTeX]
\RequirePackage{tikz}
\RequirePackage{xparse}
\RequirePackage{expl3}
\RequirePackage{collcell}
\usetikzlibrary{shadings,patterns}

\definecolor{gradbarsBlue}{HTML}{2563EB}
\definecolor{gradbarsBlueLight}{HTML}{93C5FD}
\definecolor{gradbarsTeal}{HTML}{0F766E}
\definecolor{gradbarsTealLight}{HTML}{5EEAD4}
\definecolor{gradbarsInk}{HTML}{243247}
\definecolor{gradbarsTrack}{HTML}{EDF2F7}
% Public built-in palette.
\definecolor{lightgreen}{RGB}{144,238,144}
\definecolor{lightyellow}{RGB}{255,255,224}
\definecolor{lightblue}{RGB}{173,216,230}
\definecolor{lightred}{RGB}{255,160,122}
\definecolor{rose}{RGB}{255,102,204}
\definecolor{skyblue}{RGB}{135,206,235}
\definecolor{gold}{RGB}{255,215,0}
\definecolor{lavender}{RGB}{230,230,250}
\definecolor{peach}{RGB}{255,218,185}
\newif\ifgradbars@valid
\newif\ifgradbars@gradient
\def\gradbars@width{24mm}
\def\gradbars@height{1.5ex}
\def\gradbars@max{100}
\def\gradbars@min{0}
\def\gradbars@rounded{0pt}
\def\gradbars@target{}
\def\gradbars@targetcolor{gradbarsInk}
\def\gradbars@negativecolor{lightred}
\def\gradbars@thresholds{}
\def\gradbars@thresholdcolors{lightred,gold,lightgreen}
\def\gradbars@errorminus{}
\def\gradbars@errorplus{}
\def\gradbars@errorcolor{gradbarsInk}
\newif\ifgradbars@error
\newsavebox\gradbars@labelbox
\def\gradbars@precision{1}
\def\gradbars@unit{}
\def\gradbars@text{}
\def\gradbars@label{outside}
\def\gradbars@format{value}
\def\gradbars@overflow{clip}
\def\gradbars@gap{.6em}
\def\gradbars@labelwidth{4.5em}
\def\gradbars@font{\relax}
\newif\ifgradbars@missing
\newif\ifgradbars@stack
\newcount\gradbars@styledepth
\def\gradbars@missingtext{--}
\def\gradbars@numberformat{fixed}
\def\gradbars@band{}
\def\gradbars@bandcolor{gold}
\def\gradbars@bandopacity{0.3}
\def\gradbars@stackcolors{gradbarsBlue,gradbarsTeal,gold}
\pgfkeys{/gradbars/.is family,/gradbars,
  style/.code={\gradbarsUseStyle{#1}},
  missing text/.store in=\gradbars@missingtext,
  number format/.is choice,
  number format/fixed/.code=\def\gradbars@numberformat{fixed},
  number format/grouped/.code=\def\gradbars@numberformat{grouped},
  number format/scientific/.code=\def\gradbars@numberformat{scientific},
  band/.store in=\gradbars@band,
  band color/.store in=\gradbars@bandcolor,
  band opacity/.store in=\gradbars@bandopacity,
  stack colors/.store in=\gradbars@stackcolors,
  width/.store in=\gradbars@width,
  height/.store in=\gradbars@height,
  max/.store in=\gradbars@max,
  min/.store in=\gradbars@min,
  rounded/.store in=\gradbars@rounded,
  target/.store in=\gradbars@target,
  target color/.store in=\gradbars@targetcolor,
  negative color/.store in=\gradbars@negativecolor,
  thresholds/.store in=\gradbars@thresholds,
  threshold colors/.store in=\gradbars@thresholdcolors,
  error/.style={error minus=#1,error plus=#1},
  error minus/.store in=\gradbars@errorminus,
  error plus/.store in=\gradbars@errorplus,
  error color/.store in=\gradbars@errorcolor,
  precision/.store in=\gradbars@precision,
  unit/.store in=\gradbars@unit,
  text/.store in=\gradbars@text,
  label gap/.store in=\gradbars@gap,
  label width/.store in=\gradbars@labelwidth,
  font/.store in=\gradbars@font,
  left color/.store in=\gradbars@left,
  right color/.store in=\gradbars@right,
  track color/.store in=\gradbars@track,
  text color/.store in=\gradbars@textcolor,
  color/.style={fill=solid,left color=#1},
  fill/.is choice,
  fill/gradient/.code=\gradbars@gradienttrue,
  fill/solid/.code=\gradbars@gradientfalse,
  label/.is choice,
  label/outside/.code=\def\gradbars@label{outside},
  label/inside/.code=\def\gradbars@label{inside},
  label/none/.code=\def\gradbars@label{none},
  label/end/.code=\def\gradbars@label{end},
  label/auto/.code=\def\gradbars@label{auto},
  value format/.is choice,
  value format/value/.code=\def\gradbars@format{value},
  value format/percent/.code=\def\gradbars@format{percent},
  overflow/.is choice,
  overflow/clip/.code=\def\gradbars@overflow{clip},
  overflow/error/.code=\def\gradbars@overflow{error},
  theme/.is choice,
  theme/blue/.style={fill=gradient,left color=gradbarsBlue,
    right color=gradbarsBlueLight,track color=gradbarsTrack,text color=gradbarsInk},
  theme/teal/.style={fill=gradient,left color=gradbarsTeal,
    right color=gradbarsTealLight,track color=gradbarsTrack,text color=gradbarsInk},
  theme/solid/.style={fill=solid,left color=gradbarsBlue,
    right color=gradbarsBlue,track color=gradbarsTrack,text color=gradbarsInk},
  theme/gray/.style={fill=solid,left color=black!65,
    right color=black!65,track color=black!8,text color=black},
  theme/lbyellow/.style={fill=gradient,left color=lightblue!90,
    right color=lightyellow!60,track color=gradbarsTrack,text color=gradbarsInk},
  theme/viblue/.style={fill=gradient,left color=violet!20,
    right color=lightblue!70,track color=gradbarsTrack,text color=gradbarsInk},
  theme/cyblu/.style={fill=gradient,left color=cyan,
    right color=blue!20,track color=gradbarsTrack,text color=gradbarsInk},
}
\pgfkeys{/gradbars,theme=blue}
\NewDocumentCommand\gradbarssetup{m}{\pgfkeys{/gradbars,#1}}
% Style definitions follow ordinary TeX grouping, like setup.
\NewDocumentCommand\gradbarsstyle{m m}{%
  \expandafter\def\csname gradbars@style@#1\endcsname{#2}}
\def\gradbarsUseStyle#1{%
  \ifcsname gradbars@style@#1\endcsname
    \ifnum\gradbars@styledepth<32
      \advance\gradbars@styledepth by 1
      \expandafter\let\expandafter\gradbars@styleoptions\csname gradbars@style@#1\endcsname
      \expandafter\pgfkeysalso\expandafter{\gradbars@styleoptions}%
      \advance\gradbars@styledepth by -1
    \else\PackageError{gradbars}{Style nesting exceeds 32 levels}{Check for recursive styles.}\fi
  \else\PackageError{gradbars}{Unknown style: #1}{Define it with gradbarsstyle first.}\fi
}

% Validate before drawing; floating point arithmetic also supports large counts.
\ExplSyntaxOn
\cs_new_protected:Npn \gradbarsCheckNumber #1#2
  {
    \tl_set:Nx \l_tmpa_tl {#1}
    \tl_trim_spaces:N \l_tmpa_tl
    \regex_match:nVTF { \A [\+\-]? ( \d+ (\.\d*)? | \.\d+ ) \Z }
      \l_tmpa_tl { }
      {
        \gradbars@validfalse
        \PackageError{gradbars}{Invalid~#2:~expected~a~decimal~number}
          {Use~0,~86.5,~or~125000.~Expressions~are~not~accepted.}
      }
  }

\cs_new_protected:Npn \gradbarsFail #1
  {\gradbars@validfalse \PackageError{gradbars}{#1}{Check~the~gradbars~options~and~data.}}
\cs_new_protected:Npn \gradbarsRange #1#2
  {
    \fp_compare:nT {#2 < \gradbars@min || #2 > \gradbars@max}
      {
        \str_if_eq:eeTF {\gradbars@overflow}{error}
          {\gradbarsFail{#1~exceeds~range}}
          {\PackageWarning{gradbars}{#1~exceeds~range;~bar~clipped,~label~unchanged}}
      }
  }
\cs_new:Npn \gradbarsPosition #1
  {\fp_eval:n {(max(\gradbars@min,min(\gradbars@max,#1))-\gradbars@min)/
    (\gradbars@max-\gradbars@min)}}
\clist_new:N \l__gradbars_thresholds_clist
\clist_new:N \l__gradbars_colors_clist
\cs_new_protected:Npn \gradbarsValidate
  {
    \gradbars@missingfalse
    \tl_set:Nx \l_tmpa_tl {\gradbars@value}
    \tl_trim_spaces:N \l_tmpa_tl
    \tl_if_empty:NT \l_tmpa_tl {\gradbars@missingtrue}
    \str_if_eq:VnT \l_tmpa_tl {NA} {\gradbars@missingtrue}
    \ifgradbars@missing \def\gradbars@value{0}\fi
    \gradbarsCheckNumber{\gradbars@value}{value}
    \gradbarsCheckNumber{\gradbars@min}{min}
    \gradbarsCheckNumber{\gradbars@max}{max}
    \tl_set:Nx \l_tmpa_tl {\gradbars@precision}
    \regex_match:nVTF {\A [0-6] \Z} \l_tmpa_tl { }
      {\gradbarsFail{Precision~must~be~an~integer~from~0~to~6}}
    \ifgradbars@valid
      \fp_compare:nNnF {\gradbars@max} > {0}
        {\gradbarsFail{Max~must~be~greater~than~zero}}
      \fp_compare:nNnT {\gradbars@min} > {0}
        {\gradbarsFail{Min~must~be~zero~or~negative}}
      \fp_compare:nNnT {\gradbars@min} = {0}
        {\fp_compare:nNnT {\gradbars@value} < {0}
          {\gradbarsFail{Negative~values~require~a~negative~min}}}
      \str_if_eq:eeT {\gradbars@format}{percent}
        {\fp_compare:nNnT {\gradbars@min} < {0}
          {\gradbarsFail{Percent~format~requires~min=0}}}
    \fi
    \tl_if_empty:oF {\gradbars@target}
      {\gradbarsCheckNumber{\gradbars@target}{target}}
    \gradbars@errorfalse
    \tl_if_empty:oF {\gradbars@errorminus}{\gradbars@errortrue}
    \tl_if_empty:oF {\gradbars@errorplus}{\gradbars@errortrue}
    \ifgradbars@error
      \tl_if_empty:oT {\gradbars@errorminus}{\def\gradbars@errorminus{0}}
      \tl_if_empty:oT {\gradbars@errorplus}{\def\gradbars@errorplus{0}}
      \gradbarsCheckNumber{\gradbars@errorminus}{error~minus}
      \gradbarsCheckNumber{\gradbars@errorplus}{error~plus}
    \fi
    \clist_set:Nx \l__gradbars_thresholds_clist {\gradbars@thresholds}
    \clist_set:Nx \l__gradbars_colors_clist {\gradbars@thresholdcolors}
    \clist_if_empty:NF \l__gradbars_thresholds_clist
      {
        \int_compare:nNnF {\clist_count:N \l__gradbars_thresholds_clist} = {2}
          {\gradbarsFail{Thresholds~must~contain~two~values}}
        \int_compare:nNnF {\clist_count:N \l__gradbars_colors_clist} = {3}
          {\gradbarsFail{Threshold~colors~must~contain~three~colors}}
        \clist_map_inline:Nn \l__gradbars_thresholds_clist
          {\gradbarsCheckNumber{##1}{threshold}}
      }
    \ifgradbars@valid
      \gradbarsRange{Value}{\gradbars@value}
      \tl_if_empty:oF {\gradbars@target}
        {
          \fp_compare:nT {\gradbars@target < \gradbars@min || \gradbars@target > \gradbars@max}
            {\gradbarsFail{Target~must~lie~within~range}}
          \cs_set:Npx \gradbars@targetpos {\gradbarsPosition{\gradbars@target}}
        }
      \ifgradbars@error
        \fp_compare:nT {\gradbars@errorminus<0 || \gradbars@errorplus<0}
          {\gradbarsFail{Error~magnitudes~must~be~nonnegative}}
        \ifgradbars@missing\else
        \gradbarsRange{Error~lower~endpoint}{\gradbars@value-\gradbars@errorminus}
        \gradbarsRange{Error~upper~endpoint}{\gradbars@value+\gradbars@errorplus}
        \cs_set:Npx \gradbars@errorlo
          {\gradbarsPosition{\gradbars@value-\gradbars@errorminus}}
        \cs_set:Npx \gradbars@errorhi
          {\gradbarsPosition{\gradbars@value+\gradbars@errorplus}}
        \fi
      \fi
      \cs_set:Npx \gradbars@zero {\gradbarsPosition{0}}
      \cs_set:Npx \gradbars@end {\gradbarsPosition{\gradbars@value}}
      \cs_set:Npx \gradbars@start {\fp_eval:n {min(\gradbars@zero,\gradbars@end)}}
      \cs_set:Npx \gradbars@stop {\fp_eval:n {max(\gradbars@zero,\gradbars@end)}}
      \fp_compare:nNnT {\gradbars@value} < {0}
        {\gradbars@gradientfalse \let\gradbars@left\gradbars@negativecolor}
      \clist_if_empty:NF \l__gradbars_thresholds_clist
        {
          \fp_compare:nNnF {\clist_item:Nn \l__gradbars_thresholds_clist {1}}
            < {\clist_item:Nn \l__gradbars_thresholds_clist {2}}
            {\gradbarsFail{Thresholds~must~be~strictly~increasing}}
          \int_set:Nn \l_tmpa_int {1}
          \fp_compare:nNnF {\gradbars@value}
            < {\clist_item:Nn \l__gradbars_thresholds_clist {1}}
            {\int_set:Nn \l_tmpa_int {2}}
          \fp_compare:nNnF {\gradbars@value}
            < {\clist_item:Nn \l__gradbars_thresholds_clist {2}}
            {\int_set:Nn \l_tmpa_int {3}}
          \cs_set:Npx \gradbars@left
            {\clist_item:Nn \l__gradbars_colors_clist
              {\str_if_eq:eeTF {\gradbars@better}{lower}{4-\l_tmpa_int}{\l_tmpa_int}}}
          \gradbars@gradientfalse
        }
      \str_if_eq:eeTF {\gradbars@format}{percent}
        {\cs_set:Npx \gradbars@number
          {\fp_eval:n {100*\gradbars@value/\gradbars@max}}}
        {\cs_set:Npx \gradbars@number
          {\fp_eval:n {\gradbars@value}}}
    \fi
  }
\clist_new:N \l__gradbars_band_clist
\clist_new:N \l__gradbars_stack_clist
\clist_new:N \l__gradbars_stackcolors_clist
\fp_new:N \l__gradbars_sum_fp
\fp_new:N \l__gradbars_before_fp
\cs_new_protected:Npn \gradbarsValidateExtras
  {
    \clist_set:Nx \l__gradbars_band_clist {\gradbars@band}
    \clist_if_empty:NF \l__gradbars_band_clist
      {
        \tl_set:Nx \l_tmpa_tl {\gradbars@band}
        \regex_match:nVT {\A\s*,|,\s*,|,\s*\Z} \l_tmpa_tl
          {\gradbarsFail{Band~must~contain~two~endpoints}}
        \int_compare:nNnF {\clist_count:N \l__gradbars_band_clist} = {2}
          {\gradbarsFail{Band~must~contain~two~endpoints}}
        \clist_map_inline:Nn \l__gradbars_band_clist
          {\gradbarsCheckNumber{##1}{band~endpoint}}
        \gradbarsCheckNumber{\gradbars@bandopacity}{band~opacity}
        \ifgradbars@valid
          \cs_set:Npx \gradbars@bandlo {\clist_item:Nn \l__gradbars_band_clist {1}}
          \cs_set:Npx \gradbars@bandhi {\clist_item:Nn \l__gradbars_band_clist {2}}
          \fp_compare:nF {\gradbars@min <= \gradbars@bandlo < \gradbars@bandhi <= \gradbars@max}
            {\gradbarsFail{Band~must~be~increasing~and~within~range}}
          \fp_compare:nF {0 <= \gradbars@bandopacity <= 1}
            {\gradbarsFail{Band~opacity~must~be~between~zero~and~one}}
        \fi
      }
    \ifgradbars@stack
      \ifgradbars@valid
        \gradbarsRange{Positive~stack~total}{\gradbars@positive}
        \gradbarsRange{Negative~stack~total}{\gradbars@negative}
        \fp_compare:nNnT {\gradbars@negative} < {0}
          {\fp_compare:nNnT {\gradbars@min} = {0}
             {\gradbarsFail{Negative~stack~requires~a~negative~min}}}
        \cs_set:Npx \gradbars@start {\gradbarsPosition{\gradbars@negative}}
        \cs_set:Npx \gradbars@stop {\gradbarsPosition{\gradbars@positive}}
      \fi
      \tl_if_empty:oF {\gradbars@thresholds}
        {\gradbarsFail{Stacked~bars~do~not~support~thresholds}}
      \ifgradbars@error \gradbarsFail{Stacked~bars~do~not~support~error~whiskers}\fi
      \clist_set:Nx \l__gradbars_stackcolors_clist {\gradbars@stackcolors}
      \clist_if_empty:NT \l__gradbars_stackcolors_clist
        {\gradbarsFail{Stack~colors~must~not~be~empty}}
    \fi
  }
\cs_new_protected:Npn \gradbarsDrawBand
  {
    \clist_if_empty:NF \l__gradbars_band_clist
      {
        \pgfmathsetlengthmacro\gradbars@bandxlo{\gradbarsPosition{\gradbars@bandlo}*(\gradbars@width)}
        \pgfmathsetlengthmacro\gradbars@bandxhi{\gradbarsPosition{\gradbars@bandhi}*(\gradbars@width)}
        \path[fill=\gradbars@bandcolor,fill~opacity=\gradbars@bandopacity,draw=none]
          (\gradbars@bandxlo,-2pt) rectangle ([yshift=2pt]\gradbars@bandxhi,\gradbars@height);
      }
  }
\cs_new_protected:Npn \gradbarsParseStack #1
  {
    \tl_set:Nx \l_tmpa_tl {#1}
    % Empty elements are invalid, not silently dropped by clist normalization.
    \regex_match:nVTF {\A\s*\Z|\A\s*,|,\s*,|,\s*\Z} \l_tmpa_tl
      {\gradbarsFail{Stack~requires~a~nonempty~list~of~numbers}} { }
    \clist_set:Nx \l__gradbars_stack_clist {#1}
    \fp_zero:N \l__gradbars_sum_fp
    \fp_zero:N \l__gradbars_positive_fp
    \fp_zero:N \l__gradbars_negative_fp
    \clist_map_inline:Nn \l__gradbars_stack_clist
      {
        \gradbarsCheckNumber{##1}{stack~segment}
        \ifgradbars@valid
          \fp_add:Nn \l__gradbars_sum_fp {##1}
          \fp_compare:nNnTF {##1} < {0}
            {\fp_add:Nn \l__gradbars_negative_fp {##1}}
            {\fp_add:Nn \l__gradbars_positive_fp {##1}}
        \fi
      }
    \cs_set:Npx \gradbars@total {\fp_to_decimal:N \l__gradbars_sum_fp}
    \cs_set:Npx \gradbars@positive {\fp_to_decimal:N \l__gradbars_positive_fp}
    \cs_set:Npx \gradbars@negative {\fp_to_decimal:N \l__gradbars_negative_fp}
    \cs_set:Npx \gradbars@gross {\fp_eval:n {\gradbars@positive-\gradbars@negative}}
  }
\cs_new_protected:Npn \gradbarsDrawStack
  {
    \gradbarsStackReset
    \int_zero:N \l_tmpa_int
    \clist_map_inline:Nn \l__gradbars_stack_clist
      {
        \gradbarsStackStep{##1}
        \int_incr:N \l_tmpa_int
        \gradbarsCategoryColor{\l_tmpa_int}
        \pgfmathsetlengthmacro\gradbars@segmentlo
          {\gradbarsPosition{\l__gradbars_before_fp}*(\gradbars@width)}
        \pgfmathsetlengthmacro\gradbars@segmenthi
          {\gradbarsPosition{\l__gradbars_sum_fp}*(\gradbars@width)}
        \ifdim\gradbars@segmenthi>\gradbars@segmentlo\relax
          \ifgradbars@outline
            \draw[draw=\gradbars@segmentcolor,line~width=\gradbars@outlinewidth]
              (\gradbars@segmentlo,0) rectangle (\gradbars@segmenthi,\gradbars@height);
          \else
            \path[fill=\gradbars@segmentcolor,draw=none]
              (\gradbars@segmentlo,0) rectangle (\gradbars@segmenthi,\gradbars@height);
            \gradbarsPatternRect{\l_tmpa_int}{\gradbars@segmentlo}{0}{\gradbars@segmenthi}{\gradbars@height}
          \fi
        \fi
      }
  }
\ExplSyntaxOff

\def\gradbars@labelcontent{%
  \ifx\gradbars@text\@empty
    \ifgradbars@missing\gradbars@missingtext\else
      \begingroup
      \def\gradbars@temp{scientific}%
      \ifx\gradbars@numberformat\gradbars@temp
        \pgfmathprintnumber[sci,sci zerofill,precision=\gradbars@precision]{\gradbars@number}%
      \else
        \def\gradbars@temp{grouped}%
        \ifx\gradbars@numberformat\gradbars@temp
          \pgfmathprintnumber[fixed,fixed zerofill,precision=\gradbars@precision,1000 sep={\mathord{,}}]{\gradbars@number}%
        \else
          \pgfmathprintnumber[fixed,fixed zerofill,precision=\gradbars@precision,1000 sep={\,}]{\gradbars@number}%
        \fi
      \fi
      \def\gradbars@temp{percent}%
      \ifx\gradbars@format\gradbars@temp\%\else\gradbars@unit\fi
      \endgroup
    \fi
  \else\gradbars@text\fi
}

\def\gradbars@draw{%
  \tikzset{gradbars label background/.style={}}%
  \ifgradbars@print
    \tikzset{gradbars label background/.style={fill=white,inner sep=1pt}}%
  \fi
  \pgfmathsetlengthmacro\gradbars@xzero{\gradbars@zero*(\gradbars@width)}%
  \pgfmathsetlengthmacro\gradbars@xend{\gradbars@end*(\gradbars@width)}%
  \pgfmathsetlengthmacro\gradbars@xstart{\gradbars@start*(\gradbars@width)}%
  \pgfmathsetlengthmacro\gradbars@xstop{\gradbars@stop*(\gradbars@width)}%
  \pgfmathsetlengthmacro\gradbars@radius{min(\gradbars@rounded,.5*(\gradbars@height),.5*(\gradbars@width))}%
  \gradbarsDrawTrack
  \gradbarsDrawBand
  \gradbarsDrawShape
  \ifgradbars@spark\else\ifdim\gradbars@xzero>0pt
    \draw[gradbarsInk!65,line width=.35pt]
      (\gradbars@xzero,0)--(\gradbars@xzero,\gradbars@height);
  \fi\fi
  \ifx\gradbars@target\@empty\else
    \pgfmathsetlengthmacro\gradbars@xtarget{\gradbars@targetpos*(\gradbars@width)}%
    \draw[draw=\gradbars@targetcolor,line width=.6pt]
      ([yshift=-1pt]\gradbars@xtarget,0)--
      ([yshift=1pt]\gradbars@xtarget,\gradbars@height);
  \fi
  \ifgradbars@missing\else\ifgradbars@error
    \pgfmathsetlengthmacro\gradbars@xlo{\gradbars@errorlo*(\gradbars@width)}%
    \pgfmathsetlengthmacro\gradbars@xhi{\gradbars@errorhi*(\gradbars@width)}%
    \draw[draw=\gradbars@errorcolor,line width=.5pt]
      ([yshift=.5*\gradbars@height]\gradbars@xlo,0)--
      ([yshift=.5*\gradbars@height]\gradbars@xhi,0);
    \foreach \gradbars@cap in {\gradbars@xlo,\gradbars@xhi}{
      \draw[draw=\gradbars@errorcolor,line width=.5pt]
        ([yshift=.2*\gradbars@height]\gradbars@cap,0)--
        ([yshift=.8*\gradbars@height]\gradbars@cap,0);
    }
  \fi\fi
  \gradbarsResetLabels
  \ifgradbars@stack
    \gradbarsDrawSegmentLabels
    \ifgradbars@legend\gradbarsDrawLegend\fi
  \fi
  \gradbarsDrawMainLabel
  \path[use as bounding box] (0,-\gradbars@rowpadding)
    rectangle (\gradbars@width,{\gradbars@height+\gradbars@rowpadding});
}
\def\gradbars@render{%
  \gradbars@validtrue
  \gradbarsResolveShapeLengths
  \gradbarsValidate
  \gradbarsValidateExtras
  \ifdim\gradbars@rounded<0pt
    \gradbars@validfalse
    \PackageError{gradbars}{Rounded radius must be nonnegative}{Use rounded=2pt.}%
  \fi
  \ifdim\gradbars@width>0pt\else
    \gradbars@validfalse
    \PackageError{gradbars}{Width must be positive}{Use a length such as 24mm.}%
  \fi
  \ifdim\gradbars@height>0pt\else
    \gradbars@validfalse
    \PackageError{gradbars}{Height must be positive}{Use a length such as 1.5ex.}%
  \fi
  \ifgradbars@valid
    \ifpgfpicture
      \begin{scope}\gradbars@draw\end{scope}%
    \else
      \tikz[baseline={([yshift=-.5ex]0,{.5*\gradbars@height})}]{\gradbars@draw}%
    \fi
  \fi
}
\NewDocumentCommand\gradbar{O{} m}{%
  \leavevmode\begingroup
    \pgfkeys{/gradbars,#1}%
    % Resolve font-relative dimensions in the surrounding text font.
    \ifpgfpicture
      \pgfinterruptpicture
      \font@name
      \edef\gradbars@resolved{%
        \noexpand\endpgfinterruptpicture
        \def\noexpand\gradbars@width{\the\dimexpr\gradbars@width\relax}%
        \def\noexpand\gradbars@height{\the\dimexpr\gradbars@height\relax}%
        \def\noexpand\gradbars@rounded{\the\dimexpr\gradbars@rounded\relax}%
        \def\noexpand\gradbars@gap{\the\dimexpr\gradbars@gap\relax}%
        \def\noexpand\gradbars@labelwidth{\the\dimexpr\gradbars@labelwidth\relax}%
      }%
      \gradbars@resolved
    \fi
    \def\gradbars@value{#2}%
    \gradbars@render
  \endgroup
}
\NewDocumentCommand\gradstack{O{} m}{%
  \leavevmode\begingroup
    \gradbars@validtrue
    \gradbarsParseStack{#2}%
    \ifgradbars@valid
      \gradbars@stacktrue
      \gradbar[#1]{\gradbars@total}%
    \fi
  \endgroup
}

% v0.0.2: labels, legends and collected table columns.
\def\gradbars@stacknames{}
\def\gradbars@segmentlabels{none}
\def\gradbars@segmentfont{\scriptsize}
\def\gradbars@segmenttextcolor{auto}
\def\gradbars@labeloverflow{auto}
\newif\ifgradbars@legend
\newif\ifgradbars@inside
\newif\ifgradbars@fit
\newsavebox\gradbars@segmentbox
\pgfkeys{/gradbars,
  stack names/.store in=\gradbars@stacknames,
  legend/.is if=gradbars@legend,legend/.default=true,
  segment font/.store in=\gradbars@segmentfont,
  segment text color/.store in=\gradbars@segmenttextcolor,
  segment labels/.is choice,
  segment labels/none/.code=\def\gradbars@segmentlabels{none},
  segment labels/value/.code=\def\gradbars@segmentlabels{value},
  segment labels/percent/.code=\def\gradbars@segmentlabels{percent},
  segment labels/name/.code=\def\gradbars@segmentlabels{name},
  segment labels/name value/.code=\def\gradbars@segmentlabels{name value},
  segment labels/name percent/.code=\def\gradbars@segmentlabels{name percent},
  label overflow/.is choice,
  label overflow/auto/.code=\def\gradbars@labeloverflow{auto},
  label overflow/allow/.code=\def\gradbars@labeloverflow{allow}
}
\def\gradbarsMeasure#1#2#3{%
  \sbox#1{\pgfinterruptpicture\font@name #2#3\endpgfinterruptpicture}}

\ExplSyntaxOn
\clist_new:N \l__gradbars_names_clist
\seq_new:N \l__gradbars_occupied_seq
\dim_new:N \l__gradbars_top_dim
\dim_new:N \l__gradbars_labelbottom_dim
\dim_new:N \l__gradbars_labelleft_dim
\dim_new:N \l__gradbars_labelright_dim
\dim_new:N \l__gradbars_leaderx_dim
\dim_new:N \l__gradbars_leaderleft_dim
\dim_new:N \l__gradbars_leaderright_dim
\fp_new:N \l__gradbars_luminance_fp
\int_new:N \l__gradbars_index_int
\cs_new_protected:Npn \gradbarsContrast #1
  {
    \convertcolorspec{named}{#1}{rgb}\gradbars@rgb
    \clist_set:Nx \l_tmpa_clist {\gradbars@rgb}
    \fp_zero:N \l__gradbars_luminance_fp
    \int_step_inline:nn {3}
      {
        \fp_set:Nn \l_tmpa_fp {\clist_item:Nn \l_tmpa_clist {##1}}
        \fp_compare:nNnTF {\l_tmpa_fp} > {0.04045}
          {\fp_set:Nn \l_tmpa_fp {((\l_tmpa_fp+0.055)/1.055)^2.4}}
          {\fp_set:Nn \l_tmpa_fp {\l_tmpa_fp/12.92}}
        \fp_add:Nn \l__gradbars_luminance_fp
          {\l_tmpa_fp*\clist_item:nn {0.2126,0.7152,0.0722}{##1}}
      }
    \fp_compare:nNnTF {\l__gradbars_luminance_fp} > {0.179}
      {\def\gradbars@labelcolor{black}}{\def\gradbars@labelcolor{white}}
  }
\let\gradbarsValidatePrevious\gradbarsValidateExtras
\cs_set_protected:Npn \gradbarsValidateExtras
  {
    \gradbarsValidatePrevious
    \clist_set:No \l__gradbars_names_clist {\gradbars@stacknames}
    \ifgradbars@stack
      \clist_if_empty:NF \l__gradbars_names_clist
        {\int_compare:nNnF {\clist_count:N \l__gradbars_names_clist}
          = {\clist_count:N \l__gradbars_stack_clist}
          {\gradbarsFail{Stack~names~must~match~the~segment~count}}}
      \bool_set_false:N \l_tmpa_bool
      \ifgradbars@legend \bool_set_true:N \l_tmpa_bool \fi
      \str_case_e:nn {\gradbars@segmentlabels}
        {{name}{\bool_set_true:N \l_tmpa_bool}
         {name~value}{\bool_set_true:N \l_tmpa_bool}
         {name~percent}{\bool_set_true:N \l_tmpa_bool}}
      \bool_if:NT \l_tmpa_bool
        {\clist_if_empty:NT \l__gradbars_names_clist
          {\gradbarsFail{Stack~names~are~required~for~names~or~legend}}}
      \ifgradbars@legend
        \ifdim\gradbars@width>14pt\relax\else
          \gradbarsFail{Legend~width~must~exceed~14pt}\fi
      \fi
    \else
      \ifgradbars@legend \gradbarsFail{Automatic~legend~requires~gradstack}\fi
    \fi
  }
\cs_new_protected:Npn \gradbarsResetLabels
  {\seq_clear:N \l__gradbars_occupied_seq
   \dim_set:Nn \l__gradbars_top_dim {\gradbars@height+4pt}}
\cs_new_protected:Npn \gradbarsCheckOverlap #1#2#3
  {
    \dim_compare:nNnT {\l__gradbars_labelleft_dim} < {#2+3pt}
      {\dim_compare:nNnT {\l__gradbars_labelright_dim} > {#1-3pt}
        {\dim_set:Nn \l__gradbars_labelbottom_dim
          {\dim_max:nn {\l__gradbars_labelbottom_dim}{#3+3pt}}}}
  }
\cs_new_protected:Npn \gradbarsLeaderBounds #1#2#3
  {
    \dim_set:Nn \l__gradbars_leaderleft_dim
      {\dim_min:nn {\l__gradbars_leaderleft_dim}{#1-4pt}}
    \dim_set:Nn \l__gradbars_leaderright_dim
      {\dim_max:nn {\l__gradbars_leaderright_dim}{#2+4pt}}
  }
\cs_new_protected:Npn \gradbarsPlaceSegment
  {
    \pgfmathsetlengthmacro\gradbars@segmentmid{.5*(\gradbars@segmentlo+\gradbars@segmenthi)}
    \gradbars@fittrue
    \ifgradbars@outline\gradbars@fitfalse\fi
    \ifdim\dimexpr\wd\gradbars@segmentbox+4pt\relax>\dimexpr\gradbars@segmenthi-\gradbars@segmentlo\relax
      \gradbars@fitfalse\fi
    \ifdim\dimexpr\ht\gradbars@segmentbox+\dp\gradbars@segmentbox+2pt\relax>\gradbars@height\relax
      \gradbars@fitfalse\fi
    \ifx\gradbars@target\@empty\else
      \pgfmathsetlengthmacro\gradbars@distance{abs(\gradbars@xtarget-\gradbars@segmentmid)}
      \ifdim\gradbars@distance<\dimexpr.5\wd\gradbars@segmentbox+2pt\relax
        \gradbars@fitfalse\fi
    \fi
    \ifgradbars@fit
      \let\gradbars@labelcolor\gradbars@segmenttextcolor
      \str_if_eq:eeT {\gradbars@segmenttextcolor}{auto}{\gradbarsContrast{\gradbars@segmentcolor}}
      \gradbarsMeasure\gradbars@segmentbox\gradbars@segmentfont{\color{\gradbars@labelcolor}\gradbars@segmentcontent}
      \node[inner~sep=0pt,outer~sep=0pt,text=\gradbars@labelcolor]
        [gradbars~label~background]
        at (\gradbars@segmentmid,.5*\gradbars@height){\usebox\gradbars@segmentbox};
    \else
      \pgfmathsetlengthmacro\gradbars@labelx
        {max(.5*\wd\gradbars@segmentbox,min(\gradbars@segmentmid,\gradbars@width-.5*\wd\gradbars@segmentbox))}
      \dim_set:Nn \l__gradbars_labelleft_dim {\gradbars@labelx-.5\wd\gradbars@segmentbox}
      \dim_set:Nn \l__gradbars_labelright_dim {\gradbars@labelx+.5\wd\gradbars@segmentbox}
      \dim_set:Nn \l__gradbars_labelbottom_dim {\gradbars@height+4pt}
      \seq_map_inline:Nn \l__gradbars_occupied_seq {\gradbarsCheckOverlap ##1}
      % Route elevated leaders around earlier label boxes, not through text.
      \dim_set:Nn \l__gradbars_leaderx_dim {\gradbars@segmentmid}
      \dim_compare:nNnT {\l__gradbars_labelbottom_dim} > {\gradbars@height+4pt}
        {
          \dim_set:Nn \l__gradbars_leaderleft_dim {\gradbars@segmentmid}
          \dim_set:Nn \l__gradbars_leaderright_dim {\gradbars@segmentmid}
          \seq_map_inline:Nn \l__gradbars_occupied_seq {\gradbarsLeaderBounds ##1}
          \dim_compare:nNnTF
            {\gradbars@segmentmid-\l__gradbars_leaderleft_dim} <
            {\l__gradbars_leaderright_dim-\gradbars@segmentmid}
            {\dim_set_eq:NN \l__gradbars_leaderx_dim \l__gradbars_leaderleft_dim}
            {\dim_set_eq:NN \l__gradbars_leaderx_dim \l__gradbars_leaderright_dim}
        }
      \seq_put_right:Nx \l__gradbars_occupied_seq
        {{\dim_use:N \l__gradbars_labelleft_dim}{\dim_use:N \l__gradbars_labelright_dim}
          {\dim_eval:n {\l__gradbars_labelbottom_dim+\ht\gradbars@segmentbox+\dp\gradbars@segmentbox}}}
      \dim_set:Nn \l__gradbars_top_dim {\dim_max:nn {\l__gradbars_top_dim}
        {\l__gradbars_labelbottom_dim+\ht\gradbars@segmentbox+\dp\gradbars@segmentbox+3pt}}
      \draw[gradbarsInk!55,line~width=.3pt]
        (\gradbars@segmentmid,\gradbars@height)--
        (\gradbars@segmentmid,\dim_eval:n {\gradbars@height+1pt})--
        (\dim_use:N \l__gradbars_leaderx_dim,\dim_eval:n {\gradbars@height+1pt})--
        (\dim_use:N \l__gradbars_leaderx_dim,\dim_eval:n {\l__gradbars_labelbottom_dim-2pt})--
        (\gradbars@labelx,\dim_eval:n {\l__gradbars_labelbottom_dim-2pt});
      \let\gradbars@labelcolor\gradbars@segmenttextcolor
      \str_if_eq:eeT {\gradbars@segmenttextcolor}{auto}{\def\gradbars@labelcolor{black}}
      \gradbarsMeasure\gradbars@segmentbox\gradbars@segmentfont{\color{\gradbars@labelcolor}\gradbars@segmentcontent}
      \node[anchor=south,inner~sep=0pt,outer~sep=0pt,text=\gradbars@labelcolor]
        at (\gradbars@labelx,\dim_use:N \l__gradbars_labelbottom_dim){\usebox\gradbars@segmentbox};
    \fi
  }
\cs_new_protected:Npn \gradbarsDrawSegmentLabels
  {
    \str_if_eq:eeF {\gradbars@segmentlabels}{none}
      {
        \gradbarsStackReset
        \int_zero:N \l__gradbars_index_int
        \clist_map_inline:Nn \l__gradbars_stack_clist
          {
            \int_incr:N \l__gradbars_index_int
            \gradbarsStackStep{##1}
            \pgfmathsetlengthmacro\gradbars@segmentlo{\gradbarsPosition{\l__gradbars_before_fp}*(\gradbars@width)}
            \pgfmathsetlengthmacro\gradbars@segmenthi{\gradbarsPosition{\l__gradbars_sum_fp}*(\gradbars@width)}
            \ifdim\gradbars@segmenthi>\gradbars@segmentlo\relax
              \gradbarsCategoryColor{\l__gradbars_index_int}
              \def\gradbars@segmentcontent
              {\def\gradbars@text{}\def\gradbars@unit{}\def\gradbars@format{value}
                \cs_set:Npx \gradbars@number {\fp_eval:n {##1}}
                \str_case_e:nn {\gradbars@segmentlabels}
                  {{percent}{\def\gradbars@format{percent}}
                   {name~percent}{\def\gradbars@format{percent}}}
                \str_if_eq:eeT {\gradbars@format}{percent}
                  {\cs_set:Npx \gradbars@number {\fp_eval:n {100*abs(##1)/\gradbars@gross}}}
                \tl_clear:N \l_tmpb_tl
                \str_case_e:nn {\gradbars@segmentlabels}
                  {{name}{\tl_set:Nn \l_tmpb_tl {name}}
                   {name~value}{\tl_set:Nn \l_tmpb_tl {both}}
                   {name~percent}{\tl_set:Nn \l_tmpb_tl {both}}}
                \tl_if_empty:NF \l_tmpb_tl
                    {\clist_item:Nn \l__gradbars_names_clist {\l__gradbars_index_int}}
                   \str_if_eq:VnT \l_tmpb_tl {both}{:\space}
                   \str_if_eq:VnF \l_tmpb_tl {name}{\gradbarsSingleLabel}}
              \gradbarsMeasure\gradbars@segmentbox\gradbars@segmentfont{\gradbars@segmentcontent}
              \gradbarsPlaceSegment
            \fi
          }
      }
  }
\cs_new_protected:Npn \gradbarsDrawLegend
  {
    \dim_set:Nn \l_tmpa_dim {6pt}
    \int_zero:N \l__gradbars_index_int
    \clist_map_inline:Nn \l__gradbars_names_clist
      {
        \int_incr:N \l__gradbars_index_int
        \gradbarsCategoryColor{\l__gradbars_index_int}
        \gradbarsMeasure\gradbars@segmentbox\gradbars@font
          {\parbox[t]{\dimexpr\gradbars@width-14pt\relax}{\raggedright\strut ##1\strut}}
        \ifgradbars@outline
          \draw[draw=\gradbars@segmentcolor,line~width=\gradbars@outlinewidth]
            (0,-\dim_use:N \l_tmpa_dim) rectangle (8pt,-\dim_eval:n {\l_tmpa_dim+6pt});
        \else
          \path[fill=\gradbars@segmentcolor,draw=none]
            (0,-\dim_use:N \l_tmpa_dim) rectangle (8pt,-\dim_eval:n {\l_tmpa_dim+6pt});
          \gradbarsPatternRect{\l__gradbars_index_int}{0}{-\dim_use:N \l_tmpa_dim}
            {8pt}{-\dim_eval:n {\l_tmpa_dim+6pt}}
        \fi
        \node[anchor=north~west,inner~sep=0pt,outer~sep=0pt,text=gradbarsInk]
          at (14pt,-\dim_use:N \l_tmpa_dim){\usebox\gradbars@segmentbox};
        \dim_add:Nn \l_tmpa_dim {\ht\gradbars@segmentbox+\dp\gradbars@segmentbox+4pt}
      }
  }
\cs_new_protected:Npn \gradbarsMainBackground
  {
    \def\gradbars@labelcolor{black}
    \ifgradbars@inside
      \let\gradbars@background\gradbars@track
      \clist_if_empty:NF \l__gradbars_band_clist
        {\dim_compare:nNnT {\gradbars@labelx} > {\gradbars@bandxlo}
          {\dim_compare:nNnT {\gradbars@labelx} < {\gradbars@bandxhi}
            {\edef\gradbars@background{\gradbars@bandcolor!
               \fp_eval:n {100*\gradbars@bandopacity}!\gradbars@track}}}}
      \ifdim\gradbars@labelx>\gradbars@xstart\relax
        \ifdim\gradbars@labelx<\gradbars@xstop\relax
          \let\gradbars@background\gradbars@left
          \ifgradbars@gradient
            \pgfmathparse{100*(\gradbars@labelx-\gradbars@xstart)/(\gradbars@xstop-\gradbars@xstart)}
            \edef\gradbars@background{\gradbars@right!\pgfmathresult!\gradbars@left}
          \fi
          \ifgradbars@stack
            \fp_zero:N \l__gradbars_sum_fp
            \int_zero:N \l__gradbars_index_int
            \clist_map_inline:Nn \l__gradbars_stack_clist
              {\fp_add:Nn \l__gradbars_sum_fp {##1}\int_incr:N \l__gradbars_index_int
               \pgfmathsetlengthmacro\gradbars@segmenthi{\gradbarsPosition{\l__gradbars_sum_fp}*(\gradbars@width)}
               \dim_compare:nNnT {\gradbars@labelx} < {\gradbars@segmenthi}
               {
                 \gradbarsCategoryColor{\l__gradbars_index_int}
                 \let\gradbars@background\gradbars@segmentcolor
                 \clist_map_break:
               }}
          \fi
        \fi
      \fi
      \gradbarsContrast{\gradbars@background}
    \fi
  }
\cs_new_protected:Npn \gradbarsDrawMainLabel
  {
    \str_if_eq:eeF {\gradbars@label}{none}
      {
        \gradbarsMeasure\gradbars@labelbox\gradbars@font{\gradbars@labelcontent}
        \let\gradbars@placement\gradbars@label
        \gradbars@insidefalse
        \str_if_eq:eeT {\gradbars@placement}{auto}
          {\def\gradbars@placement{outside}
           \ifdim\dimexpr\wd\gradbars@labelbox+4pt\relax<\dimexpr\gradbars@xstop-\gradbars@xstart\relax
             \def\gradbars@placement{center}\fi}
        \str_if_eq:eeF {\gradbars@labeloverflow}{allow}
          {\str_if_eq:eeT {\gradbars@placement}{inside}
             {\ifdim\dimexpr\wd\gradbars@labelbox+4pt\relax>\gradbars@width\relax
                \def\gradbars@placement{outside}\fi}
           \str_if_eq:eeT {\gradbars@placement}{end}
             {\ifdim\wd\gradbars@labelbox>\gradbars@width\relax
                \def\gradbars@placement{outside}\fi}}
        \str_case_e:nn {\gradbars@placement}
          {{inside}{\gradbars@insidetrue}{center}{\gradbars@insidetrue}}
        \ifgradbars@inside
          \str_if_eq:eeF {\gradbars@shape}{bar}
            {\def\gradbars@placement{outside}\gradbars@insidefalse}
          \ifgradbars@stack
            \fp_compare:nNnT {\gradbars@negative} < {0}
              {\def\gradbars@placement{outside}\gradbars@insidefalse}
          \fi
          \ifgradbars@compare \def\gradbars@placement{outside}\gradbars@insidefalse\fi
          \ifgradbars@outline \def\gradbars@placement{outside}\gradbars@insidefalse\fi
          \ifgradbars@rangemode
            \tl_if_empty:oF {\gradbars@rangepoint}
              {\def\gradbars@placement{outside}\gradbars@insidefalse}
          \fi
        \fi
        \ifgradbars@inside
          \str_if_eq:eeF {\gradbars@labeloverflow}{allow}
            {\ifdim\dimexpr\ht\gradbars@labelbox+\dp\gradbars@labelbox+2pt\relax>\gradbars@height\relax
               \def\gradbars@placement{outside}\gradbars@insidefalse\fi}
          \ifgradbars@stack
            \str_if_eq:eeF {\gradbars@segmentlabels}{none}
              {\def\gradbars@placement{outside}\gradbars@insidefalse}
          \fi
          \ifgradbars@error \def\gradbars@placement{outside}\gradbars@insidefalse\fi
          \ifgradbars@inside
            \str_if_eq:eeTF {\gradbars@placement}{center}
              {\pgfmathsetlengthmacro\gradbars@labelx{.5*(\gradbars@xstart+\gradbars@xstop)}}
              {\pgfmathsetlengthmacro\gradbars@labelx{\gradbars@width-2pt-.5*\wd\gradbars@labelbox}}
            \ifx\gradbars@target\@empty\else
              \pgfmathsetlengthmacro\gradbars@distance{abs(\gradbars@xtarget-\gradbars@labelx)}
              \ifdim\gradbars@distance<\dimexpr.5\wd\gradbars@labelbox+2pt\relax
                \def\gradbars@placement{outside}\gradbars@insidefalse\fi
            \fi
          \fi
        \fi
        \let\gradbars@labelcolor\gradbars@textcolor
        \str_if_eq:eeT {\gradbars@textcolor}{auto}{\gradbarsMainBackground}
        \gradbarsMeasure\gradbars@labelbox\gradbars@font{\color{\gradbars@labelcolor}\gradbars@labelcontent}
        \ifgradbars@inside
          \node[inner~sep=0pt,outer~sep=0pt,text=\gradbars@labelcolor]
            [gradbars~label~background]
            at (\gradbars@labelx,.5*\gradbars@height){\usebox\gradbars@labelbox};
        \else
          \str_if_eq:eeTF {\gradbars@placement}{end}
            {\pgfmathsetlengthmacro\gradbars@labelx
              {max(.5*\wd\gradbars@labelbox,min(\gradbars@xend,\gradbars@width-.5*\wd\gradbars@labelbox))}
             \node[anchor=south,inner~sep=0pt,outer~sep=0pt,text=\gradbars@labelcolor]
               at (\gradbars@labelx,\dim_use:N \l__gradbars_top_dim){\usebox\gradbars@labelbox};}
            {\dim_set:Nn \l_tmpa_dim {\gradbars@labelwidth}
             \str_if_eq:eeF {\gradbars@labeloverflow}{allow}
               {\dim_set:Nn \l_tmpa_dim {\dim_max:nn {\l_tmpa_dim}{\wd\gradbars@labelbox}}}
             \node[anchor=west,inner~sep=0pt,outer~sep=0pt,text=\gradbars@labelcolor]
               at ([xshift=\gradbars@gap,yshift=.5*\gradbars@height]\gradbars@width,0)
               {\makebox[\dim_use:N \l_tmpa_dim][r]{\usebox\gradbars@labelbox}};}
        \fi
      }
  }
\ExplSyntaxOff
\NewDocumentCommand\gradbarslegend{O{} m}{%
  \begingroup\pgfkeys{/gradbars,#1,stack names={#2}}%
  \begingroup\font@name
    \edef\gradbars@next{\endgroup
      \def\noexpand\gradbars@width{\the\dimexpr\gradbars@width\relax}}%
  \gradbars@next
  \gradbarsResolveShapeLengths
  \gradbarsStandaloneLegend
  \endgroup}
\ExplSyntaxOn
\cs_new_protected:Npn \gradbarsStandaloneLegend
  {
    \gradbars@validtrue
    \clist_set:No \l__gradbars_names_clist {\gradbars@stacknames}
    \clist_set:No \l__gradbars_stackcolors_clist {\gradbars@stackcolors}
    \clist_if_empty:NT \l__gradbars_names_clist {\gradbarsFail{Legend~names~must~not~be~empty}}
    \clist_if_empty:NT \l__gradbars_stackcolors_clist {\gradbarsFail{Stack~colors~must~not~be~empty}}
    \gradbarsValidatePatterns
    \ifdim\gradbars@width>14pt\else\gradbarsFail{Legend~width~must~exceed~14pt}\fi
    \ifgradbars@valid
      \ifpgfpicture \gradbarsDrawLegend
      \else\tikz[baseline=0pt]{\gradbarsDrawLegend}\fi
    \fi
  }
\ExplSyntaxOff
\NewDocumentCommand\gradbarscolumn{m m}{%
  \gradbarsDefineColumn{#1}{#2}}
\ExplSyntaxOn
\cs_new_protected:Npn \gradbarsDefineColumn #1#2
  {
    \regex_match:nnTF {\A[A-Za-z]\Z} {#1}
      {\expandafter\newcolumntype\expandafter{#1}
        {>{\collectcell{\gradbar[#2]}}l<{\endcollectcell}}}
      {\PackageError{gradbars}{Column~name~must~be~one~ASCII~letter}
        {Choose~a~single~unused~letter,~for~example~G.}}
  }
\ExplSyntaxOff

% Data import, print patterns and metric direction (v0.0.2).
\def\gradbars@better{higher}
\def\gradbars@pattern{none}
\def\gradbars@patterncolor{black}
\def\gradbars@stackpatterns{none}
\newif\ifgradbars@print
\pgfkeys{/gradbars,
  print labels/.is if=gradbars@print,
  better/.is choice,
  better/higher/.code=\def\gradbars@better{higher},
  better/lower/.code=\def\gradbars@better{lower},
  pattern/.store in=\gradbars@pattern,
  pattern color/.store in=\gradbars@patterncolor,
  stack patterns/.store in=\gradbars@stackpatterns,
  print/.style={outline=false,print labels=true,theme=gray,color=white,track color=black!8,
    negative color=white,text color=black,segment text color=black,
    pattern=diagonal,pattern color=black,
    stack colors={white},stack patterns={diagonal,dots,crosshatch},
    threshold colors={black!10,black!25,black!45}}
}
\ExplSyntaxOn
\prop_const_from_keyval:Nn \c__gradbars_patterns_prop
  {none=none,diagonal=north~east~lines,reverse=north~west~lines,
   dots=dots,crosshatch=crosshatch,horizontal=horizontal~lines,vertical=vertical~lines}
\clist_new:N \l__gradbars_patterns_clist
\tl_new:N \l__gradbars_pattern_tl
\cs_new_protected:Npn \gradbarsPatternRect #1#2#3#4#5
  {
    \int_compare:nNnTF {#1} = {0}
      {\tl_set:No \l__gradbars_pattern_tl {\gradbars@pattern}}
      {\clist_set:No \l__gradbars_patterns_clist {\gradbars@stackpatterns}
       \tl_set:Nx \l__gradbars_pattern_tl
         {\clist_item:Nn \l__gradbars_patterns_clist
           {1+\int_mod:nn {#1-1}{\clist_count:N \l__gradbars_patterns_clist}}}}
    \gradbarsCategoryPattern{#1}
    \prop_get:NVNTF \c__gradbars_patterns_prop \l__gradbars_pattern_tl \l__gradbars_pattern_tl
      {\tl_if_eq:NnF \l__gradbars_pattern_tl {none}
        {\path[pattern=\l__gradbars_pattern_tl,pattern~color=\gradbars@patterncolor,draw=none]
          (#2,#3) rectangle (#4,#5);}}
      {\gradbarsFail{Unknown~pattern}}
  }
\cs_new_eq:NN \gradbarsValidateBeforePatterns \gradbarsValidateExtras
\cs_set_protected:Npn \gradbarsValidateExtras
  {
    \gradbarsValidateBeforePatterns
    \gradbarsValidatePatterns
  }
\cs_new_protected:Npn \gradbarsValidatePatterns
  {
    \clist_set:No \l__gradbars_patterns_clist {\gradbars@stackpatterns}
    \clist_if_empty:NT \l__gradbars_patterns_clist {\gradbarsFail{Stack~patterns~must~not~be~empty}}
    \clist_put_left:No \l__gradbars_patterns_clist {\gradbars@pattern}
    \clist_map_inline:Nn \l__gradbars_patterns_clist
      {\prop_if_in:NnF \c__gradbars_patterns_prop {##1}{\gradbarsFail{Unknown~pattern:~##1}}}
  }

% CSV is read as character data; fields are never executed as TeX.
\ior_new:N \l__gradbars_csv_ior
\prop_new:N \l__gradbars_csv_prop
\seq_new:N \l__gradbars_csv_fields_seq
\tl_new:N \l__gradbars_csv_field_tl
\tl_new:N \l__gradbars_csv_line_tl
\tl_new:N \l__gradbars_csv_value_tl
\tl_new:N \l__gradbars_csv_table_tl
\clist_new:N \l__gradbars_csv_missing_clist
\int_new:N \l__gradbars_csv_state_int
\int_new:N \l__gradbars_csv_row_int
\int_new:N \l__gradbars_csv_col_int
\int_new:N \l__gradbars_csv_width_int
\fp_new:N \l__gradbars_csv_min_fp
\fp_new:N \l__gradbars_csv_max_fp
\keys_define:nn {gradbars/csv}
  {missing .clist_set:N=\l__gradbars_csv_missing_clist}
\cs_generate_variant:Nn \prop_put:Nnn {Nxn,Nxx,NVx}
\cs_generate_variant:Nn \prop_get:NnN {cxN}
\cs_generate_variant:Nn \prop_if_in:NnTF {cxTF}
\cs_new_protected:Npn \gradbarsCSVField
  {\tl_trim_spaces:N \l__gradbars_csv_field_tl
   \seq_put_right:NV \l__gradbars_csv_fields_seq \l__gradbars_csv_field_tl
   \tl_clear:N \l__gradbars_csv_field_tl
   \int_zero:N \l__gradbars_csv_state_int}
\cs_new_protected:Npn \gradbarsCSVParse #1
  {
    \seq_clear:N \l__gradbars_csv_fields_seq
    \tl_clear:N \l__gradbars_csv_field_tl
    \int_zero:N \l__gradbars_csv_state_int
    % states: 0=unquoted, 1=quoted, 2=closing quote, 3=trailing space.
    \str_map_inline:nn {#1}
      {
        \int_case:nn {\l__gradbars_csv_state_int}
          {
            {0}{\str_case:nnF {##1}
              {{,}{\gradbarsCSVField}
               {"}{\tl_if_blank:VTF \l__gradbars_csv_field_tl
                    {\tl_clear:N \l__gradbars_csv_field_tl\int_set:Nn \l__gradbars_csv_state_int {1}}
                    {\gradbarsFail{Quote~inside~unquoted~CSV~field}}}}
              {\tl_put_right:Nn \l__gradbars_csv_field_tl {##1}}}
            {1}{\str_if_eq:nnTF {##1}{"}
              {\int_set:Nn \l__gradbars_csv_state_int {2}}
              {\tl_put_right:Nn \l__gradbars_csv_field_tl {##1}}}
            {2}{\str_case:nnF {##1}
              {{"}{\tl_put_right:Nn \l__gradbars_csv_field_tl {"}\int_set:Nn \l__gradbars_csv_state_int {1}}
               {,}{\gradbarsCSVField}}
              {\str_if_eq:nnTF {##1}{~}
                {\int_set:Nn \l__gradbars_csv_state_int {3}}
                {\gradbarsFail{Unexpected~text~after~CSV~quote}}}}
            {3}{\str_if_eq:nnTF {##1}{,}{\gradbarsCSVField}
              {\str_if_eq:nnF {##1}{~}{\gradbarsFail{Unexpected~text~after~CSV~quote}}}}
          }
      }
    \int_compare:nNnT {\l__gradbars_csv_state_int} = {1}
      {\gradbarsFail{Unclosed~CSV~quote;~multiline~fields~are~not~supported}}
    \gradbarsCSVField
  }
\cs_new_protected:Npn \gradbarsCSVLine #1
  {
    \tl_set:Nn \l__gradbars_csv_line_tl {#1}
    % pdfTeX reads UTF-8 as bytes; Unicode engines read a single BOM character.
    \sys_if_engine_pdftex:TF
      {\regex_replace_once:nnN {\A\x{EF}\x{BB}\x{BF}} {} \l__gradbars_csv_line_tl}
      {\regex_replace_once:nnN {\A\x{FEFF}} {} \l__gradbars_csv_line_tl}
    \tl_if_blank:VF \l__gradbars_csv_line_tl
      {
        \exp_args:NV \gradbarsCSVParse \l__gradbars_csv_line_tl
        \int_compare:nNnTF {\l__gradbars_csv_row_int} = {0}
          {
            \int_set:Nn \l__gradbars_csv_width_int {\seq_count:N \l__gradbars_csv_fields_seq}
            \int_zero:N \l__gradbars_csv_col_int
            \seq_map_inline:Nn \l__gradbars_csv_fields_seq
              {\int_incr:N \l__gradbars_csv_col_int
               \tl_if_blank:nT {##1}{\gradbarsFail{CSV~header~must~not~be~empty}}
               \prop_if_in:NnT \l__gradbars_csv_prop {col/##1}{\gradbarsFail{Duplicate~CSV~header:~##1}}
               \prop_put:Nnx \l__gradbars_csv_prop {col/##1}{\int_use:N \l__gradbars_csv_col_int}}
          }
          {
            \int_compare:nNnF {\seq_count:N \l__gradbars_csv_fields_seq} = {\l__gradbars_csv_width_int}
              {\gradbarsFail{CSV~row~has~wrong~field~count}}
            \int_zero:N \l__gradbars_csv_col_int
            \seq_map_inline:Nn \l__gradbars_csv_fields_seq
              {\int_incr:N \l__gradbars_csv_col_int
               \prop_put:Nxn \l__gradbars_csv_prop
                 {row/\int_use:N \l__gradbars_csv_row_int/\int_use:N \l__gradbars_csv_col_int}{##1}}
          }
        \int_incr:N \l__gradbars_csv_row_int
      }
  }
\NewDocumentCommand\gradbarsloadcsv{O{} m m}
  {
    \gradbars@validtrue
    \regex_match:nnF {\A[A-Za-z][A-Za-z0-9_\-]*\Z}{#2}
      {\gradbarsFail{CSV~dataset~name~must~start~with~an~ASCII~letter}}
    \clist_set:Nn \l__gradbars_csv_missing_clist {NA,N/A,null}
    \keys_set:nn {gradbars/csv}{#1}
    \prop_clear:N \l__gradbars_csv_prop
    \int_zero:N \l__gradbars_csv_row_int
    \file_if_exist:nTF {#3}
      {\ior_open:Nn \l__gradbars_csv_ior {#3}
       \ior_str_map_inline:Nn \l__gradbars_csv_ior {\gradbarsCSVLine{##1}}
       \ior_close:N \l__gradbars_csv_ior}
      {\gradbarsFail{CSV~file~not~found:~#3}}
    \int_compare:nNnF {\l__gradbars_csv_row_int} > {0}{\gradbarsFail{CSV~requires~a~header}}
    \ifgradbars@valid
      \prop_put:Nnx \l__gradbars_csv_prop {rows}{\int_eval:n {\l__gradbars_csv_row_int-1}}
      \prop_put:NnV \l__gradbars_csv_prop {missing}\l__gradbars_csv_missing_clist
      \prop_if_exist:cF {l__gradbars_csv_#2_prop}{\prop_new:c {l__gradbars_csv_#2_prop}}
      \prop_set_eq:cN {l__gradbars_csv_#2_prop}\l__gradbars_csv_prop
    \fi
  }
\cs_new_protected:Npn \gradbarsCSVColumn #1#2
  {
    \gradbars@validtrue
    \prop_if_exist:cTF {l__gradbars_csv_#1_prop}
      {\prop_if_in:cxTF {l__gradbars_csv_#1_prop}{col/\tl_to_str:n {#2}}
        {\prop_get:cxN {l__gradbars_csv_#1_prop}{col/\tl_to_str:n {#2}}\l_tmpa_tl
         \int_set:Nn \l__gradbars_csv_col_int {\l_tmpa_tl}}
        {\gradbarsFail{Unknown~CSV~column:~#2}}}
      {\gradbarsFail{Unknown~CSV~dataset:~#1}}
  }
\cs_new_protected:Npn \gradbarsCSVGet #1#2#3
  {
    \gradbarsCSVColumn{#1}{#3}
    \regex_match:nnF {\A[1-9][0-9]*\Z}{#2}{\gradbarsFail{CSV~row~must~be~a~positive~integer}}
    \ifgradbars@valid
      \prop_if_in:cxTF {l__gradbars_csv_#1_prop}{row/#2/\int_use:N \l__gradbars_csv_col_int}
        {\prop_get:cxN {l__gradbars_csv_#1_prop}{row/#2/\int_use:N \l__gradbars_csv_col_int}\l__gradbars_csv_value_tl}
        {\gradbarsFail{CSV~row~out~of~range}}
    \fi
  }
\cs_new_protected:Npn \gradbarsCSVNumber #1
  {
    \prop_get:cnN {l__gradbars_csv_#1_prop}{missing}\l_tmpa_tl
    \clist_set:NV \l__gradbars_csv_missing_clist \l_tmpa_tl
    \bool_set_false:N \l_tmpa_bool
    \tl_if_blank:VT \l__gradbars_csv_value_tl {\bool_set_true:N \l_tmpa_bool}
    \clist_map_inline:Nn \l__gradbars_csv_missing_clist
      {\str_if_eq:eeT {\l__gradbars_csv_value_tl}{\tl_to_str:n {##1}}{\bool_set_true:N \l_tmpa_bool}}
    \bool_if:NTF \l_tmpa_bool
      {\tl_set:Nn \l__gradbars_csv_value_tl {NA}}
      {\gradbarsCheckNumber{\l__gradbars_csv_value_tl}{CSV~numeric~cell}}
  }
\NewDocumentCommand\gradbarscsvcell{m m m}
  {\group_begin:\gradbarsCSVGet{#1}{#2}{#3}
   \ifgradbars@valid\tl_use:N \l__gradbars_csv_value_tl\fi\group_end:}
\NewDocumentCommand\gradbarcsv{O{} m m m}
  {\group_begin:\gradbarsCSVGet{#2}{#3}{#4}
   \ifgradbars@valid\gradbarsCSVNumber{#2}
     \ifgradbars@valid\gradbar[#1]{\l__gradbars_csv_value_tl}\fi\fi\group_end:}
\cs_new_protected:Npn \gradbarsCSVRange #1#2
  {
    \gradbarsCSVColumn{#1}{#2}
    \fp_zero:N \l__gradbars_csv_min_fp
    \fp_zero:N \l__gradbars_csv_max_fp
    \ifgradbars@valid
      \int_step_inline:nn {\prop_item:cn {l__gradbars_csv_#1_prop}{rows}}
        {\prop_get:cxN {l__gradbars_csv_#1_prop}{row/##1/\int_use:N \l__gradbars_csv_col_int}\l__gradbars_csv_value_tl
         \gradbarsCSVNumber{#1}
         \ifgradbars@valid
           \str_if_eq:VnF \l__gradbars_csv_value_tl {NA}
             {\fp_set:Nn \l__gradbars_csv_min_fp {min(\l__gradbars_csv_min_fp,\l__gradbars_csv_value_tl)}
              \fp_set:Nn \l__gradbars_csv_max_fp {max(\l__gradbars_csv_max_fp,\l__gradbars_csv_value_tl)}}
         \fi}
      % Keep a positive upper limit even for all-zero/all-missing/negative columns.
      \fp_compare:nNnT {\l__gradbars_csv_max_fp} = {0}{\fp_set:Nn \l__gradbars_csv_max_fp {1}}
    \fi
  }
\NewDocumentCommand\gradbarscsvstyle{m m m}
  {\gradbarsCSVRange{#2}{#3}
   \ifgradbars@valid
     \cs_set:cpx {gradbars@style@#1}
       {min=\fp_to_decimal:N \l__gradbars_csv_min_fp,max=\fp_to_decimal:N \l__gradbars_csv_max_fp}
   \fi}
\NewDocumentCommand\gradbarscsvtable{O{} m m m}
  {
    \group_begin:
    \gradbarsCSVRange{#2}{#4}
    \ifgradbars@valid
      \tl_set:Nx \l__gradbars_csv_table_tl
        {\exp_not:N \gradbarssetup{min=\fp_to_decimal:N \l__gradbars_csv_min_fp,
          max=\fp_to_decimal:N \l__gradbars_csv_max_fp}
         \exp_not:N \begin{tabular}{@{}ll@{}}\exp_not:N \hline
         \tl_to_str:n {#3}&\tl_to_str:n {#4}\exp_not:N \\\exp_not:N \hline}
      \int_step_inline:nn {\prop_item:cn {l__gradbars_csv_#2_prop}{rows}}
        {\gradbarsCSVGet{#2}{##1}{#3}
         \ifgradbars@valid
           \tl_put_right:Nx \l__gradbars_csv_table_tl
             {\exp_not:V \l__gradbars_csv_value_tl &
              \exp_not:N \gradbarcsv[\exp_not:n {#1}]{#2}{##1}{\exp_not:n {#4}}\exp_not:N \\}
         \fi}
      \tl_put_right:Nn \l__gradbars_csv_table_tl {\hline\end{tabular}}
      \ifgradbars@valid\tl_use:N \l__gradbars_csv_table_tl\fi
    \fi
    \group_end:
  }
\ExplSyntaxOff

% Compact comparison shapes and reusable visual presets, v0.0.2.
\newif\ifgradbars@compare
\newif\ifgradbars@rangemode
\newif\ifgradbars@referencemissing
\newif\ifgradbars@rangemissing
\newif\ifgradbars@outline
\def\gradbars@shape{bar}
\def\gradbars@reference{0}
\def\gradbars@comparecolor{black!20}
\def\gradbars@compareratio{.45}
\def\gradbars@rangelo{0}
\def\gradbars@rangehi{0}
\def\gradbars@rangepoint{}
\def\gradbars@markersize{2pt}
\def\gradbars@stemwidth{.6pt}
\def\gradbars@outlinewidth{.4pt}
\def\gradbars@rowpadding{0pt}
\pgfkeys{/gradbars,
  shape/.is choice,
  shape/bar/.code=\def\gradbars@shape{bar},
  shape/lollipop/.code=\def\gradbars@shape{lollipop},
  compare color/.store in=\gradbars@comparecolor,
  compare ratio/.store in=\gradbars@compareratio,
  range point/.store in=\gradbars@rangepoint,
  marker size/.store in=\gradbars@markersize,
  stem width/.store in=\gradbars@stemwidth,
  outline/.is if=gradbars@outline,outline/.default=true,
  outline width/.store in=\gradbars@outlinewidth,
  row padding/.store in=\gradbars@rowpadding,
  preset/.is choice,
  preset/paper/.style={theme=solid,shape=bar,outline=false,
    height=5pt,rounded=0pt,track color=black!4,label=outside,
    font=\small,label gap=.35em,row padding=.5pt,pattern=none,stack patterns={none},print labels=false},
  preset/report/.style={theme=solid,shape=bar,outline=false,
    height=12pt,rounded=3pt,track color=black!6,label=outside,
    font=\normalsize,label gap=.6em,row padding=2pt,target color=gradbarsInk,
    threshold colors={lightred,gold,lightgreen},pattern=none,stack patterns={none},print labels=false},
  preset/presentation/.style={theme=solid,shape=bar,outline=false,
    height=18pt,rounded=2pt,label=auto,text color=auto,
    font=\large,label gap=.7em,row padding=4pt,track color=black!10,
    pattern=none,stack patterns={none},print labels=false},
  preset/outline/.style={theme=gray,shape=bar,outline=true,
    height=7pt,rounded=0pt,track color=white,left color=black!80,
    compare color=black!35,label=outside,font=\small,
    outline width=.35pt,stem width=.4pt,label gap=.4em,row padding=1pt,
    pattern=none,stack patterns={none},print labels=false}
}
\def\gradbarsResolveShapeLengths{%
  \begingroup\font@name
  \edef\gradbars@next{\endgroup
    \def\noexpand\gradbars@markersize{\the\dimexpr\gradbars@markersize\relax}%
    \def\noexpand\gradbars@stemwidth{\the\dimexpr\gradbars@stemwidth\relax}%
    \def\noexpand\gradbars@outlinewidth{\the\dimexpr\gradbars@outlinewidth\relax}%
    \def\noexpand\gradbars@rowpadding{\the\dimexpr\gradbars@rowpadding\relax}}%
  \gradbars@next}
\NewDocumentCommand\gradcompare{O{} m m}{%
  \begingroup\gradbars@comparetrue
    \def\gradbars@reference{#2}%
    \gradbar[shape=bar,#1]{#3}%
  \endgroup}
\NewDocumentCommand\gradrange{O{} m m}{%
  \begingroup\gradbars@rangemodetrue
    \def\gradbars@rangelo{#2}\def\gradbars@rangehi{#3}%
    \gradbar[shape=bar,#1]{#3}%
  \endgroup}
\ExplSyntaxOn
\cs_new_eq:NN \gradbarsValidateBeforeShapes \gradbarsValidateExtras
\cs_set_protected:Npn \gradbarsValidateExtras
  {
    \gradbarsValidateBeforeShapes
    \clist_map_inline:nn {markersize,stemwidth,outlinewidth}
      {\dim_compare:nNnF {\use:c {gradbars@##1}} > {0pt}
        {\gradbarsFail{Marker~and~line~sizes~must~be~positive}}}
    \dim_compare:nNnT {\gradbars@rowpadding} < {0pt}
      {\gradbarsFail{Row~padding~must~be~nonnegative}}
    \ifgradbars@stack
      \str_if_eq:eeF {\gradbars@shape}{bar}{\gradbarsFail{Stacked~bars~require~shape=bar}}
    \fi
    \ifgradbars@compare
      \str_case_e:nnF {\gradbars@shape}{{bar}{}{dumbbell}{}}
        {\gradbarsFail{Comparison~requires~shape=bar~or~dumbbell}}
      \gradbarsCheckNumber{\gradbars@compareratio}{compare~ratio}
      \gradbars@referencemissingfalse
      \tl_set:Nx \l_tmpa_tl {\gradbars@reference}\tl_trim_spaces:N \l_tmpa_tl
      \tl_if_empty:NT \l_tmpa_tl {\gradbars@referencemissingtrue}
      \str_if_eq:VnT \l_tmpa_tl {NA}{\gradbars@referencemissingtrue}
      \ifgradbars@referencemissing\else\gradbarsCheckNumber{\gradbars@reference}{reference}\fi
      \ifgradbars@valid
        \fp_compare:nF {0<\gradbars@compareratio<1}{\gradbarsFail{Compare~ratio~must~lie~strictly~between~0~and~1}}
        \ifgradbars@referencemissing\else
          \fp_compare:nNnT {\gradbars@min} = {0}
            {\fp_compare:nNnT {\gradbars@reference} < {0}
              {\gradbarsFail{Negative~reference~requires~a~negative~min}}}
          \gradbarsRange{Reference}{\gradbars@reference}
        \fi
      \fi
    \fi
    \ifgradbars@rangemode
      \str_if_eq:eeF {\gradbars@shape}{bar}{\gradbarsFail{Range~requires~shape=bar}}
      \ifgradbars@error\gradbarsFail{Range~bars~do~not~accept~error~whiskers}\fi
      \clist_if_empty:NF \l__gradbars_thresholds_clist
        {\gradbarsFail{Range~bars~do~not~accept~thresholds}}
      \gradbars@rangemissingfalse
      \tl_set:Nx \l_tmpa_tl {\gradbars@rangelo}\tl_trim_spaces:N \l_tmpa_tl
      \tl_if_empty:NT \l_tmpa_tl {\gradbars@rangemissingtrue}
      \str_if_eq:VnT \l_tmpa_tl {NA}{\gradbars@rangemissingtrue}
      \ifgradbars@rangemissing
        \ifgradbars@missing\else\gradbarsFail{Both~range~endpoints~must~be~present~or~missing}\fi
      \else
        \ifgradbars@missing\gradbarsFail{Both~range~endpoints~must~be~present~or~missing}\fi
        \gradbarsCheckNumber{\gradbars@rangelo}{range~lower~endpoint}
      \fi
      \tl_if_empty:oF {\gradbars@rangepoint}{\gradbarsCheckNumber{\gradbars@rangepoint}{range~point}}
      \ifgradbars@valid
        \ifgradbars@missing
          \tl_if_empty:oF {\gradbars@rangepoint}{\gradbarsFail{Missing~range~cannot~have~a~point}}
        \else
          \fp_compare:nNnT {\gradbars@rangelo} > {\gradbars@rangehi}
            {\gradbarsFail{Range~lower~endpoint~must~not~exceed~upper}}
          \fp_compare:nNnT {\gradbars@min} = {0}
            {\fp_compare:nNnT {\gradbars@rangelo} < {0}
              {\gradbarsFail{Negative~range~requires~a~negative~min}}}
          \gradbarsRange{Range~lower~endpoint}{\gradbars@rangelo}
          \tl_if_empty:oF {\gradbars@rangepoint}
            {\fp_compare:nF {\gradbars@rangelo<=\gradbars@rangepoint<=\gradbars@rangehi}
              {\gradbarsFail{Range~point~must~lie~between~endpoints}}}
          \cs_set:Npx \gradbars@start {\gradbarsPosition{\gradbars@rangelo}}
          \cs_set:Npx \gradbars@stop {\gradbarsPosition{\gradbars@rangehi}}
        \fi
      \fi
    \else
      \tl_if_empty:oF {\gradbars@rangepoint}{\gradbarsFail{Range~point~requires~gradrange}}
    \fi
  }
% Format each endpoint independently using the ordinary number formatter.
\cs_new_eq:NN \gradbarsSingleLabel \gradbars@labelcontent
\cs_new_protected:Npn \gradbarsFormatEndpoint #1
  {\group_begin:\gradbars@missingfalse\def\gradbars@text{}
   \cs_set:Npx \gradbars@number {\fp_eval:n {#1}}
   \str_if_eq:eeT {\gradbars@format}{percent}
     {\cs_set:Npx \gradbars@number {\fp_eval:n {100*(#1)/\gradbars@max}}}
   \gradbarsSingleLabel\group_end:}
\cs_set_protected:Npn \gradbars@labelcontent
  {
    \tl_if_empty:oTF {\gradbars@text}
      {\ifgradbars@compare
         \gradbarsSingleLabel\,/\,
         \ifgradbars@referencemissing\gradbars@missingtext
         \else\gradbarsFormatEndpoint{\gradbars@reference}\fi
       \else\ifgradbars@rangemode
         \ifgradbars@missing\gradbars@missingtext
         \else[\gradbarsFormatEndpoint{\gradbars@rangelo},\,
               \gradbarsFormatEndpoint{\gradbars@rangehi}]\fi
       \else\gradbarsSingleLabel\fi\fi}
      {\gradbars@text}
  }
\cs_new_protected:Npn \gradbarsDrawTrack
  {
    \str_if_eq:eeTF {\gradbars@shape}{lollipop}
      {\draw[draw=\gradbars@track,line~width=\gradbars@stemwidth]
        (0,.5*\gradbars@height)--(\gradbars@width,.5*\gradbars@height);}
      {\ifgradbars@outline
        \path[rounded~corners=\gradbars@radius,fill=\gradbars@track,
          draw=gradbarsInk!35,line~width=\gradbars@outlinewidth]
          (0,0) rectangle (\gradbars@width,\gradbars@height);
       \else
        \path[rounded~corners=\gradbars@radius,fill=\gradbars@track,draw=none]
          (0,0) rectangle (\gradbars@width,\gradbars@height);
       \fi}
  }
\cs_new_protected:Npn \gradbarsDrawDot #1
  {
    \pgfmathsetlengthmacro\gradbars@dotradius{min(\gradbars@markersize,.5*\gradbars@height)}
    \ifgradbars@outline
      \path[fill=white,draw=\gradbars@left,line~width=\gradbars@stemwidth]
        (#1,.5*\gradbars@height) circle[radius=\gradbars@dotradius];
    \else
      \path[fill=\gradbars@left,draw=none]
        (#1,.5*\gradbars@height) circle[radius=\gradbars@dotradius];
    \fi
  }
\cs_new_protected:Npn \gradbarsDrawShape
  {
    \str_if_eq:eeTF {\gradbars@shape}{lollipop}
      {\ifgradbars@missing\else
         \draw[draw=\gradbars@left,line~width=\gradbars@stemwidth]
           (\gradbars@xzero,.5*\gradbars@height)--(\gradbars@xend,.5*\gradbars@height);
         \gradbarsDrawDot{\gradbars@xend}
       \fi}
      {
        \ifgradbars@compare
          \ifgradbars@referencemissing\else
            \pgfmathsetlengthmacro\gradbars@xref{\gradbarsPosition{\gradbars@reference}*(\gradbars@width)}
            \ifgradbars@outline
              \draw[draw=\gradbars@comparecolor,line~width=\gradbars@outlinewidth]
                (\gradbars@xzero,0) rectangle (\gradbars@xref,\gradbars@height);
            \else
              \path[rounded~corners=\gradbars@radius,fill=\gradbars@comparecolor,draw=none]
                (\gradbars@xzero,0) rectangle (\gradbars@xref,\gradbars@height);
            \fi
          \fi
          \begin{scope}[yshift={.5*(1-\gradbars@compareratio)*\gradbars@height}]
            \pgfmathsetlengthmacro\gradbars@height{\gradbars@compareratio*\gradbars@height}
            \pgfmathsetlengthmacro\gradbars@radius{min(\gradbars@radius,.5*\gradbars@height)}
            \gradbarsDrawBarBody
          \end{scope}
        \else
          \gradbarsDrawBarBody
          \ifgradbars@rangemode\ifgradbars@missing\else
            \draw[draw=\gradbars@left,line~width=\gradbars@stemwidth]
              (\gradbars@xstart,0)--(\gradbars@xstart,\gradbars@height)
              (\gradbars@xstop,0)--(\gradbars@xstop,\gradbars@height);
            \tl_if_empty:oF {\gradbars@rangepoint}
              {\pgfmathsetlengthmacro\gradbars@xpoint{\gradbarsPosition{\gradbars@rangepoint}*(\gradbars@width)}
               \pgfmathsetlengthmacro\gradbars@dotradius{min(\gradbars@markersize,.5*\gradbars@height)}
               \path[fill=white,draw=gradbarsInk,line~width=\gradbars@stemwidth]
                 (\gradbars@xpoint,.5*\gradbars@height) circle[radius=\gradbars@dotradius];}
          \fi\fi
        \fi
      }
  }
\ExplSyntaxOff
\def\gradbarsDrawBarBody{%
  \ifdim\gradbars@xstop>\gradbars@xstart\relax
    \pgfmathsetlengthmacro\gradbars@fillradius{min(\gradbars@radius,.5*(\gradbars@xstop-\gradbars@xstart))}%
    \begin{scope}
      \clip[rounded corners=\gradbars@radius]
        (0,0) rectangle (\gradbars@width,\gradbars@height);
      \ifgradbars@stack
        \clip[rounded corners=\gradbars@fillradius]
          (\gradbars@xstart,0) rectangle (\gradbars@xstop,\gradbars@height);
        \gradbarsDrawStack
      \else\ifgradbars@outline
        \draw[rounded corners=\gradbars@fillradius,draw=\gradbars@left,line width=\gradbars@outlinewidth]
          (\gradbars@xstart,0) rectangle (\gradbars@xstop,\gradbars@height);
      \else\ifgradbars@gradient
        \path[rounded corners=\gradbars@fillradius,shade,
          left color=\gradbars@left,right color=\gradbars@right]
          (\gradbars@xstart,0) rectangle (\gradbars@xstop,\gradbars@height);
      \else
        \path[rounded corners=\gradbars@fillradius,fill=\gradbars@left,draw=none]
          (\gradbars@xstart,0) rectangle (\gradbars@xstop,\gradbars@height);
      \fi\fi\fi
      \ifgradbars@stack\else\ifgradbars@outline\else
        \clip[rounded corners=\gradbars@fillradius]
          (\gradbars@xstart,0) rectangle (\gradbars@xstop,\gradbars@height);
        \gradbarsPatternRect{0}{\gradbars@xstart}{0}{\gradbars@xstop}{\gradbars@height}
      \fi
      \fi
    \end{scope}
  \fi
}

% v0.0.3: comparison, signed compositions, series and semantic palettes.
\definecolor{gradbarsOrange}{HTML}{D55E00}
\definecolor{gradbarsPurple}{HTML}{AA4499}
\definecolor{gradbarsCyan}{HTML}{56B4E9}
\definecolor{gradbarsGreen}{HTML}{009E73}
\def\gradbars@comparelabel{values}
\def\gradbars@stacktotals{net}
\def\gradbars@qualitytarget{}
\def\gradbars@qualityrange{}
\def\gradbars@sparkrange{auto}
\def\gradbars@sparkpoints{extrema}
\def\gradbars@sparkhigh{gradbarsTeal}
\def\gradbars@sparklow{gradbarsOrange}
\newif\ifgradbars@spark
\pgfkeys{/gradbars,
  shape/dumbbell/.code=\def\gradbars@shape{dumbbell},
  shape/sparkline/.code=\def\gradbars@shape{sparkline},
  compare label/.is choice,
  compare label/values/.code=\def\gradbars@comparelabel{values},
  compare label/delta/.code=\def\gradbars@comparelabel{delta},
  stack totals/.is choice,
  stack totals/net/.code=\def\gradbars@stacktotals{net},
  stack totals/separate/.code=\def\gradbars@stacktotals{separate},
  better/target/.code=\def\gradbars@better{target},
  better/interval/.code=\def\gradbars@better{interval},
  quality target/.store in=\gradbars@qualitytarget,
  quality range/.store in=\gradbars@qualityrange,
  spark range/.is choice,
  spark range/auto/.code=\def\gradbars@sparkrange{auto},
  spark range/fixed/.code=\def\gradbars@sparkrange{fixed},
  spark points/.is choice,
  spark points/extrema/.code=\def\gradbars@sparkpoints{extrema},
  spark points/all/.code=\def\gradbars@sparkpoints{all},
  spark points/last/.code=\def\gradbars@sparkpoints{last},
  spark points/none/.code=\def\gradbars@sparkpoints{none},
  spark high color/.store in=\gradbars@sparkhigh,
  spark low color/.store in=\gradbars@sparklow,
  palette/.is choice,
  palette/categorical/.style={stack colors={gradbarsBlue,gradbarsOrange,gradbarsGreen,gradbarsPurple,gradbarsCyan,gold},
    stack patterns={none},compare color=gradbarsOrange,negative color=gradbarsOrange},
  palette/sequential/.style={stack colors={gradbarsBlue!20,gradbarsBlue!40,gradbarsBlue!60,gradbarsBlue!80,gradbarsBlue},
    stack patterns={none}},
  palette/diverging/.style={stack colors={gradbarsOrange!85,gradbarsOrange!40,black!12,gradbarsTeal!40,gradbarsTeal!85},
    threshold colors={gradbarsOrange,gold,gradbarsTeal},negative color=gradbarsOrange,
    compare color=gradbarsOrange,left color=gradbarsTeal,right color=gradbarsTeal!35,stack patterns={none}},
  palette/mono/.style={print,stack colors={white,black!10,black!20},
    stack patterns={diagonal,dots,crosshatch},compare color=black!45,
    spark high color=black,spark low color=black},
}
\NewDocumentCommand\graddumbbell{O{} m m}{%
  \gradcompare[#1,shape=dumbbell]{#2}{#3}}
\ExplSyntaxOn
\fp_new:N \l__gradbars_positive_fp
\fp_new:N \l__gradbars_negative_fp
\cs_new_protected:Npn \gradbarsStackReset
  {\fp_zero:N \l__gradbars_positive_fp\fp_zero:N \l__gradbars_negative_fp}
\cs_new_protected:Npn \gradbarsStackStep #1
  {
    \fp_compare:nNnTF {#1} < {0}
      {\fp_set_eq:NN \l__gradbars_sum_fp \l__gradbars_negative_fp
       \fp_add:Nn \l__gradbars_negative_fp {#1}
       \fp_set_eq:NN \l__gradbars_before_fp \l__gradbars_negative_fp}
      {\fp_set_eq:NN \l__gradbars_before_fp \l__gradbars_positive_fp
       \fp_add:Nn \l__gradbars_positive_fp {#1}
       \fp_set_eq:NN \l__gradbars_sum_fp \l__gradbars_positive_fp}
  }
% Category identity is scoped and keyed by literal names, independent of order.
\prop_new:N \l__gradbars_category_colors_prop
\prop_new:N \l__gradbars_category_patterns_prop
\NewDocumentCommand\gradbarscategory{m m m}
  {
    \tl_if_blank:nTF {#1}{\gradbarsFail{Category~name~must~not~be~empty}}
      {\prop_if_in:NnTF \c__gradbars_patterns_prop {#3}
        {\prop_put:Nnn \l__gradbars_category_colors_prop {#1}{#2}
         \prop_put:Nnn \l__gradbars_category_patterns_prop {#1}{#3}}
        {\gradbarsFail{Unknown~category~pattern}}}
  }
\cs_new_protected:Npn \gradbarsCategoryColor #1
  {
    \cs_set:Npx \gradbars@segmentcolor
      {\clist_item:Nn \l__gradbars_stackcolors_clist
        {1+\int_mod:nn {#1-1}{\clist_count:N \l__gradbars_stackcolors_clist}}}
    \tl_set:Nx \l_tmpa_tl {\clist_item:Nn \l__gradbars_names_clist {#1}}
    \ifgradbars@print\else
      \prop_get:NVNT \l__gradbars_category_colors_prop \l_tmpa_tl \l_tmpb_tl
        {\cs_set:Npx \gradbars@segmentcolor {\exp_not:V \l_tmpb_tl}}
    \fi
  }
\cs_new_protected:Npn \gradbarsCategoryPattern #1
  {
    \int_compare:nNnT {#1} > {0}
      {\tl_set:Nx \l_tmpa_tl {\clist_item:Nn \l__gradbars_names_clist {#1}}
       \prop_get:NVNT \l__gradbars_category_patterns_prop \l_tmpa_tl \l_tmpb_tl
         {\tl_set_eq:NN \l__gradbars_pattern_tl \l_tmpb_tl}}
  }
\fp_new:N \l__gradbars_distance_fp
\clist_new:N \l__gradbars_quality_clist
\cs_new_protected:Npn \gradbarsQuality
  {
    \str_case_e:nn {\gradbars@better}
      {{target}{\gradbarsCheckNumber{\gradbars@qualitytarget}{quality~target}}
       {interval}{\clist_set:Nx \l__gradbars_quality_clist {\gradbars@qualityrange}
         \tl_set:Nx \l_tmpa_tl {\gradbars@qualityrange}
         \regex_match:nVT {\A\s*,|,\s*,|,\s*\Z} \l_tmpa_tl
           {\gradbarsFail{Quality~range~requires~two~endpoints}}
         \int_compare:nNnF {\clist_count:N \l__gradbars_quality_clist} = {2}
           {\gradbarsFail{Quality~range~requires~two~endpoints}}
         \clist_map_inline:Nn \l__gradbars_quality_clist
           {\gradbarsCheckNumber{##1}{quality~endpoint}}}}
    \clist_if_empty:NT \l__gradbars_thresholds_clist
      {\gradbarsFail{Target~and~interval~quality~require~distance~thresholds}}
    \ifgradbars@stack\gradbarsFail{Stack~does~not~support~quality~evaluation}\fi
    \ifgradbars@rangemode\gradbarsFail{Range~does~not~support~quality~evaluation}\fi
    \ifgradbars@valid
      \fp_compare:nNnT {\clist_item:Nn \l__gradbars_thresholds_clist {1}} < {0}
        {\gradbarsFail{Distance~thresholds~must~be~nonnegative}}
      \str_if_eq:eeTF {\gradbars@better}{target}
        {\fp_set:Nn \l__gradbars_distance_fp {abs(\gradbars@value-\gradbars@qualitytarget)}}
        {\fp_compare:nNnT {\clist_item:Nn \l__gradbars_quality_clist {1}}
           > {\clist_item:Nn \l__gradbars_quality_clist {2}}
           {\gradbarsFail{Quality~range~must~be~ordered}}
         \fp_set:Nn \l__gradbars_distance_fp
           {max(0,\clist_item:Nn \l__gradbars_quality_clist {1}-\gradbars@value,
             \gradbars@value-\clist_item:Nn \l__gradbars_quality_clist {2})}}
      \int_set:Nn \l_tmpa_int {1}
      \fp_compare:nT {\l__gradbars_distance_fp <= \clist_item:Nn \l__gradbars_thresholds_clist {2}}
        {\int_set:Nn \l_tmpa_int {2}}
      \fp_compare:nT {\l__gradbars_distance_fp <= \clist_item:Nn \l__gradbars_thresholds_clist {1}}
        {\int_set:Nn \l_tmpa_int {3}}
      \cs_set:Npx \gradbars@left {\clist_item:Nn \l__gradbars_colors_clist {\l_tmpa_int}}
      \gradbars@gradientfalse
    \fi
  }
\cs_new_eq:NN \gradbarsValidateBeforeThree \gradbarsValidateExtras
\cs_set_protected:Npn \gradbarsValidateExtras
  {
    \gradbarsValidateBeforeThree
    \str_case_e:nn {\gradbars@better}{{target}{\gradbarsQuality}{interval}{\gradbarsQuality}}
    \str_if_eq:eeT {\gradbars@shape}{dumbbell}
      {\ifgradbars@compare\else\gradbarsFail{Dumbbell~requires~graddumbbell}\fi}
    \str_if_eq:eeT {\gradbars@shape}{sparkline}
      {\ifgradbars@spark\else\gradbarsFail{Sparkline~requires~gradspark}\fi}
    \ifgradbars@spark
      \str_if_eq:eeF {\gradbars@shape}{sparkline}{\gradbarsFail{Spark~requires~shape=sparkline}}
      \ifgradbars@error\gradbarsFail{Spark~does~not~support~error~whiskers}\fi
      \tl_if_empty:oF {\gradbars@target}{\gradbarsFail{Spark~does~not~support~bar~targets}}
      \tl_if_empty:oF {\gradbars@band}{\gradbarsFail{Spark~does~not~support~bar~bands}}
      \tl_if_empty:oF {\gradbars@thresholds}{\gradbarsFail{Spark~does~not~support~thresholds}}
      \gradbarsValidateSpark
    \fi
  }
\cs_new_eq:NN \gradbarsLabelBeforeThree \gradbars@labelcontent
\cs_set_protected:Npn \gradbars@labelcontent
  {
    \tl_if_empty:oTF {\gradbars@text}
      {\ifgradbars@compare
        \str_if_eq:eeTF {\gradbars@comparelabel}{delta}
          {\ifgradbars@missing\gradbars@missingtext\else
             \ifgradbars@referencemissing\gradbars@missingtext\else
               $\Delta$\,\gradbarsFormatEndpoint{\gradbars@value-\gradbars@reference}\fi\fi}
          {\gradbarsLabelBeforeThree}
       \else\ifgradbars@stack
         \str_if_eq:eeTF {\gradbars@stacktotals}{separate}
           {+\gradbarsFormatEndpoint{\gradbars@positive}\,/\,\gradbarsFormatEndpoint{\gradbars@negative}}
           {\gradbarsLabelBeforeThree}
       \else\gradbarsLabelBeforeThree\fi\fi}
      {\gradbars@text}
  }
\cs_new_eq:NN \gradbarsShapeBeforeThree \gradbarsDrawShape
\cs_set_protected:Npn \gradbarsDrawShape
  {
    \str_case_e:nnF {\gradbars@shape}
      {{dumbbell}{\gradbarsDrawDumbbell}{sparkline}{\gradbarsDrawSpark}}
      {\gradbarsShapeBeforeThree}
  }
\cs_new_protected:Npn \gradbarsDrawDumbbell
  {
    \gradbarsMarkerBounds
    \ifgradbars@referencemissing\else
      \pgfmathsetlengthmacro\gradbars@xref{\gradbarsPosition{\gradbars@reference}*\gradbars@width}
      \ifgradbars@missing\else
        \draw[draw=\gradbars@left,line~width=\gradbars@stemwidth]
          (\gradbars@xref,.5*\gradbars@height)--(\gradbars@xend,.5*\gradbars@height);
      \fi
      \group_begin:\let\gradbars@left\gradbars@comparecolor
        \gradbars@outlinetrue\gradbarsDrawDot{\gradbars@xref}\group_end:
    \fi
    \ifgradbars@missing\else\gradbarsDrawDot{\gradbars@xend}\fi
  }
\seq_new:N \l__gradbars_series_seq
\seq_new:N \l__gradbars_series_clean_seq
\fp_new:N \l__gradbars_series_min_fp
\fp_new:N \l__gradbars_series_max_fp
\fp_new:N \l__gradbars_series_low_fp
\fp_new:N \l__gradbars_series_high_fp
\bool_new:N \l__gradbars_series_any_bool
\bool_new:N \l__gradbars_series_prev_bool
\bool_new:N \l__gradbars_series_mark_bool
\int_new:N \l__gradbars_series_index_int
\cs_new_protected:Npn \gradbarsParseSpark #1
  {
    \fp_zero:N \l__gradbars_series_min_fp
    \fp_zero:N \l__gradbars_series_max_fp
    \seq_set_split:Nnn \l__gradbars_series_seq {,}{#1}
    \seq_clear:N \l__gradbars_series_clean_seq
    \bool_set_false:N \l__gradbars_series_any_bool
    \seq_map_inline:Nn \l__gradbars_series_seq
      {\tl_set:Nx \l_tmpa_tl {##1}\tl_trim_spaces:N \l_tmpa_tl
       \tl_if_empty:NT \l_tmpa_tl {\tl_set:Nn \l_tmpa_tl {NA}}
       \str_if_eq:VnF \l_tmpa_tl {NA}
         {\gradbarsCheckNumber{\l_tmpa_tl}{spark~observation}
          \ifgradbars@valid
            \bool_if:NTF \l__gradbars_series_any_bool
              {\fp_set:Nn \l__gradbars_series_min_fp {min(\l__gradbars_series_min_fp,\l_tmpa_tl)}
               \fp_set:Nn \l__gradbars_series_max_fp {max(\l__gradbars_series_max_fp,\l_tmpa_tl)}}
              {\fp_set:Nn \l__gradbars_series_min_fp {\l_tmpa_tl}
               \fp_set:Nn \l__gradbars_series_max_fp {\l_tmpa_tl}
               \bool_set_true:N \l__gradbars_series_any_bool}
          \fi}
       \seq_put_right:NV \l__gradbars_series_clean_seq \l_tmpa_tl}
    \seq_set_eq:NN \l__gradbars_series_seq \l__gradbars_series_clean_seq
    \cs_set:Npx \gradbars@sparklast {\seq_item:Nn \l__gradbars_series_seq {-1}}
  }
\NewDocumentCommand\gradspark{O{} m}
  {\group_begin:\gradbars@validtrue\gradbarsParseSpark{#2}
   \ifgradbars@valid
     \gradbars@sparktrue
     \gradbar[height=4ex,#1,shape=sparkline]{\gradbars@sparklast}
   \fi\group_end:}
\cs_new_protected:Npn \gradbarsValidateSpark
  {
    \str_if_eq:eeTF {\gradbars@sparkrange}{fixed}
      {\fp_set:Nn \l__gradbars_series_low_fp {\gradbars@min}
       \fp_set:Nn \l__gradbars_series_high_fp {\gradbars@max}
       \seq_map_inline:Nn \l__gradbars_series_seq
         {\str_if_eq:nnF {##1}{NA}{\gradbarsRange{Spark~observation}{##1}}}}
      {\fp_set_eq:NN \l__gradbars_series_low_fp \l__gradbars_series_min_fp
       \fp_set_eq:NN \l__gradbars_series_high_fp \l__gradbars_series_max_fp}
  }
\cs_new_protected:Npn \gradbarsDrawSpark
  {
    \gradbarsMarkerBounds
    \bool_set_false:N \l__gradbars_series_prev_bool
    \int_zero:N \l__gradbars_series_index_int
    \seq_map_inline:Nn \l__gradbars_series_seq
      {\int_incr:N \l__gradbars_series_index_int
       \str_if_eq:nnTF {##1}{NA}
         {\bool_set_false:N \l__gradbars_series_prev_bool}
         {\pgfmathsetlengthmacro\gradbars@sparkx
            {\fp_eval:n {\seq_count:N \l__gradbars_series_seq=1 ? .5 :
              (\l__gradbars_series_index_int-1)/max(1,\seq_count:N \l__gradbars_series_seq-1)}*\gradbars@width}
          \fp_compare:nNnTF {\l__gradbars_series_low_fp} = {\l__gradbars_series_high_fp}
            {\pgfmathsetlengthmacro\gradbars@sparky{.5*\gradbars@height}}
            {\pgfmathsetlengthmacro\gradbars@sparky
              {\fp_eval:n {max(0,min(1,(##1-\l__gradbars_series_low_fp)/
                (\l__gradbars_series_high_fp-\l__gradbars_series_low_fp)))}*\gradbars@height}}
          \bool_if:NT \l__gradbars_series_prev_bool
            {\draw[draw=\gradbars@left,line~width=\gradbars@stemwidth]
              (\gradbars@sparkprevx,\gradbars@sparkprevy)--(\gradbars@sparkx,\gradbars@sparky);}
          \bool_set_false:N \l__gradbars_series_mark_bool
          \let\gradbars@pointcolor\gradbars@left
          \str_case_e:nn {\gradbars@sparkpoints}
            {{all}{\bool_set_true:N \l__gradbars_series_mark_bool}
             {extrema}{\fp_compare:nNnT {##1} = {\l__gradbars_series_min_fp}
                {\bool_set_true:N \l__gradbars_series_mark_bool\let\gradbars@pointcolor\gradbars@sparklow}
               \fp_compare:nNnT {##1} = {\l__gradbars_series_max_fp}
                {\bool_set_true:N \l__gradbars_series_mark_bool\let\gradbars@pointcolor\gradbars@sparkhigh}}}
          \str_if_eq:eeF {\gradbars@sparkpoints}{none}
            {\int_compare:nNnT {\l__gradbars_series_index_int} = {\seq_count:N \l__gradbars_series_seq}
               {\bool_set_true:N \l__gradbars_series_mark_bool}}
          \bool_if:NT \l__gradbars_series_mark_bool
            {\path[fill=\gradbars@pointcolor,draw=none]
               (\gradbars@sparkx,\gradbars@sparky) circle[radius=\gradbars@markersize];}
          \let\gradbars@sparkprevx\gradbars@sparkx\let\gradbars@sparkprevy\gradbars@sparky
          \bool_set_true:N \l__gradbars_series_prev_bool}}
  }
% Auto series scaling applies to the y axis; it must not inherit bar clipping.
\cs_new_eq:NN \gradbarsValidateBeforeSpark \gradbarsValidate
\cs_set_protected:Npn \gradbarsValidate
  {
    \ifgradbars@spark
      \str_if_eq:eeT {\gradbars@sparkrange}{auto}
        {\cs_set:Npx \gradbars@min {\fp_eval:n {min(0,\l__gradbars_series_min_fp)}}
         \cs_set:Npx \gradbars@max {\fp_eval:n {max(1,\l__gradbars_series_max_fp)}}
         \str_if_eq:eeT {\gradbars@format}{percent}
           {\gradbarsFail{Auto~spark~range~does~not~support~percent~format}}}
      \str_if_eq:eeF {\gradbars@label}{none}{\def\gradbars@label{outside}}
    \fi
    \let\gradbars@sparkbasecolor\gradbars@left
    \gradbarsValidateBeforeSpark
    \ifgradbars@spark\let\gradbars@left\gradbars@sparkbasecolor\fi
  }
\cs_new_protected:Npn \gradbarsMarkerBounds
  {\path
     (-\gradbars@markersize,-\gradbars@markersize) rectangle
     ([xshift=\gradbars@markersize,yshift=\gradbars@markersize]\gradbars@width,\gradbars@height);}
\cs_new_eq:NN \gradbarsTrackBeforeThree \gradbarsDrawTrack
\cs_set_protected:Npn \gradbarsDrawTrack
  {\group_begin:
   \str_if_eq:eeT {\gradbars@shape}{dumbbell}{\def\gradbars@shape{lollipop}}
   \gradbarsTrackBeforeThree\group_end:}
\ExplSyntaxOff
\endinput
