Fe Curve
Fractional solubility of aerosol iron
.charts {
width: 100%;
display: block;
clear: both;
overflow: hidden;
}
.hidden {
opacity: 0;
}
.sliders {
text-align: center;
}
circle {
fill: rgba(51, 102, 204, 0.2) !important;
r: 4 !important;
}
//#####################################################
//### For plots
//#####################################################
//Increase this if graph resolution is too low
var numPoints = 1000;
//The bounds for the values on the sliders
var feMin = 0;
var feMax = 500;
var feDefault = 100;
var solMin = 0;
var solMax = 10;
var solDefault = 3;
function ratio(fn, sol) {
return sol / fn;
}
//#####################################################
// Some google plot options
//#####################################################
// hAxis.title and vAxis.title can now use and tags
// to respectively designate superscript and subscript text in the
// labels. Note, this is actually not supported by the google
// visualization API, so we had to do some hacks to get there.
var scatterOptions = {
hAxis: {
title: ‘FeT‘
},
vAxis: {
title: ‘DFe’
},
legend: {
position: ‘none’
},
seriesType: ‘scatter’
};
var ratioOptions = {
hAxis: {
title: ‘FeT‘
},
vAxis: {
title: ‘%DFe’,
viewWindowMode: ‘explicit’,
viewWindow: {
max: 100,
min: 0
}
},
legend: {
position: ‘none’
},
seriesType: ‘scatter’
};
var logOptions = {
hAxis: {
title: ‘LogFeT‘,
viewWindowMode: ‘explicit’,
viewWindow: {
max: 2,
min: 0
}
},
vAxis: {
title: ‘Log%DFe’,
},
legend: {
position: ‘none’
},
seriesType: ‘scatter’,
series: {
1: {
type: “line”
}
}
};
//#####################################################
// Everything below this shouldn’t
// need to be changed unless you
// need different functionality
//#####################################################
//Used for processing – shouldn’t have to change
var sliderMin = 0;
var sliderMax = 1000;
var variableInformation = {
sol: {
min: solMin,
max: solMax,
default: solDefault
},
fe: {
min: feMin,
max: feMax,
default: feDefault
}
};
init();
function init() {
bindInputs(“fe”);
bindInputs(“sol”);
google.charts.load(‘current’, {
packages: [‘corechart’, ‘line’, ‘scatter’]
});
google.charts.setOnLoadCallback(initializeCharts);
}
function initializeCharts() {
var feMax = Number(document.getElementById(“fe-value”).value);
var solMax = Number(document.getElementById(“sol-value”).value);
if (feMax == NaN || solMax == NaN) {
alert(“Error”);
return;
}
var rawData = generateScatterData(feMax, solMax);
var scatterData = createChartData(rawData);
renderData(“scatter”, scatterData, scatterOptions);
var ratioData = createChartData(generateRatioData(rawData));
renderData(“ratio”, ratioData, ratioOptions);
var logData = createDoubleChartData(generateLogData(rawData));
renderData(“log”, logData, logOptions);
}
function createChartData(rawDataPoints) {
var data = new google.visualization.DataTable();
data.addColumn(‘number’, ‘Scatter Col 1’);
data.addColumn(‘number’, ‘Scatter Col 2’);
data.addRows(rawDataPoints);
return data;
}
function createDoubleChartData(rawDataPoints) {
var data = new google.visualization.DataTable();
data.addColumn(‘number’, ‘Scatter Col 1’);
data.addColumn(‘number’, ‘Scatter Col 2’);
data.addColumn(‘number’, ‘Scatter Col 3’);
data.addRows(rawDataPoints);
return data;
}
function renderData(prefix, data, options) {
var renderElem = document.getElementById(prefix + ‘-chart’);
var chart = new google.visualization.ComboChart(renderElem);
options = JSON.parse(JSON.stringify(options));
var titleMap = {
hLabel: {
guid: guid(),
label: options.hAxis.title
},
vLabel: {
guid: guid(),
label: options.vAxis.title
}
};
options.hAxis.title = titleMap.hLabel.guid;
options.vAxis.title = titleMap.vLabel.guid;
function fixMarkup(labelText){
var result = labelText
.replace(/(.*?)/, “$1”)
.replace(/(.*?)/, “$1”);
return result;
}
function fixLabels(guid, labelSvg) {
var htmlCollection = renderElem.getElementsByTagName(“text”);
var texts = [].slice.call(htmlCollection);
var label = texts.filter(function(txt) {
return txt.innerHTML == guid;
})[0];
label.innerHTML = fixMarkup(labelSvg);
}
renderElem.className += “hidden”
chart.draw(data, options);
setTimeout(function(){
fixLabels(titleMap.hLabel.guid, titleMap.hLabel.label);
fixLabels(titleMap.vLabel.guid, titleMap.vLabel.label);
renderElem.className = “”;
});
}
function generateScatterData(feMax, solMax) {
var data = [];
for (var i = 0; i < numPoints; i++) {
data.push([
random(feMin, feMax),
random(solMin, solMax)
]);
}
return data;
}
function generateRatioData(rawData) {
var data = [];
for (var i = 0; i < rawData.length; i++) {
var fe = rawData[i][0];
var sol = rawData[i][1];
data.push([fe, 100 * sol / fe]);
}
return data;
}
function generateLogData(rawData) {
var data = generateRatioData(rawData);
for (var i = 0; i < data.length; i++) {
data[i][0] = Math.log10(data[i][0]);
data[i][1] = Math.log10(data[i][1]);
data[i][2] = null;
}
var fit = fitData(data);
var note = document.getElementById("fit-note");
note.innerHTML = "y = " + fit.slope.toFixed(2) + "x + " + fit.intercept.toFixed(2) + " ; R2 = ” + fit.rSquared.toFixed(2);
var allXs = data.map(function(pt) {
return pt[0];
});
var minX = Math.min(…allXs);
var maxX = Math.max(…allXs);
data.push([minX, null, fit.intercept + fit.slope * minX]);
data.push([maxX, null, fit.intercept + fit.slope * maxX]);
return data;
}
function random(min, max) {
return min + Math.random() * (max – min);
}
function bindInputs(prefix) {
var slider = document.getElementById(prefix + “-slider”);
var valueElem = document.getElementById(prefix + “-value”);
var defaultVal = variableInformation[prefix].default;
var varMin = variableInformation[prefix].min;
var varMax = variableInformation[prefix].max;
setSliderValue(defaultVal);
valueElem.value = defaultVal;
slider.oninput = function() {
var rawSliderVal = slider.value;
var sliderPercent = rawSliderVal / (sliderMax – sliderMin);
var actualValue = varMin + sliderPercent * (varMax – varMin);
valueElem.value = actualValue;
initializeCharts();
}
valueElem.onchange = function() {
var y = Number(valueElem.value);
if (y != NaN) {
if (y > varMax) {
y = varMax;
}
if (y < varMin) {
y = varMin;
}
}
valueElem.value = y;
setSliderValue(y);
initializeCharts();
}
function setSliderValue(y) {
var percent = (y – varMin) / (varMax – varMin);
slider.value = sliderMin + percent * (sliderMax – sliderMin);
}
}
//Courtesy of https://stackoverflow.com/questions/50257760/regression-that-fits-best-scatter-plot
function fitData(points) {
var rV = {},
N = points.length,
sumX = 0,
sumY = 0,
sumXx = 0,
sumYy = 0,
sumXy = 0;
// can't fit with 0 or 1 point
if (N < 2) {
return rV;
}
for (var i = 0; i < N; i++) {
var x = points[i][0],
y = points[i][1];
sumX += x;
sumY += y;
sumXx += (x * x);
sumYy += (y * y);
sumXy += (x * y);
}
// calc slope and intercept
rV['slope'] = ((N * sumXy) – (sumX * sumY)) / (N * sumXx – (sumX * sumX));
rV['intercept'] = (sumY – rV['slope'] * sumX) / N;
rV['rSquared'] = Math.abs((rV['slope'] * (sumXy – (sumX * sumY) / N)) / (sumYy – ((sumY * sumY) / N)));
return rV;
}
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4();
}