Commit 78a3b7cf by Valera Rozuvan Committed by Alexander Kryklia

Added documentation for code. Fixed several minor issues. Added checks for some error cases.

parent 8efab2c9
...@@ -9,29 +9,39 @@ define('Graph', [], function () { ...@@ -9,29 +9,39 @@ define('Graph', [], function () {
function Graph(gstId, config, state) { function Graph(gstId, config, state) {
var plotDiv, dataSeries, functions, xaxis, yaxis, xrange; var plotDiv, dataSeries, functions, xaxis, yaxis, xrange;
// We must have a graph container DIV element available in order to
// proceed.
plotDiv = $('#' + gstId + '_plot'); plotDiv = $('#' + gstId + '_plot');
if (plotDiv.length === 0) { if (plotDiv.length === 0) {
return; return;
} }
// Configure some settings for the graph.
setGraphDimensions(); setGraphDimensions();
setGraphAxes(); setGraphAxes();
setGraphXRange(); setGraphXRange();
state.bindUpdatePlotEvent(plotDiv, onUpdatePlot); // Get the user defined functions. If there aren't any, don't do
// anything else.
createFunctions(); createFunctions();
if (functions.length === 0) {
return;
}
// Create the initial graph and plot it for the user to see.
generateData(); generateData();
updatePlot(); updatePlot();
// Bind an event. Whenever some constant changes, the graph will be
// redrawn
state.bindUpdatePlotEvent(plotDiv, onUpdatePlot);
return; return;
function setGraphDimensions() { function setGraphDimensions() {
var dimObj, width, height, tempInt; var dimObj, width, height, tempInt;
// If no dimensions are specified by the user, the graph have // If no dimensions are specified by the user, the graph will have
// predefined dimensions. // predefined dimensions.
width = 300; width = 300;
height = 300; height = 300;
...@@ -51,27 +61,30 @@ define('Graph', [], function () { ...@@ -51,27 +61,30 @@ define('Graph', [], function () {
} }
} }
// Apply the dimensions to the graph container DIV element.
plotDiv.width(width); plotDiv.width(width);
plotDiv.height(height); plotDiv.height(height);
} }
function setGraphAxes() { function setGraphAxes() {
// Define the xaxis Flot configuration, and then see if the user
// supplied custom values.
xaxis = { xaxis = {
'min': 0, 'min': 0,
'tickSize': 3, 'tickSize': 1,
'max': 30 'max': 10
}; };
if (typeof config.plot['xticks'] === 'string') { if (typeof config.plot['xticks'] === 'string') {
processTicks(config.plot['xticks'], xaxis); processTicks(config.plot['xticks'], xaxis);
} }
// Define the yaxis Flot configuration, and then see if the user
// supplied custom values.
yaxis = { yaxis = {
'min': -5, 'min': 0,
'tickSize': 1, 'tickSize': 1,
'max': 5 'max': 10
}; };
if (typeof config.plot['yticks'] === 'string') { if (typeof config.plot['yticks'] === 'string') {
processTicks(config.plot['yticks'], yaxis); processTicks(config.plot['yticks'], yaxis);
} }
...@@ -79,34 +92,41 @@ define('Graph', [], function () { ...@@ -79,34 +92,41 @@ define('Graph', [], function () {
return; return;
function processTicks(ticksStr, ticksObj) { function processTicks(ticksStr, ticksObj) {
var ticksBlobs, min, tickSize, max; var ticksBlobs, tempFloat;
// The 'ticks' setting is a string containing 3 floating-point
// numbers.
ticksBlobs = ticksStr.split(','); ticksBlobs = ticksStr.split(',');
if (ticksBlobs.length !== 3) { if (ticksBlobs.length !== 3) {
return; return;
} }
min = parseFloat(ticksBlobs[0]); tempFloat = parseFloat(ticksBlobs[0]);
if (isNaN(min) === false) { if (isNaN(tempFloat) === false) {
ticksObj.min = min; ticksObj.min = tempFloat;
} }
tickSize = parseFloat(ticksBlobs[1]); tempFloat = parseFloat(ticksBlobs[1]);
if (isNaN(tickSize) === false) { if (isNaN(tempFloat) === false) {
ticksObj.tickSize = tickSize; ticksObj.tickSize = tempFloat;
} }
max = parseFloat(ticksBlobs[2]); tempFloat = parseFloat(ticksBlobs[2]);
if (isNaN(max) === false) { if (isNaN(tempFloat) === false) {
ticksObj.max = max; ticksObj.max = tempFloat;
} }
// Is the starting tick to the left of the ending tick (on the
// x-axis)? If not, set default starting and ending tick.
if (ticksObj.min >= ticksObj.max) { if (ticksObj.min >= ticksObj.max) {
ticksObj.min = 0; ticksObj.min = 0;
ticksObj.max = 10; ticksObj.max = 10;
} }
// Make sure the range makes sense - i.e. that there are at
// least 3 ticks. If not, set a tickSize which will produce
// 11 ticks. tickSize is the spacing between the ticks.
if (ticksObj.tickSize * 2 >= ticksObj.max - ticksObj.min) { if (ticksObj.tickSize * 2 >= ticksObj.max - ticksObj.min) {
ticksObj.tickSize = (ticksObj.max - ticksObj.min) / 10.0; ticksObj.tickSize = (ticksObj.max - ticksObj.min) / 10.0;
} }
...@@ -118,10 +138,13 @@ define('Graph', [], function () { ...@@ -118,10 +138,13 @@ define('Graph', [], function () {
xrange = { xrange = {
'start': 0, 'start': 0,
'end': 30, 'end': 10,
'step': 0.1 'step': 0.1
}; };
// The 'xrange' is a string containing two floating point numbers
// separated by a comma. The first number is the starting
// x-coordinate , the second number is the ending x-coordinate
if (typeof config.plot['xrange'] === 'string') { if (typeof config.plot['xrange'] === 'string') {
xRangeStr = config.plot['xrange']; xRangeStr = config.plot['xrange'];
xRangeBlobs = xRangeStr.split(','); xRangeBlobs = xRangeStr.split(',');
...@@ -138,13 +161,25 @@ define('Graph', [], function () { ...@@ -138,13 +161,25 @@ define('Graph', [], function () {
xrange.end = tempNum; xrange.end = tempNum;
} }
if (xrange.start >= xrange.end) {
xrange.start = 0;
xrange.end = 10;
}
} }
} }
// The user can specify the number of points. However, internally
// we will use it to generate a 'step' - i.e. the distance (on
// x-axis) between two adjacent points.
if (typeof config.plot['num_points'] === 'string') { if (typeof config.plot['num_points'] === 'string') {
tempNum = parseInt(config.plot['num_points'], 10); tempNum = parseInt(config.plot['num_points'], 10);
if (isNaN(tempNum) === false) { if (
xrange.step = (xrange.end - xrange.start) / tempNum; (isNaN(tempNum) === false) &&
(tempNum >= 2) &&
(tempNum <= 500)
) {
xrange.step = (xrange.end - xrange.start) / (tempNum - 1);
} }
} }
} }
...@@ -192,67 +227,97 @@ define('Graph', [], function () { ...@@ -192,67 +227,97 @@ define('Graph', [], function () {
return; return;
// This function will reduce code duplications. We have to call // This function will reduce code duplication. We have to call
// the function addFunction() several times passing object // the function addFunction() several times passing object
// properties. A parameters. Rather than writing them out every // properties as parameters. Rather than writing them out every
// time, we will have a single point of // time, we will have a single place where it is done.
function callAddFunction(obj) { function callAddFunction(obj) {
addFunction( addFunction(
obj['#text'], obj['#text'],
obj['@color'], obj['@color'],
obj['@line'], obj['@line'],
obj['@dot'], obj['@dot'],
obj['@label'], obj['@label']
obj['@style'],
obj['@point_size']
); );
} }
function addFunction(funcString, color, line, dot, label, style, point_size) { function addFunction(funcString, color, line, dot, label) {
var newFunctionObject, func, constNames; var newFunctionObject, func, constNames;
// The main requirement is function string. Without it we can't
// create a function, and the series cannot be calculated.
if (typeof funcString !== 'string') { if (typeof funcString !== 'string') {
return; return;
} }
newFunctionObject = {}; // Some defaults. If no options are set for the graph, we will
// make sure that at least a line is drawn for a function.
newFunctionObject = {
'line': true,
'dot': false
};
// Get all of the constant names defined by the user in the
// XML.
constNames = state.getAllConstantNames(); constNames = state.getAllConstantNames();
// The 'x' is always one of the function parameters. // The 'x' is always one of the function parameters.
constNames.push('x'); constNames.push('x');
// Must make sure that the function body also gets passed to // Must make sure that the function body also gets passed to
// the Function cosntructor. // the Function constructor.
constNames.push(funcString); constNames.push(funcString);
func = Function.apply(null, constNames); // Create the function from the function string, and all of the
// available constants + the 'x' variable as it's parameters.
// For this we will use the built-in Function object
// constructor.
//
// If something goes wrong during this step, most
// likely the user supplied an invalid JavaScript function body
// string. In this case we will not proceed.
try {
func = Function.apply(null, constNames);
} catch (err) {
// Let's tell the user. He will see a nice red error
// message instead of a graph.
plotDiv.html(
'<span style="color: red;">' +
'Error while parsing JavaScript function body string!' +
'</span>'
);
return;
}
newFunctionObject['func'] = func; newFunctionObject['func'] = func;
if (typeof color === 'string') { if (typeof color === 'string') {
newFunctionObject['color'] = color; newFunctionObject['color'] = color;
} }
if ((typeof line === 'boolean') || (typeof line === 'string')) { if (typeof line === 'string') {
if ((line === 'true') || (line === true)) { if (line === 'true') {
newFunctionObject['line'] = true; newFunctionObject['line'] = true;
} else { } else if (line === 'false') {
newFunctionObject['line'] = false; newFunctionObject['line'] = false;
} }
} }
if ((typeof dot === 'boolean') || (typeof dot === 'string')) { if (typeof dot === 'string') {
if ((dot === 'true') || (dot === true)) { if (dot === 'true') {
newFunctionObject['dot'] = true; newFunctionObject['dot'] = true;
} else { } else if (dot === 'false') {
newFunctionObject['dot'] = false; newFunctionObject['dot'] = false;
} }
} }
// By default, if no preference was set, or if the preference // If the preference is conflicting (we must have either line
// is conflicting (we must have either line or dot, none is // or dot, none is not an option), we will show line.
// not an option), we will show line. if (
if ((newFunctionObject['dot'] === false) && (newFunctionObject['line'] === false)) { (newFunctionObject['dot'] === false) &&
(newFunctionObject['line'] === false)
) {
newFunctionObject['line'] = true; newFunctionObject['line'] = true;
} }
...@@ -264,6 +329,8 @@ define('Graph', [], function () { ...@@ -264,6 +329,8 @@ define('Graph', [], function () {
} }
} }
// The callback that will be called whenever a constant changes (gets
// updated via a slider or a text input).
function onUpdatePlot(event) { function onUpdatePlot(event) {
generateData(); generateData();
updatePlot(); updatePlot();
...@@ -282,6 +349,7 @@ define('Graph', [], function () { ...@@ -282,6 +349,7 @@ define('Graph', [], function () {
seriesObj = {}; seriesObj = {};
dataPoints = []; dataPoints = [];
// Generate the data points.
for (x = xrange.start; x <= xrange.end; x += xrange.step) { for (x = xrange.start; x <= xrange.end; x += xrange.step) {
// Push the 'x' variable to the end of the parameter array. // Push the 'x' variable to the end of the parameter array.
...@@ -314,19 +382,25 @@ define('Graph', [], function () { ...@@ -314,19 +382,25 @@ define('Graph', [], function () {
seriesObj.label = functionObj.label; seriesObj.label = functionObj.label;
} }
// Should the data points be connected by a line?
seriesObj.lines = { seriesObj.lines = {
'show': functionObj.line 'show': functionObj.line
}; };
// Should each data point be represented by a point on the
// graph?
seriesObj.points = { seriesObj.points = {
'show': functionObj.dot 'show': functionObj.dot
}; };
// Add the newly created series object to the series set which
// will be plotted by Flot.
dataSeries.push(seriesObj); dataSeries.push(seriesObj);
} }
} }
function updatePlot() { function updatePlot() {
// Tell Flot to draw the graph to our specification.
$.plot( $.plot(
plotDiv, plotDiv,
dataSeries, dataSeries,
...@@ -350,6 +424,13 @@ define('Graph', [], function () { ...@@ -350,6 +424,13 @@ define('Graph', [], function () {
} }
); );
// The first time that the graph gets added to the page, the legend
// is created from scratch. When it appears, MathJax works some
// magic, and all of the specially marked TeX gets rendered nicely.
// The next time when we update the graph, no such thing happens.
// We must ask MathJax to typeset the legend again (well, we will
// ask it to look at our entire graph DIV), the next time it's
// worker queue is available.
MathJax.Hub.Queue([ MathJax.Hub.Queue([
'Typeset', 'Typeset',
MathJax.Hub, MathJax.Hub,
......
...@@ -16,13 +16,21 @@ define( ...@@ -16,13 +16,21 @@ define(
function GstMain(gstId) { function GstMain(gstId) {
var config, state; var config, state;
// Get the JSON configuration, and parse it, and store as an object.
config = JSON.parse($('#' + gstId + '_json').html()).root; config = JSON.parse($('#' + gstId + '_json').html()).root;
// Parse the configuration settings for sliders and text inputs, and
// extract all of the defined constants (their names along with their
// initial values).
state = State(gstId, config); state = State(gstId, config);
// Create the sliders and the text inputs, attaching them to
// approriate constants.
Sliders(gstId, config, state); Sliders(gstId, config, state);
Inputs(gstId, config, state); Inputs(gstId, config, state);
// Configure and display the loop. Attach event for the graph to be
// updated on any change of a slider or a text input.
Graph(gstId, config, state); Graph(gstId, config, state);
} }
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment