diff --git a/htdocs/js/LiveGraphics/liveGraphics.js b/htdocs/js/LiveGraphics/liveGraphics.js
index 6e6ce88e88..fc16a4a71b 100644
--- a/htdocs/js/LiveGraphics/liveGraphics.js
+++ b/htdocs/js/LiveGraphics/liveGraphics.js
@@ -1,913 +1,882 @@
// liveGraphics.js
// This is a javascript based replacement for the LiveGraphics3D java library
-//
+//
// This program is free software; you can redistribute it and/or modify it under
// the terms of either: (a) the GNU General Public License as published by the
// Free Software Foundation; either version 2, or (at your option) any later
// version, or (b) the "Artistic License" which comes with this package.
-//
+//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
// Artistic License for more details.
var LiveGraphics3D = function (container, options) {
- var my = this;
-
- // define x3d container and scene
- var x3d = $("").appendTo(container)
- .css('width',options.width+'px')
- .css('height',options.height+'px')
- .css('border','none')
- .css('overflow','hidden')
- .attr('swfpath','/webwork2_files/js/vendor/x3dom/x3dom.swf');
-
- $("
").addClass('sr-only')
- .text('A manipulable 3d graph.')
- .prependTo(container);
-
- // disable mousewheel on container because its used for zoom
- $(x3d).bind('DOMMouseScroll mousewheel',function(event) {
- event.preventDefault();
- });
-
- var scene = $("").appendTo(x3d);
-
- // extend options by default values
- var defaults = {
- width : 200,
- height : 200,
- // Controls if axis are shown or not
- showAxes : false,
- // If the axis are shown determines if a full cube is drawn or just
- // the three axis lines
- showAxesCube : true,
- numTicks : 4,
- tickSize : .1,
- tickFontSize : .15,
- axisKey : ['X','Y','Z'],
- // Determines if the polygons forming the surface have their edges
- // drawn
- drawMesh : true,
- };
-
- var options = $.extend({}, defaults, options);
-
- //global variables
- //arrays of colors and thicknesses drawn from input
- var colors = {};
- var lineThickness = {};
-
- //scale elements capturing scale of plotted data
- var windowScale;
- var coordMins;
- var coordMaxs;
-
- //block indexes are used to associate objects to colors and thicknesses
- var blockIndex = 0;
- var surfaceBlockIndex = 0;
-
- //data from input
- var surfaceCoords = [];
- var surfaceIndex = [];
- var lineCoords = [];
- var lonePoints = [];
- var loneLabels = [];
-
- // This is the color map for shading surfaces based on elevation
- var colormap = [
- [0.00000, 0.00000, 0.50000],
- [0.00000, 0.00000, 0.56349],
- [0.00000, 0.00000, 0.62698],
- [0.00000, 0.00000, 0.69048],
- [0.00000, 0.00000, 0.75397],
- [0.00000, 0.00000, 0.81746],
- [0.00000, 0.00000, 0.88095],
- [0.00000, 0.00000, 0.94444],
- [0.00000, 0.00794, 1.00000],
- [0.00000, 0.07143, 1.00000],
- [0.00000, 0.13492, 1.00000],
- [0.00000, 0.19841, 1.00000],
- [0.00000, 0.26190, 1.00000],
- [0.00000, 0.32540, 1.00000],
- [0.00000, 0.38889, 1.00000],
- [0.00000, 0.45238, 1.00000],
- [0.00000, 0.51587, 1.00000],
- [0.00000, 0.57937, 1.00000],
- [0.00000, 0.64286, 1.00000],
- [0.00000, 0.70635, 1.00000],
- [0.00000, 0.76984, 1.00000],
- [0.00000, 0.83333, 1.00000],
- [0.00000, 0.89683, 1.00000],
- [0.00000, 0.96032, 1.00000],
- [0.02381, 1.00000, 0.97619],
- [0.08730, 1.00000, 0.91270],
- [0.15079, 1.00000, 0.84921],
- [0.21429, 1.00000, 0.78571],
- [0.27778, 1.00000, 0.72222],
- [0.34127, 1.00000, 0.65873],
- [0.40476, 1.00000, 0.59524],
- [0.46825, 1.00000, 0.53175],
- [0.53175, 1.00000, 0.46825],
- [0.59524, 1.00000, 0.40476],
- [0.65873, 1.00000, 0.34127],
- [0.72222, 1.00000, 0.27778],
- [0.78571, 1.00000, 0.21429],
- [0.84921, 1.00000, 0.15079],
- [0.91270, 1.00000, 0.08730],
- [0.97619, 1.00000, 0.02381],
- [1.00000, 0.96032, 0.00000],
- [1.00000, 0.89683, 0.00000],
- [1.00000, 0.83333, 0.00000],
- [1.00000, 0.76984, 0.00000],
- [1.00000, 0.70635, 0.00000],
- [1.00000, 0.64286, 0.00000],
- [1.00000, 0.57937, 0.00000],
- [1.00000, 0.51587, 0.00000],
- [1.00000, 0.45238, 0.00000],
- [1.00000, 0.38889, 0.00000],
- [1.00000, 0.32540, 0.00000],
- [1.00000, 0.26190, 0.00000],
- [1.00000, 0.19841, 0.00000],
- [1.00000, 0.13492, 0.00000],
- [1.00000, 0.07143, 0.00000],
- [1.00000, 0.00794, 0.00000],
- [0.94444, 0.00000, 0.00000],
- [0.88095, 0.00000, 0.00000],
- [0.81746, 0.00000, 0.00000],
- [0.75397, 0.00000, 0.00000],
- [0.69048, 0.00000, 0.00000],
- [0.62698, 0.00000, 0.00000],
- [0.56349, 0.00000, 0.00000],
- [0.50000, 0.00000, 0.00000]];
-
- // intialization function. This takes the mathmatica data string
- // and actually sets up the dom structure for the graph.
- // the actual graphing is done automatically by x3dom
- var initialize = function (datastring) {
-
- // parse matlab string
- parseLive3DData(datastring);
-
- // find extremum for axis and window scale
- setExtremum();
-
- // set up scene veiwpoint to be along the x axis looking to the
- // origin
- scene.append($("").
- attr('rotation',[1,0,0,Math.PI/2])
- .append($("")
- .attr( "fieldofview", .9)
- .attr( "position", [2*windowScale,0,0] )
- .attr( "orientation", [0,1,0,Math.PI/2])));
-
- scene.append($('').attr('skycolor','1 1 1'));
-
- // draw components of scene
- if (options.showAxes) {
- drawAxes();
- }
-
- drawSurface();
- drawLines();
- drawLonePoints();
- drawLoneLabels();
-
- };
-
- var parseLive3DData = function(text) {
- // Set up variables
- $.each(options.vars, function (name, data) {
- eval(name+'='+data);
+ // define x3d container and scene
+ var x3d = $('')
+ .appendTo(container)
+ .css('width', options.width + 'px')
+ .css('height', options.height + 'px')
+ .css('border', 'none')
+ .css('overflow', 'hidden');
+
+ $('').addClass('sr-only').text('A manipulable 3d graph.').prependTo(container);
+
+ // disable mousewheel on container because its used for zoom
+ $(x3d).bind('DOMMouseScroll mousewheel', function (event) {
+ event.preventDefault();
});
- // this parses axes commands.
- if (text.match(/Axes\s*->\s*True/)) {
- options.showAxes = true;
- }
+ var scene = $('').appendTo(x3d);
+
+ // extend options by default values
+ var defaults = {
+ width: 200,
+ height: 200,
+ // Controls if axis are shown or not
+ showAxes: false,
+ // If the axis are shown determines if a full cube is drawn or just
+ // the three axis lines
+ showAxesCube: true,
+ numTicks: 4,
+ tickSize: 0.1,
+ tickFontSize: 0.15,
+ axisKey: ['X', 'Y', 'Z'],
+ // Determines if the polygons forming the surface have their edges
+ // drawn
+ drawMesh: true
+ };
+
+ var options = $.extend({}, defaults, options);
+
+ //global variables
+ //arrays of colors and thicknesses drawn from input
+ var colors = {};
+ var lineThickness = {};
+
+ //scale elements capturing scale of plotted data
+ var windowScale;
+ var coordMins;
+ var coordMaxs;
+
+ //block indexes are used to associate objects to colors and thicknesses
+ var blockIndex = 0;
+ var surfaceBlockIndex = 0;
+
+ //data from input
+ var surfaceCoords = [];
+ var surfaceIndex = [];
+ var lineCoords = [];
+ var lonePoints = [];
+ var loneLabels = [];
+
+ // This is the color map for shading surfaces based on elevation
+ var colormap = [
+ [0.0, 0.0, 0.5],
+ [0.0, 0.0, 0.56349],
+ [0.0, 0.0, 0.62698],
+ [0.0, 0.0, 0.69048],
+ [0.0, 0.0, 0.75397],
+ [0.0, 0.0, 0.81746],
+ [0.0, 0.0, 0.88095],
+ [0.0, 0.0, 0.94444],
+ [0.0, 0.00794, 1.0],
+ [0.0, 0.07143, 1.0],
+ [0.0, 0.13492, 1.0],
+ [0.0, 0.19841, 1.0],
+ [0.0, 0.2619, 1.0],
+ [0.0, 0.3254, 1.0],
+ [0.0, 0.38889, 1.0],
+ [0.0, 0.45238, 1.0],
+ [0.0, 0.51587, 1.0],
+ [0.0, 0.57937, 1.0],
+ [0.0, 0.64286, 1.0],
+ [0.0, 0.70635, 1.0],
+ [0.0, 0.76984, 1.0],
+ [0.0, 0.83333, 1.0],
+ [0.0, 0.89683, 1.0],
+ [0.0, 0.96032, 1.0],
+ [0.02381, 1.0, 0.97619],
+ [0.0873, 1.0, 0.9127],
+ [0.15079, 1.0, 0.84921],
+ [0.21429, 1.0, 0.78571],
+ [0.27778, 1.0, 0.72222],
+ [0.34127, 1.0, 0.65873],
+ [0.40476, 1.0, 0.59524],
+ [0.46825, 1.0, 0.53175],
+ [0.53175, 1.0, 0.46825],
+ [0.59524, 1.0, 0.40476],
+ [0.65873, 1.0, 0.34127],
+ [0.72222, 1.0, 0.27778],
+ [0.78571, 1.0, 0.21429],
+ [0.84921, 1.0, 0.15079],
+ [0.9127, 1.0, 0.0873],
+ [0.97619, 1.0, 0.02381],
+ [1.0, 0.96032, 0.0],
+ [1.0, 0.89683, 0.0],
+ [1.0, 0.83333, 0.0],
+ [1.0, 0.76984, 0.0],
+ [1.0, 0.70635, 0.0],
+ [1.0, 0.64286, 0.0],
+ [1.0, 0.57937, 0.0],
+ [1.0, 0.51587, 0.0],
+ [1.0, 0.45238, 0.0],
+ [1.0, 0.38889, 0.0],
+ [1.0, 0.3254, 0.0],
+ [1.0, 0.2619, 0.0],
+ [1.0, 0.19841, 0.0],
+ [1.0, 0.13492, 0.0],
+ [1.0, 0.07143, 0.0],
+ [1.0, 0.00794, 0.0],
+ [0.94444, 0.0, 0.0],
+ [0.88095, 0.0, 0.0],
+ [0.81746, 0.0, 0.0],
+ [0.75397, 0.0, 0.0],
+ [0.69048, 0.0, 0.0],
+ [0.62698, 0.0, 0.0],
+ [0.56349, 0.0, 0.0],
+ [0.5, 0.0, 0.0]
+ ];
+
+ // intialization function. This takes the mathmatica data string
+ // and actually sets up the dom structure for the graph.
+ // the actual graphing is done automatically by x3dom
+ var initialize = function (datastring) {
+ // parse matlab string
+ parseLive3DData(datastring);
+
+ // find extremum for axis and window scale
+ setExtremum();
+
+ // set up scene veiwpoint to be along the x axis looking to the
+ // origin
+ scene.append(
+ $('')
+ .attr('rotation', [1, 0, 0, Math.PI / 2])
+ .append(
+ $('')
+ .attr('fieldofview', 0.9)
+ .attr('position', [2 * windowScale, 0, 0])
+ .attr('orientation', [0, 1, 0, Math.PI / 2])
+ )
+ );
+
+ scene.append($('').attr('skycolor', '1 1 1'));
+
+ // draw components of scene
+ if (options.showAxes) {
+ drawAxes();
+ }
- // get some initial global configuration
- var labels = text.match(/AxesLabel\s*->\s*\{\s*(\w+),\s*(\w+),\s*(\w+)\s*\}/);
+ drawSurface();
+ drawLines();
+ drawLonePoints();
+ drawLoneLabels();
+ };
- if (labels) {
- options.axisKey = [labels[1],labels[2],labels[3]];
- }
+ var parseLive3DData = function (text) {
+ // Set up variables
+ $.each(options.vars, function (name, data) {
+ eval(name + '=' + data);
+ });
- // split the input into blocks and parse
- var blocks = recurseMathematicaBlocks(text);
-
- parseMathematicaBlocks(blocks);
-
- };
-
- // find max and min of all mesh coordinate points and
- // the maximum coordinate value for the scale.
- var setExtremum = function () {
- var min = [0,0,0];
- var max = [0,0,0];
-
- surfaceCoords.forEach(function(point) {
- for (var i=0; i< 3; i++) {
- if (point[i] < min[i]) {
- min[i] = point[i];
- } else if (point[i]>max[i]) {
- max[i] = point[i];
- }
- }
- });
-
- lineCoords.forEach(function(line) {
- for (var i=0; i<2; i++) {
- for (var j=0; j<3; j++) {
- if (line[i][j] < min[j]) {
- min[j] = line[i][j];
- } else if (line[i][j]>max[j]) {
- max[j] = line[i][j];
- }
+ // this parses axes commands.
+ if (text.match(/Axes\s*->\s*True/)) {
+ options.showAxes = true;
}
- }
- });
- coordMins = min;
- coordMaxs = max;
-
- var sum = 0;
- for (var i=0; i< 3; i++) {
- sum += max[i]-min[i];
- }
-
- windowScale = sum/3;
- };
-
- var drawLines = function() {
- if (lineCoords.length==0) {
- return;
- }
+ // get some initial global configuration
+ var labels = text.match(/AxesLabel\s*->\s*\{\s*(\w+),\s*(\w+),\s*(\w+)\s*\}/);
- // Add surface to scene as an indexedfaceset
-
- var linegroup = $('');
-
- lineCoords.forEach(function(line){
-
- // lines are cylinders that start centered at the origin
- // along the y axis. We have to translate and rotate them
- // into place
- var length = Math.sqrt(Math.pow((line[0][0]-line[1][0]),2)+
- Math.pow((line[0][1]-line[1][1]),2)+
- Math.pow((line[0][2]-line[1][2]),2));
- var rotation = [];
-
- if (length == 0) {
- return;
- }
-
- rotation[0] = (line[1][2]-line[0][2]);
- rotation[1] = 0;
- rotation[2] = (line[0][0]-line[1][0]);
- rotation[3] = Math.acos((line[1][1]-line[0][1])/length);
-
- var trans = [0,0,0];
-
- for (var i=0; i < 3; i++) {
- trans[i] = (line[1][i] + line[0][i])/2;
- }
-
- var shape = $("").appendTo($("")
- .attr('translation',trans)
- .attr('rotation',rotation)
- .appendTo(linegroup));
- var color = [0,0,0];
- var radius = .005;
-
- // line[2] contains the block index
- if (line[2] in colors) {
- color = colors[line[2]];
- }
-
- if (line[2] in lineThickness) {
- radius = Math.max(lineThickness[line[2]],.005);
- }
-
- $("").appendTo(shape)
- .append($("")
- .attr('diffusecolor',color));
-
- shape.append($("")
- .attr("height", length)
- .attr("radius", radius*2));
- });
-
- scene.append(linegroup);
- }
-
- var drawSurface = function() {
- var coordstr = '';
- var indexstr = '';
- var colorstr = '';
- var colorindstr = '';
-
- if (surfaceCoords.length == 0) {
- return;
- }
+ if (labels) {
+ options.axisKey = [labels[1], labels[2], labels[3]];
+ }
- // build a string with all the surface coodinates
- surfaceCoords.forEach(function(point) {
- coordstr += point[0]+' '+point[1]+' '+point[2]+' ';
- });
-
- // build a string with all the surface indexes
- // at the same time build a string with color data
- // and the associated color indexes
- surfaceIndex.forEach(function(index) {
- indexstr += index+' ';
-
- if (index == -1) {
- colorindstr += '-1 ';
- return;
- }
-
- var cindex = parseInt((surfaceCoords[index][2]-coordMins[2])/(coordMaxs[2]-coordMins[2])*colormap.length);
-
- if (cindex == colormap.length) {
- cindex--;
- }
-
- colorindstr += cindex+' ';
-
- });
-
- colormap.forEach(function(color) {
- for (var i=0; i<3; i++) {
- color[i] += .2;
- color[i] = Math.min(color[i],1);
- }
-
- colorstr += color[0]+' '+color[1]+' '+color[2]+' ';
- });
-
- var flatcolor = false;
- var color = [];
+ // split the input into blocks and parse
+ var blocks = recurseMathematicaBlocks(text);
- if (surfaceBlockIndex in colors) {
- flatcolor = true;
- color = colors[surfaceBlockIndex];
- }
+ parseMathematicaBlocks(blocks);
+ };
- // Add surface to scene as an indexedfaceset
- var shape = $("").appendTo(scene);
-
- var appearance = $("").appendTo(shape);
-
- appearance .append($("")
- .attr("ambientIntensity",'0')
- .attr('convex','false')
- .attr('creaseangle',Math.PI)
- .attr('diffusecolor',color)
- .attr("shininess",".015"));
-
- var indexedfaceset = $("")
- .attr('coordindex',indexstr)
- .attr('solid','false');
-
- indexedfaceset.append($("")
- .attr('point',coordstr));
-
- if (!flatcolor) {
- indexedfaceset.attr('colorindex',colorindstr);
- indexedfaceset.append($("")
- .attr('color',colorstr));
- }
+ // find max and min of all mesh coordinate points and
+ // the maximum coordinate value for the scale.
+ var setExtremum = function () {
+ var min = [0, 0, 0];
+ var max = [0, 0, 0];
- // append the indexed face set to the shape after its assembled.
- // otherwise sometimes x3d tries to access the various data before
- // its ready
- indexedfaceset.appendTo(shape);
-
- if (options.drawMesh) {
-
- shape = $("").appendTo(scene);
-
- appearance = $("").appendTo(shape);
-
- appearance .append($("")
- .attr('diffusecolor',[0,0,0]));
-
- var indexedlineset = $("")
- .attr('coordindex',indexstr)
- .attr('solid','true');
-
- indexedlineset.append($("")
- .attr('point',coordstr));
-
- indexedlineset.appendTo(shape);
- }
+ surfaceCoords.forEach(function (point) {
+ for (var i = 0; i < 3; i++) {
+ if (point[i] < min[i]) {
+ min[i] = point[i];
+ } else if (point[i] > max[i]) {
+ max[i] = point[i];
+ }
+ }
+ });
- }
-
- var drawAxes = function() {
-
- // build x axis and add the ticks.
- // all of this is done in two dimensions and then rotated and shifted
- // into place
- var xgroup = $("").appendTo($("")
- .appendTo(scene)
- .attr('translation',[0,coordMins[1],coordMins[2]]));
-
- var xaxis = $("").append($("")
- .append($("")
- .attr("emissiveColor", 'black')
- ))
- .append($("")
- .attr("lineSegments", coordMins[0]+' 0 '+coordMaxs[0]+' 0'));
- xgroup.append(xaxis);
-
- $.each(makeAxisTicks(0),function() {
- this.appendTo(xgroup)});
-
- if (options.showAxesCube) {
-
- var trans = [[0,coordMins[1],coordMaxs[2]],
- [0,coordMaxs[1],coordMins[2]],
- [0,coordMaxs[1],coordMaxs[2]]];
-
- trans.forEach(function (tran) {
- $("").attr('translation',tran)
- .appendTo(scene)
- .append($("").append($("")
- .append($("")
- .attr("emissiveColor", 'black')
- ))
- .append($("")
- .attr("lineSegments", coordMins[0]+' 0 '+coordMaxs[0]+' 0')));
- });
- }
+ lineCoords.forEach(function (line) {
+ for (var i = 0; i < 2; i++) {
+ for (var j = 0; j < 3; j++) {
+ if (line[i][j] < min[j]) {
+ min[j] = line[i][j];
+ } else if (line[i][j] > max[j]) {
+ max[j] = line[i][j];
+ }
+ }
+ }
+ });
+ coordMins = min;
+ coordMaxs = max;
- // build y axis and add the ticks
- var ygroup = $("").appendTo($("")
- .appendTo(scene)
- .attr('translation',[coordMins[0],0,coordMins[2]])
- .attr('rotation',[0,0,1,Math.PI/2]));
-
- var yaxis = $("").append($("")
- .append($("")
- .attr("emissiveColor", 'black')
- ))
- .append($("")
- .attr("lineSegments", coordMins[1]+' 0 '+coordMaxs[1]+' 0'));
- ygroup.append(yaxis);
-
- $.each(makeAxisTicks(1),function() {
- this.appendTo(ygroup)});
-
- if (options.showAxesCube) {
-
- var trans = [[coordMins[0],0,coordMaxs[2]],
- [coordMaxs[0],0,coordMins[2]],
- [coordMaxs[0],0,coordMaxs[2]]];
-
- trans.forEach(function (tran) {
- $("").attr('translation',tran)
- .attr('rotation',[0,0,1,Math.PI/2])
- .appendTo(scene)
- .append($("").append($("")
- .append($("")
- .attr("emissiveColor", 'black')
- ))
- .append($("")
- .attr("lineSegments", coordMins[1]+' 0 '+coordMaxs[1]+' 0')));
- });
- }
-
- // build z axis and add the ticks
- var zgroup = $("").appendTo($("")
- .appendTo(scene)
- .attr('translation',[coordMins[0],coordMins[1],0])
- .attr('rotation',[0,1,0,-Math.PI/2]));
-
- var zaxis = $("").append($("")
- .append($("")
- .attr("emissiveColor", 'black')
- ))
- .append($("")
- .attr("lineSegments", coordMins[2]+' 0 '+coordMaxs[2]+' 0'));
-
- zgroup.append(zaxis);
-
- $.each(makeAxisTicks(2),function() {
- this.appendTo(zgroup)});
-
- if (options.showAxesCube) {
-
- var trans = [[coordMins[0],coordMaxs[1],0],
- [coordMaxs[0],coordMins[1],0],
- [coordMaxs[0],coordMaxs[1],0]];
-
- trans.forEach(function (tran) {
- $("").attr('translation',tran)
- .attr('rotation',[0,1,0,-Math.PI/2])
- .appendTo(scene)
- .append($("").append($("")
- .append($("")
- .attr("emissiveColor", 'black')
- ))
- .append($("")
- .attr("lineSegments", coordMins[2]+' 0 '+coordMaxs[2]+' 0')));
- });
- }
-
- }
-
- // biuilds the ticks, the tick labels, and the axis label for
- // axisindex I
- var makeAxisTicks = function (I) {
- var shapes = [];
-
- for(var i=0; i").append($($("")
- .append($("")
- .attr("diffuseColor","black"))));
- tick.appendTo($("")
- .attr('translation',[coord,0,0]));
-
- tick.append($("")
- .attr('size', options.tickSize+' '
- +options.tickSize+' '+
- options.tickSize));
-
- shapes.push(tick.parent());
-
- // labels have two decimal places and always point towards view
- var ticklabel = $("").append($($("")
- .append($("")
- .attr("diffuseColor","black"))));
-
- ticklabel.appendTo($("")
- .attr("axisOfRotation", "0 0 0")
- .appendTo($("")
- .attr('translation',[coord,.1,0])));
-
- ticklabel.append($("")
- .attr('string',coord.toFixed(2))
- .attr('solid','true')
- .append($("")
- .attr('size',options.tickFontSize*windowScale)
- .attr('family', "mono")
- .attr('style', 'bold')
- .attr('justify', 'MIDDLE')));
-
- shapes.push(ticklabel.parent().parent());
+ var sum = 0;
- }
-
- // axis label goes on the end of the axis.
- var axislabel = $("").append($($("")
- .append($("")
- .attr("diffuseColor","black"))));
-
- axislabel.appendTo($("")
- .attr("axisOfRotation", "0 0 0")
- .appendTo($("")
- .attr('translation',[coordMaxs[I],.1,0])));
-
- axislabel.append($("")
- .attr('string',options.axisKey[I])
- .attr('solid','true')
- .append($("")
- .attr('size',options.tickFontSize*windowScale)
- .attr('family', "mono")
- .attr('style', 'bold')
- .attr('justify', 'MIDDLE')));
-
- shapes.push(axislabel.parent().parent());
-
- return shapes;
- }
-
- var drawLonePoints = function () {
-
- lonePoints.forEach(function (point) {
-
- var color = 'black';
- if (point.rgb) {
- color=point.rgb;
- }
-
- // lone points are drawn as spheres so they have mass
- var sphere = $("").append($($("")
- .append($("")
- .attr("diffuseColor",color))));
- sphere.appendTo($("")
- .attr('translation',point.coords));
-
- sphere.append($("")
- .attr('radius',point.radius*2.25));
-
- sphere.parent().appendTo(scene);
-
- });
-
- }
-
- var drawLoneLabels = function () {
-
- loneLabels.forEach(function (label) {
-
- // the text is a billboard that automatically faces the user
- var text = $("").append($($("")
- .append($("")
- .attr("diffuseColor",'black'))));
-
- text.appendTo($("")
- .attr("axisOfRotation", "0 0 0")
- .appendTo($("")
- .attr('translation',label.coords)));
-
- var size = '.5';
- if (label.size) {
- //mathematica label sizes are fontsizes, where
- //the units for x3dom are local coord sizes
- size = label.size/(1.5*windowScale);
- }
-
- text.append($("")
- .attr('string',label.text)
- .attr('solid','true')
- .append($("")
- .attr('size',size)
- .attr('family', "mono")
- .attr('justify', 'MIDDLE')));
-
- text.parent().parent().appendTo(scene);
-
- });
-
- }
-
- var parseMathematicaBlocks = function (blocks) {
-
- blocks.forEach(function(block) {
- blockIndex++;
-
- if (block.match(/^\s*\{/)) {
- // This is a block inside of a block.
- // so recurse
- var subblocks = recurseMathematicaBlocks(block);
- parseMathematicaBlocks(subblocks);
-
- } else if (block.match(/Point/)) {
- // now find any individual points that need to be plotted
- // points are defined by short blocks so we dont split into
- // individual commands
- var str = block.match(/Point\[\s*\{\s*(-?\d*\.?\d*)\s*,\s*(-?\d*\.?\d*)\s*,\s*(-?\d*\.?\d*)\s*\}/);
- var point = {};
-
- if (!str) {
- console.log('Error Parsing Point');
- return;
- }
-
- point.coords = [parseFloat(str[1]),parseFloat(str[2]),parseFloat(str[3])];
-
- str = block.match(/PointSize\[\s*(\d*\.?\d*)\s*\]/);
-
- if (str) {
- point.radius = parseFloat(str[1]);
+ for (var i = 0; i < 3; i++) {
+ sum += max[i] - min[i];
}
-
- str = block.match(/RGBColor\[\s*(\d*\.?\d*)\s*,\s*(\d*\.?\d*)\s*,\s*(\d*\.?\d*)\s*\]/);
-
- if (str) {
- point.rgb = [parseFloat(str[1]),parseFloat(str[2]),parseFloat(str[3])];
+
+ windowScale = sum / 3;
+ };
+
+ var drawLines = function () {
+ if (lineCoords.length == 0) {
+ return;
}
-
- lonePoints.push(point);
-
- } else {
- // Otherwise its a list of commands that we need to
- // process individually
- var commands = splitMathematicaBlocks(block);
-
- commands.forEach(function(command) {
- if (command.match(/^\s*\{/)) {
- // This is a block inside of a block.
- // so recurse
- var subblocks = recurseMathematicaBlocks(block);
- parseMathematicaBlocks(subblocks);
- } else if (command.match(/Polygon/)) {
- if (!surfaceBlockIndex) {
- surfaceBlockIndex = blockIndex;
- }
- var polystring = command.replace(/Polygon\[([^\]]*)\]/,"$1");
- var pointstrings = recurseMathematicaBlocks(polystring,-1);
- // for each polygon extract all the points
- pointstrings.forEach(function(pointstring) {
- pointstring = pointstring.replace(/\{([^\{]*)\}/,"$1");
-
- var splitstring = pointstring.split(',');
- var point = [];
-
- for (var i=0; i < 3; i++) {
- point[i] = parseFloat(eval(splitstring[i]));
- }
-
- // find the index of the point in surfaceCoords. If
- // the point is not in surfaceCoords, add it
- for (var i=0; i');
+
+ lineCoords.forEach(function (line) {
+ // lines are cylinders that start centered at the origin
+ // along the y axis. We have to translate and rotate them
+ // into place
+ var length = Math.sqrt(
+ Math.pow(line[0][0] - line[1][0], 2) +
+ Math.pow(line[0][1] - line[1][1], 2) +
+ Math.pow(line[0][2] - line[1][2], 2)
+ );
+ var rotation = [];
+
+ if (length == 0) {
return;
- }
}
- line.push(blockIndex);
+ rotation[0] = line[1][2] - line[0][2];
+ rotation[1] = 0;
+ rotation[2] = line[0][0] - line[1][0];
+ rotation[3] = Math.acos((line[1][1] - line[0][1]) / length);
- lineCoords.push(line);
+ var trans = [0, 0, 0];
- } else if (command.match(/RGBColor/)) {
- var str = command.match(/RGBColor\[\s*(\d*\.?\d*)\s*,\s*(\d*\.?\d*)\s*,\s*(\d*\.?\d*)\s*\]/);
+ for (var i = 0; i < 3; i++) {
+ trans[i] = (line[1][i] + line[0][i]) / 2;
+ }
- colors[blockIndex] = [parseFloat(str[1]),parseFloat(str[2]),parseFloat(str[3])];
+ var shape = $('').appendTo(
+ $('').attr('translation', trans).attr('rotation', rotation).appendTo(linegroup)
+ );
+ var color = [0, 0, 0];
+ var radius = 0.005;
- } else if (command.match(/Thickness/)) {
- var str = command.match(/Thickness\[\s*(\d*\.?\d*)\s*\]/);
+ // line[2] contains the block index
+ if (line[2] in colors) {
+ color = colors[line[2]];
+ }
- lineThickness[blockIndex] = parseFloat(str[1]);
-
- } else if (command.match(/Text/)) {
- // now find any individual labels that need to be plotted
- var str = command.match(/\{\s*(-?\d*\.?\d*)\s*,\s*(-?\d*\.?\d*)\s*,\s*(-?\d*\.?\d*)\s*\}/);
- var label = {};
-
- if (!str) {
- console.log('Error Parsing Label');
- return;
+ if (line[2] in lineThickness) {
+ radius = Math.max(lineThickness[line[2]], 0.005);
}
-
- label.coords = [parseFloat(str[1]),parseFloat(str[2]),parseFloat(str[3])];
- str = command.match(/StyleForm\[\s*(\w+),\s*FontSize\s*->\s*(\d+)\s*\]/);
-
- if (!str) {
- console.log('Error Parsing Label');
- return;
+
+ $('').appendTo(shape).append($('').attr('diffusecolor', color));
+
+ shape.append(
+ $('')
+ .attr('height', length)
+ .attr('radius', radius * 2)
+ );
+ });
+
+ scene.append(linegroup);
+ };
+
+ var drawSurface = function () {
+ var coordstr = '';
+ var indexstr = '';
+ var colorstr = '';
+ var colorindstr = '';
+
+ if (surfaceCoords.length == 0) {
+ return;
+ }
+
+ // build a string with all the surface coodinates
+ surfaceCoords.forEach(function (point) {
+ coordstr += point[0] + ' ' + point[1] + ' ' + point[2] + ' ';
+ });
+
+ // build a string with all the surface indexes
+ // at the same time build a string with color data
+ // and the associated color indexes
+ surfaceIndex.forEach(function (index) {
+ indexstr += index + ' ';
+
+ if (index == -1) {
+ colorindstr += '-1 ';
+ return;
+ }
+
+ var cindex = parseInt(
+ ((surfaceCoords[index][2] - coordMins[2]) / (coordMaxs[2] - coordMins[2])) * colormap.length
+ );
+
+ if (cindex == colormap.length) {
+ cindex--;
}
-
- label.text = str[1];
- label.fontSize = str[2];
-
- loneLabels.push(label);
-
- }
+
+ colorindstr += cindex + ' ';
});
-
- }
- });
- }
-
- var splitMathematicaBlocks = function (text) {
- // This splits a list of mathematica commands on the commas
-
- var bracketcount = 0;
- var blocks = [];
- var block = '';
-
- for (var i=0; i < text.length; i++) {
-
- block += text.charAt(i);
-
- if (text.charAt(i) === '[') {
- bracketcount++;
- }
-
- if (text.charAt(i) == ']') {
- bracketcount--;
- if (bracketcount == 0) {
- i++;
- blocks.push(block);
- block = '';
+
+ colormap.forEach(function (color) {
+ for (var i = 0; i < 3; i++) {
+ color[i] += 0.2;
+ color[i] = Math.min(color[i], 1);
+ }
+
+ colorstr += color[0] + ' ' + color[1] + ' ' + color[2] + ' ';
+ });
+
+ var flatcolor = false;
+ var color = [];
+
+ if (surfaceBlockIndex in colors) {
+ flatcolor = true;
+ color = colors[surfaceBlockIndex];
}
- }
- }
+ // Add surface to scene as an indexedfaceset
+ var shape = $('').appendTo(scene);
- return blocks;
- }
+ var appearance = $('').appendTo(shape);
+ appearance.append(
+ $('')
+ .attr('ambientIntensity', '0')
+ .attr('convex', 'false')
+ .attr('creaseangle', Math.PI)
+ .attr('diffusecolor', color)
+ .attr('shininess', '.015')
+ );
+ var indexedfaceset = $('').attr('coordindex', indexstr).attr('solid', 'false');
- var recurseMathematicaBlocks = function (text,initialcount) {
- // the mathematica code comes in blocks encolsed by {}
- // this code makes an array of those blocks. The largest of them will
- // be the polygon block which defines the surface.
- var bracketcount = 0;
- var blocks = [];
- var block = '';
-
- if (initialcount) {
- bracketcount = initialcount;
- }
+ indexedfaceset.append($('').attr('point', coordstr));
+
+ if (!flatcolor) {
+ indexedfaceset.attr('colorindex', colorindstr);
+ indexedfaceset.append($('').attr('color', colorstr));
+ }
- for (var i=0; i < text.length; i++) {
+ // append the indexed face set to the shape after its assembled.
+ // otherwise sometimes x3d tries to access the various data before
+ // its ready
+ indexedfaceset.appendTo(shape);
- if (text.charAt(i) === '{') {
- bracketcount++;
- }
+ if (options.drawMesh) {
+ shape = $('').appendTo(scene);
- if (bracketcount > 0) {
- block += text.charAt(i);
- }
+ appearance = $('').appendTo(shape);
- if (text.charAt(i) == '}') {
- bracketcount--;
- if (bracketcount == 0) {
- blocks.push(block.substring(1,block.length-1));
- block = '';
+ appearance.append($('').attr('diffusecolor', [0, 0, 0]));
+
+ var indexedlineset = $('').attr('coordindex', indexstr).attr('solid', 'true');
+
+ indexedlineset.append($('').attr('point', coordstr));
+
+ indexedlineset.appendTo(shape);
}
+ };
+
+ var drawAxes = function () {
+ // build x axis and add the ticks.
+ // all of this is done in two dimensions and then rotated and shifted
+ // into place
+ var xgroup = $('').appendTo(
+ $('').appendTo(scene).attr('translation', [0, coordMins[1], coordMins[2]])
+ );
+
+ var xaxis = $('')
+ .append($('').append($('').attr('emissiveColor', 'black')))
+ .append($('').attr('lineSegments', coordMins[0] + ' 0 ' + coordMaxs[0] + ' 0'));
+ xgroup.append(xaxis);
+
+ $.each(makeAxisTicks(0), function () {
+ this.appendTo(xgroup);
+ });
- }
- }
+ if (options.showAxesCube) {
+ var trans = [
+ [0, coordMins[1], coordMaxs[2]],
+ [0, coordMaxs[1], coordMins[2]],
+ [0, coordMaxs[1], coordMaxs[2]]
+ ];
+
+ trans.forEach(function (tran) {
+ $('')
+ .attr('translation', tran)
+ .appendTo(scene)
+ .append(
+ $('')
+ .append($('').append($('').attr('emissiveColor', 'black')))
+ .append($('').attr('lineSegments', coordMins[0] + ' 0 ' + coordMaxs[0] + ' 0'))
+ );
+ });
+ }
- return blocks;
- }
-
-
- // This section of code is run whenever the object is created
- // run intialize with the mathematica string, possibly getting the string
- // form an ajax call if necessary
-
- if (options.input) {
- initialize(options.input);
- } else if (options.archive) {
- // If an archive file is provided then that is the file we get
- // the file name is then the file we want inside the archive.
- JSZipUtils.getBinaryContent(options.archive, function (error, data) {
- if (error) {
- console.log(error);
- $(container).html('Failed to get input archive');
- }
-
- JSZip.loadAsync(data).then((zip) => {
- zip.file(options.file).async('string').then((string) => initialize(string));
+ // build y axis and add the ticks
+ var ygroup = $('').appendTo(
+ $('')
+ .appendTo(scene)
+ .attr('translation', [coordMins[0], 0, coordMins[2]])
+ .attr('rotation', [0, 0, 1, Math.PI / 2])
+ );
+
+ var yaxis = $('')
+ .append($('').append($('').attr('emissiveColor', 'black')))
+ .append($('').attr('lineSegments', coordMins[1] + ' 0 ' + coordMaxs[1] + ' 0'));
+ ygroup.append(yaxis);
+
+ $.each(makeAxisTicks(1), function () {
+ this.appendTo(ygroup);
});
- });
-
- } else if (options.file) {
-
- $.ajax({
- url : options.file,
- dataType : 'text',
- async : 'true',
- success : function(data) {
- initialize(data);
- },
- error : function(x,y,error) {
- console.log(error);
- $(container).html('Failed to get input file');
- }});
-
- } else {
- $(container).html('No input data provided');
- }
-}
+ if (options.showAxesCube) {
+ var trans = [
+ [coordMins[0], 0, coordMaxs[2]],
+ [coordMaxs[0], 0, coordMins[2]],
+ [coordMaxs[0], 0, coordMaxs[2]]
+ ];
+
+ trans.forEach(function (tran) {
+ $('')
+ .attr('translation', tran)
+ .attr('rotation', [0, 0, 1, Math.PI / 2])
+ .appendTo(scene)
+ .append(
+ $('')
+ .append($('').append($('').attr('emissiveColor', 'black')))
+ .append($('').attr('lineSegments', coordMins[1] + ' 0 ' + coordMaxs[1] + ' 0'))
+ );
+ });
+ }
+
+ // build z axis and add the ticks
+ var zgroup = $('').appendTo(
+ $('')
+ .appendTo(scene)
+ .attr('translation', [coordMins[0], coordMins[1], 0])
+ .attr('rotation', [0, 1, 0, -Math.PI / 2])
+ );
+
+ var zaxis = $('')
+ .append($('').append($('').attr('emissiveColor', 'black')))
+ .append($('').attr('lineSegments', coordMins[2] + ' 0 ' + coordMaxs[2] + ' 0'));
+
+ zgroup.append(zaxis);
+
+ $.each(makeAxisTicks(2), function () {
+ this.appendTo(zgroup);
+ });
+
+ if (options.showAxesCube) {
+ var trans = [
+ [coordMins[0], coordMaxs[1], 0],
+ [coordMaxs[0], coordMins[1], 0],
+ [coordMaxs[0], coordMaxs[1], 0]
+ ];
+
+ trans.forEach(function (tran) {
+ $('')
+ .attr('translation', tran)
+ .attr('rotation', [0, 1, 0, -Math.PI / 2])
+ .appendTo(scene)
+ .append(
+ $('')
+ .append($('').append($('').attr('emissiveColor', 'black')))
+ .append($('').attr('lineSegments', coordMins[2] + ' 0 ' + coordMaxs[2] + ' 0'))
+ );
+ });
+ }
+ };
+
+ // biuilds the ticks, the tick labels, and the axis label for
+ // axisindex I
+ var makeAxisTicks = function (I) {
+ var shapes = [];
+
+ for (var i = 0; i < options.numTicks - 1; i++) {
+ // coordinate of tick and label
+ var coord = ((coordMaxs[I] - coordMins[I]) / options.numTicks) * (i + 1) + coordMins[I];
+
+ //ticks are boxes defined by tickSize
+ var tick = $('').append(
+ $($('').append($('').attr('diffuseColor', 'black')))
+ );
+ tick.appendTo($('').attr('translation', [coord, 0, 0]));
+
+ tick.append($('').attr('size', options.tickSize + ' ' + options.tickSize + ' ' + options.tickSize));
+
+ shapes.push(tick.parent());
+
+ // labels have two decimal places and always point towards view
+ var ticklabel = $('').append(
+ $($('').append($('').attr('diffuseColor', 'black')))
+ );
+
+ ticklabel.appendTo(
+ $('')
+ .attr('axisOfRotation', '0 0 0')
+ .appendTo($('').attr('translation', [coord, 0.1, 0]))
+ );
+
+ ticklabel.append(
+ $('')
+ .attr('string', coord.toFixed(2))
+ .attr('solid', 'true')
+ .append(
+ $('')
+ .attr('size', options.tickFontSize * windowScale)
+ .attr('family', 'mono')
+ .attr('style', 'bold')
+ .attr('justify', 'MIDDLE')
+ )
+ );
+
+ shapes.push(ticklabel.parent().parent());
+ }
+
+ // axis label goes on the end of the axis.
+ var axislabel = $('').append(
+ $($('').append($('').attr('diffuseColor', 'black')))
+ );
+
+ axislabel.appendTo(
+ $('')
+ .attr('axisOfRotation', '0 0 0')
+ .appendTo($('').attr('translation', [coordMaxs[I], 0.1, 0]))
+ );
+
+ axislabel.append(
+ $('')
+ .attr('string', options.axisKey[I])
+ .attr('solid', 'true')
+ .append(
+ $('')
+ .attr('size', options.tickFontSize * windowScale)
+ .attr('family', 'mono')
+ .attr('style', 'bold')
+ .attr('justify', 'MIDDLE')
+ )
+ );
+
+ shapes.push(axislabel.parent().parent());
+
+ return shapes;
+ };
+
+ var drawLonePoints = function () {
+ lonePoints.forEach(function (point) {
+ var color = 'black';
+ if (point.rgb) {
+ color = point.rgb;
+ }
+
+ // lone points are drawn as spheres so they have mass
+ var sphere = $('').append(
+ $($('').append($('').attr('diffuseColor', color)))
+ );
+ sphere.appendTo($('').attr('translation', point.coords));
+
+ sphere.append($('').attr('radius', point.radius * 2.25));
+
+ sphere.parent().appendTo(scene);
+ });
+ };
+
+ var drawLoneLabels = function () {
+ loneLabels.forEach(function (label) {
+ // the text is a billboard that automatically faces the user
+ var text = $('').append(
+ $($('').append($('').attr('diffuseColor', 'black')))
+ );
+
+ text.appendTo(
+ $('')
+ .attr('axisOfRotation', '0 0 0')
+ .appendTo($('').attr('translation', label.coords))
+ );
+
+ var size = '.5';
+ if (label.size) {
+ //mathematica label sizes are fontsizes, where
+ //the units for x3dom are local coord sizes
+ size = label.size / (1.5 * windowScale);
+ }
+
+ text.append(
+ $('')
+ .attr('string', label.text)
+ .attr('solid', 'true')
+ .append($('').attr('size', size).attr('family', 'mono').attr('justify', 'MIDDLE'))
+ );
+
+ text.parent().parent().appendTo(scene);
+ });
+ };
+
+ var parseMathematicaBlocks = function (blocks) {
+ blocks.forEach(function (block) {
+ blockIndex++;
+
+ if (block.match(/^\s*\{/)) {
+ // This is a block inside of a block.
+ // so recurse
+ var subblocks = recurseMathematicaBlocks(block);
+ parseMathematicaBlocks(subblocks);
+ } else if (block.match(/Point/)) {
+ // now find any individual points that need to be plotted
+ // points are defined by short blocks so we dont split into
+ // individual commands
+ var str = block.match(/Point\[\s*\{\s*(-?\d*\.?\d*)\s*,\s*(-?\d*\.?\d*)\s*,\s*(-?\d*\.?\d*)\s*\}/);
+ var point = {};
+
+ if (!str) {
+ console.log('Error Parsing Point');
+ return;
+ }
+
+ point.coords = [parseFloat(str[1]), parseFloat(str[2]), parseFloat(str[3])];
+
+ str = block.match(/PointSize\[\s*(\d*\.?\d*)\s*\]/);
+
+ if (str) {
+ point.radius = parseFloat(str[1]);
+ }
+
+ str = block.match(/RGBColor\[\s*(\d*\.?\d*)\s*,\s*(\d*\.?\d*)\s*,\s*(\d*\.?\d*)\s*\]/);
+
+ if (str) {
+ point.rgb = [parseFloat(str[1]), parseFloat(str[2]), parseFloat(str[3])];
+ }
+
+ lonePoints.push(point);
+ } else {
+ // Otherwise its a list of commands that we need to
+ // process individually
+ var commands = splitMathematicaBlocks(block);
+
+ commands.forEach(function (command) {
+ if (command.match(/^\s*\{/)) {
+ // This is a block inside of a block.
+ // so recurse
+ var subblocks = recurseMathematicaBlocks(block);
+ parseMathematicaBlocks(subblocks);
+ } else if (command.match(/Polygon/)) {
+ if (!surfaceBlockIndex) {
+ surfaceBlockIndex = blockIndex;
+ }
+
+ var polystring = command.replace(/Polygon\[([^\]]*)\]/, '$1');
+ var pointstrings = recurseMathematicaBlocks(polystring, -1);
+ // for each polygon extract all the points
+ pointstrings.forEach(function (pointstring) {
+ pointstring = pointstring.replace(/\{([^\{]*)\}/, '$1');
+
+ var splitstring = pointstring.split(',');
+ var point = [];
+
+ for (var i = 0; i < 3; i++) {
+ point[i] = parseFloat(eval(splitstring[i]));
+ }
+
+ // find the index of the point in surfaceCoords. If
+ // the point is not in surfaceCoords, add it
+ for (var i = 0; i < surfaceCoords.length; i++) {
+ if (
+ surfaceCoords[i][0] == point[0] &&
+ surfaceCoords[i][1] == point[1] &&
+ surfaceCoords[i][2] == point[2]
+ ) {
+ surfaceIndex.push(i);
+
+ return;
+ }
+ }
+
+ surfaceIndex.push(surfaceCoords.length);
+ surfaceCoords.push(point);
+ });
+
+ surfaceIndex.push(-1);
+ } else if (command.match(/Line/)) {
+ //Add a line to the line array
+
+ var str = command.replace(/Line\[([^\]]*)\],/, '$1');
+
+ var pointstrings = recurseMathematicaBlocks(str, -1);
+
+ var line = [];
+
+ for (var i = 0; i < 2; i++) {
+ pointstrings[i] = pointstrings[i].replace(/\{([^\{]*)\}/, '$1');
+ var splitstring = pointstrings[i].split(',');
+ var point = [];
+
+ for (var j = 0; j < 3; j++) {
+ point[j] = parseFloat(eval(splitstring[j]));
+ }
+
+ if (point) {
+ line.push(point);
+ } else {
+ console.log('Error Parsing Line');
+ return;
+ }
+ }
+
+ line.push(blockIndex);
+
+ lineCoords.push(line);
+ } else if (command.match(/RGBColor/)) {
+ var str = command.match(/RGBColor\[\s*(\d*\.?\d*)\s*,\s*(\d*\.?\d*)\s*,\s*(\d*\.?\d*)\s*\]/);
+
+ colors[blockIndex] = [parseFloat(str[1]), parseFloat(str[2]), parseFloat(str[3])];
+ } else if (command.match(/Thickness/)) {
+ var str = command.match(/Thickness\[\s*(\d*\.?\d*)\s*\]/);
+
+ lineThickness[blockIndex] = parseFloat(str[1]);
+ } else if (command.match(/Text/)) {
+ // now find any individual labels that need to be plotted
+ var str = command.match(/\{\s*(-?\d*\.?\d*)\s*,\s*(-?\d*\.?\d*)\s*,\s*(-?\d*\.?\d*)\s*\}/);
+ var label = {};
+
+ if (!str) {
+ console.log('Error Parsing Label');
+ return;
+ }
+
+ label.coords = [parseFloat(str[1]), parseFloat(str[2]), parseFloat(str[3])];
+ str = command.match(/StyleForm\[\s*(\w+),\s*FontSize\s*->\s*(\d+)\s*\]/);
+
+ if (!str) {
+ console.log('Error Parsing Label');
+ return;
+ }
+
+ label.text = str[1];
+ label.fontSize = str[2];
+
+ loneLabels.push(label);
+ }
+ });
+ }
+ });
+ };
+
+ var splitMathematicaBlocks = function (text) {
+ // This splits a list of mathematica commands on the commas
+
+ var bracketcount = 0;
+ var blocks = [];
+ var block = '';
+
+ for (var i = 0; i < text.length; i++) {
+ block += text.charAt(i);
+
+ if (text.charAt(i) === '[') {
+ bracketcount++;
+ }
+
+ if (text.charAt(i) == ']') {
+ bracketcount--;
+ if (bracketcount == 0) {
+ i++;
+ blocks.push(block);
+ block = '';
+ }
+ }
+ }
+
+ return blocks;
+ };
+
+ var recurseMathematicaBlocks = function (text, initialcount) {
+ // the mathematica code comes in blocks encolsed by {}
+ // this code makes an array of those blocks. The largest of them will
+ // be the polygon block which defines the surface.
+ var bracketcount = 0;
+ var blocks = [];
+ var block = '';
+
+ if (initialcount) {
+ bracketcount = initialcount;
+ }
+
+ for (var i = 0; i < text.length; i++) {
+ if (text.charAt(i) === '{') {
+ bracketcount++;
+ }
+
+ if (bracketcount > 0) {
+ block += text.charAt(i);
+ }
+
+ if (text.charAt(i) == '}') {
+ bracketcount--;
+ if (bracketcount == 0) {
+ blocks.push(block.substring(1, block.length - 1));
+ block = '';
+ }
+ }
+ }
+
+ return blocks;
+ };
+
+ // This section of code is run whenever the object is created
+ // run intialize with the mathematica string, possibly getting the string
+ // form an ajax call if necessary
+
+ if (options.input) {
+ initialize(options.input);
+ } else if (options.archive) {
+ // If an archive file is provided then that is the file we get
+ // the file name is then the file we want inside the archive.
+ JSZipUtils.getBinaryContent(options.archive, function (error, data) {
+ if (error) {
+ console.log(error);
+ $(container).html('Failed to get input archive');
+ }
+
+ JSZip.loadAsync(data).then((zip) => {
+ zip.file(options.file)
+ .async('string')
+ .then((string) => initialize(string));
+ });
+ });
+ } else if (options.file) {
+ $.ajax({
+ url: options.file,
+ dataType: 'text',
+ async: 'true',
+ success: function (data) {
+ initialize(data);
+ },
+ error: function (x, y, error) {
+ console.log(error);
+ $(container).html('Failed to get input file');
+ }
+ });
+ } else {
+ $(container).html('No input data provided');
+ }
+};
diff --git a/htdocs/js/Problem/details-accordion.js b/htdocs/js/Problem/details-accordion.js
new file mode 100644
index 0000000000..621de1e95d
--- /dev/null
+++ b/htdocs/js/Problem/details-accordion.js
@@ -0,0 +1,39 @@
+(() => {
+ const setupAccordion = (accordion) => {
+ const collapseEl = accordion.querySelector('.collapse');
+ const button = accordion.querySelector('summary.accordion-button');
+ const details = accordion.querySelector('details.accordion-item');
+ if (!collapseEl || !button || !details) return;
+
+ const collapse = new bootstrap.Collapse(collapseEl, { toggle: false });
+ button.addEventListener('click', () => collapse.toggle());
+
+ details.addEventListener('click', (e) => e.preventDefault());
+ collapseEl.addEventListener('show.bs.collapse', () => {
+ details.open = true;
+ button.classList.remove('collapsed');
+ });
+ collapseEl.addEventListener('hide.bs.collapse', () => button.classList.add('collapsed'));
+ collapseEl.addEventListener('hidden.bs.collapse', () => (details.open = false));
+ };
+
+ // Deal with solution/hint details that are already on the page.
+ document.querySelectorAll('.solution.accordion, .hint.accordion').forEach(setupAccordion);
+
+ // Deal with solution/hint details that are added to the page later.
+ const observer = new MutationObserver((mutationsList) => {
+ mutationsList.forEach((mutation) => {
+ mutation.addedNodes.forEach((node) => {
+ if (node instanceof Element) {
+ if (
+ (node.classList.contains('solution') || node.classList.contains('hint')) &&
+ node.classList.contains('accordion')
+ )
+ setupAccordion(node);
+ else node.querySelectorAll('.solution.accordion, .hint.accordion').forEach(setupAccordion);
+ }
+ });
+ });
+ });
+ observer.observe(document.body, { childList: true, subtree: true });
+})();
diff --git a/macros/PG.pl b/macros/PG.pl
index 2770286d50..5573910422 100644
--- a/macros/PG.pl
+++ b/macros/PG.pl
@@ -395,11 +395,13 @@ sub ADD_JS_FILE {
# Some problems use jquery-ui still, and so the requestor should also load the js for that if those problems are used,
# although those problems should also be rewritten to not use jquery-ui.
sub load_js() {
- ADD_JS_FILE('js/Feedback/feedback.js', 0, { defer => undef });
- ADD_JS_FILE('js/Base64/Base64.js', 0, { defer => undef });
- ADD_JS_FILE('js/Knowls/knowl.js', 0, { defer => undef });
- ADD_JS_FILE('js/ImageView/imageview.js', 0, { defer => undef });
- ADD_JS_FILE('js/Essay/essay.js', 0, { defer => undef });
+
+ ADD_JS_FILE('js/Feedback/feedback.js', 0, { defer => undef });
+ ADD_JS_FILE('js/Base64/Base64.js', 0, { defer => undef });
+ ADD_JS_FILE('js/Knowls/knowl.js', 0, { defer => undef });
+ ADD_JS_FILE('js/Problem/details-accordion.js', 0, { defer => undef });
+ ADD_JS_FILE('js/ImageView/imageview.js', 0, { defer => undef });
+ ADD_JS_FILE('js/Essay/essay.js', 0, { defer => undef });
if ($envir{useMathQuill}) {
ADD_JS_FILE('node_modules/mathquill/dist/mathquill.js', 0, { defer => undef });
diff --git a/macros/core/PGbasicmacros.pl b/macros/core/PGbasicmacros.pl
index 1670ff8491..a2c5017ef2 100644
--- a/macros/core/PGbasicmacros.pl
+++ b/macros/core/PGbasicmacros.pl
@@ -1066,8 +1066,24 @@ sub SOLUTION {
return "" if $solution_body eq "";
if ($displayMode =~ /HTML/) {
- TEXT('',
- knowlLink(SOLUTION_HEADING(), value => $solution_body, type => 'solution'), '
');
+ TEXT(tag(
+ 'div',
+ class => 'solution accordion my-3',
+ tag(
+ 'details',
+ class => 'accordion-item',
+ tag(
+ 'summary',
+ class => 'accordion-button collapsed text-primary fw-bold py-2',
+ tag('div', class => 'accordion-header user-select-none', SOLUTION_HEADING())
+ )
+ . tag(
+ 'div',
+ class => 'accordion-collapse collapse',
+ tag('div', class => 'accordion-body', $solution_body)
+ )
+ )
+ ));
} elsif ($displayMode =~ /TeX/) {
TEXT(
"\n%%% BEGIN SOLUTION\n"
@@ -1092,7 +1108,24 @@ sub HINT {
my $hint_body = hint(@_);
return unless $hint_body;
if ($displayMode =~ /HTML/) {
- TEXT('', knowlLink(HINT_HEADING(), value => $hint_body, type => 'hint'), '
');
+ TEXT(tag(
+ 'div',
+ class => 'hint accordion my-3',
+ tag(
+ 'details',
+ class => 'accordion-item',
+ tag(
+ 'summary',
+ class => 'accordion-button collapsed text-primary fw-bold py-2',
+ tag('div', class => 'accordion-header user-select-none', HINT_HEADING())
+ )
+ . tag(
+ 'div',
+ class => 'accordion-collapse collapse',
+ tag('div', class => 'accordion-body', $hint_body)
+ )
+ )
+ ));
} elsif ($displayMode =~ /TeX/) {
TEXT(
"\n%%% BEGIN HINT\n"
@@ -1390,7 +1423,7 @@ sub END_ONE_COLUMN { # deprecated
sub SOLUTION_HEADING {
MODES(
TeX => '{\\bf ' . maketext('Solution:') . ' }',
- HTML => '' . maketext('Solution:') . ' ',
+ HTML => maketext('Solution'),
PTX => ''
);
}
@@ -1398,7 +1431,7 @@ sub SOLUTION_HEADING {
sub HINT_HEADING {
MODES(
TeX => '{\\bf ' . maketext('Hint:') . ' }',
- HTML => '' . maketext('Hint:') . ' ',
+ HTML => maketext('Hint'),
PTX => ''
);
}
diff --git a/macros/graph/LiveGraphics3D.pl b/macros/graph/LiveGraphics3D.pl
index fee16e8d95..402362d531 100644
--- a/macros/graph/LiveGraphics3D.pl
+++ b/macros/graph/LiveGraphics3D.pl
@@ -126,12 +126,6 @@ sub LiveGraphics3D {
# In html mode check to see if we use javascript or not
} else {
my ($w, $h) = @{ $options{size} };
- $out .= $bHTML if ($main::displayMode eq "Latex2HTML");
- #
- # Put the js in a table
- #
- $out .= qq{\n\n};
- $out .= qq{};
$archive_input = $options{archive} // '';
$file_input = $options{file} // '';
@@ -139,16 +133,14 @@ sub LiveGraphics3D {
$direct_input =~ s/\n//g;
- #
- # include any independent variables
- #
+ # include any independent variables
$ind_vars = '{}';
if ($options{vars}) {
$ind_vars = "{";
%vars = @{ $options{vars} };
- foreach $var (keys %vars) {
+ for $var (keys %vars) {
$ind_vars .= "\"$var\":\"" . $vars{$var} . "\",";
}
@@ -156,26 +148,25 @@ sub LiveGraphics3D {
}
$out .= <
- var thisTD = jQuery('script:last').parent();
- var options = { width : $w,
- height : $h,
- file : '$file_input',
- input : '$direct_input',
- archive : '$archive_input',
- vars : $ind_vars,
- };
-
- if (typeof LiveGraphics3D !== 'undefined') {
- var graph = new LiveGraphics3D(thisTD[0],options);
- }
-
-
+
+
+
EOS
-
- $out .= " | \n
\n";
- $out .= $eHTML if ($main::displayMode eq "Latex2HTML");
- # otherwise use the applet
}
return $out;
diff --git a/t/pg_problems/problem_file.t b/t/pg_problems/problem_file.t
index 38b46983f4..9b751f6523 100644
--- a/t/pg_problems/problem_file.t
+++ b/t/pg_problems/problem_file.t
@@ -107,6 +107,7 @@ is(
{ file => 'js/Feedback/feedback.js', external => 0, attributes => { defer => undef } },
{ file => 'js/Base64/Base64.js', external => 0, attributes => { defer => undef } },
{ file => 'js/Knowls/knowl.js', external => 0, attributes => { defer => undef } },
+ { file => 'js/Problem/details-accordion.js', external => 0, attributes => { defer => undef } },
{ file => 'js/ImageView/imageview.js', external => 0, attributes => { defer => undef } },
{ file => 'js/Essay/essay.js', external => 0, attributes => { defer => undef } },
{ file => 'node_modules/mathquill/dist/mathquill.js', external => 0, attributes => { defer => undef } },