How can I modify bmatrix to gray out certain values?

109 views
Skip to first unread message

Michael Hucka

unread,
May 28, 2022, 11:30:28 PM5/28/22
to mathja...@googlegroups.com
I'm trying to figure out how to replace or augment bmatrix in a way that
would display certain values in a gray color in a way that mimics what
has been done in this answer to a TeX Stack Exchange question:
https://tex.stackexchange.com/a/53113/8318

The idea is that in a matrix that consists of a large number of 0's and
a few 1's, it's much easier to spot the 1's visually if the 0's are
printed in a lighter color, a gray color, and the 1's are left black.

Could someone give me some guidance on how to get started? I can
program in Javascript but have never attempted to write a MathJax
extension, and I'm not sure how to go about replacing a built-in like
bmatrix. An example of doing something similar would be great, but even
just general tips and directions would be helpful at this point.

Best regards,
MH
--
Mike Hucka, Ph.D. (he/him/his)
mhu...@caltech.edu -- http://www.cds.caltech.edu/~mhucka
California Institute of Technology

Murray

unread,
May 29, 2022, 9:14:01 PM5/29/22
to MathJax Users
Michael

I'm sure the MathJax gurus will have a solution regarding your extension request, but you may also wish to consider something like the following.

It involves pre-processing the LaTeX so the zeros are replaced with a color directive, then getting MathJax to process the page.

You can see it in action here:


It's not overly robust as it relies on consistent spacing in the LaTeX, but there are ways around that.

Hope it helps
Murray

Davide Cervone

unread,
May 30, 2022, 10:58:11 AM5/30/22
to mathja...@googlegroups.com
There are a number of possible approaches to this.  You didn't give an explicit example, so I hope these address your needs.

The first can be done without any javascript, but does require modifying the array:

$$
\newcommand{\0}{{\color{gray} 0}}
\begin{bmatrix}
 1 & \0 & \0 \\
\0 &  1 & \0 \\
\0 & \0 &  1
\end{bmatrix}
$$

Here we define \0 to be a gray 0 and use that in the matrix.  Works, but isn't automatic.

The second approach uses a pre-filter like Murray suggested, but you can do that within MathJax itself rather than with external javascript.  Here is a configuration that does that:

MathJax = {
  startup: {
    ready() {
      MathJax.startup.defaultReady();
      const TEX = MathJax.startup.document.inputJax[0];
      TEX.preFilters.add(({math}) => {
        math.math = math.math.replace(/\\begin\{color-matrix\}(.*?)\\end\{color-matrix\}/s,
          (match, body) => '\\begin{bmatrix}' + body.replace(/0/g, '\\gray 0') + '\\end{bmatrix}'
        );
      });
    }
  },
  tex: {
    macros: {
      gray: ['{\\color{gray} #1}', 1],
  }
};

It defines a macro \gray that can be used to make its argument gray, and adds a pre-filter to the TeX input jax (assuming you are using only one input format).  The filter looks for \begin{color-matrix}...\end{color-matrix} and replaces his with \begin{bmatrix}...\end{bmatrix} where the 0's in the content are replaces by \gray 0 instead.  You may need to adjust the regular expression if the entries are more complicated than just 0 and 1.  With this configuration

$$
\begin{color-matrix}
1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{color-matrix}
$$

will produce the desired matrix with gray 0's.

A third approach uses a post-filter to modify the internal MathML generated by the array.  Here, we define a cmatrix environment that creates the bmatrix but tags it with a class that the post-filter uses to define which matrices to modify.  Then it walks the elements of the array looking for <mn>0</mn> nodes in the table cells and sets their mathcolor attributes.  In order to be able to use the \class{...}{...} macro around the matrix, however, we need to use a special macro that grabs the whole matrix (up to \end{cmatrix}), and inserts that into the \class{...} {...} macro.  This is necessary because the closing brace for \class{...}{...} can't be in the replacement text for \end{cmatrix}, as that is not yet inserted when the initial \class{...}{... is looking for its end brace.  We can include the needed macro and environment definitions in the configuration:

MathJax = {
  startup: {
    ready() {
      MathJax.startup.defaultReady();
      const TEX = MathJax.startup.document.inputJax[0];
      TEX.postFilters.add(({data}) => {
        for (const mtable of data.getList('mtable')) {
          if (mtable.attributes.get('class') !== 'cmatrix') continue;
          mtable.walkTree((node) => {
            if (node.isKind('mn')) {
              if (node.getText() === '0') {
                node.attributes.set('mathcolor', 'grey');
              }
            }
          });
        }
      });
    }
  },
  tex: {
    macros: {
      cmatrix: [
        '\\left[\\class{cmatrix}{\\begin{matrix}#1\\end{matrix}}\\right]\\end{cmatrix}',
         1, ['', '\\end{cmatrix}']]
    },
    environments: {
      'cmatrix': ['\\cmatrix', '']
    }
  }
};

With this, the matrix

$$
\begin{cmatrix}
1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{cmatrix}
$$

produces the desired colored entries.  Again, if the entries are not just 0 and 1, you may need to use a more complicated test for which are to be colored.

This works, but is a bit awkward, and you can't nest the cmatrix environments (since the \cmatrix macro ends at the first \end{cmatrix}).  It is the \class macro that is causing the problems, so an alternative that avoids that is to use a marker node just before the array that can have its class set within the initial code of the environment, avoiding the need to enclose the entire matrix in the argument to \class.  In the configuration bellow, we define a Cmatrix environment that uses a MathML <ms> element to indicate that the following <mtable> is to be processed for colored entries.  (Why use <ms>?  Because it is unlikely to occur elsewhere in the TeX output, and so the list of them to look at will not include anything else that needs to be skipped, so it should run faster.  The element is removed in the end, so it really doesn't matter what it is, as it will not show up in the final output.)

The following configuration implements this approach:

MathJax = {
  startup: {
    ready() {
      MathJax.startup.defaultReady();
      const TEX = MathJax.startup.document.inputJax[0];
      TEX.postFilters.add(({data}) => {
        const remove = [];
        for (const ms of data.getList('ms')) {
          if (ms.attributes.get('class') !== 'Cmatrix') continue;
          const i = ms.childPosition();
          ms.parent.childNodes.splice(i, 1);
          remove.push(ms);
          ms.parent.childNodes[i].walkTree((node) => {
            if (node.isKind('mn')) {
              if (node.getText() === '0') {
                node.attributes.set('mathcolor', 'grey');
              }
            }
          });
        }
        data.removeFromList('mtext', remove);
      }, -10);
    }
  },
  tex: {
    environments: {
      'Cmatrix': ['\\left[\\mmlToken{ms}[class="Cmatrix"]{}\\begin{matrix}', '\\end{matrix}\\right]']
    }
  }
};

This works basically like the previous example, but looks for the <ms> elements instead of the tables, and gets the table as the next element in the parent's child list.  The <ms> element is removed from the expression, and from the lists of elements being managed by the TeX input jax (via the data.remove() call).  This approach does allow nesting the colored environments.

The other way to do this would be to implement the cmatrix environment as a MathJax TeX extension, but that is probably not worth the effort, as I think these should do the trick with less complication.

Davide


--
You received this message because you are subscribed to the Google Groups "MathJax Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mathjax-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/mathjax-users/2D7BD9A3-614C-41A2-90F8-E54274610A3E%40library.caltech.edu.

Reply all
Reply to author
Forward
0 new messages