Front End Code
Sending Form Data to Handler
Initial page stateForm Verification
Resetting Chart.js
Posting data to back end
Back End Processing
Reading posted dataSerializing JSON data
Generating Base64 image string
Catching errors
Displaying Results
Parsing JSON dataCatching errors
Displaying new images
Parsing chart data
Displaying new charts
This page contains front-end and back-end code samples used to generate the graphics and images displayed on this web site. The code demonstrates how to collect user-inputed form data, send that data to a back-end processor, collect the processed data, and use it to display the charts and graphs. The code here does not represent a complete description of all of the various functions this web site uses. However I hope the samples below are helpful for those curious about how information is communicated between the part of the web site a user interacts with and the "unseen" back-end processing.
Here we are going to look at the front-end and back-end code from the Seidel Aberrations page.
Sending Form Data to Back End Handler
The following section describes how user-inputed form data is collected, parsed, and sent to the back end code for processing.Initial Page State
When the page is first loaded, the charts and graphics containing the computed data are empty, because we do not yet have the computed data from the back end. First we have three empty Chart.js elements:
<h3>Wave fans</h3>
<p></p>
<div style="height: 400px">
<canvas id="wavefanX"></canvas>
</div>
<p></p>
<div style="height: 400px">
<canvas id="wavefanY"></canvas>
</div>
<h3>Modulation</h3>
<div style="height: 400px">
<canvas id="modulation"></canvas>
</div>
Additionally we have four empty images that will eventually contain images sent from the back end
<h3>Coherent transfer function</h3>
<img class="img-responsive" id="CTFimg" src="//:0" />
<h3>Point spread function</h3>
<img class="img-responsive" id="PSFimg" src="//:0" />
<h3>Modulation transfer function</h3>
<img class="img-responsive" id="MTFimg" src="//:0" />
<h3>Image plane PSF</h3>
<img class="img-responsive" id="MULTIimg" src="//:0" />
Form Verification
Before any data is sent to the back end for processing, we must first perform input verification. We check to make sure the user hasn't entered nonsensical input, such as letters or negative wavelengths. If the verification fails, the script stops executing.
if (!formVerify())
return;
Now let's take a look at what happens during the form verification. First we go through every input using the javascript built in function checkValidity(). If any inputs fail the check, they are added to a table, including the input name, user-inputed value, and minimum and maximum valid values. Then a modal dialog box is displayed to the user containing the error message and table. If all fields are valid, nothing is displayed to the user and processing can continue.
function formVerify() {
var allValid = true;
var modelString = "One or more input parameters you entered are not valid:\
<div class=\"table-responsive\">\
<table class=\"table table-striped\">\
<thead>\
<tr>\
<th>Paramter</th>\
<th>You Entered</th>\
<th>Min Allowable Value</th>\
<th>Max Allowable Value</th>\
</tr>\
</thead>\
<tbody>\
";
inputs = document.querySelectorAll("input");
inputs = [].slice.call(inputs);
inputs.forEach(function (input) {
if (!input.checkValidity()) {
modelString = modelString + "<tr>\
<td>" + input.id + "</td>\
<td>" + input.value + "</td>\
<td>" + input.min + "</td>\
<td>" + input.max + "</td>\
</tr>"
document.getElementById(input.id).parentElement.classList.add("has-error");
allValid = false;
}
else {
document.getElementById(input.id).parentElement.classList.remove("has-error");
}
});
modelString = modelString + "</tbody>\
</table>\
</div>"
if (!allValid) {
$('#formValidationModal').find('.modal-body')[0].innerHTML = modelString;
$('#formValidationModal').modal('show');
}
return allValid;
};
Resetting Chart.js
Next, we just need to do a little bit of housekeeping; we need to clear any pre-existing Chart.js charts.
$('#wavefany').replaceWith('<canvas id="wavefany"></canvas>');
$('#wavefanX').replaceWith('<canvas id="wavefanX"></canvas>');
$('#modulation').replaceWith('<canvas id="modulation"></canvas>');
Posting Data to Back End
Finally we are ready to post the user-inputed form data to the back end for processing! We are going to use the $.ajax functionality of jQuery to perform an XMLHttpRequest. First we specify the method ("POST"), then the URL of the back end HTTP handler, and finally the user-inputed form data.
$.ajax({
type: "POST",
url: "AberrationsHandler.ashx",
data: {
lambda: document.getElementById("lambda").value,
l: document.getElementById("l").value,
z: document.getElementById("z").value,
dxp: document.getElementById("dxp").value,
u0: document.getElementById("u0").value,
v0: document.getElementById("v0").value,
w020: document.getElementById("w020").value,
w111: document.getElementById("w111").value,
w040: document.getElementById("w040").value,
w220: document.getElementById("w220").value,
w131: document.getElementById("w131").value,
w311: document.getElementById("w311").value,
w222: document.getElementById("w222").value,
w060: document.getElementById("w060").value,
w240: document.getElementById("w240").value,
w420: document.getElementById("w420").value,
w151: document.getElementById("w151").value,
w331: document.getElementById("w331").value,
w511: document.getElementById("w511").value,
w242: document.getElementById("w242").value,
w422: document.getElementById("w422").value,
w333: document.getElementById("w333").value
},
Back End Processing
Now that we have posted the form data to the back end, we will investigate how the back end uses the posted data to generate the image and chart data we will need to display the results.
Reading Posted Data
First, we collect the posted form data and assign it to variables in the back end code.
double l = Convert.ToDouble(context.Request.Form["l"]);
double lambda = Convert.ToDouble(context.Request.Form["lambda"]);
double z = Convert.ToDouble(context.Request.Form["z"]);
double d = Convert.ToDouble(context.Request.Form["dxp"]);
double u0 = Convert.ToDouble(context.Request.Form["u0"]);
double v0 = -Convert.ToDouble(context.Request.Form["v0"]);
double w020 = Convert.ToDouble(context.Request.Form["w020"]) * lambda;
double w111 = Convert.ToDouble(context.Request.Form["w111"]) * lambda;
double w040 = Convert.ToDouble(context.Request.Form["w040"]) * lambda;
double w220 = Convert.ToDouble(context.Request.Form["w220"]) * lambda;
double w131 = Convert.ToDouble(context.Request.Form["w131"]) * lambda;
double w311 = Convert.ToDouble(context.Request.Form["w311"]) * lambda;
double w222 = Convert.ToDouble(context.Request.Form["w222"]) * lambda;
double w060 = Convert.ToDouble(context.Request.Form["w060"]) * lambda;
double w240 = Convert.ToDouble(context.Request.Form["w240"]) * lambda;
double w420 = Convert.ToDouble(context.Request.Form["w420"]) * lambda;
double w151 = Convert.ToDouble(context.Request.Form["w151"]) * lambda;
double w331 = Convert.ToDouble(context.Request.Form["w331"]) * lambda;
double w511 = Convert.ToDouble(context.Request.Form["w511"]) * lambda;
double w242 = Convert.ToDouble(context.Request.Form["w242"]) * lambda;
double w422 = Convert.ToDouble(context.Request.Form["w422"]) * lambda;
double w333 = Convert.ToDouble(context.Request.Form["w333"]) * lambda;
Serializing JSON Data
I am now going to skip over the portion of the back-end code that does the mathemtical computations and so on. For examples on back end computations, check out the Back End Code page. After we have completed the mathematical calculations, we need to prepare the data to send back to the front end web page. We will do that by serializing the data as a JSON object.
JavaScriptSerializer JsonSerializer = new JavaScriptSerializer();
context.Response.ContentType = "text/plain";
try
{
context.Response.Write(
JsonSerializer.Serialize(
new
{
CTFout = getBase64ImageString(thisCTF, 0.3),
MTFout = getBase64ImageString(thisMTF, 0.3),
PSFout = getBase64ImageString(thisPSF, 0.3),
MULTIout = getBase64ImageString(multi, 0.3),
xaxis = val,
x0,
x07,
xh,
x1,
y0,
y07,
yh,
y1,
MTFuout,
MTFvout,
MTF_anout,
MTFx,
returnError = "no error"
}
)
);
}
Generating Base64 Image String
Chart.js is able to directly read in numeric array data to display charts. However it's not so simple to return an image in a JSON object. We have to use a format called a Base64 Image String. This is a compressed image in the form of a string which the browser will decompress and display. We generate the Base64 Image String using the following function:
// Description : This function generates a Base64 Image String from a numeric array
// Inputs : u2 - matrix of numeric data
// : power - a number to element-wise raise the numeric matrix by,
// used to increase or decrease the image contrast
// Outputs : A Base64 Image String
public static string getBase64ImageString(double[,] u2, double power)
{
double[] pixels = new double[arraySize * arraySize];
byte[] pixelsAsBytes = new byte[arraySize * arraySize];
double max_final_image = 0;
int pixelIdx = 0;
for (int i = 0; i < arraySize; i++)
{
for (int j = 0; j < arraySize; j++)
{
if (u2[i, j] > max_final_image)
{
max_final_image = u2[i, j];
}
}
}
for (int i = 0; i < arraySize; i++)
{
for (int j = 0; j < arraySize; j++)
{
u2[i, j] = u2[i, j] / max_final_image;
pixels[pixelIdx] = Math.Pow(u2[i, j], power);
pixelsAsBytes[pixelIdx] = Convert.ToByte(pixels[pixelIdx] * 255.0);
pixelIdx++;
}
}
Bitmap diff_pattern = new Bitmap(arraySize, arraySize, PixelFormat.Format8bppIndexed);
ColorPalette pal = diff_pattern.Palette;
for (int i = 0; i < 256; i++)
{
pal.Entries[i] = Color.FromArgb(i, i, i);
}
diff_pattern.Palette = pal;
Rectangle rect = new Rectangle(0, 0, diff_pattern.Width, diff_pattern.Height);
BitmapData diff_pattern_data = diff_pattern.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
Marshal.Copy(pixelsAsBytes, 0, diff_pattern_data.Scan0, arraySize * arraySize);
diff_pattern.UnlockBits(diff_pattern_data);
System.IO.MemoryStream ms = new System.IO.MemoryStream();
diff_pattern.Save(ms, ImageFormat.Jpeg);
return Convert.ToBase64String(ms.ToArray());
}
Catching Errors
It is possible that nonsensical form data could get past the input verification function and result in data that contains NaNs, nulls, empty data, and so forth. When serializing the data, this could cause the program to error out. So we will catch this error and return it to the front end for later use.
catch (Exception ex)
{
context.Response.Write(
JsonSerializer.Serialize(
new
{
returnError = ex.ToString()
}
)
);
}
Displaying Results
Parsing JSON Data
The back end sends the data to the front end as a JSON object. The first thing we need to do is parse this object (we are still within the $.ajax block)
success: function (returnData) {
try {
var obj = JSON.parse(returnData);
}
Catching Errors
It is possible that nonsensical form data could get past the input verification function and the JSON serializer and contain NaNs, nulls, empty data, and so forth. When deserializing the data, this could cause the script to error out and stop. So we will catch any remaining errors. If there is an error, we will display an error message to the user.
catch (err) {
$('#backendErrorModal').modal('show');
return;
}
Now is also the time to check if an error was returned by the back end during JSON serialization. Again, if this is the case, we will display an error message to the user.
if (obj.returnError != "no error") {
$('#backendErrorModal').modal('show');
return;
}
Displaying New Images
We are now at the last steps! It's time to display our newly computed images! To do so, we simply assign the src of each image to the corresponding Base64 Image String.
$("#CTFimg").attr("src", "data:image/jpeg;base64," + obj.CTFout);
$("#PSFimg").attr("src", "data:image/jpeg;base64," + obj.PSFout);
$("#MTFimg").attr("src", "data:image/jpeg;base64," + obj.MTFout);
$("#MULTIimg").attr("src", "data:image/jpeg;base64," + obj.MULTIout);
Parsing Chart Data
Next we are going to parse the data that will go into the Charts.js charts.
var plotData_x0 = obj.xaxis.map(function (x, i) {
return {
x: x,
y: obj.x0[i]
};
});
var plotData_xh = obj.xaxis.map(function (x, i) {
return {
x: x,
y: obj.xh[i]
};
});
var plotData_x07 = obj.xaxis.map(function (x, i) {
return {
x: x,
y: obj.x07[i]
};
});
var plotData_x1 = obj.xaxis.map(function (x, i) {
return {
x: x,
y: obj.x1[i]
};
});
var plotData_y0 = obj.xaxis.map(function (x, i) {
return {
x: x,
y: obj.y0[i]
};
});
var plotData_yh = obj.xaxis.map(function (x, i) {
return {
x: x,
y: obj.yh[i]
};
});
var plotData_y07 = obj.xaxis.map(function (x, i) {
return {
x: x,
y: obj.y07[i]
};
});
var plotData_y1 = obj.xaxis.map(function (x, i) {
return {
x: x,
y: obj.y1[i]
};
});
var MTFu = obj.MTFx.map(function (x, i) {
return {
x: x,
y: obj.MTFuout[i]
};
});
var MTFv = obj.MTFx.map(function (x, i) {
return {
x: x,
y: obj.MTFvout[i]
};
});
var MTF_an = obj.MTFx.map(function (x, i) {
return {
x: x,
y: obj.MTF_anout[i]
};
});
Displaying the Charts
Now that we have parsed the data that will go into the charts, we simply insert them into three new Charts.js charts.
var ctx = document.getElementById("wavefanX");
var myChart = new Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
label: "H=0",
data: plotData_x0,
showLine: true,
fill: false,
pointRadius: 2,
pointHitRadius: 4,
borderColor: '#f4bf00',
backgroundColor: '#f4bf00'
}, {
label: "H=0.7",
data: plotData_x07,
showLine: true,
fill: false,
pointRadius: 2,
pointHitRadius: 4,
borderColor: '#f7941d',
backgroundColor: '#f7941d'
}, {
label: "H=1",
data: plotData_x1,
showLine: true,
fill: false,
pointRadius: 2,
pointHitRadius: 4,
borderColor: '#e10337',
backgroundColor: '#e10337'
}, {
label: "H=User Defined",
data: plotData_xh,
showLine: true,
fill: false,
pointRadius: 2,
pointHitRadius: 4,
borderColor: '#3c70b4',
backgroundColor: '#3c70b4'
}]
},
options: {
title: {
display: true,
text: 'Wave Fan in X Direction',
fontSize: 16,
fontColor: '#ddd'
},
maintainAspectRatio: false, animation: false,
scales: {
x: {
position: 'bottom',
title: {
display: true,
text: 'Pupil Position',
fontColor: '#ddd'
},
gridLines: {
color: '#888',
zeroLineColor: '#888'
},
ticks: {
fontColor: '#ddd'
}
},
y: {
title: {
display: true,
text: 'Error (Waves)',
fontColor: '#ddd'
},
gridLines: {
color: '#888',
zeroLineColor: '#888'
},
ticks: {
fontColor: '#ddd'
}
}]
},
tooltip: {
callbacks: {
label: function (context) {
return Number(context.parsed.x).toFixed(3) + ', ' + Number(context.parsed.y).toFixed(3);
}
}
},
legend: {
display: true,
position: 'top',
labels: {
fontColor: '#ddd'
}
}
}
});
var ctx2 = document.getElementById("wavefanY");
var myChart2 = new Chart(ctx2, {
type: 'scatter',
data: {
datasets: [{
label: "H=0",
data: plotData_y0,
showLine: true,
fill: false,
pointRadius: 2,
pointHitRadius: 4,
borderColor: '#f4bf00',
backgroundColor: '#f4bf00'
}, {
label: "H=0.7",
data: plotData_y07,
showLine: true,
fill: false,
pointRadius: 2,
pointHitRadius: 4,
borderColor: '#f7941d',
backgroundColor: '#f7941d'
}, {
label: "H=1",
data: plotData_y1,
showLine: true,
fill: false,
pointRadius: 2,
pointHitRadius: 4,
borderColor: '#e10337',
backgroundColor: '#e10337'
}, {
label: "H=User Defined",
data: plotData_yh,
showLine: true,
fill: false,
pointRadius: 2,
pointHitRadius: 4,
borderColor: '#3c70b4',
backgroundColor: '#3c70b4'
}]
},
options: {
title: {
display: true,
text: 'Wave Fan in Y Direction',
fontSize: 16,
fontColor: '#ddd'
},
maintainAspectRatio: false, animation: false,
scales: {
x: {
position: 'bottom',
title: {
display: true,
text: 'Pupil Position',
fontColor: '#ddd'
},
gridLines: {
color: '#888',
zeroLineColor: '#888'
},
ticks: {
fontColor: '#ddd'
}
},
y: {
title: {
display: true,
text: 'Error (Waves)',
fontColor: '#ddd'
},
gridLines: {
color: '#888',
zeroLineColor: '#888'
},
ticks: {
fontColor: '#ddd'
}
}]
},
tooltip: {
callbacks: {
label: function (context) {
return Number(context.parsed.x).toFixed(3) + ', ' + Number(context.parsed.y).toFixed(3);
}
}
},
legend: {
display: true,
position: 'top',
labels: {
fontColor: '#ddd'
}
}
}
});
var ctx3 = document.getElementById("modulation");
var myChart3 = new Chart(ctx3, {
type: 'scatter',
data: {
datasets: [{
label: "u",
data: MTFu,
showLine: true,
fill: false,
pointRadius: 2,
pointHitRadius: 4,
borderColor: '#f4bf00',
backgroundColor: '#f4bf00'
}, {
label: "v",
data: MTFv,
showLine: true,
fill: false,
pointRadius: 2,
pointHitRadius: 4,
borderColor: '#f7941d',
backgroundColor: '#f7941d'
}, {
label: "Diffraction Limit",
data: MTF_an,
showLine: true,
fill: false,
pointRadius: 2,
pointHitRadius: 4,
borderColor: '#e10337',
backgroundColor: '#e10337'
}]
},
options: {
title: {
display: true,
text: 'Modulation',
fontSize: 16,
fontColor: '#ddd'
},
maintainAspectRatio: false, animation: false,
scales: {
x: {
position: 'bottom',
title: {
display: true,
text: 'f (cyc/m)',
fontColor: '#ddd'
},
gridLines: {
color: '#888',
zeroLineColor: '#888'
},
ticks: {
fontColor: '#ddd'
}
},
y: {
title: {
display: true,
text: 'Modulation',
fontColor: '#ddd'
},
gridLines: {
color: '#888',
zeroLineColor: '#888'
},
ticks: {
fontColor: '#ddd'
}
}]
},
tooltip: {
callbacks: {
label: function (context) {
return Number(context.parsed.x).toFixed(3) + ', ' + Number(context.parsed.y).toFixed(3);
}
}
},
legend: {
display: true,
position: 'top',
labels: {
fontColor: '#ddd'
}
}
}
});