7

I'm trying to color the cells in a few columns with different colors, depending on bounds passed as an argument. I would like to have one macro that takes two arguments: a .csv file and a comma separated list sub-delimited by forward slashes (/). Each element of the second argument will have two values: a column index and a boundary.

I'd like for the macro to build the table from the .csv and loop through the second argument coloring the cells in the columns, based on the column index and the boundary provided.

For example: the argument 1/0.2, 2/0.5 will color the cells in column 1 above 0.2 and those in column 2 above 0.5 blue and the other cells orange. I almost have it. I have lua code that handles the second argument and creates a list of colors.

My question is: why won't it color the cells in the below structure?

I know there are better ways to do the specific MWE below, such as taking things out of the loops, but I need the loops there, because I'm hoping to pass multiple lists into the second argument of \buildTable, which will then get passed to my lua code creating and passing the list to \ColorColumn.

My idea is: to have one loop that loops through each set of column indices and boundaries and passes it to lua, then passes the produced list to \ColorColumn.

So we don't have to worry about lua, I've included one example of the color list below in the format in which it'll be produced.

I suspect it's just an expansion issue, but I'm not sure.

MWE:

\documentclass{article}
\RequirePackage{etoolbox}
\RequirePackage{pgfplotstable}
\RequirePackage{colortbl}
\RequirePackage{pgfplots}
\RequirePackage{xparse}
\RequirePackage{listofitems}

\begin{filecontents*}{test.csv}
    ColA, ColB, ColC
    0.17, 0.91, 0.67
    0.15, 0.17, 0.92
    0.48, 0.1, 0.28
\end{filecontents*}

\def\assigncolor#1#2{%
    \pgfplotstableset{
        every row \thecount\space column #2/.style={
            postproc cell content/.style={
            @cell content/.add={\cellcolor{#1}}{}%
            },%
        },
    }%
}%

\newcounter{count}%
\def\ColorColumn#1#2{%
    \def\ArgOne{#1}%
    \ifdef{\thecount}{}{\newcounter{count}}%
    \readlist*\mylist{#2}% star option removes surrounding whitespace
    \setcounter{count}{-1}%
    \foreachitem\x\in\mylist[]{%
        \stepcounter{count}%
        \expandafter\assigncolor\expandafter{\x}{\ArgOne}%
    }%
}%

\def\buildTable#1#2{%
    \pgfplotstableread[col sep = comma]{#1}\rawdata%
    \def\selection{}%
    \edef\tempor{%
        columns = {\selection},%
    }%
    \expandafter\xappto\expandafter\parser\expandafter{\tempor}%    
    \foreach \x/\y in {#2}{
        \ColorColumn{\x}{blue, blue, orange}
    }%
    \xdef\theTable{\noexpand\pgfplotstabletypeset[\parser]}%
    \theTable{\rawdata}%
}%

\begin{document}
\begin{table}
    \buildTable
    {%CSV file
        test.csv%
    }
    {%
        1/0.1%
    }%
\end{table}
\end{document}
6
  • \stepcounter is global. I don't know about \assigncolor. Anything local will be lost. May 18, 2018 at 0:36
  • Why is that a problem? May 18, 2018 at 1:08
  • I’ve tried different combinations of \expandafter and \noexpand but nothing seems to be working May 20, 2018 at 16:18
  • I've been looking at this post: tex.stackexchange.com/questions/89628/… Maybe a \ForEachX statement will work better? I've tried it but can't get that working either. From what I've found online, it looks like the \ForEachX needs to be split by something, and mine isn't. It's strange because my MWE doesn't give any errors. It just doesn't work. May 21, 2018 at 13:44
  • It is a problem because you need the colour to be applied later i.e. outside the loop, don't you? So if it is only local, it's gone.
    – cfr
    May 23, 2018 at 2:01

2 Answers 2

7
+50

I'm not sure that I did understand what you are actually trying to do. But instead of \foreach you should use \pgfplotsforeachungrouped. And you should be a bit careful when using \x everywhere. An \x inside a macro or a loop is not automatically local to this macro.

\documentclass{article}
\RequirePackage{etoolbox}
\RequirePackage{pgfplotstable}
\RequirePackage{colortbl}
\RequirePackage{pgfplots}
\RequirePackage{xparse}
\RequirePackage{listofitems}

\begin{filecontents*}{testnew.csv}
    ColA, ColB, ColC
    0.17, 0.91, 0.67
    0.15, 0.17, 0.92
    0.48, 0.1,  0.28
\end{filecontents*}

\def\assigncolor#1#2{%
    \pgfplotstableset{
        every row \thecount\space column #2/.style={
            postproc cell content/.style={
            @cell content/.add={\cellcolor{#1}}{}%
            },%
        },
    }%
}%

\newcounter{count}%
\def\ColorColumn#1#2{%
    \edef\ArgOne{#1}% <----------changed 
    \ifdef{\thecount}{}{\newcounter{count}}%
    \readlist*\mylist{#2}% star option removes surrounding whitespace
    \setcounter{count}{-1}%
    \foreachitem\x\in\mylist[]{%
        \stepcounter{count}%
        \expandafter\assigncolor\expandafter{\x}{\ArgOne}%
    }%
}%

\def\buildTable#1#2{%
    \pgfplotstableread[col sep = comma]{#1}\rawdata%
    \def\selection{}%
    \edef\tempor{%
        columns = {\selection},%
    }%
    \expandafter\xappto\expandafter\parser\expandafter{\tempor}%
    \pgfplotsforeachungrouped \x/\y in {#2}{
        \ColorColumn{\x}{blue, blue, orange}
    }%
    \xdef\theTable{\noexpand\pgfplotstabletypeset[\parser]}%
    \theTable{\rawdata}%
}%

\begin{document}
\begin{table}
    \buildTable
    {%CSV file
        testnew.csv%
    }
    {%
        1/0.1%
    }%
\end{table}
\end{document}

enter image description here

2
  • Why does \pgfplotsforeachungrouped work here where \foreach doesn't? May 23, 2018 at 11:03
  • @KevinGregory Because it doesn't create groups - in groups variable assignments are local unless some specifically global assignment methods are used. May 23, 2018 at 22:01
4

Here is an implementation with expl3. Table rows are stored in a sequence and then each item is split again for building the table body.

The special column can be decided for the single table or set at the outset with

\settable{column=2}

It wouldn't be really difficult to do the treatment to as many columns as you like.

Here's the code:

\begin{filecontents*}{\jobname.csv}
ColA, ColB, ColC
0.17, 0.91, 0.67
0.15, 0.17, 0.92
0.48, 0.1, 0.28
\end{filecontents*}

\documentclass{article}
\usepackage[table,dvipsnames]{xcolor}
\usepackage{xparse}

\ExplSyntaxOn
\ior_new:N \g_kevin_table_readfile_stream
\tl_new:N \l__kevin_table_body_tl
\seq_new:N \l__kevin_table_rows_seq
\seq_new:N \l__kevin_table_singlerow_seq

\keys_define:nn { kevin/table }
 {
  column       .int_set:N = \l__kevin_table_column_int,
  upper-bound  .fp_set:N  = \l__kevin_table_upper_fp,
  lower-bound  .fp_set:N  = \l__kevin_table_lower_fp,
  upper-color  .tl_set:N  = \l__kevin_table_upper_tl,
  lower-color  .tl_set:N  = \l__kevin_table_lower_tl,
  normal-color .tl_set:N  = \l__kevin_table_normal_tl,
 }

\NewDocumentCommand{\settable}{m}
 {
  \kevin_table_set:n { #1 }
 }

\NewDocumentCommand{\buildtable}{O{}m}
 {
  % #1 = options
  % #2 = file name
  \group_begin:
  \settable{#1}
  \kevin_table_build:n { #2 }
  \group_end:
 }

\cs_new_protected:Nn \kevin_table_set:n
 {
  \keys_set:nn { kevin/table } { #1 }
 }

\cs_new_protected:Nn \kevin_table_build:n
 {
  \tl_clear:N \l__kevin_table_body_tl
  \seq_clear:N \l__kevin_table_rows_seq
  \ior_open:Nn \g_kevin_table_readfile_stream { #1 }
  \ior_map_inline:Nn \g_kevin_table_readfile_stream
   {
    \seq_put_right:Nn \l__kevin_table_rows_seq { ##1 }
   }
  % get the number of columns and the header row
  \seq_set_from_clist:Nx \l__kevin_table_singlerow_seq
   {
    \seq_item:Nn \l__kevin_table_rows_seq { 1 }
   }
  \tl_put_right:Nx \l__kevin_table_body_tl
   {
    \seq_use:Nn \l__kevin_table_singlerow_seq { & } \exp_not:N \\
   }
  % process each row
  \int_step_function:nnnN { 2 } { 1 } { \seq_count:N \l__kevin_table_rows_seq } \__kevin_table_process_row:n
  % build the table
  \begin{tabular}{*{100}{c}}
  \tl_use:N \l__kevin_table_body_tl
  \end{tabular}
}
\cs_generate_variant:Nn \seq_set_from_clist:Nn { Nx }

\cs_new_protected:Nn \__kevin_table_process_row:n
 {
  \seq_set_from_clist:Nx \l__kevin_table_singlerow_seq
   {
    \seq_item:Nn \l__kevin_table_rows_seq { #1 }
   }
  \int_step_inline:nnnn { 1 } { 1 } { \seq_count:N \l__kevin_table_singlerow_seq }
   {
    \tl_put_right:Nx \l__kevin_table_body_tl
     {
      \int_compare:nF { ##1 = 1 } { & }
      \int_compare:nT { ##1 = \l__kevin_table_column_int }
       {
        \__kevin_table_addcolor:n { \seq_item:Nn \l__kevin_table_singlerow_seq { ##1 } }
       }
      \seq_item:Nn \l__kevin_table_singlerow_seq { ##1 }
     }
   }
  \tl_put_right:Nn \l__kevin_table_body_tl { \\ }
 }

\cs_new_protected:Nn \__kevin_table_addcolor:n
 {
  \fp_compare:nTF { #1 >= \l__kevin_table_upper_fp }
   { \cellcolor{\l__kevin_table_upper_tl} }
   {
    \fp_compare:nTF { #1 <= \l__kevin_table_lower_fp }
     { \cellcolor{\l__kevin_table_lower_tl} }
     { \cellcolor{\l__kevin_table_normal_tl} }
   }
 }
\ExplSyntaxOff

\settable{upper-color=red,lower-color=blue,normal-color=orange}

\begin{document}

\buildtable[column=2,upper-bound=1,lower-bound=0.1]{\jobname.csv}

\bigskip

\buildtable[column=2,upper-bound=0.5,lower-bound=0.1]{\jobname.csv}


\end{document}

enter image description here

A different version where you can do the coloring in as many columns as you please.

\begin{filecontents*}{\jobname.csv}
ColA, ColB, ColC
0.17, 0.91, 0.67
0.15, 0.17, 0.92
0.48, 0.1, 0.28
\end{filecontents*}

\documentclass{article}
\usepackage[table,dvipsnames]{xcolor}
\usepackage{xparse}

\ExplSyntaxOn
\ior_new:N \g_kevin_table_readfile_stream
\tl_new:N \l__kevin_table_body_tl
\seq_new:N \l__kevin_table_rows_seq
\seq_new:N \l__kevin_table_singlerow_seq

\keys_define:nn { kevin/table }
 {
  column       .clist_set:N = \l__kevin_table_column_clist,
  upper-bound  .fp_set:N    = \l__kevin_table_upper_fp,
  lower-bound  .fp_set:N    = \l__kevin_table_lower_fp,
  upper-color  .tl_set:N    = \l__kevin_table_upper_tl,
  lower-color  .tl_set:N    = \l__kevin_table_lower_tl,
  normal-color .tl_set:N    = \l__kevin_table_normal_tl,
 }

\NewDocumentCommand{\settable}{m}
 {
  \kevin_table_set:n { #1 }
 }

\NewDocumentCommand{\buildtable}{O{}m}
 {
  % #1 = options
  % #2 = file name
  \group_begin:
  \settable{#1}
  \kevin_table_build:n { #2 }
  \group_end:
 }

\cs_new_protected:Nn \kevin_table_set:n
 {
  \keys_set:nn { kevin/table } { #1 }
 }

\cs_new_protected:Nn \kevin_table_build:n
 {
  \tl_clear:N \l__kevin_table_body_tl
  \seq_clear:N \l__kevin_table_rows_seq
  \ior_open:Nn \g_kevin_table_readfile_stream { #1 }
  \ior_map_inline:Nn \g_kevin_table_readfile_stream
   {
    \seq_put_right:Nn \l__kevin_table_rows_seq { ##1 }
   }
  % get the number of columns and the header row
  \seq_set_from_clist:Nx \l__kevin_table_singlerow_seq
   {
    \seq_item:Nn \l__kevin_table_rows_seq { 1 }
   }
  \tl_put_right:Nx \l__kevin_table_body_tl
   {
    \seq_use:Nn \l__kevin_table_singlerow_seq { & } \exp_not:N \\
   }
  % process each row
  \int_step_function:nnnN { 2 } { 1 } { \seq_count:N \l__kevin_table_rows_seq } \__kevin_table_process_row:n
  % build the table
  \begin{tabular}{*{100}{c}}
  \tl_use:N \l__kevin_table_body_tl
  \end{tabular}
}
\cs_generate_variant:Nn \seq_set_from_clist:Nn { Nx }

\cs_new_protected:Nn \__kevin_table_process_row:n
 {
  \seq_set_from_clist:Nx \l__kevin_table_singlerow_seq
   {
    \seq_item:Nn \l__kevin_table_rows_seq { #1 }
   }
  \int_step_inline:nnnn { 1 } { 1 } { \seq_count:N \l__kevin_table_singlerow_seq }
   {
    \int_compare:nF { ##1 = 1 } { \tl_put_right:Nn \l__kevin_table_body_tl { & } }
    \clist_if_in:NnT \l__kevin_table_column_clist { ##1 }
     {
      \tl_put_right:Nx \l__kevin_table_body_tl
       {
        \__kevin_table_addcolor:n { \seq_item:Nn \l__kevin_table_singlerow_seq { ##1 } }
       }
     }
    \tl_put_right:Nx \l__kevin_table_body_tl
     {
      \seq_item:Nn \l__kevin_table_singlerow_seq { ##1 }
     }
   }
  \tl_put_right:Nn \l__kevin_table_body_tl { \\ }
 }

\cs_new_protected:Nn \__kevin_table_addcolor:n
 {
  \fp_compare:nTF { #1 >= \l__kevin_table_upper_fp }
   { \cellcolor{\l__kevin_table_upper_tl} }
   {
    \fp_compare:nTF { #1 <= \l__kevin_table_lower_fp }
     { \cellcolor{\l__kevin_table_lower_tl} }
     { \cellcolor{\l__kevin_table_normal_tl} }
   }
 }
\ExplSyntaxOff

\settable{upper-color=red,lower-color=blue,normal-color=orange}

\begin{document}

\buildtable[column=2,upper-bound=1,lower-bound=0.1]{\jobname.csv}

\bigskip

\buildtable[column={2,3},upper-bound=0.5,lower-bound=0.1]{\jobname.csv}


\bigskip

\buildtable[column={1,3},upper-bound=0.5,lower-bound=0.1]{\jobname.csv}


\end{document}

enter image description here

2
  • Would you be able to do this with multiple columns at the same time? May 24, 2018 at 11:03
  • 1
    @KevinGregory Added
    – egreg
    May 24, 2018 at 16:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .