diff --git a/bin/databases-config.js b/bin/databases-config.js
index c4684b9..eec74d5 100644
--- a/bin/databases-config.js
+++ b/bin/databases-config.js
@@ -4,7 +4,8 @@ module.exports = {
"mongo-port": 27017,
"mongo-database": "events",
"mongo-username": null,
- "mongo-password": null
+ "mongo-password": null,
+ "collectionSize": 1024 * 1024 * 1024
},
aggregations: {
"mongo-host": "192.168.56.101",
diff --git a/bin/generator-config.js b/bin/generator-config.js
index be36e9f..d4640bc 100644
--- a/bin/generator-config.js
+++ b/bin/generator-config.js
@@ -1,5 +1,5 @@
module.exports = {
- "packetsInInterval": 100000,
+ "packetsInInterval": 150000,
"intervalInMS": 60000,
"sampleRateInMS": 100,
"data": {
diff --git a/bin/generator.js b/bin/generator.js
index 7d6150b..7cf4007 100644
--- a/bin/generator.js
+++ b/bin/generator.js
@@ -38,11 +38,10 @@ var options = require("./generator-config.js"),
}
return a;
},
- sendPacket: function () {
+ sendPacket: function (type) {
var country = options.data.country[randomTools.getRandomInt(0, options.data.country.length - 1)],
gender = options.data.gender[randomTools.getRandomInt(0, options.data.gender.length - 1)],
device = options.data.device[randomTools.getRandomInt(0, options.data.device.length - 1)],
- type = options.data.type[randomTools.getRandomInt(0, options.data.type.length - 1)],
packet = JSON.stringify({
type: type,
data: {
@@ -54,11 +53,11 @@ var options = require("./generator-config.js"),
// console.log(packet);
ws.send(packet);
},
- sendPackets: function (n) {
+ sendPackets: function (n, type) {
var i;
console.log((new Date()).getTime() + ": Sending packets: " + n);
for (i = 0; i < n; i++) {
- this.sendPacket();
+ this.sendPacket(type);
}
},
@@ -66,7 +65,8 @@ var options = require("./generator-config.js"),
var i,
doSetTimeout = function (i) {
setTimeout(function () {
- packetGenerator.sendPackets(arrayOfPacketsInInterval[i]);
+ var type = options.data.type[randomTools.getRandomInt(0, options.data.type.length - 1)];
+ packetGenerator.sendPackets(arrayOfPacketsInInterval[i], type);
},
i * options.sampleRateInMS);
};
diff --git a/bower.json b/bower.json
index 1c0253f..6884790 100644
--- a/bower.json
+++ b/bower.json
@@ -14,7 +14,7 @@
"jquery": "2.1.0",
"bootstrap": "3.1.1",
"d3": "3.3.*",
- "crossfilter": "1.2.0",
+ "crossfilter": "1.3.*",
"nvd3": "1.1.*",
"dcjs": "1.6.0"
},
diff --git a/examples/event-stream/event-get.html b/examples/event-stream/event-get.html
index 8d0bcb4..5805f66 100644
--- a/examples/event-stream/event-get.html
+++ b/examples/event-stream/event-get.html
@@ -30,8 +30,7 @@
Streaming Events
console.log("connected!");
socket.send(JSON.stringify({
type: type,
- stop: new Date(),
- start: new Date().getTime()-5*60*1000
+ start: new Date().getTime()-60*1000
}));
};
diff --git a/lib/analytics/disperser.js b/lib/analytics/disperser.js
index f6c2e5a..3ced22c 100644
--- a/lib/analytics/disperser.js
+++ b/lib/analytics/disperser.js
@@ -12,10 +12,12 @@ exports.register = function (dbs, endpoints, options) {
var eventsDB = dbs.events,
aggregationsDB = dbs.aggregations,
eventGetter = require("./event.js").getterInit(eventsDB),
+ eventCounter = require("./event.js").counterInit(eventsDB),
aggregationGetter = require("./aggregation.js").getterInit(aggregationsDB);
endpoints.ws.push(
endpoint("/1.0/event/get", eventGetter),
+ endpoint("/1.0/event/getCount", eventCounter),
endpoint("/1.0/aggregation/get", aggregationGetter)
);
@@ -30,7 +32,7 @@ exports.register = function (dbs, endpoints, options) {
request.limit = LIMIT_MAX;
}
- function documentHandler(dataElem) {
+ function messageSenderHttp(dataElem) {
if (dataElem === null) {
response.end(JSON.stringify(data.reverse()));
}
@@ -39,7 +41,7 @@ exports.register = function (dbs, endpoints, options) {
}
}
- if (eventGetter(request, documentHandler) < 0) {
+ if (eventGetter(request, messageSenderHttp) < 0) {
response.writeHead(400, headers);
response.end(JSON.stringify(data[0]));
} else {
@@ -59,7 +61,7 @@ exports.register = function (dbs, endpoints, options) {
request.limit = LIMIT_MAX;
}
- function documentHandler(dataElem) {
+ function messageSenderHttp(dataElem) {
if (dataElem === null) {
response.end(JSON.stringify(data.reverse()));
}
@@ -68,7 +70,36 @@ exports.register = function (dbs, endpoints, options) {
}
}
- if (aggregationGetter(request, documentHandler) < 0) {
+ if (aggregationGetter(request, messageSenderHttp) < 0) {
+ response.writeHead(400, headers);
+ response.end(JSON.stringify(data[0]));
+ } else {
+ response.writeHead(200, headers);
+ }
+
+ }
+
+ function eventCount(request, response) {
+ request = url.parse(request.url, true).query;
+ var data = [];
+
+ if (!request.hasOwnProperty('start')) {
+ request.start = 0;
+ }
+ if ((request.hasOwnProperty('limit') && (request.limit >= LIMIT_MAX))) {
+ request.limit = LIMIT_MAX;
+ }
+
+ function messageSenderHttp(dataElem) {
+ if (dataElem === null) {
+ response.end(JSON.stringify(data.reverse()));
+ }
+ else {
+ data.push(dataElem);
+ }
+ }
+
+ if (eventCounter(request, messageSenderHttp) < 0) {
response.writeHead(400, headers);
response.end(JSON.stringify(data[0]));
} else {
@@ -79,6 +110,7 @@ exports.register = function (dbs, endpoints, options) {
endpoints.http.push(
endpoint("GET", "/1.0/event/get", eventGet),
+ endpoint("GET", "/1.0/event/getCount", eventCount),
endpoint("GET", "/1.0/aggregation/get", aggregationGet)
);
diff --git a/lib/analytics/event.js b/lib/analytics/event.js
index dd1840d..bde0879 100644
--- a/lib/analytics/event.js
+++ b/lib/analytics/event.js
@@ -2,18 +2,20 @@
var mongodb = require("mongodb"),
type_re = /^[a-z][a-zA-Z0-9_]+$/,
genericGetter = require("./genericGetter.js"),
+ genericCounter = require("./genericCounter.js"),
myutils = require("./myutils.js"),
- util = require("util");
+ util = require("util"),
+ mongoConfigs = require("../../bin/databases-config");
exports.putterInit = function (db, options) {
var eventsCollectionCreated = 0,
eventsToSave = [],
events = [],
- collectionSize = options.collectionSize,
+ collectionSize = mongoConfigs.events.collectionSize,
bufferSize = 10000,
bufferTimestamp = (new Date()).getTime();
- if (myutils.isInt(collectionSize)) {
+ if (!myutils.isInt(collectionSize)) {
throw "Invalid collection size: " + collectionSize;
}
function handle(error) {
@@ -61,7 +63,8 @@ exports.putterInit = function (db, options) {
request = [request];
}
for (i = 0; i < request.length; i++) {
- events[i] = {t: time, d: request[i].data, type: request[i].type};
+ //diminishing time granularity, as it is not needed and saving just the second allows us to aggregate on time
+ events[i] = {t: time - time % 1000, d: request[i].data, type: request[i].type};
}
// If eventsCollectionCreated, save immediately.
if (eventsCollectionCreated === 1) {
@@ -93,7 +96,7 @@ exports.putterInit = function (db, options) {
return save();
}
- // Events are indexed by time which is _id, which is natural order.
+ // Events are indexed by _id, which is natural order. (which is time)
db.createCollection("events", {capped: true, autoIndexId: true, size: collectionSize}, function (error, result) {
handle(error);
eventsCollectionCreated = 1;
@@ -108,3 +111,6 @@ exports.putterInit = function (db, options) {
exports.getterInit = function (eventsDB) {
return genericGetter(eventsDB);
};
+exports.counterInit = function (eventsDB) {
+ return genericCounter(eventsDB);
+};
diff --git a/lib/analytics/genericCounter.js b/lib/analytics/genericCounter.js
new file mode 100644
index 0000000..8d627e5
--- /dev/null
+++ b/lib/analytics/genericCounter.js
@@ -0,0 +1,193 @@
+"use strict";
+var myutils = require("./myutils.js"),
+ mongodb = require("mongodb"),
+ type_re = /^[a-z][a-zA-Z0-9_]+$/,
+ MAX_RETURNED_RECORDS = 10000,
+ util = require("util");
+function customQuery(collectionObj, filter, limit, streamified, messageSender) {
+ var temp;
+ if (filter === undefined) {
+ filter = {};
+ }
+ if (limit === undefined) {
+ limit = MAX_RETURNED_RECORDS;
+ }
+
+ collectionObj.aggregate([
+ {
+ "$match": filter
+ },
+ {
+ "$group": {
+ "_id": "$t",
+ count: {
+ $sum: 1
+ }
+ }
+ },
+ {
+ "$sort": {
+ _id: 1
+ }
+ },
+ {
+ $limit: limit
+ }
+ ],
+ {},
+ function (error, result) {
+ if (error) {
+ throw error;
+ }
+ messageSender(result);
+ if (streamified) {
+ // this is hardcoded, but with good reason. In a stream, we only provide 5 seconds back.
+ // We do not provide just the last second as there can be additional updates happening in the database
+ // If 5 seconds is not enough, the DB is screwed anyway
+ limit = 5;
+ setTimeout(
+ function () {
+ //move filter to cover just the last 5 seconds
+ temp = new Date().getTime();
+ temp = temp - temp % 1000 - 5000;
+ filter._id.$gte= myutils.objectIdFromDate(temp);
+ filter._id.$lt= myutils.objectIdFromDate(temp+1000);
+ customQuery(collectionObj, filter, limit, streamified, messageSender);
+ },
+ 1000
+ );
+ }
+ });
+}
+
+module.exports = function (db) {
+ var collection,
+ streamsByName = {};
+
+ function open(messageSender) {
+ return !messageSender.closed;
+ }
+
+ function counter(request, messageSender) {
+
+ var stream = !request.hasOwnProperty("stop"),
+ start = new Date(request.start).getTime(),
+ stop = stream ? undefined : (request.stop ? (new Date(request.stop)).getTime() : undefined),
+ name = request.hasOwnProperty("name") ? request.name : undefined,
+ type = request.hasOwnProperty("type") ? request.type : undefined,
+ limit,
+ filter,
+ streams,
+ collectionName;
+
+ // Validate the date and type.
+ if (!type_re.test(request.type)) {
+ messageSender({error: "invalid type"});
+ return -1;
+ }
+
+ // Validate the dates.
+ if (isNaN(start)) {
+ messageSender({error: "invalid start"});
+ return -1;
+ }
+ if (isNaN(stop)) {
+ stop = undefined;
+ }
+
+ if (!stream) {
+ if (stop === undefined) {
+ messageSender({error: "invalid stop"});
+ return -1;
+ }
+ }
+
+ // Set an optional limit on the number of documents to return.
+ if (request.hasOwnProperty("limit")) {
+ limit = request.limit;
+ }
+
+ if (limit !== undefined || limit > MAX_RETURNED_RECORDS) {
+ limit = MAX_RETURNED_RECORDS;
+ }
+
+ // Copy any expression filters into the match object.
+ filter = {_id: {$gte: myutils.objectIdFromDate(start)}};
+ if (stop !== undefined) {
+ filter._id.$lt = myutils.objectIdFromDate(stop);
+ }
+
+ if (name === undefined) {
+ collectionName = "events";
+ if (type !== undefined) {
+ filter.type = type;
+ }
+ else {
+ throw "We cannot query events without a type";
+ }
+ } else {
+ collectionName = name;
+ }
+
+ db.collectionNames(collectionName, {}, function (err, result) {
+ if (err) {
+ throw err;
+ }
+ if (result.length !== 1) {
+ throw "The number of results for collection: " + collectionName + " is different than 1";
+ }
+ collection = db.collection(collectionName);
+
+ // For streaming queries, share streams for efficient polling.
+ if (stream && collectionName && streamsByName) {
+ streams = streamsByName[collectionName];
+
+ if (streams && streams.waiting && Array.isArray(streams.waiting)) {
+ streams.waiting.push(messageSender);
+ customQuery(collection, filter, limit, true, messageSender);
+ }
+ else {
+ if (streamsByName && collectionName) {
+ streams = streamsByName[collectionName] = {waiting: [], active: [messageSender]};
+ customQuery(collection, filter, limit, true, function (document) {
+
+ // If there's a name, send it to all active, open clients.
+ if (document) {
+ streams.active.forEach(function (messageSender) {
+ if (!messageSender.closed) {
+ messageSender(document);
+ }
+ });
+ }
+ else {
+ streams.active = streams.active.concat(streams.waiting).filter(open);
+ streams.waiting = [];
+
+ // If no clients remain, then it's safe to delete the shared
+ // stream, and we'll no longer be responsible for polling.
+ if (!streams.active.length) {
+ delete streamsByName[collectionName];
+ }
+ }
+ });
+ }
+ }
+ }
+ else {
+ customQuery(collection, filter, limit, false, messageSender);
+
+ }
+ });
+
+ }
+
+ counter.close = function (messageSender) {
+ messageSender.closed = true;
+ };
+
+ return counter;
+};
+
+
+
+
diff --git a/lib/analytics/genericGetter.js b/lib/analytics/genericGetter.js
index 6f4cfb8..1557640 100644
--- a/lib/analytics/genericGetter.js
+++ b/lib/analytics/genericGetter.js
@@ -4,7 +4,7 @@ var myutils = require("./myutils.js"),
type_re = /^[a-z][a-zA-Z0-9_]+$/,
MAX_RETURNED_RECORDS = 10000,
util = require("util");
-function customQuery(collectionObj, filter, sort, limit, batchSize, streamified, documentHandler) {
+function customQuery(collectionObj, filter, sort, limit, batchSize, streamified, messageSender) {
// set MongoDB cursor options
var cursorOptions = {},
cursor,
@@ -30,13 +30,14 @@ function customQuery(collectionObj, filter, sort, limit, batchSize, streamified,
stream = cursor.sort({$natural: -1}).stream();
stream.on('data', function (document) {
document.d = (document.d === undefined ? document.key : document.d);
- documentHandler(document);
+ delete document.key;
+ messageSender(document);
});
}
else {
cursor = cursor.sort(sort).limit(limit).batchSize(batchSize);
cursor.each(function (error, document) {
- if (documentHandler.closed) {
+ if (messageSender.closed) {
//noinspection JSLint
return cursor.close(function (error, result) {
if (error) {
@@ -53,10 +54,11 @@ function customQuery(collectionObj, filter, sort, limit, batchSize, streamified,
// A null name indicates that there are no more results.
if (document) {
document.d = (document.d === undefined ? document.key : document.d);
- documentHandler(document);
+ delete document.key;
+ messageSender(document);
}
else {
- documentHandler(null);
+ messageSender(null);
}
});
}
@@ -66,11 +68,11 @@ module.exports = function (db) {
var collection,
streamsByName = {};
- function open(documentHandler) {
- return !documentHandler.closed;
+ function open(messageSender) {
+ return !messageSender.closed;
}
- function getter(request, documentHandler) {
+ function getter(request, messageSender) {
var stream = !request.hasOwnProperty("stop"),
start = new Date(request.start).getTime(),
@@ -86,13 +88,13 @@ module.exports = function (db) {
// Validate the date and type.
if (!type_re.test(request.type)) {
- documentHandler({error: "invalid type"});
+ messageSender({error: "invalid type"});
return -1;
}
// Validate the dates.
if (isNaN(start)) {
- documentHandler({error: "invalid start"});
+ messageSender({error: "invalid start"});
return -1;
}
if (isNaN(stop)) {
@@ -101,7 +103,7 @@ module.exports = function (db) {
if (!stream) {
if (stop === undefined) {
- documentHandler({error: "invalid stop"});
+ messageSender({error: "invalid stop"});
return -1;
}
}
@@ -149,20 +151,19 @@ module.exports = function (db) {
streams = streamsByName[collectionName];
if (streams && streams.waiting && Array.isArray(streams.waiting)) {
- streams.waiting.push(documentHandler);
- customQuery(collection, filter, sort, limit, batchSize, true, documentHandler);
+ streams.waiting.push(messageSender);
+ customQuery(collection, filter, sort, limit, batchSize, true, messageSender);
}
else {
if (streamsByName && collectionName) {
- streams = streamsByName[collectionName] = {waiting: [], active: [documentHandler]};
+ streams = streamsByName[collectionName] = {waiting: [], active: [messageSender]};
customQuery(collection, filter, sort, limit, batchSize, true, function (document) {
- // If there's an name, send it to all active, open clients.
+ // If there's a name, send it to all active, open clients.
if (document) {
- streams.active.forEach(function (documentHandler) {
- if (!documentHandler.closed) {
- console.log("document handled: " + util.inspect(document, {colors: true, depth: null}));
- documentHandler(document);
+ streams.active.forEach(function (messageSender) {
+ if (!messageSender.closed) {
+ messageSender(document);
}
});
}
@@ -181,15 +182,15 @@ module.exports = function (db) {
}
}
else {
- customQuery(collection, filter, sort, limit, batchSize, false, documentHandler);
+ customQuery(collection, filter, sort, limit, batchSize, false, messageSender);
}
});
}
- getter.close = function (documentHandler) {
- documentHandler.closed = true;
+ getter.close = function (messageSender) {
+ messageSender.closed = true;
};
return getter;
diff --git a/lib/analytics/server.js b/lib/analytics/server.js
index a330bec..7b1d180 100644
--- a/lib/analytics/server.js
+++ b/lib/analytics/server.js
@@ -7,19 +7,7 @@ var util = require("util"),
staticModule = require("node-static"),
database = require('./database.js');
-// Configuration for WebSocket requests.
-
-//var wsOptions = {
-// maxReceivedFrameSize: 0x10000,
-// maxReceivedMessageSize: 0x100000,
-// fragmentOutgoingMessages: true,
-// fragmentationThreshold: 0x4000,
-// keepalive: true,
-// keepaliveInterval: 20000,
-// assembleFragments: true,
-// disableNagleAlgorithm: true,
-// closeTimeout: 5000
-//};
+
function ignore() {
console.log("Ignoring...");
@@ -65,7 +53,7 @@ module.exports = function (options) {
i,
n,
endpoint,
- messageSender;
+ messageSenderWebSocket;
// Forward messages to the appropriate endpoint, or close the connection.
n = endpoints.ws.length;
@@ -76,21 +64,21 @@ module.exports = function (options) {
}
}
if (foundMatch) {
- messageSender = function (response) {
+ messageSenderWebSocket = function (response) {
socket.send(JSON.stringify(response));
};
- messageSender.id = ++id;
+ messageSenderWebSocket.id = ++id;
// Listen for socket disconnect.
if (endpoint.dispatch.close) {
socket.on("end", function () {
- endpoint.dispatch.close(messageSender);
+ endpoint.dispatch.close(messageSenderWebSocket);
});
}
socket.on("message", function (message) {
- endpoint.dispatch(JSON.parse(message), messageSender);
+ endpoint.dispatch(JSON.parse(message), messageSenderWebSocket);
});
return;
}
diff --git a/public/js/analytics.js b/public/js/analytics.js
index 56862f6..71a62cd 100644
--- a/public/js/analytics.js
+++ b/public/js/analytics.js
@@ -1,639 +1,403 @@
"use strict";
/*global nv*/
/*global d3*/
+/*global dc*/
/*global $*/
-/*global createElem*/
/*global WebSocket*/
+/*global document*/
/*global crossfilter*/
/*global window*/
+var myUtils = {
+ createElem: function (type, attrs, text) {
+ var elem = document.createElement(type),
+ attr;
-var realTimeEvents = {
- refreshChart: function (callback) {
- d3.select(this.chartAnchor + ' svg')
+ if (attrs !== undefined && attrs instanceof Object) {
+ for (attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ elem[attr] = attrs[attr];
+ }
+ }
+ }
+
+ if (text !== undefined) {
+ elem.innerText = text;
+ }
+
+ return elem;
+ }
+};
+
+function Chart(config) {
+ var that = this;
+ this.addCharts = function (callback) {
+
+ this.chart.width($("#" + this.chartAnchor).width())
+ .height(250)
+ .margins({top: 10, right: 30, bottom: 60, left: 60})
+ .dimension(this.dimensionTime)
+ .group(this.dimensionTimeGroup)
+ .elasticY(true)
+ // (optional) whether bar should be center to its x value. Not needed for ordinal chart, :default=false
+ .centerBar(true)
+ // (optional) set gap between bars manually in px, :default=2
+ .gap(1)
+ // (optional) set filter brush rounding
+ .round(dc.round.floor)
+ // .alwaysUseRounding(true)
+ .x(d3.scale.linear().domain([new Date().getTime() - config.chartParams.length, new Date().getTime()]))
+ .renderHorizontalGridLines(true);
+ this.formatChart();
+ // customize the filter displayed in the control span
+ // .filterPrinter(function (filters) {
+ // var filter = filters[0], s = "";
+ // s += numberFormat(filter[0]) + "% -> " + numberFormat(filter[1]) + "%";
+ // return s;
+ // });
+
+ if (callback) {
+ callback();
+ }
+ };
+ this.initSocket = function (callback) {
+ this.socket = new WebSocket(config.chartParams.wsAddress);
+ this.socket.onopen = function () {
+ console.log(config.name + ": connected to " + config.chartParams.wsAddress);
+ that.socket.send(JSON.stringify(that.socketPacket));
+ };
+ this.socket.onmessage = function (message) {
+ var events;
+ if (message) {
+ if (message.data) {
+ events = JSON.parse(message.data);
+ if (events && (events.d || events.count || events.length)) {
+ //console.log(config.name + ": events " + events.toString());
+ that.dataHandler(events);
+ }
+ }
+ }
+ };
+ this.socket.onclose = function () {
+ console.log(config.name + ": closed");
+ };
+ this.socket.onerror = function (error) {
+ console.log(config.name + ": error", error);
+ };
+ if (callback) {
+ callback();
+ }
+ };
+ this.initHtml = function (callback) {
+ if ($(this.chartAnchor + ' svg').length === 0) { // create svg
+ $("#charts")
+ .append(myUtils.createElem('div', {id: this.chartAnchor}));
+ $("#" + this.chartAnchor)
+ .append(myUtils.createElem('h2', {className: 'title'}, config.name)) // title
+ .append(myUtils.createElem('div', {className: 'control'})) // for control buttons
+ .append(''); // svg for chart
+ }
+
+ $("#" + this.chartAnchor + ' .control')
+ .append(myUtils.createElem('p'))
+ .append(myUtils.createElem('button', {className: 'pause'}, 'Pause')) // pause button
+ .append(myUtils.createElem('button', {className: 'resume'}, 'Resume')) // resume
+ ;
+
+ $("#" + this.chartAnchor + ' button.pause')
+ .click(function () {
+ that.pause();
+ })
+ ;
+ $("#" + this.chartAnchor + ' button.resume')
+ .click(function () {
+ that.resume();
+ });
+ if (callback) {
+ callback();
+ }
+ };
+ this.refreshAll = function () {
+ // - no need for now - this.dataHandler();
+ that.refreshChart();
+ };
+ this.setChartAnchor = function (chartAnchor) {
+ chartAnchor = chartAnchor.toLocaleLowerCase().replace(/[^a-z0-9]/g, "_");
+ this.chartAnchor = chartAnchor;
+ console.log(config.name + ": chartAnchor is set to " + this.chartAnchor);
+ };
+ this.pause = function () {
+ clearInterval(that.intervalHandle);
+ };
+ this.resume = function () {
+ setTimeout(function () {
+ that.intervalHandle = setInterval(function () {
+ that.refreshAll();
+ }, config.refreshFrequency);
+ }, 1e3);
+ };
+ this.chartAnchor = '';
+ this.socket = null;
+ if (config.chartParams.stop instanceof Date) {
+ this.socketPacket.stop = config.chartParams.stop;
+ }
+ this.intervalHandle = 0;
+ this.start = function () {
+ this.setChartAnchor(config.name);
+ that.initHtml(function () {
+ that.initCustomForChart(function () {
+ that.addCharts(function () {
+ that.initSocket(function () {
+ that.resume();
+ });
+ });
+ });
+ });
+ };
+ this.dimensions = [];
+ this.initCustomForChart = function (callback) {
+ var i,
+ temp;
+ this.chart = dc.barChart("#" + this.chartAnchor);
+ this.crossfilter = crossfilter();
+ for (i in config.chartParams.dimensionsNames) {
+ if (config.chartParams.dimensionsNames.hasOwnProperty(i)) {
+
+ temp = {};
+ temp.name = i;
+ (function (i, temp) {
+ temp.dimension = that.crossfilter.dimension(
+ function (d) {
+ return d[i];
+ }
+ );
+ temp.dimensionGroup = temp.dimension.group();
+ that.dimensions.push(temp);
+ }(i, temp));
+ }
+ }
+ this.dimensionTime = this.crossfilter.dimension(function (d) {
+ return d.t;
+ });
+ this.dimensionTimeGroup = this.dimensionTime.group().reduceSum(function (d) {
+ return d.count;
+ });
+
+ if (callback) {
+ callback();
+ }
+ };
+ this.refreshChart = function (callback) {
+ this.chart.x(d3.scale.linear().domain([new Date().getTime() - config.chartParams.length, new Date().getTime()]));
+ dc.renderAll();
+ if (callback) {
+ callback();
+ }
+ };
+ this.dataHandler = function (doc) {
+ var i;
+
+ if (doc) {
+ //format the doc in data to match crossfilter style
+ for (i in doc.d) {
+ if (doc.d.hasOwnProperty(i)) {
+ doc[i] = doc.d[i];
+ }
+ }
+ delete doc.d;
+ this.crossfilter.add([doc]);
+
+ config.cutOff = new Date().getTime();
+ config.cutOff = config.cutOff - config.length;
+ this.dimensionTime.filterFunction(function (d) {
+ return d < config.cutOff;
+ });
+ this.crossfilter.remove();
+ this.dimensionTime.filterAll();
+ this.refreshAll();
+ }
+ };
+
+}
+
+function EventsChart(config) {
+ var that = this;
+ //defining dataHandler,chart and addCharts
+ this.socketPacket = {
+ type: config.chartParams.query.type,
+ start: config.chartParams.start
+ };
+ this.formatChart = function () {
+ this.chart.xAxis().tickFormat(function (d) {
+ return ("0" + new Date(d).getMinutes()).substr(-2) + ":" + ("0" + new Date(d).getSeconds()).substr(-2);
+ });
+ };
+ this.chartOLD = nv.models.lineChart();
+ this.addChartsOLD = function (callback) {
+ nv.addGraph(function () {
+ that.chart
+ .showLegend(false)
+ .margin({top: 10, bottom: 30, left: 60, right: 60})
+ .useInteractiveGuideline(true);
+
+ that.chart.xAxis // chart sub-models (ie. xAxis, yAxis, etc) when accessed directly, return themselves, not the partent chart, so need to chain separately
+ .tickFormat(function (d) {
+ return d3.time.format('%X')(new Date(d));
+ });
+
+ that.chart.yAxis
+ .tickFormat(function (d) {
+ return d3.format('d')(d);
+ })
+ .axisLabel('Count');
+ return that.chart;
+ });
+
+ if (callback) {
+ callback();
+ }
+ };
+ this.refreshChartOLD = function (callback) {
+ if (that.transitionDuration === undefined) {
+ that.transitionDuration = 1;
+ }
+ d3.select("#" + that.chartAnchor + ' svg')
.datum([
{
key: 'Events',
- values: this.chartData
+ values: that.chartData
}
])
- .transition().duration(800)
- .call(this.chart);
+ .transition().duration(that.transitionDuration)
+ .call(that.chart);
//Figure out a good way to do this automatically
- nv.utils.windowResize(this.chart.update);
+ nv.utils.windowResize(that.chart.update);
if (callback) {
callback();
}
- },
- dataHandler: function (eventData) {
+ };
+ this.dataHandlerOLD = function (eventCounts) {
var t,
- i,
- lastTime;
-
- if (eventData) {
- t = eventData.t;
- } else {
- t = (new Date()).getTime();
- }
- t = t - t % 1e3;
+ i;
- //get the last timestamp
- if (this.chartData.length === 0) {
- //add the first element
- lastTime = (new Date()).getTime();
- lastTime = lastTime - lastTime % 1e3;
- this.chartData.push({x: new Date(lastTime), y: 0});
- } else {
- lastTime = this.chartData[this.chartData.length - 1].x.getTime();
- }
-
- if (lastTime < t) {
- // console.log("add zeroes as necessary then insert t: " + t);
- for (i = lastTime + 1e3; i < t; i += 1e3) {
- this.chartData.push({x: new Date(i), y: 0});
- }
- if (eventData) {
- this.chartData.push({x: new Date(t), y: 1});
- }
- else {
- this.chartData.push({x: new Date(t), y: 0});
- }
- } else if (lastTime === t) {
- // console.log("lastTime is t: " + t);
- if (eventData) {
- this.chartData[this.chartData.length - 1].y++;
- }
- } else if (lastTime > t) {
- //console.log("go back and look for the correct record to increment, but don,t go back too much t: " + t);
- for (i = this.chartData.length - 2; i > ((this.chartData.length - 7) > 0 ? this.chartData.length - 7 : 0); i--) {
- if (this.chartData[i].x.getTime() === t) {
- this.chartData[i].y++;
- break;
+ if (eventCounts && eventCounts.length > 0) {
+ for (i = 0; i < eventCounts.length; i++) {
+ if (eventCounts[i]._id && eventCounts[i].count) {
+ this.data[eventCounts[i]._id] = eventCounts[i].count;
}
}
}
+ t = (new Date()).getTime();
+ t = t - t % 1e3;
+ if (this.data[t] === undefined) {
+ this.data[t] = 0;
+ }
while (this.chartData.length > 60) {
+ t = this.chartData[0].x.getTime();
+ delete this.data[t];
this.chartData.shift();
}
- },
- refreshAll: function () {
- //this.refreshChartData(function () {
- this.dataHandler();
- if (this.chartData && this.chartData.length) {
- this.refreshChart();
+ //convert data intro chartData
+ this.chartData = [];
+ for (i in this.data) {
+ if (this.data.hasOwnProperty(i)) {
+ this.chartData.push({x: new Date(parseInt(i, 10)), y: this.data[i]});
+ }
+ }
+ this.chartData.sort(function (a, b) {
+ return a.x.getTime() - b.x.getTime();
+ });
+
+ };
+ this.initCustomForChartOLD = function (callback) {
+ var currentTime = new Date().getTime(),
+ i;
+ currentTime = currentTime - currentTime % 1000;
+ for (i = currentTime - 60 * 1e3; i <= currentTime + 1e3; i += 1000) {
+ this.data[i] = 0;
}
- },
- init: function () {
- var that = this,
- initSocket = function (callback) {
- that.socket = new WebSocket(that.socketConnection);
- that.socket.onopen = function () {
- console.log("connected to " + that.socketConnection);
- that.socket.send(JSON.stringify(that.socketPacket()));
- };
- that.socket.onmessage = function (message) {
- var event;
- if (message) {
- if (message.data) {
- event = JSON.parse(message.data);
- if (event && event.d) {
- that.dataHandler(event);
- }
- }
- }
- };
- that.socket.onclose = function () {
- console.log("closed");
- };
- that.socket.onerror = function (error) {
- console.log("error", error);
- };
- if (callback) {
- callback();
- }
- },
- initHtml = function (callback) {
- if ($(that.chartAnchor + ' svg').length === 0) { // create svg
- $(that.chartAnchor)
- .append(createElem('div', {className: 'control'})) // for control buttons
- .append('') // svg for chart
- ;
- }
- $(that.chartAnchor + ' .control')
- .append(createElem('span', {className: 'title'}, 'Events')) // title
- .append(createElem('button', {className: 'pause'}, 'Pause')) // pause button
- .append(createElem('button', {className: 'resume'}, 'Resume')) // resume
- ;
+ if (callback) {
+ callback();
+ }
+ };
- $(that.chartAnchor + ' button.pause')
- .click(function () {
- that.pause();
- })
- ;
- $(that.chartAnchor + ' button.resume')
- .click(function () {
- that.resume();
- });
- if (callback) {
- callback();
- }
- },
- addCharts = function (callback) {
- nv.addGraph(function () {
- that.chart
- .showLegend(false)
- .margin({top: 10, bottom: 30, left: 60, right: 60})
- .useInteractiveGuideline(true)
- ;
+ //applying the upper level constructor
+ this.baseClass = Chart;
+ this.baseClass(config);
+}
+EventsChart.prototype = Chart;
- that.chart.xAxis // chart sub-models (ie. xAxis, yAxis, etc) when accessed directly, return themselves, not the partent chart, so need to chain separately
- .tickFormat(function (d) {
- return d3.time.format('%X')(new Date(d));
- });
+function AggregatedChart(config) {
+ //defining dataHandler,chart and addCharts
+ this.socketPacket = {
+ name: config.chartParams.query.name,
+ start: config.chartParams.start
+ };
+ this.formatChart = function () {
+ this.chart.xAxis().tickFormat(function (d) {
+ return ("0" + new Date(d).getHours()).substr(-2) + ":" + ("0" + new Date(d).getMinutes()).substr(-2);
+ });
+ };
- that.chart.yAxis
- .tickFormat(function (d) {
- return d3.format('d')(d);
- })
- .axisLabel('Count')
- ;
- return that.chart;
- });
+ //applying the upper level constructor
+ this.baseClass = Chart;
+ this.baseClass(config);
+}
+AggregatedChart.prototype = Chart;
- if (callback) {
- callback();
+var chartsConfig = [
+ // {
+ // name: "T1 Aggregations",
+ // chartType: "AggregatedChart", //can be "EventsChart" or "AggregatedChart" - meaning one or multiple dimensions
+ // renderingType: "xxx", //choose a style
+ // chartParams: {
+ // length: 3600 * 1e3, //1 hour
+ // wsAddress: "ws://localhost:1081/1.0/aggregation/get",
+ // query: {name: "agg1_1m"},
+ // start: new Date(new Date().getTime() - 3600 * 1e3), //miliseconds ago
+ // stop: null, //null for a streaming chart
+ // dimensionsNames: {
+ // "v1": "Gender",
+ // "v2": "Platform",
+ // "v3": "Country"
+ // },
+ // metricsNames: {
+ // c: "Count",
+ // rt: "Response time"
+ // }
+ // },
+ // refreshFrequency: 10 * 1e3
+ // }
+ {
+ name: "T1 Events",
+ chartType: "EventsChart", //can be "EventsChart" or "AggregatedChart" - meaning one or multiple dimensions
+ renderingType: "xxx", //choose a style
+ chartParams: {
+ length: 1e3,
+ wsAddress: "ws://localhost:1081/1.0/event/get",
+ query: {type: "t1"},
+ start: new Date(new Date().getTime() - 60 * 1e3), //miliseconds ago
+ stop: null, //null for a streaming chart
+ dimensionsNames: {
+ "v1": "Gender",
+ "v2": "Platform",
+ "v3": "Country"
+ },
+ metricsNames: {
+ c: "Count",
+ rt: "Response time"
}
- };
- initSocket(function () {
- initHtml(function () {
- addCharts(function () {
- that.intervalHandle = setInterval(
- function () {
- that.refreshAll();
- },
- that.refreshFrequency);
- });
- });
- });
- },
- setEventType: function (eventType) {
- this.eventType = eventType;
- console.log('eventType is set to ' + this.eventType);
- },
- setChartAnchor: function (chartAnchor) {
- if (!chartAnchor.match('^#.*')) {
- chartAnchor = '#' + chartAnchor;
+ },
+ refreshFrequency: 1e3
}
- this.chartAnchor = chartAnchor;
- console.log('chartAnchor is set to ' + this.chartAnchor);
- },
- pause: function () {
- clearInterval(this.intervalHandle);
- },
- resume: function () {
- setTimeout(function () {
- this.intervalHandle = setInterval(function () {
- this.refreshAll();
- }, this.refreshFrequency);
- }, 1e3);
- },
- start: function (eventType) {
- this.setEventType(eventType);
- this.setChartAnchor("line-chart");
- this.init();
- },
- socketConnection: 'ws://localhost:1081/1.0/event/get',
- eventType: 't1', // default event type
- chartAnchor: '',
- chartData: [], // chartData should be formatted like {x: new Date, y: 130}...
- timer: {
- timePeriod: 60 * 1e3, // 60 seconds
- now: null,
- nowReal: null,
- min: null,
- max: null
- },
- refreshFrequency: 1e3,
- chart: nv.models.lineChart(),
- socket: null,
- socketPacket: function () {
- return {
- type: this.eventType,
- start: new Date(new Date().getTime() - this.timer.timePeriod)
- };
- },
- intervalHandle: 0
-};
-
-/**
- * create a real time instance for aggregation stack chart.
- * @type {RealTimeEvents}
- */
- //function RealTimeAggregations() {
- // var this = this;
- //
- // /**
- // * add chart objects
- // * @param callback
- // */
- // this.addCharts = function (callback) {
- // this.dimTargetKeys.forEach(function (dimTargetKey) {
- // var chart = nv.models.stackedAreaChart();
- // nv.addGraph(function () {
- // chart
- // .showLegend(false)
- // .margin({top: 10, bottom: 30, left: 60, right: 60})
- // .useInteractiveGuideline(true)
- // ;
- //
- // chart.xAxis // chart sub-models (ie. xAxis, yAxis, etc) when accessed directly, return themselves, not the partent chart, so need to chain separately
- // .tickFormat(function (d) {
- // return d3.time.format('%X')(new Date(d));
- // });
- //
- // chart.yAxis
- // .tickFormat(function (d) {
- // return d3.format('d')(d);
- // })
- // .axisLabel('Count')
- // ;
- // return chart;
- // });
- //
- // this.charts[dimTargetKey] = chart;
- // });
- //
- // if (callback) {
- // callback();
- // }
- // };
- //
- // /**
- // * refresh chart
- // * @param callback
- // */
- // this.refreshChart = function (callback) {
- // this.dimTargetKeys.forEach(function (dimTargetKey) {
- // var chartId = this.chartAnchor.replaceAll('#') + '-' + dimTargetKey.replaceAll('.', '-'),
- // chart = this.charts[dimTargetKey],
- // chartData = this.chartDataList[dimTargetKey];
- // d3.select('#' + chartId + ' svg')
- // .datum(chartData)
- // .transition().duration(1)
- // .call(chart);
- // });
- //
- // //Figure out a good way to do this automatically
- // //nv.utils.windowResize(this.chart.update);
- //
- // if (callback) {
- // callback();
- // }
- // };
- //
- // /**
- // * refresh chartData
- // * @param callback
- // */
- // this.refreshChartData = function (callback) {
- // this.chartDataList = {};
- // var ndx = crossfilter(this._data),
- // dimTime = ndx.dimension(function (d) {
- // return new Date(d.time);
- // });
- // // console.log(grpDimTime.all());//debug
- // this.dimTargetKeys.forEach(function (dimTargetKey) {
- // var chartData = [], // store all data series for a stack chart
- // dimTarget = ndx.dimension(function (d) {
- // return d[dimTargetKey];
- // }),
- // dimTargetValsUniq = this._data.ix(dimTargetKey).unique();
- // dimTargetValsUniq.forEach(function (v) {
- // var t,
- // tmp = {},
- // values,
- // x,
- // y;
- // dimTarget.filter(v);
- // values = dimTime.group().reduceSum(function (d) {
- // return d.count;
- // }).all(); // store values for one series in a stack chart
- // values.forEach(function (d) {
- // tmp[d.key.getTime()] = d.value;
- // });
- // values = [];
- // for (t = this.timer.min; t < this.timer.now; t += this.resolution) {
- // x = new Date(t);
- // y = tmp[t] || 0;
- // values.push({
- // x: x,
- // y: y
- // });
- // }
- // chartData.push({
- // key: v,
- // values: values
- // });
- // });
- // dimTarget.filter(null);
- // this.chartDataList[dimTargetKey] = chartData;
- // });
- //
- // if (callback) {
- // callback();
- // }
- // };
- //
- // /**
- // * handle incoming event data
- // * @param aggregationData
- // */
- // this.dataHandler = function (aggregationData) {
- // // console.log(JSON.flatten(aggregationData));//debug
- // this._data.push(JSON.flatten(aggregationData));
- // };
- //
- // /**
- // * reset data
- // */
- // this.resetData = function (callback) {
- // var tmp = [];
- // this._data.forEach(function (d) {
- // if (d.time >= this.timer.min) {
- // tmp.push(d);
- // }
- // });
- // this._data = tmp;
- // if (callback) {
- // callback();
- // }
- // };
- //
- // /**
- // * update timer
- // * @param [callback]
- // */
- // this.updateTimer = function (callback) {
- // var timer = this.timer;
- // timer.nowReal = new Date().getTime();
- // timer.now = timer.nowReal - timer.nowReal % this.resolution;
- // timer.min = (timer.now - timer.timePeriod) - (timer.now - timer.timePeriod) % this.resolution;
- // timer.max = timer.now - timer.now % this.resolution;
- // if (callback) {
- // callback();
- // }
- // };
- //
- // /**
- // * refresh all
- // */
- // this.refreshAll = function () {
- // this.refreshChartData(function () {
- // this.refreshChart(function () {
- // this.resetData();
- // });
- // this.updateTimer();
- // });
- // };
- //
- // /**
- // * init HTML
- // */
- // this.initHtml = function (callback) {
- // var $tmp = $(''),
- // $anchor = $(this.chartAnchor),
- // $control = $(createElem('div', {className: 'control'})), // elements in control block
- // selectorLookup = {
- // 'data.v1': ['male', 'female', 'all'],
- // 'data.v2': ['web', 'android', 'web', 'all'],
- // 'data.v3': ['GB', 'US', 'IN', 'JP', 'all']
- // };// control block TODO: add listeners to control stack area
- // this.dimTargetKeys.forEach(function (dimTargetKey) {
- // var selectorId = this.chartAnchor.replaceAll('#') + '-selector-' + dimTargetKey.replaceAll('.', '-'),
- // $selector = $(createElem('select', {id: selectorId}));
- // $selector.append(''); // default option
- // selectorLookup[dimTargetKey].forEach(function (option) {
- // $selector.append(createElem('option', {'value': option}, option));
- // });
- // $control
- // .append($tmp.clone().append($selector).html())
- // ;
- // });
- // $anchor
- // .append($tmp.clone().append($control).html())
- // ;
- // this.dimTargetKeys.forEach(function (dimTargetKey) {
- // var chartId = this.chartAnchor.replaceAll('#') + '-' + dimTargetKey.replaceAll('.', '-'),
- // $elem = createElem('div', {id: chartId});
- // $($elem)
- // .append(createElem('svg'))
- // ;
- // $anchor
- // .append($tmp.clone().append($elem).html())
- // ;
- // });
- //
- // if (callback) {
- // callback();
- // }
- // };
- //
- // /**
- // * major init
- // */
- // this.init = function () {
- // this.initSocket(function () {
- // this.initHtml(function () {
- // this.addCharts(function () {
- // setTimeout(function () {
- // this.intervalHandle = setInterval(function () {
- // this.refreshAll();
- // }, this.refreshFrequency); // refresh frequency
- // }, 2e3); // initial waiting time
- // });
- // });
- // });
- // };
- //
- // /**
- // * initialize socket
- // * @param [callback]
- // */
- // this.initSocket = function (callback) {
- // if (this.socket !== null || this.socket !== undefined) {
- // this.resetData(function () {
- // this._data = [];
- // this.chartData = [];
- // });
- // }
- // this.socket = new WebSocket(this.socketConnection);
- // this.socket.onopen = function () {
- // console.log("connected to " + this.socketConnection);
- // this.socket.send(JSON.stringify(this.socketPacket));
- // };
- // this.socket.onmessage = function (message) {
- // var jsonData;
- // if (message) {
- // if (message.data) {
- // jsonData = JSON.parse(message.data);
- // if (jsonData && jsonData.data) {
- // this.dataHandler(jsonData);
- // }
- // }
- // }
- // };
- // this.socket.onclose = function () {
- // console.log("closed");
- // };
- // this.socket.onerror = function (error) {
- // console.log("error", error);
- // };
- // if (callback) {
- // callback();
- // }
- // };
- //
- // /**
- // * init listeners
- // * @param [callback]
- // */
- // this.initListners = function (callback) {
- // console.log("Not implemented yet");
- // if (callback) {
- // callback();
- // }
- // };
- //
- // /**
- // * set eventType
- // * @param value
- // */
- // this.setAggType = function (value) {
- // this.aggType = value;
- // console.log('aggType is set to ' + this.aggType);
- // };
- //
- // /**
- // * set chart html anchor
- // * @param chartAnchor
- // */
- // this.setChartAnchor = function (chartAnchor) {
- // if (!chartAnchor.match('^#.*')) {
- // chartAnchor = '#' + chartAnchor;
- // }
- // this.chartAnchor = chartAnchor;
- // console.log('chartAnchor is set to ' + this.chartAnchor);
- // };
- //
- // /**
- // * set timePeriod
- // * @param timePeriod
- // * @param [callback]
- // */
- // this.setTimePeriod = function (timePeriod, callback) {
- // this.timer.timePeriod = timePeriod;
- // this._updateSocketPacket();
- // console.log('timePeriod is set to ' + this.timer.timePeriod);
- // if (callback) {
- // callback();
- // }
- // };
- //
- // this._updateSocketPacket = function () {
- // this.socketPacket = {
- // name: this.aggType,
- // start: new Date(new Date().getTime() - this.timer.timePeriod)
- // };
- // };
- //
- // /**
- // * set socketConnection
- // * @param value
- // */
- // this.setSocketConnection = function (value) {
- // this.socketConnection = value;
- // };
- //
- // /**
- // * set socketPacket
- // * @param value
- // */
- // this.setSocketPacket = function (value) {
- // this.socketPacket = value;
- // };
- // /**
- // * set timePeriod
- // * @param value
- // */
- // this.setsampleDuration = function (value) {
- // this.timer.timePeriod = value;
- // };
- //
- // /**
- // * pause refresh data
- // */
- // this.pause = function () {
- // clearInterval(this.intervalHandle);
- // };
- //
- // /**
- // * resume
- // */
- // this.resume = function () {
- // setTimeout(function () {
- // this.intervalHandle = setInterval(function () {
- // this.refreshAll();
- // }, this.refreshFrequency);
- // }, 1e3);
- // };
- //
- // /**
- // * test
- // */
- // this.test = function () {
- // this.setAggType('t1');
- // this.setChartAnchor('stack-charts');
- // this.init();
- // };
- //
- // // variables
- // this.aggType = 'agg1';
- // this.chartAnchor = '';
- // this.chartDataList = {};
- // this._data = []; // temporary data dictionary for aggregation
- // this.timer = {
- // timePeriod: 60 * 60 * 1e3, // 60 minutes
- // now: null,
- // nowReal: null,
- // min: null,
- // max: null
- // };
- // this.resolutionLookup = {
- // '1m': 60 * 1e3,
- // '5m': 5 * 60 * 1e3,
- // '1h': 60 * 60 * 1e3
- // };
- // this.resolutionName = '1m';
- // this.resolution = this.resolutionLookup[this.resolutionName];
- // this.refreshFrequency = 5 * 1e3; // refresh every 5 seconds, will be deprecated
- // this.dimTargetKeys = [
- // 'data.v1',
- // 'data.v2',
- // 'data.v3'
- // ];
- // this.charts = {};
- // this.socket = null;
- // this.socketConnection = 'ws://localhost:1081/1.0/aggregation/get';
- // this.socketPacket = {
- // name: this.aggType + '_' + this.resolutionName,
- // start: new Date(new Date().getTime() - this.timer.timePeriod)
- // };
- // this.intervalHandle = null;
- //}
+ ],
+ charts = [],
+ i;
-function testRealTime() {
- realTimeEvents.start("t1");
+for (i = 0; i < chartsConfig.length; i++) {
+ charts[i] = new window[chartsConfig[i].chartType](chartsConfig[i]);
+ charts[i].start();
}
\ No newline at end of file
diff --git a/public/js/util.js b/public/js/util.js
deleted file mode 100644
index def8ae1..0000000
--- a/public/js/util.js
+++ /dev/null
@@ -1,354 +0,0 @@
-"use strict";
-/*global document*/
-
-/**
- * get a column of multi-dimensional array or DataFrame-like array
- * @param index
- * @param deep
- * @returns {Array}
- */
-Array.prototype.getColumn = function (index, deep) {
- var ret = [],
- i;
- if (deep === undefined) {
- deep = 0;
- }
- if (this[0] instanceof Object) {
- this.forEach(function (d) {
- if (deep) {
- ret.push(getDeepValue(d, index));
- } else {
- ret.push(d[index]);
- }
- });
- } else {
- for (i = 0; i < this.getColumnLength(); i++) {
- ret.push(this[i][index]);
- }
- }
- return ret;
-};
-/**
- * index by key
- * key elements MUST be unique
- * @param key
- * @returns {{}}
- */
-Array.prototype.indexBy = function (key) {
- var ret = {};
- this.forEach(function (d) {
- ret[d[key]] = d;
- });
- return ret;
-};
-/**
- * index by key
- * similar to indexBy but key elements can be non-unique and elements of return object are array
- * @param key
- * @return {*}
- */
-Array.prototype.indexMultiBy = function (key) {
- var ret = {};
- this.forEach(function (d) {
- if (ret[d[key]] == null) {
- ret[d[key]] = [];
- }
- ret[d[key]].push(d);
- });
- return ret;
-};
-/**
- * index by key
- * key elements MUST be unique
- * @param key
- * @returns {{}}
- */
-Array.prototype.indexBy = function (key) {
- var ret = {};
- this.forEach(function (d) {
- ret[d[key]] = d;
- });
- return ret;
-};
-/**
- * sortBy
- * only applies to arrays like [{a:1,b:2},...]
- * may conflict with cubism
- * @param key
- * @param direction
- */
-Array.prototype.sortBy = function (key, direction) {
- if (direction === null || direction === undefined) {
- direction = 'desc';
- }
- switch (("" + direction).toLowerCase()) {
- case 'desc':
- direction = 1;
- break;
- case 'asc':
- direction = -1;
- break;
- default :
- }
- var lookup = this.indexBy(key),
- ret = [],
- indexes = this.getColumn(key)._sort(direction),
- i;
- for (i = 0; i < indexes.length; i++) {
- ret.push(lookup[indexes[i]]);
- }
- return ret;
-};
-/**
- * select by indexes
- * use indexMultiBy and _.flatten
- * only applies to arrays like [{a:1,b:2},...]
- * may conflict with cubism
- * @param key
- * @param [indexes]
- */
-Array.prototype.ix = function (key, indexes) {
- if (indexes === null || indexes === undefined) {
- return this.getColumn(key);
- }
- var lookup = this.indexMultiBy(key),
- i,
- ret = [];
- if (!(indexes instanceof Array)) {
- indexes = [indexes];
- }
- for (i = 0; i < indexes.length; i++) {
- if (lookup[indexes[i]] !== null) {
- ret.push(lookup[indexes[i]]);
- }
- }
- ret = _.flatten(ret);
- return ret;
-};
-/**
- * select columns
- * @param keys
- */
-Array.prototype.icol = function (keys) {
- if (keys === null || keys === undefined) {
- throw new Error('keys should not be null');
- }
- if (!(keys instanceof Array)) {
- keys = [keys];
- }
- var row,
- ret = [];
- this.forEach(function (d) {
- row = {};
- keys.forEach(function (key) {
- row[key] = d[key];
- });
- ret.push(row);
- });
- return ret;
-};
-/**
- * sort a simple array
- * 1 ascending
- * -1 descending
- * @param direction
- * @returns {Array}
- * @private
- */
-Array.prototype._sort = function (direction) {
- return this.sort(function (a, b) {
- switch (direction) {
- case 1:
- return (a > b) ? 1 : -1;
- case 2:
- return (a < b) ? 1 : -1;
- default :
- return 1;
- }
- });
-};
-/**
- * return a unique array
- * @returns {Array}
- */
-Array.prototype.unique = function () {
- var i,
- ret = [],
- tmp = {};
- for (i = 0; i < this.length; i++) {
- tmp[this[i]] = 1;
- }
- for (i in tmp) {
- if (tmp.hasOwnProperty(i)) {
- ret.push(i);
- }
- }
- return ret;
-};
-/**
- * max
- * @returns {*}
- */
-Array.prototype.max = function () {
- var ret,
- i;
- for (i = 0; i < this.length; i++) {
- if (ret === undefined) {
- ret = this[i];
- }
- if (ret < this[i]) {
- ret = this[i];
- }
- }
- return ret;
-};
-/**
- * min
- * @returns {*}
- */
-Array.prototype.min = function () {
- var ret,
- i;
- for (i = 0; i < this.length; i++) {
- if (ret === undefined) {
- ret = this[i];
- }
- if (ret > this[i]) {
- ret = this[i];
- }
- }
- return ret;
-};
-/**
- * unflatten
- * @param data
- * @returns {*}
- */
-JSON.unflatten = function (data) {
- //noinspection JSLint
- var property,
- cur,
- prop = "",
- match,
- regex = /\.?([^.\[\]]+)|\[(\d+)\]/g,
- resultholder = {};
- if (Object.create(data) !== data || Array.isArray(data)) {
- return data;
- }
- for (property in data) {
- if (data.hasOwnProperty(property)) {
- cur = resultholder;
- while ((match = regex.exec(property)) !== false) {
- cur = cur[prop] || (cur[prop] = (match[2] ? [] : {}));
- prop = match[2] || match[1];
- }
- cur[prop] = data[property];
- }
- }
- return resultholder[""] || resultholder;
-};
-/**
- *
- * @param data
- * @returns {{}}
- */
-JSON.flatten = function (data) {
- var result = {};
-
- function recurse(cur, prop) {
- if (Object(cur) !== cur) {
- result[prop] = cur;
- } else if (Array.isArray(cur)) {
- var i,
- l;
- for (i = 0, l = cur.length; i < l; i++) {
- recurse(cur[i], prop + "[" + i + "]");
- }
- if (l === 0) {
- result[prop] = [];
- }
- } else {
- var isEmpty = true,
- p;
- for (p in cur) {
- if (cur.hasOwnProperty(p)) {
- isEmpty = false;
- recurse(cur[p], prop ? prop + "." + p : p);
- }
- }
- if (isEmpty && prop) {
- result[prop] = {};
- }
- }
- }
-
- recurse(data, "");
- return result;
-};
-
-/**
- * flatten a dataframe-like array
- * require JSON.flatten
- * @returns {Array}
- */
-Array.prototype.flatten = function () {
- var ret = [];
- this.forEach(function (d) {
- ret.push(JSON.flatten(d));
- });
- return ret;
-};
-/**
- * return size of an object
- * @param obj
- * @returns {number}
- */
-function size(obj) {
- var n = 0,
- i;
- for (i in obj) {
- if (obj.hasOwnProperty(i)) {
- n++;
- }
- }
- return n;
-}
-
-/**
- * create element
- * @param type
- * @param [attrs]
- * @param [text]
- * @returns {HTMLElement}
- */
-function createElem(type, attrs, text) {
- var elem = document.createElement(type),
- attr;
-
- if (attrs !== undefined && attrs instanceof Object) {
- for (attr in attrs) {
- if (attrs.hasOwnProperty(attr)) {
- elem[attr] = attrs[attr];
- }
- }
- }
-
- if (text !== undefined) {
- elem.innerText = text;
- }
-
- return elem;
-}
-
-/**
- * replace all elements in a string
- * @param oldVal
- * @param [newVal]
- * @returns {string}
- */
-String.prototype.replaceAll = function (oldVal, newVal) {
- if (newVal === null || newVal === undefined) {
- newVal = '';
- }
- return this.split(oldVal).join(newVal);
-};
\ No newline at end of file
diff --git a/public/views/includes/footer.jade b/public/views/includes/footer.jade
index aa23783..8206a53 100644
--- a/public/views/includes/footer.jade
+++ b/public/views/includes/footer.jade
@@ -1,11 +1,8 @@
-//script(src='/lib/requirejs/require.js')
script(src='/lib/jquery/dist/jquery.min.js')
script(src='/lib/bootstrap/dist/js/bootstrap.min.js')
-//script(src='/lib/socket.io/lib/socket.io.js')
script(src='/lib/d3/d3.js')
script(src='/lib/crossfilter/crossfilter.min.js')
script(src='/lib/dcjs/dc.min.js')
script(src='/lib/nvd3/nv.d3.min.js')
-script(src='/js/util.js')
script(src='/js/analytics.js')
block footer-extra
\ No newline at end of file
diff --git a/public/views/real-time.jade b/public/views/real-time.jade
index ef0df56..18670d7 100644
--- a/public/views/real-time.jade
+++ b/public/views/real-time.jade
@@ -1,10 +1,6 @@
extends layout
block append content
- div(id='line-chart', style='width: 750px;')
- div(id='stack-charts', style='width: 750px;')
+ div(id='charts', style='width: 750px;')
block append footer-extra
- script(type="text/javascript").
- testRealTime()
- //script(type="text/javascript").
- // quickDirtyAggregation()
\ No newline at end of file
+ script(type="text/javascript")
\ No newline at end of file