File: src/core/web_service_data_cache_node.js
window.multigraph.util.namespace("window.multigraph.core", function (ns) {
"use strict";
/**
* @module multigraph
* @submodule core
*/
/**
* A WebServiceDataCacheNode represents a single node in the
* doubly-linked list holding the data for a WebServiceDataCache.
* The WebServiceDataCacheNode has an array of data (which may
* actually be null, if the node's data has not yet been loaded),
* next and prev pointers to the next and previous nodes in the
* linked list, and coveredMin and coveredMax values that indicate
* the min and max values of the "covered" range of data for this
* node.
*
* The "covered" range is the interval of the data number line for
* which this node is responsible for storing data; Multigraph
* uses range this to avoid requesting the same data twice --- it
* never requests data for a range already covered by an existing
* cache node.
*
* Initially, when the WebServiceDataCacheNode is created, the
* limits of the covered range are specified in the constructor.
* Later on, when the node's data is actually populated, the
* limits are potentially adjusted outward, if the range of data
* received is larger than the initially specified covered range.
* So in all cases, the covered range indicates the range for
* which no more data is needed, because it's covered by this
* node.
*
* Note that the covered range is never adjusted to be smaller.
*
* The WebServiceDataCacheNode does not actually fetch any data
* --- it is simply a storage container for fetched data; it's up
* to other code outside of this object to fetch and populate the
* data.
*
* @class WebServiceDataCacheNode
* @for WebServiceDataCacheNode
* @constructor
* @param {DataValue} coveredMin
* @param {DataValue} coveredMax
*/
ns.WebServiceDataCacheNode = window.jermaine.Model(function () {
/**
* The actual data for this node.
*
* @property data
* @type {Array|null}
* @author jrfrimme
*/
this.hasA("data").which.defaultsTo(null).and.validatesWith(function (data) {
var UF = window.multigraph.util.namespace("window.multigraph.utilityFunctions");
// accept null
if (data === null) { return true; }
// only accept arrays
if (UF.typeOf(data) !== "array") {
this.message = "WebServiceDataCacheNode's data attribute is not an Array";
return false;
}
// if the array contains anything, do a cursory check that it looks
// like an array of DataValue arrays (just check the first row)
if (data.length > 0) {
var firstRow = data[0],
i;
if (UF.typeOf(firstRow) !== "array") {
this.message = "WebServiceDataCacheNode's data attribute is not an Array of Arrays";
return false;
}
for (i = 0; i < firstRow.length; ++i) {
if (!ns.DataValue.isInstance(firstRow[i])) {
this.message = "WebServiceDataCacheNode's data attribute is not an Array of Arrays of DataValues (bad value in position " + i + " of first row";
return false;
}
}
}
return true;
});
/**
* The next node in the cache's linked list
*
* @property next
* @type {WebServiceDataCacheNode|null}
* @author jrfrimme
*/
this.hasA("next").which.defaultsTo(null).and.validatesWith(function (x) {
return x === null || x instanceof ns.WebServiceDataCacheNode;
});
/**
* The previous node in the cache's linked list
*
* @property prev
* @type {WebServiceDataCacheNode|null}
* @author jrfrimme
*/
this.hasA("prev").which.defaultsTo(null).and.validatesWith(function (x) {
return x === null || x instanceof ns.WebServiceDataCacheNode;
});
/**
* The min of the covered value range
*
* @property coveredMin
* @type {DataValue}
* @author jrfrimme
*/
this.hasA("coveredMin").which.validatesWith(ns.DataValue.isInstance);
/**
* The max of the covered value range
*
* @property coveredMax
* @type {DataValue}
* @author jrfrimme
*/
this.hasA("coveredMax").which.validatesWith(ns.DataValue.isInstance);
/**
* Return the next node in the cache that actually has data,
* or null if none exists.
*
* @method dataNext
* @author jrfrimme
* @return {WebServiceDataCacheNode|null}
*/
this.respondsTo("dataNext", function () {
var node = this.next();
while (node !== null && !node.hasData()) {
node = node.next();
}
return node;
});
/**
* Return the previous node in the cache that actually has data,
* or null if none exists.
*
* @method dataPrev
* @author jrfrimme
* @return {WebServiceDataCacheNode|null}
*/
this.respondsTo("dataPrev", function () {
var node = this.prev();
while (node !== null && !node.hasData()) {
node = node.prev();
}
return node;
});
/**
* Return the minimum (column 0) data value for this node. Returns null
* if the node has no data yet.
*
* @method dataMin
* @author jrfrimme
* @return {DataValue|null}
*/
this.respondsTo("dataMin", function () {
var data = this.data();
if (data === null) { return null; }
if (data.length === 0) { return null; }
if (data[0] === null) { return null; }
if (data[0].length === 0) { return null; }
return data[0][0];
});
/**
* Return the maximum (column 0) data value for this node. Returns null
* if the node has no data yet.
*
* @method dataMax
* @author jrfrimme
* @return {DataValue|null}
*/
this.respondsTo("dataMax", function() {
var data = this.data();
if (data === null) { return null; }
if (data.length === 0) { return null; }
if (data[data.length-1] === null) { return null; }
if (data[data.length-1].length === 0) { return null; }
return data[data.length-1][0];
});
/**
* Return true if this node has data; false if not.
*
* @method hasData
* @author jrfrimme
* @return Boolean
*/
this.respondsTo("hasData", function() {
return this.data() !== null;
});
this.isBuiltWith("coveredMin", "coveredMax");
/**
* Populate this node's data array by parsing the values
* contained in the 'dataText' string, which should be a
* string of comma-separated values of the same sort expected
* by ArrayData and CSVData. The first argument, `columns`,
* should be a plain javascript array of DataVariable instances,
* of the sort returned by `Data.getColumns()`.
*
* This method examines other nodes in the cache in order
* insure that values included in this node's data array
* are (a) strictly greater than the maximum value present in the
* cache prior to this node, and (b) strictly less than the
* minimum value present in the cache after this node.
* This guarantees that there is no overlap between the
* data in this node and other nodes in the cache.
*
* @method parseData
* @param {DataVariable Attr_List} columns
* @param {String} dataText
* @author jrfrimme
*/
this.respondsTo("parseData", function (columns, dataText) {
var i, b,
maxPrevValue = null,
minNextValue = null,
arrayDataArray,
data,
row;
// set maxPrevValue to the max value in column0 in the cache prior to this block, if any:
b = this.dataPrev();
if (b !== null) {
maxPrevValue = b.dataMax();
}
// set minNextValue to the min value in column0 in the cache after this block, if any:
b = this.dataNext();
if (b !== null) {
minNextValue = b.dataMin();
}
// convert the csv dataText string to an array
arrayDataArray = ns.ArrayData.textToDataValuesArray(columns, dataText);
// populate the data array by copying values from the converted array, skipping any
// values that are already within the range covered by the rest of the cache
data = [];
for (i = 0; i < arrayDataArray.length; ++i) {
row = arrayDataArray[i];
if ((maxPrevValue === null || row[0].gt(maxPrevValue)) &&
(minNextValue === null || row[0].lt(minNextValue))) {
data.push( row );
}
}
// if we didn't get any new values, we're done
if (data.length === 0) {
return;
}
// lower the coveredMin value if the actual data received is lower than the current coveredMin value
if (data[0][0].lt(this.coveredMin())) {
this.coveredMin(data[0][0]);
}
// raise the coveredMax value if the actual data received is higher than the current coveredMax value
if (data[data.length-1][0].gt(this.coveredMax())) {
this.coveredMax(data[data.length-1][0]);
}
// load the data
this.data( data );
});
});
});