diff --git a/htdocs/js/LiveGraphics/liveGraphics.js b/htdocs/js/LiveGraphics/liveGraphics.js index fc16a4a71b..6e6ce88e88 100644 --- a/htdocs/js/LiveGraphics/liveGraphics.js +++ b/htdocs/js/LiveGraphics/liveGraphics.js @@ -1,882 +1,913 @@ // 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) { - // 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(); + 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); }); - 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(); - } + // this parses axes commands. + if (text.match(/Axes\s*->\s*True/)) { + options.showAxes = true; + } - drawSurface(); - drawLines(); - drawLonePoints(); - drawLoneLabels(); - }; + // get some initial global configuration + var labels = text.match(/AxesLabel\s*->\s*\{\s*(\w+),\s*(\w+),\s*(\w+)\s*\}/); - var parseLive3DData = function (text) { - // Set up variables - $.each(options.vars, function (name, data) { - eval(name + '=' + data); - }); + if (labels) { + options.axisKey = [labels[1],labels[2],labels[3]]; + } - // this parses axes commands. - if (text.match(/Axes\s*->\s*True/)) { - options.showAxes = true; + // 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]; } - - // get some initial global configuration - var labels = text.match(/AxesLabel\s*->\s*\{\s*(\w+),\s*(\w+),\s*(\w+)\s*\}/); - - if (labels) { - options.axisKey = [labels[1], labels[2], labels[3]]; + } + }); + + 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; + + var sum = 0; - // split the input into blocks and parse - var blocks = recurseMathematicaBlocks(text); + for (var i=0; i< 3; i++) { + sum += max[i]-min[i]; + } + + windowScale = sum/3; + }; + + var drawLines = function() { + if (lineCoords.length==0) { + return; + } - parseMathematicaBlocks(blocks); - }; + // 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; + } - // 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]; + // 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 = []; - 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]; - } - } - }); + if (surfaceBlockIndex in colors) { + flatcolor = true; + color = colors[surfaceBlockIndex]; + } - 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; + // 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)); + } - var sum = 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); + } - for (var i = 0; i < 3; i++) { - sum += max[i] - min[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'))); + }); + } - windowScale = sum / 3; - }; + // 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 drawLines = function () { - if (lineCoords.length == 0) { - return; + } + + // 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]); + } + + 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; + } - // 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) { + 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').appendTo( - $('').attr('translation', trans).attr('rotation', rotation).appendTo(linegroup) - ); - var color = [0, 0, 0]; - var radius = 0.005; + colors[blockIndex] = [parseFloat(str[1]),parseFloat(str[2]),parseFloat(str[3])]; - // line[2] contains the block index - if (line[2] in colors) { - color = colors[line[2]]; - } + } else if (command.match(/Thickness/)) { + var str = command.match(/Thickness\[\s*(\d*\.?\d*)\s*\]/); - if (line[2] in lineThickness) { - radius = Math.max(lineThickness[line[2]], 0.005); + 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; } - - $('').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.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; } - - colorindstr += cindex + ' '; + + label.text = str[1]; + label.fontSize = str[2]; + + loneLabels.push(label); + + } }); - - 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); - - 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)); + + } + }); + } + + 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 = ''; } - // 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); + return blocks; + } - appearance.append($('').attr('diffusecolor', [0, 0, 0])); - var indexedlineset = $('').attr('coordindex', indexstr).attr('solid', 'true'); - indexedlineset.append($('').attr('point', coordstr)); + 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; + } - 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); - }); + for (var i=0; i < text.length; i++) { - 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')) - ); - }); - } + if (text.charAt(i) === '{') { + bracketcount++; + } - // 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 (bracketcount > 0) { + block += text.charAt(i); + } - 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')) - ); - }); + if (text.charAt(i) == '}') { + bracketcount--; + if (bracketcount == 0) { + blocks.push(block.substring(1,block.length-1)); + block = ''; } - // 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); - } - }); - } + 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)); }); - }; - - 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'); - } -}; + + } 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/macros/graph/LiveGraphics3D.pl b/macros/graph/LiveGraphics3D.pl index 402362d531..fee16e8d95 100644 --- a/macros/graph/LiveGraphics3D.pl +++ b/macros/graph/LiveGraphics3D.pl @@ -126,6 +126,12 @@ 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{\n
}; $archive_input = $options{archive} // ''; $file_input = $options{file} // ''; @@ -133,14 +139,16 @@ 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} }; - for $var (keys %vars) { + foreach $var (keys %vars) { $ind_vars .= "\"$var\":\"" . $vars{$var} . "\","; } @@ -148,25 +156,26 @@ sub LiveGraphics3D { } $out .= < - - + EOS + + $out .= "
\n"; + $out .= $eHTML if ($main::displayMode eq "Latex2HTML"); + # otherwise use the applet } return $out;