Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add interactive 3D graphs using plotly JavaScript library #728

Merged
merged 1 commit into from
Mar 9, 2023

Conversation

somiaj
Copy link
Contributor

@somiaj somiaj commented Sep 13, 2022

Adds a macro that uses the plotly JavaScript library to create parametric surface plots.

https://plotly.com/javascript/3d-surface-plots/

This could be a possible replacement of the LiveGraphics3D java app that no longer functions, but is currently just an option to include interactive 3D graphics in a pg problem. This is just a draft. Sharing in case others want to use it and to get feedback. Its features are fairly limited at the moment and it won't produce hardcopy TeX output (looking into a way to achieve this -- plotly has a python library as well that could be used for this if javascript cannot be run on the server to create a .png output).

Here is an example problem that creates multiple 3D graphs:

DOCUMENT();
loadMacros(
    'PGstandard.pl',
    'PGML.pl',
    'plotly3D.pl',
    'PGcourse.pl'
);

$gr[0] = Graph3D(
    height => 300,
    width  => 300,
    title  => 'A',
    xFunc  => sub { $_[0]*cos($_[1]) },
    yFunc  => sub { $_[0]*sin($_[1]) },
    zFunc  => sub { $_[0]**2 },
    uMin   => 0,
    uMax   => 2,
    vMin   => 0,
    vMax   => 2*pi + 0.1,
);
$gr[1] = Graph3D(
    height => 300,
    width  => 300,
    title  => 'B',
    zFunc  => sub { $_[0]**2 - $_[1]**2 },
);
$gr[2] = Graph3D(
    height => 300,
    width  => 300,
    title  => 'C',
    xFunc  => sub { $_[0]*cos($_[1]) },
    yFunc  => sub { $_[0]*sin($_[1]) },
    zFunc  => sub { $_[0] },
    uMin   => 0,
    uMax   => 2,
    vMin   => 0,
    vMax   => 2*pi + 0.1,
);
$gr[3] = Graph3D(
    height => 300,
    width  => 300,
    title  => 'D',
    zFunc  => sub { exp(-$_[0]**2 - $_[1]**2) },
    uMin   => -2,
    uMax   => 2,
    vMin   => -2,
    vMax   => 2,
);

BEGIN_PGML
Below are graphs for the following functions of two variables:

A) [``f(x,y) = x^2 + y^2``]

B) [``f(x,y) = x^2 - y^2``]

C) [``f(x,y) = \sqrt{x^2 + y^2}``]

D) [``f(x,y) = e^{-x^2 - y^2}``]

[@ $gr[0]->Print @]*
[@ $gr[1]->Print @]*
[@ $gr[2]->Print @]*
[@ $gr[3]->Print @]*

END_PGML
ENDDOCUMENT();

@somiaj
Copy link
Contributor Author

somiaj commented Sep 18, 2022

Added functionality to use static data (vs generate it) and provide an image file for TeX output. I have looked into the LiveGraphics3D.pl macro, and don't see an easy way to make that macro use this instead of the old java app. I'm using this currently and it is working good, so it could be an alternative for 3D graphics. There are currently some OPL problems that are no longer functional that could be switched to this if the desire was there.

@taniwallach
Copy link
Member

I have not yet tested this, but the idea of providing easy to generate interactive 3D surface plots seems very nice. I would say that for static plots - it certainly does make sense to provide precalculated data.

Another option for interactive 3D in WeBWorK is CalcPlot3D: https://c3d.libretexts.org/CalcPlot3D/index.html . Monica VanDieren has spoken about her use of CalcPlot3D with WeBWorK via an iFrame embedding, where the URL was generated to include parameters based on parameters chosen in the PG code. There are slides which mention the techniques used at https://meetings.ams.org/math/jmm2021/mediafile/Handout/Paper2767/JMM2021-vandieren-Webwork%2Bflipgrid%2Bcalcplot3D.pdf


I just spent some time looking into the LiveGraphics3D issue, and found this PR and am posting my thoughts here, as it seems an suitable place to consider how to "fix" the OPL problems which depend on functionality dropped in WeBWorK 2.17, and what is the best manner for PG to handle them.

It turns out that the total removal of LiveGraphics3D support from WW 2.17 (done in openwebwork/webwork2#1553 ) may not have been strictly necessary, as it no longer really depended on either a Java applet or Flash. Back in 2015 a JavaScript replacement was coded to allow the OPL problems which had 3D objects using LiveGraphics3D to work again (without needing Java). However, continuing to maintain the converter in https://github.com/goehle/webwork2/blob/7c21e4ca204382d0de7b9915dc728dd927c086a8/htdocs/js/apps/LiveGraphics/liveGraphics.js just to support a small number of legacy problems using the LiveGraphics3D file format does not seem necessary or wise. Thus, in the long-term the removal of that code from the webwork codebase would eventually have probably become necessary.

The JavaScript support for the LiveGraphics3D format was developed by Geoff Goehle in openwebwork/webwork2#587 and #211

From what I can tell his approach replaced the Java applet by converting the Mathematica code from the "object" file into the X3D format (a public standard https://www.web3d.org/x3d/what-x3d ) in a manner focused on use by the free x3dom JavaScript framework. However, the conversion was being done each time such a problem was loaded. It seems we would be better off just providing "converted data" directly and loading the relevant tool (x3dom) to display it.

To the best of my understanding, LiveGraphics3D supported more general 3-D objects than surface plots, so I'm not sure that plotly can provide all the functionality that used to be supported. Maybe the isosurface support would suffice? https://plotly.com/javascript/3d-isosurface-plots/

Although, it may be possible to build on the work Geoff did to convert the LiveGraphics3D files into data supported by plotly, I doubt that it is worth the effort to investigate plotly as an option for a "general fix" for the OPL problems which depend on LiveGraphics3D at present. For the existing problems it is probably far simpler to just use the output of the existing converter to x3dom format rather than put in the effort to create a converter which outputs data/code in some format plotly can support.

Bottom line: It seems to me that we would be better off by doing a one-time conversion into the "x3d" structure needed and providing that directly in the OPL as something to be loaded into the HTML version of the problems, and having these problems request the additional JS and CSS files needed to use x3dom.

What is the best mechanism which would allow storing the x3d object data in a separate file and have it loaded either by PG (on the server side) or directly by the browser for inclusion? I suspect it is more convenient to let the browser handle the loading of the "data file" and then put the entire "structure" into a prepared placeholder in the HTML DOM on the client side by JavaScript.

@somiaj @drgrice1 @pstaabp @drdrew42 What do you think of the general idea or replacing the LiveGraphic3D data files by "x3d" files? How do you think it should be implemented?


I looked at the problem Library/Union/setMVlevelsets/levels-4/levels-4.pg in a WW 2.16 Docker container, and copy-pasted the entire block from that problem into a modified version of https://examples.x3dom.org/example/x3dom_helloWorld.html changed to use the CDN source as appears in https://github.com/x3dom/dist but made manual changes to the opening tag from the sample file instead of using the one from the copy-pasted data. I could then see and manipulate the 3-D "graph".

I take that a simple proof-of-concept that a dump of the output from the current conversion code should suffice to provide the necessary data for rendering the desired interactive objects without ongoing use of htdocs/js/apps/LiveGraphics/liveGraphics.js.


Could the code in htdocs/js/apps/LiveGraphics/liveGraphics.js be easily used to build a script to automatically do the conversion of the LiveGraphics3D files (those in the OPL) into the relevant x3d XML data and save that into a file?

Is it worth the effort to automate the process? It could be that we could just manually open the relevant 50 or so OPL problems (on WW 2.16) and manually copy-past the relevant data into files to put into the OPL by hand


Note: x3dom seems to be available via npm (but a somewhat old version) so there is certainly no need to include the library in the webwork repositories as was done in the past.

Note: Geoff's code apparently only depended on Flash as a fallback for when the JavaScript approach failed. (See openwebwork/webwork2@93fa745 )

@drgrice1
Copy link
Sponsor Member

@taniwallach: Note that the "converter" method was also not working anymore, and that also relies on obsolete techniques.

@taniwallach
Copy link
Member

@taniwallach: Note that the "converter" method was also not working anymore, and that also relies on obsolete techniques.

@drgrice1 Thanks. I was not aware that it "broke". Would dumping the X3D "DOM" structure on a WeBWorK 2.16 server into a file (everything between the opening<x3d> tag and the closing </x3d> tag) suffice to allow reading that file (say via an AJAX request) and placing it into the problem where it would have gone before? A revised LiveGraphics3D.pl could then use PGalias to put the file in a web-accessible location, and the browser client could place it where needed. Alternately, it could just be put straight into the generated HTML of the problem. One of those options seems to me to be the path of least resistance to fixing those OPL problems.

@somiaj
Copy link
Contributor Author

somiaj commented Sep 29, 2022

I do like CalcPlot3D, though would prefer to be able to just use their javascript library in a problem vs an iframe. I hadn't realized there was some work to use CalcPlot3D in webwork, might have saved me some time and their surface graphs are a bit more standard for what is seen in Calculus III texts.

I went with plotly since I like matplotlib and it has a similar feel, was well documented, and I was able to get something up and running for my class this semester fairly quickly. Since I haven't used LiveGraphics3D, I'm unsure of all of its capabilities, but I do agree that to get the OPL problems up and running again, the easiest approach would be preferable, and plotly might not be that.

I have thought about extending the capabilities to use plotly to create other graph types, since it has a wide range of graphs it can create, both 2D and 3D, but the graphs are more geared towards data visualization.

@lahvak
Copy link

lahvak commented Sep 30, 2022

Just today I was experimenting with another option for including static interactive 3d objects in WebWorK: asymptote can generate 3d interactive images in html files, like for example this one. They can be easily included in a WeBWorK problem in an iframe. You can just include them in the problem's directory like png images. Asymptote can also create a pdf version to include in the hardcopy.

Theoretically it would be possible to even generate these dynamically on the server, similarly to tikz images, but I think that would be too slow and resource hungry, asymptote used gpu to create these and takes quite a bit of time.

@drgrice1
Copy link
Sponsor Member

It seems that you are correct @taniwallach. The javascript method still does work. I guess I didn't test that like I thought I did. I will put in a pull request to reinstate that.

drgrice1 added a commit to drgrice1/pg that referenced this pull request Sep 30, 2022
It turns out @taniwallach was correct in that the javascript conversion
method does work.  See the discussion in openwebwork#728.

There is still some more work that is needed here (cleanup really).  The
se_javascript_for_live3d option needs to be removed, and that needs to
be always used.  Setting that option to 0 will fail in any case.  This
pull request makes these problems work at least.
drgrice1 added a commit to drgrice1/pg that referenced this pull request Sep 30, 2022
It turns out @taniwallach was correct in that the javascript conversion
method does work.  See the discussion in openwebwork#728.

There is still some more work that is needed here (cleanup really).  The
se_javascript_for_live3d option needs to be removed, and that needs to
be always used.  Setting that option to 0 will fail in any case.  This
pull request makes these problems work at least.
drgrice1 added a commit to drgrice1/pg that referenced this pull request Sep 30, 2022
It turns out @taniwallach was correct in that the javascript conversion
method does work.  See the discussion in openwebwork#728.

There is still some more work that is needed here (cleanup really).  The
se_javascript_for_live3d option needs to be removed, and that needs to
be always used.  Setting that option to 0 will fail in any case.  This
pull request makes these problems work at least.
drgrice1 added a commit to drgrice1/pg that referenced this pull request Oct 3, 2022
It turns out @taniwallach was correct in that the javascript conversion
method does work.  See the discussion in openwebwork#728.

There is still some more work that is needed here (cleanup really).  The
se_javascript_for_live3d option needs to be removed, and that needs to
be always used.  Setting that option to 0 will fail in any case.  This
pull request makes these problems work at least.
@pstaabp
Copy link
Sponsor Member

pstaabp commented Oct 5, 2022

The LiveGraphics3D.pl has been fixed in #732 and #33.

I think this can still be considered and perhaps a replacement for that however. We may want to discuss how to move forward with 3D graphics.

@drgrice1
Copy link
Sponsor Member

drgrice1 commented Oct 6, 2022

I agree with @pstaabp. Although the LiveGraphics3D.pl macro is fixed, a better replacement is needed. This may be a good way to go. Another option would be to use the JSXGraph library that the GraphTool.pl macro uses. JSXGraph supports 3D graphs as well. It is better for interactive graphing. Plotly is better for data visualization. So the choice depends somewhat on what is desired.

@somiaj
Copy link
Contributor Author

somiaj commented Nov 1, 2022

I have updated how this work slightly by moving adding a surface into its own method. This currently allows adding multiple surfaces. Here is the example problem updated to fit the changes.

DOCUMENT();
loadMacros(
    'PGstandard.pl',
    'PGML.pl',
    'plotly3D.pl',
);

$gr[0] = Graph3D(
    height => 300,
    width  => 300,
    title  => 'Graph A',
);
$gr[0]->addSurface(
    xFunc  => sub { $_[0]*cos($_[1]) },
    yFunc  => sub { $_[0]*sin($_[1]) },
    zFunc  => sub { $_[0]**2 },
    uMin   => 0,
    uMax   => 2,
    vMin   => 0,
    vMax   => 2*pi + 0.1,
);
$gr[1] = Graph3D(
    height => 300,
    width  => 300,
    title  => 'Graph B',
);
$gr[1]->addSurface(zFunc  => sub { $_[0]**2 - $_[1]**2 });
$gr[2] = Graph3D(
    height => 300,
    width  => 300,
    title  => 'Graph C',
);
$gr[2]->addSurface(
    xFunc  => sub { $_[0]*cos($_[1]) },
    yFunc  => sub { $_[0]*sin($_[1]) },
    zFunc  => sub { $_[0] },
    uMin   => 0,
    uMax   => 2,
    vMin   => 0,
    vMax   => 2*pi + 0.1,
);
$gr[3] = Graph3D(
    height => 300,
    width  => 300,
    title  => 'Graph D',
);
$gr[3]->addSurface(
    zFunc  => sub { exp(-$_[0]**2 - $_[1]**2) },
    uMin   => -2,
    uMax   => 2,
    vMin   => -2,
    vMax   => 2,
);

BEGIN_PGML
Below are graphs for the following functions of two variables:

A) [``f(x,y) = x^2 + y^2``]

        [@ $gr[0]->Print @]*

B) [``f(x,y) = x^2 - y^2``]

        [@ $gr[1]->Print @]*

C) [``f(x,y) = \sqrt{x^2 + y^2}``]

        [@ $gr[2]->Print @]*

D) [``f(x,y) = e^{-x^2 - y^2}``]

        [@ $gr[3]->Print @]*

END_PGML
ENDDOCUMENT();

@pstaabp
Copy link
Sponsor Member

pstaabp commented Nov 2, 2022

This looks nice (much better than the current live3d code) and the interaction works quite well. I'm guess we can also use this for 3D line graphs and perhaps 3D vector fields.

@drgrice1 asked about using JSXgraph underneath as another option. Seems like we should test both before making a decision for the direction to go with interactive 3D graphs.

@somiaj
Copy link
Contributor Author

somiaj commented Nov 6, 2022

@pstaabp There might also be room for both, as they do slightly different things. Also plotly could be extended to barcharts, scatter plots, histograms, piecharts, and much more depending on what people need from it. Though a lot might depend on what people want/need and who is going to put in the effort.

I am continuing to improve/develop this as I'm currently using it for my Calculus III course and so far it is working well, creates nice graphs that are helping student visualize some common surfaces in 3D. I have added the ability to add curves to the 3D plot. I have also added the ability to use JavaScript functions client side instead of perl functions server side to generate the plots.

I'll leave this here as I keep improving it for my use, though probably won't need to be merged unless others start using it. If anyone starts using this and needs features added, just let me know.

@somiaj
Copy link
Contributor Author

somiaj commented Nov 8, 2022

Yet again updated this. I switched to using JavaScript functions by default to save on server load, and added a converter from math formulas to JavaScript so the parametric curves/surfaces can be written easier. Also simplified the input to give an array of functions vs having to define each and every option. Here is the new test case.

DOCUMENT();
loadMacros(
    'PGstandard.pl',
    'PGML.pl',
    'plotly3D.pl',
);

$gr[0] = Graph3D(height => 300, width => 300, title => 'Graph A');
$gr[0]->addSurface(['u*cos(v)', 'u*sin(v)', 'u^2'], [0, 2], [0, 2*pi]);

$gr[1] = Graph3D(height => 300, width => 300, title => 'Graph B');
$gr[1]->addSurface(['u', 'v', 'u^2 + v^2'], [-2, 2], [-2, 2]);

$gr[2] = Graph3D(height => 300, width => 300, title => 'Graph C');
$gr[2]->addSurface(['u*cos(v)', 'u*sin(v)', 'u'], [0, 2], [0, 2*pi]);

$gr[3] = Graph3D(height => 300, width => 300, title => 'Graph D');
$gr[3]->addSurface(['u', 'v', 'exp(-(u^2 + v^2))'], [-2, 2], [-2, 2]);

$gr[4] = Graph3D(height => 300, width => 300, title => 'Graph E');
$gr[4]->addSurface(
    ['2*sin(v)*cos(u)', '2*sin(v)*sin(u)', '2*cos(v)'],
    [0, 2*pi],
    [0, pi],
    colorscale => 'Greys',
    opacity    => 0.3
);
$gr[4]->addCurve(
    ['2*sqrt(1 - (t/(6*pi))^2)*cos(t)', '2*sqrt(1 - (t/(6*pi))^2)*sin(t)', 't/(3*pi)'],
    [-6*pi, 6*pi, 400],
    width => 7
);

BEGIN_PGML
Below are graphs for the following functions of two variables:

A) [``f(x,y) = x^2 + y^2``]

        [@ $gr[0]->Print @]*

B) [``f(x,y) = x^2 - y^2``]

        [@ $gr[1]->Print @]*

C) [``f(x,y) = \sqrt{x^2 + y^2}``]

        [@ $gr[2]->Print @]*

D) [``f(x,y) = e^{-x^2 - y^2}``]

        [@ $gr[3]->Print @]*

E) Curve wrapped around a sphere.

        [@ $gr[4]->Print @]*

END_PGML
ENDDOCUMENT();

  This macro adds a Graph3D function which creates parametric
  surface plots using the plotly JavaScript library.
  https://plotly.com/javascript/
@somiaj somiaj marked this pull request as ready for review March 3, 2023 17:33
@somiaj
Copy link
Contributor Author

somiaj commented Mar 3, 2023

Moved the macro into the new location, and this is no longer a draft. I'm currently using this macro in my classes, but it might take others using it to help spot issues or improve it. For my use case it works well to create interactive 3D graphs for my Calc III course.

@pstaabp pstaabp merged commit 8340d9c into openwebwork:develop Mar 9, 2023
Copy link
Sponsor Member

@drgrice1 drgrice1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was merged before it was properly reviewed. There are some changes needed. Please make the requested changes and put in another pull request.

macros/graph/plotly3D.pl Show resolved Hide resolved
macros/graph/plotly3D.pl Show resolved Hide resolved
macros/graph/plotly3D.pl Show resolved Hide resolved
@drgrice1
Copy link
Sponsor Member

This macro will also need some more improvements to work for webwork3.

@drgrice1
Copy link
Sponsor Member

This is really injecting a lot of javascript into the page. It would be better to move that javascript into an actual javascript file in htdocs. It should be possible to pass in the data by injecting a minimal amount of javascript into the page. See the way the parserGraphTool.pl macro does this. Although, I think I inject to much into the page with that macro, and intend to do that differently. I think we need modules!

@somiaj
Copy link
Contributor Author

somiaj commented Mar 13, 2023

I'll have to look at what you did to try to move some of the javascript out of the page. It might take me a bit to figure out how to do this nicely, since from my viewpoint, a lot of the javascript is dependent on what the function the user wants to plot is.

Is there a macro where the javascript could be moved to the <head></head> of the page, or by modules, would this be a way we can generate a .js file that is then loaded?

Anyways, I created a pull request with the other requested changes, we can move the conversation over there.

@drgrice1
Copy link
Sponsor Member

No javascript should be added to <head></head>. Never do that.

Don't worry about this for now if it is too much work. I can take a look at it at some point. Eventually we will want to convert all of our javascript to using modules. It is better than using deferred javascript as it makes it possible to avoid polluting the global namespace.

@drgrice1
Copy link
Sponsor Member

By the way, that is the worst part of the javascript in this macro. It horrendously pollutes the global namespace. That is something that really does need work.

@somiaj
Copy link
Contributor Author

somiaj commented Mar 13, 2023

Even though all the javascript is inside functions() that are run on page load?

@drgrice1
Copy link
Sponsor Member

Yeah, I see now. It is all inside a function. So that is good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants