Skip to main content

export - How to translate interactive graphics from Mathematica to standard HTML+SVG?


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.)



Answer



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 = " 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 = " frameEndTag = "\">";
movieFrames = Apply[If[("Parallel" /. {opts}) === False,
Map, ParallelMap], {ExportString[#, "PNG"] &, scaledFrames}],
frameStartTag = " frameEndTag = "\" type=\"image/svg+xml\">";
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 <> "
";

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.


Comments

Popular posts from this blog

plotting - Filling between two spheres in SphericalPlot3D

Manipulate[ SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, Mesh -> None, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], {n, 0, 1}] I cant' seem to be able to make a filling between two spheres. I've already tried the obvious Filling -> {1 -> {2}} but Mathematica doesn't seem to like that option. Is there any easy way around this or ... Answer There is no built-in filling in SphericalPlot3D . One option is to use ParametricPlot3D to draw the surfaces between the two shells: Manipulate[ Show[SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], ParametricPlot3D[{ r {Sin[t] Cos[1.5 Pi], Sin[t] Sin[1.5 Pi], Cos[t]}, r {Sin[t] Cos[0 Pi], Sin[t] Sin[0 Pi], Cos[t]}}, {r, 1, 2 - n}, {t, 0, Pi}, PlotStyle -> Yellow, Mesh -> {2, 15}]], {n, 0, 1}]

plotting - Plot 4D data with color as 4th dimension

I have a list of 4D data (x position, y position, amplitude, wavelength). I want to plot x, y, and amplitude on a 3D plot and have the color of the points correspond to the wavelength. I have seen many examples using functions to define color but my wavelength cannot be expressed by an analytic function. Is there a simple way to do this? Answer Here a another possible way to visualize 4D data: data = Flatten[Table[{x, y, x^2 + y^2, Sin[x - y]}, {x, -Pi, Pi,Pi/10}, {y,-Pi,Pi, Pi/10}], 1]; You can use the function Point along with VertexColors . Now the points are places using the first three elements and the color is determined by the fourth. In this case I used Hue, but you can use whatever you prefer. Graphics3D[ Point[data[[All, 1 ;; 3]], VertexColors -> Hue /@ data[[All, 4]]], Axes -> True, BoxRatios -> {1, 1, 1/GoldenRatio}]

plotting - Mathematica: 3D plot based on combined 2D graphs

I have several sigmoidal fits to 3 different datasets, with mean fit predictions plus the 95% confidence limits (not symmetrical around the mean) and the actual data. I would now like to show these different 2D plots projected in 3D as in but then using proper perspective. In the link here they give some solutions to combine the plots using isometric perspective, but I would like to use proper 3 point perspective. Any thoughts? Also any way to show the mean points per time point for each series plus or minus the standard error on the mean would be cool too, either using points+vertical bars, or using spheres plus tubes. Below are some test data and the fit function I am using. Note that I am working on a logit(proportion) scale and that the final vertical scale is Log10(percentage). (* some test data *) data = Table[Null, {i, 4}]; data[[1]] = {{1, -5.8}, {2, -5.4}, {3, -0.8}, {4, -0.2}, {5, 4.6}, {1, -6.4}, {2, -5.6}, {3, -0.7}, {4, 0.04}, {5, 1.0}, {1, -6.8}, {2, -4.7}, {3, -1.