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