EmbeddedRelated.com
Blogs
The 2024 Embedded Online Conference

How to Include MathJax Equations in SVG With Less Than 100 Lines of JavaScript!

Jason SachsMay 23, 20149 comments

Today’s short and tangential note is an account of how I dug myself out of Documentation Despair. I’ve been working on some block diagrams. You know, this sort of thing, to describe feedback control systems:

And I had a problem. How do I draw diagrams like this?

This article is available in PDF format for easy printing

I don’t have Visio and I don’t like Visio. I used to like Visio. But then it got Microsofted.

I can use MATLAB and Simulink, which are great for drawing block diagrams. Normally you use them to create a model for simulation, but you can just use them for illustrations. Except that Simulink’s support for TeX output is very limited and not very easy to use. You have to create a masked subsystem and use icon drawing commands with (..., 'texmode', 'on'). Basically it’s just a bunch of basic math characters, and the ability to use subscripts and superscripts. You can’t even draw simple rational transfer functions like \( H(s) = \frac{1}{s^2 + 2Kbs + b^2} \) because MATLAB’s TeX support hasn’t done anything new in at least the last 12 years, and use of the TeX \frac{}{} macro isn’t supported.

I found the blockdiag program, which is Python-based (yay!!!), and kind of like the dot program in graphviz, but a little bit better-suited to block diagrams. But it doesn’t have equation support either.

Argh! So I turned to the Internet and looked up to see how to put MathJax equations in SVG. MathJax is the client-side JavaScript framework that is the de facto standard for math typesetting in HTML. I use it all the time in my articles. You can do stuff like this without lifting an eyelash:

$$ \gamma = \lim_{n \to \infty} \left(2\sqrt{n+1} - \sum\limits_{k=1}^n \tan^{-1}\frac{1}{\sqrt{k}}\right) = 2.157782996659446…$$

Here’s how MathJax works: it goes in like a bunch of mini-math ninjas and rips through your HTML and looks for text in special delimiters \( \) or \[ \] or $$ $$, and merrily interprets it as TeX math mode, typesetting the results as a huge ugly nested tree of HTML <span> elements to place math text characters in exactly the right places so it looks like a professionally-typeset math equation. It’s not a bitmap! You can zoom in on equations to your heart’s content and it gets sharper-looking.

But it doesn’t work in SVG, at least not without some hackery. One approach is to use the SVG <foreignObject> tag, and I tried it and couldn’t get that to work either. Then I found this post on StackOverflow which uses a different approach. Basically all it does is to render MathJax equations normally, outside of the desired SVG elements, and render the equations as SVG, then copy the resulting SVG nodes into the relevant place inside the desired SVG elements. It took me a little while to figure out what it was doing. The author uses CoffeeScript and jQuery to create equations in SVG. Which is great, but I don’t know CoffeeScript, and I don’t want to mess around with something I don’t know unless I’m going to use it a lot, and it doesn’t seem like these dependencies are really necessary to get things done.

So I wrote my own method, which is pretty simple. You just add a few lines of Javascript at the top of your HTML file, and pretend that MathJax markup works properly in SVG:

Here’s how it works:

  • Include the MathJax scripts with SVG rendering
  • Before MathJax runs:
    • For each <text> element inside a <svg> element, where the <text> node’s content consists entirely of a MathJax equation (with optional spaces outside the equation delimiters):
      • Copy the text content of the <text> element to a temporary <div> element elsewhere on-screen, so that MathJax can typeset the equation properly.
      • Copy these two elements (the original <text> element and the temporary <div> element) to a temporary list in Javascript.
  • After MathJax runs:
    • For each pair of elements in the temporary list:
      • Clone the resulting elements and replace the original <text> element in the SVG with the results of MathJax typesetting.
    • Remove the temporary <div> elements.

That’s it! Excluding the Apache License notice at the top of the file, the Javascript source is less than 100 lines.

I’m happy with the results; I’ve been using this technique to add blocks to Simulink diagrams which have MathJax equations in them.

Here’s an example of a Simulink diagram with the raw MathJax markup: (sorry, it’s only a PNG file so you can’t zoom in)

And here’s what happens when I include it in an HTML file along with my SVG_MathJax script: Presto-chango!

I put the Javascript file svg_mathjax.js into a Bitbucket repository — feel free to use it yourself. Maybe someday soon, the MathJax people will make it obsolete by getting their excellent software to work properly when used in SVG elements.

In the meantime, happy typesetting!


© 2014 Jason M. Sachs, all rights reserved.



The 2024 Embedded Online Conference
[ - ]
Comment by amooreMay 26, 2014
Thanks for figuring this out and publishing it. I'm always looking for better documentation tools. Minor nit: in the block diagram following "So I wrote my own method...", it appears the two alignment labels are reversed. The text "aligned at the right" belongs with the H(s) block and "aligned at the left" should be with 3/(s**2+1).
[ - ]
Comment by jms_nhMay 26, 2014
Thanks for the feedback. The diagram is correct. The text is aligned with a coordinate that is at the middle of the document. So text that is right-aligned to this point is actually shifted to the left; text that is left-aligned to this point is actually shifted to the right.
[ - ]
Comment by McCullerlpFebruary 19, 2015
Excellent post and repository with the code you used.

I added two things to it to make it work better for me:
The mathbucket div can have style "display: none" set so that you can not see the intermediate typesetting.

Furthermore I like using this in ipython notebooks, so I changed the StartupHook lines to

MathJax.Hub.Register.MessageHook("Begin PreProcess",function (message) { ... }
MathJax.Hub.Register.MessageHook("Begin PreProcess",function (message) { ... }

for the startup and end respectively. This allows the conversion to be done live so that you can have systems like the ipy notebook embedding such SVG files live.

[ - ]
Comment by david72April 13, 2015
This is great, thanks for publishing it!
When I tried it on my own document, the mathJax came out approximately twice as large as it should. I changed your "scale" option to 0.0008 and now it looks fine.
How did you come up with the original scale of 0.0016? was that just trial and error?
Your own test cases look fine on my machine, so it must be something in my css or html that is different, but I'm not sure what yet.
[ - ]
Comment by jms_nhApril 14, 2015
SVG typically has scaling transforms in a number of places, so depending on what is used in the SVG you may have to change the MathJax scaling. I honestly don't remember, I think I just went through trial and error until I got something that looked right.
[ - ]
Comment by vektor8891March 9, 2016
thanks a lot for this script! i have a question though:
how to render a svg object appended into a div element after a successful AJAX query?
[ - ]
Comment by johnseecgJune 29, 2022

This looks like it might be an excellent way to embed equations into svg figures.  However, the bitbucket link no longer works.  Is the code available anywhere else?

[ - ]
Comment by jms_nhJune 29, 2022

Yeah, Atlassian killed Bitbucket when they dropped Mercurial support.

I don't have the repo up somewhere else, but this is a small enough set of source code that I put it in a Github gist: https://gist.github.com/jason-s/15e8d676408a17c985...

[ - ]
Comment by johnseecgJuly 1, 2022

Thanks for posting this.  I tried the test example but it appears not to work.  Does this still work for you (I wonder whether it might be due to mathjax being a different version).

To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Please login (on the right) if you already have an account on this platform.

Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: