44
$\begingroup$

I have a simple interactive plot in Mathematica, with controls for various parameters. The point of the plot is simply that users can explore how the plot changes as the parameters change by manipulating controls. IOW, this is Dynamic-101 stuff (which is my level at the moment).

I would like to convert this to (non-proprietary) HTML + SVG (+ CSS, JavaScript, etc., as needed).

Is there a straightforward path for doing this? I know that one can export Mathematica graphics to SVG, but I'm not sure how one would export the dynamic behavior described above.

(BTW, I've looked for ways to do this starting from JavaScript to begin with, but I have found that JavaScript support for plotting is rather primitive. JavaScript graphics seem to be mostly about Illustrator-type stuff, and more recently, "data visualization". In comparison, there's almost nothing in the JavaScript graphics universe for plotting functions, the way Plot et al. do.)

$\endgroup$
15
  • 4
    $\begingroup$ There's no built-in functionality for export of dynamic SVG + JS or HTML5. The best high-level way to create HTML + JS in my opinion is Processing/JS - in particular, look at the reference for processing.js which exports interactive pages without requiring Java applets. Their older web export is Java applet based, but is very capable. Here is an example of an added library for plotting, combined with interactivity. $\endgroup$
    – Jens
    Jan 22, 2013 at 19:42
  • 3
    $\begingroup$ There's also geogebra, if you're especially interested in math visualization. $\endgroup$
    – Jens
    Jan 22, 2013 at 19:44
  • 1
    $\begingroup$ webMathematica might be the most straightforward for this, though not ideal. $\endgroup$
    – amr
    Jan 22, 2013 at 20:34
  • 2
    $\begingroup$ When you create interactive demonstrations in Mathematica, computations are typically done live. For any non-trivial problem you won't have access to all the function needed for this in JavaScript, so a more practical solution would be to pre-compute everything for a set of values of a manipulated parameter and export the results (say, as images). If we could find some existing browser based solution which allows dynamically seeking anywhere in an animation (e.g. animated GIF), we could use that to play back the results. Perhaps yo can try to find something like this. $\endgroup$
    – Szabolcs
    Jan 22, 2013 at 20:43
  • 2
    $\begingroup$ See also my related question that focuses on D3 viz: mathematica.stackexchange.com/questions/5416/… $\endgroup$ Jan 22, 2013 at 22:58

4 Answers 4

28
$\begingroup$

Animations as interactive visualizations

The simplest form of interactive graphics is an animation in which the play head can be moved by the user. That doesn't sound very interactive, but in terms of functionality the play head is nothing but a type of Slider.

With this simple interpretation of interactivity, any movie format supported by Export would be a way to create a standalone "interactive" visualization.

The starting point for this approach would be to generate a list of graphics that will form the frames of the animation:

frames = Table[
   Plot[Sin[φ x]/(φ x), {x, -1, 1}, Background -> White, Frame -> True, 
    PlotRange -> {{-1, 1}, {-.5, 1}}], {φ, Pi,10 Pi, Pi/2}];

I've added a Background to the plot because it will be needed to make the SVG version of the movie come out properly below.

Frame based animations with SVG

But the other part of your question is specifically asking about SVG and HTML format. In SVG, you can create animations by moving elements around using JavaScript. But that's not something you can easily automate in exporting a Mathematica dynamic object - it would require case-by-case fine tuning.

So instead, I pursued a totally different way to combine the first point (the movie paradigm) with SVG: export an animation in which each frame is a static SVG vector graphic. Then the interactive element is again realized by the mere presence of the play head as a slider.

To make this a little more interesting than a typical movie player, I also added the ability to export a sequence of N graphics but create a larger number M > N of frames from them in the animation. This is achieved by allowing an indexed list to specify the movie frames, so the M frames can walk through the N graphics in any order with arbitrary repetitions during playback.

The Javascript movie player

The whole thing is based on a JavaScript movie player I had written earlier, so you also get the ability to encode your frames as standard PNG bitmaps instead of SVG.

The special thing about the player is that it's a single standalone HTML file. All movie frames are embedded in the HTML using base64 encoding, so the animation remains just as portable as a normal movie, GIF etc.

But the SVG playback ability is what makes this most relevant to your question. Since SVG takes more resources to store and interpret during the display, one can notice that the player is somewhat slower to start up when you choose SVG format instead of the default PNG format.

However, the nice thing is that SVG animations can be enlarged without loss of quality, even while the movie is running.

I'm putting this out there for experimentation, and SVG may not turn out to be the best choice for your application. But then you can still go with PNG and get a smooth frame animation with full slider control.

The JavaScript player has some additional features in addition to a draggable play head. Since it's frame-based, you can interactively change the frame delay, press a to display all frames side-by-side, and change the looping behavior.

htmlTemplate = 
    Import["http://pages.uoregon.edu/noeckel/jensPlayer/jensPlayerTemplate.js", "Text"];
jensPlayer[name_?StringQ, a_?ListQ, opts : OptionsPattern[]] := Module[
  {delay = 50, dataType = "img", htmlString, htmlBody,
   scaledFrames, n, i, movieFrames, dimensions, frameStartTag, 
   frameEndTag, exportFormat, imgSizeRule, 
   loopOptions = {"Loop" -> "rightLoopButton", 
     "None" -> "noLoopButton", "Palindrome" -> "palindromeButton"},
   toolHeight = 25},
  n = Range[Length[a]];
  imgSizeRule = FilterRules[{opts}, ImageSize];
  If[imgSizeRule == {}, imgSizeRule = (ImageSize -> Automatic)];
  scaledFrames = Map[Show[#, imgSizeRule] &, a];
  dimensions = ImageDimensions[Rasterize[scaledFrames[[1]]]];
  With[{del = ("Delay" /. {opts})}, If[NumericQ[del], delay = del]];
  With[{ind = ("Indices" /. {opts})}, 
   If[ListQ[ind], i = ind - 1, i = n - 1]];
  Which[("SVG" /. {opts}) === True, 
   dataType = "object", ("SVGZ" /. {opts}) === True, 
   dataType = "embed"];
  If[dataType == "embed",
   frameStartTag = "<embed src=\"";
   frameEndTag = 
    "\" width=\"" <> ToString[dimensions[[1]]] <> "\"height=\"" <> 
     ToString[dimensions[[2]]] <> "\">";
   htmlString = "";
   movieFrames =
    Table[
     With[{svgName = 
        name <> ToString[
          PaddedForm[i, Total@DigitCount[Length[scaledFrames]], 
           NumberPadding -> {"0", " "}]] <> ".svgz"},
      Export[svgName, scaledFrames[[i]], "SVGZ"]; 
      frameStartTag <> svgName <> frameEndTag], {i, 
      Length[scaledFrames]}];
   htmlString = StringJoin[movieFrames],
   If[dataType == "img",
    frameStartTag = "<img src=\"data:image/png;base64,";
    frameEndTag = "\">";
    movieFrames = Apply[If[("Parallel" /. {opts}) === False,
       Map, ParallelMap], {ExportString[#, "PNG"] &, scaledFrames}],
    frameStartTag = "<object data=\"data:image/svg+xml;base64,";
    frameEndTag = "\" type=\"image/svg+xml\"></object>";
    movieFrames = Apply[If[("Parallel" /. {opts}) === False,
       Map, ParallelMap], {ExportString[#, "SVG"] &, scaledFrames}]
    ];
   htmlString = 
    StringJoin@
     If[("Parallel" /. {opts}) === False, 
      Map[(StringJoin[frameStartTag, 
          StringReplace[ExportString[#, "Base64"], "\n" -> ""], 
          frameEndTag]) &, movieFrames],
      DistributeDefinitions[frameStartTag]; 
      DistributeDefinitions[frameEndTag]; 
      ParallelMap[(StringJoin[frameStartTag, 
          StringReplace[ExportString[#, "Base64"], "\n" -> ""], 
          frameEndTag]) &, movieFrames]
      ]
   ];
  htmlBody = 
   StringReplace[
     htmlTemplate, {"**DATE**" -> DateString[], 
      "**DATATYPE**" -> dataType, 
      "**WIDTH**" -> ToString[dimensions[[1]]], 
      "**HEIGHT**" -> ToString[dimensions[[2]]], 
      "**TOOLHEIGHT**" -> ToString[toolHeight], 
      "**DELAY**" -> ToString[delay], 
      "**COMBINEDWIDTH**" ->  ToString[Length[a]*dimensions[[1]]], 
      "**USERNAME**" -> $UserName, 
      "**LOOPCONTROL**" -> 
       With[{loopOption = ("Looping" /. {opts}) /. loopOptions}, 
        If[MemberQ[loopOptions[[All, 2]], loopOption], loopOption, 
         "None" /. loopOptions]], 
      "**INDICES**" -> StringJoin@Riffle[Map[ToString, i], ","], 
      "**AUTOPLAY**" -> 
       If[("AutoPlay" /. {opts}) === True, "true", "false"], 
      "**TITLE**" -> name}] <> htmlString <> "</div></body></html>";
  Export[name, htmlBody, "TEXT"];
  ]

Examples

With this function you can now export the frames created earlier:

jensPlayer["sombrero.html", frames]

SystemOpen["sombrero.html"]

jensPlayer["sombreroIndexed.html", frames, "Delay" -> 60, 
 "AutoPlay" -> True, "Looping" -> "Palindrome", "Parallel" -> True, 
 "Indices" -> {19, 18, 17, 16, 15, 15, 17, 18, 19, 19, 18, 17, 16, 15,
    14, 13, 11, 9, 7, 5, 4, 3, 2, 2, 1, 1, 1, 1, 1, 2, 2, 3, 4, 5, 7, 
   9, 11, 13, 15, 16, 17, 18, 19, 19, 19, 19}]

SystemOpen["sombreroIndexed.html"]

jensPlayer["sombreroSVG.html", frames, "SVG" -> True]

SystemOpen["sombreroSVG.html"]

Each of the export commands illustrates a different export format.

Options

By default, the images in list images are displayed sequentially with a constant time delay, in a single playback run. If the option "Indices" is specified, frames are played back in the order determined by that option, see below.

Options (except for ImageSize, all options are strings requiring quotation marks):

  • ImageSize
    • number of pixels in the horizontal direction. If unspecified, will be determined by size of the graphics in images.
  • "Looping"
    • "None" (default),
    • "Loop"
    • "Palindrome"
  • "Delay"
    • number of milliseconds between frames (default 50)
  • "Indices"
    • {i1, i2,... iN}, a list of (not necessarily different) integers between 1 and Length[images]. The number of frames in the movie is N. in (n = 1,...N) is the index in list a of the image to be displayed as frame n.
  • "AutoPlay"
    • True
    • False (default)
    • sets whether the movie should start playing as soon as loaded.
  • "Parallel"
    • True (default)
    • False
    • switches between parallel/sequential image conversion. Parallel speeds up the export if more than one Mathematica Kernel is available.
  • "SVG"
    • True
    • False (default)
    • switches between SVG or PNG format for the frame export. SVG format is provided on an experimental basis because browser support is evolving slowly. It is a resolution-independent graphics format that can be resized seamlessly in Firefox. SVG requires larger startup time, so it should not be used for large movies. With SVG you should also specify an explicit background for the frames, e.g. by doing images=Map[Show[#,Background->White]&,originalImages]. Although SVG movies yield much larger HTML files, gzip compression can make file sizes comparable to those of PNG movies (or even smaller). Since web servers increasingly are configured to transmit HTML files using on-the-fly compression, SVG does not necessarily increase download times.

Additional notes

Because the JavaScript file is too unwieldy for this post, I put it on my web site and use Import in the jensPlayer function to load it. I would suggest that you download the file

http://pages.uoregon.edu/noeckel/jensPlayer/jensPlayerTemplate.js

and copy and paste it as a string into the htmlTemplate = assignment. Then you can use the function offline.

Also, I had tried SVGZ instead of SVG, and the function allows that as an option. However, this compressed SVG format doesn't play back so well apparently. Maybe the decompression is causing problems - I haven't had time to debug that yet. So I'd recommend you not use SVGZ at the moment, even though you may have seen it in the code above.

Illustrated example

To show what the movie should look like, here is a sequence of images that represent an electric field distribution for a range of separations d between two charged objects. The parameter d is then controlled with the movie slider:

field[a_, d_, x_, y_] = 
  D[Log[((x - d)^2 + y^2)/((x - a)^2 + y^2)], {{x, y}}];
images = With[{r = 1.}, Table[
    Show[
     StreamPlot[field[1/d, d, x, y], {x, -2, 4}, {y, -3, 3}, 
      FrameLabel -> {"x", "y"}, StreamColorFunctionScaling -> False, 
      StreamColorFunction -> "Rainbow", 
      PlotLabel -> Row[{"d = ", PaddedForm[N@d, {4, 2}]}]], 
     Graphics[{Thickness[.009], White, Circle[{0, 0}, r], 
       PointSize[.03], Point[{d, 0}]}], Background -> GrayLevel[.2], 
     LabelStyle -> White, 
     FrameStyle -> Directive[Lighter@Gray, Thickness[.005]], 
     ImageMargins -> 5],
    {d, 1.5 r, 4 r, r/4}]];

jensPlayer["imageChargesSVG.html", images, "SVG" -> True, 
 "Parallel" -> False]

example movie

The movie is a screen recording of the JavaScript movie player in Google Chrome. The controls at the bottom fade in and out. The left-most control switches between single run, back-and-forth (palindromic) playback and looping. I show how to stop the movie by pressing the red button, and then drag the slider to get to a particular frame. After restarting and stopping agin, I click on the frame counter to get a dialog that allows interactive adjustment of the frame delay. After changing the delay to 250 milliseconds, the playback is slower.

There are several other keyboard shortcuts that are explained if you click the question mark at the bottom right. In particular, you can use arrow keys to move from frame to frame while the movie is stopped.

$\endgroup$
0
9
$\begingroup$

For the benefit of those who come across this old question, I have recently written a small open-source library called MathCell that replicates the behavior of Mathematica's Manipulate using pure JavaScript. Documentation is available here.

The library is not for converting Mathematica code, but is meant as a replacement for people who want to embed interactive math in a web page. There is a template in the documentation to set up an area on the page containing HTML elements (range sliders, number inputs, checkboxes) for parameter input, with an output below them that can be an SVG plot, a 3D graphic using Three.js or simple text as desired.

And since JavaScript is limited in its available functions, I also started a library simply called Math to provide special functions and basic numerical operations, such as root finding, quadrature, integration of differential equations and eigensystems of real symmetric matrices.

$\endgroup$
3
  • $\begingroup$ Could you give a short description of how it works? I.e. what it the input to MathCell? I assume the output is the Javascript? Do you mean you convert Manipulate code to Javascript? $\endgroup$
    – Nasser
    Nov 15, 2017 at 0:19
  • $\begingroup$ @Nasser description updated $\endgroup$ Nov 15, 2017 at 1:15
  • 1
    $\begingroup$ The JSForm function creates JavaScript output which can be used with the Math, MathCell and JSXGraph libraries. $\endgroup$
    – axelclk
    Mar 31, 2021 at 8:56
9
$\begingroup$

As Jens points out, we can certainly generate a single slider version of Manipulate in a web page. Evidently, someone else has recently taken up this challenge.

The correct, general answer is still no, there is no straightforward (built-in) way to convert Mathematica's dynamic objects to non-proprietary HTML+SVG/JS. To see why, consider how you might try to represent the following very simple example in HMTL/SVG:

Manipulate[With[{pts = {#, Sin[a*#]} & /@ (x /.
  Quiet[Solve[Sin[a*x] == b*AiryAi[-x] && 0 < x < 10, x],
    {Solve::nint, Solve::ratnz}])},
  Plot[{Sin[a*x], b*AiryAi[-x]}, {x, 0, 10}, Epilog -> 
   {PointSize[Large], Point[pts]}, PlotRange -> {-1.1, 1.1}]],
  {{a, 1}, 1, 10}, {{b, 2}, 1, 3}]

enter image description here


Of course, the point is that most interesting dynamic illustrations in Mathematica will make use of Mathematica's extensive library of mathematical functionality - functionality not generally available in GeoGebra, D3, or javascript libraries such as processing.js or numeric.js. This exact point was made in the comments, but without example.

It is not my point to demean those other tools at all - simply to point out that their emphasis is different. If your objective to create broadly accessible (i.e., without plugins) illustrations based on simple mathematics, then perhaps you should focus on those types of tools. If you need to create illustrations that require more advanced (mathematical) functionality, then it will be hard to beat Mathematica. Clearly, Wolfram Research is aware of the need to disseminate material created with Mathematica and have released web Mathematica and CDF for exactly this purpose.

The particular example that I gave is very easy in Mathematica precisely because it has an excellent library of special functions and well integrated tools for solving equations involving those functions. (Any number of other examples could be given.) Implementing this example in javascript would be challenging, since you'd have to implement the fundamentals yourself.

It's amusing that some anonymous user chose to downvote this answer without accepting that challenge.

$\endgroup$
6
  • $\begingroup$ "Whatever your approach, you'd certainly need a good library of special functions and tools to solve equations involving these over entire intervals" Well, a point to such a library was one of the types of answers I was hoping for... Thanks for the nice example, BTW! $\endgroup$
    – kjo
    Jan 22, 2013 at 20:02
  • $\begingroup$ I don't know much about SVG yet, but my impressions about it so far are consistent with your comments. Namely, SVG provides the graphics primitives, but there do not seem be any decent libraries (in JavaScript or anything else) to support higher-level programming of SVG-based plots. $\endgroup$
    – kjo
    Jan 22, 2013 at 20:08
  • 2
    $\begingroup$ @kjo, The D3 API developed at Stanford by Heer and others, has been used extensively by NYTimes among others for interactive viz (see the link in my comment above for examples) $\endgroup$ Jan 22, 2013 at 23:03
  • $\begingroup$ I think the question is valid because CDF isn't viable in all scenarios where HTML + SVG is available. So I still want to post an alternative answer - but I didn't downvote yours because it's certainly a correct descriptin of the difficulties. And a constructive answer unfortunately takes a little longer... $\endgroup$
    – Jens
    Jan 24, 2013 at 18:02
  • $\begingroup$ @Jens Clearly, the question is valid - I don't mean to imply otherwise. It's just that the short answer is no, there is no straightforward (built-in) way to convert Mathematica's dynamic objects to non-proprietary HTML+SVG/JS. The longer answer illustrates why. I wouldn't think you're the down-voter, by the way! $\endgroup$ Jan 24, 2013 at 18:39
3
$\begingroup$

There are a few possibilities, I can suggest from my side

Either use WLJS Frontend, that supports exporting notebooks to an HTML or React component, however it is still very raw, has no direct compatibility with Mathematica and the second option might suit better.

Second option - export your graphs to ExpressionJSON and feed it to a Javascript Wolfram Language runtime. The graphics library is quite limited, but for the regular plots it works well.

Both of those options provides dynamics, sliders and etc, however in a different form. It might take some time to get used to this frameworks. Not event mentioning a lot of bugs and so on... ;)

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.