var ajax_site = 'ajax'; var refresh_rate = 3000; var send_timeout = 20000; var retry_rate = 3000; function Blocker(sleeper) { this.counter = 0; this.waiting = false; this.sleeper = sleeper; } Blocker.prototype.setHook = function() { var blocker = this; return function() { blocker.counter++; } } Blocker.prototype.setNotifier = function(cb) { var blocker = this; return function() { cb.apply(this, arguments); if (--blocker.counter == 0 && blocker.waiting) blocker.sleeper(); } }; Blocker.prototype.go = function() { if (this.counter == 0) this.sleeper(); this.waiting = true; } function send_request(on_success) { $.ajax({ url: ajax_site, type: 'GET', cache: false, dataType: 'json', async: true, success: on_success, timeout: send_timeout, error: function(a, b, c) { console.log("failed while connecting, reason: " + b); setTimeout(function() { send_request(on_success); }, retry_rate); } }); } function hex(x) { return ("0" + parseInt(x).toString(16)).slice(-2); } function random_range(min, max) { return Math.random() * (max - min) + min; } function random_color() { var r = (random_range(0, 200) + 150) / 2; var g = (random_range(0, 200) + 150) / 2; var b = (random_range(0, 200) + 150) / 2; return "#" + hex(r) + hex(g) + hex(b); } function LineGraph() {} LineGraph.prototype.recalc_y_domain = function(mdata) { var min = d3.min($.map(mdata, function (data) { return d3.min(data, function(d) { return d.y; }); })); var max = d3.max($.map(mdata, function (data) { return d3.max(data, function(d) { return d.y; }); })); var delta = max - min; this.y.domain([min - Math.max(delta / 3.0, 1), max + Math.max(delta / 3.0, 1)]); } LineGraph.prototype.recalc_x_domain = function(data) { this.x.domain(d3.extent(data, function(d) { return d.x; })); } LineGraph.prototype.recalc_domain = function(mdata) { this.recalc_x_domain(mdata[0]); this.recalc_y_domain(mdata); } LineGraph.prototype.parse_data = function(d) { var data = []; for (var i = 0; i < d.records.length; i++) { var row = d.records[i]; while (data.length < row.rec.length) data.push([]); for (var j = 0; j < row.rec.length; j++) data[j].push({x: i, y: +row.rec[j], rid: row.rid}); } return data; } LineGraph.prototype.update = function(d, i, b) { var graph = this; var mdata = this.parse_data(d); if (mdata.length == 0) return; var recalced = false; var svg = d3.select(this.elem).select("svg"); this.recalc_y_domain(mdata); var ya = svg.select(".y.axis"); ya.transition() .duration(750) .call(this.yAxis); var maxw = ya.node().getBBox().width; svg.select("g") .transition().duration(750) .attr("transform", "translate(" + (maxw + this.margin.left) + "," + this.margin.top + ")"); var niw = this.width - maxw; if (Math.abs(niw - this.inner_width) > 1e-6) { this.inner_width = niw; this.x = d3.scale.linear().range([0, this.inner_width]); this.xAxis = d3.svg.axis().scale(this.x) .orient("bottom").ticks(5).tickFormat(d3.format("d")); } this.recalc_x_domain(this.pdata[0]); svg.transition().duration(750) .select(".x.axis") .call(this.xAxis); svg.select("rect") .transition().duration(750) .attr("width", this.inner_width); for (var i = 0; i < mdata.length; i++) { var data = mdata[i]; if (data.length == 0) continue; var pdata = this.pdata[i]; var path = this.line[i]; var shift = pdata.length ? data[0].rid - pdata[0].rid : 0; var add = pdata.length ? data[data.length - 1].rid - pdata[pdata.length - 1].rid : 0; if (pdata.length == 0 || pdata[pdata.length - 1].rid < data[0].rid || pdata[0] > data[data.length - 1].rid || shift < 0 || add < 0) { if (data.length == 1) { data = []; console.log("clear"); } if (!recalced) { this.recalc_x_domain(data); svg.transition() .duration(750) .select(".x.axis") .call(this.xAxis); svg.select("rect") .transition().duration(750) .attr("width", this.inner_width); recalced = true; } path.transition() .duration(200) .style("opacity", 1e-6) .transition() .attr("d", graph.valueline(data)) .transition() .duration(200) .style("opacity", 1) .each(b.setHook()) .each("end", b.setNotifier(function(i, data) { return function() { graph.pdata[i] = data; console.log("refresh complete"); }}(i, data))); console.log("refresh started"); } else { //console.log("shift: " + shift + "add: " + add); data = pdata.concat(data.slice(data.length - add, data.length)); for (var j = 0; j < data.length; j++) data[j].x = j; if (shift > 0) path.transition().duration(750) .attr("d", graph.valueline(data)) .transition() .duration(750) .attr("transform", "translate(" + this.x(-shift) + ")") .each(b.setHook()) .each("end", b.setNotifier(function(i, data) { return function() { for (var j = 0; j < shift; j++) data.shift(); for (var j = 0; j < data.length; j++) data[j].x = j; graph.pdata[i] = data; d3.select(this).attr("d", graph.valueline(data)) .attr("transform", "translate(" + graph.x(0) + ")"); }}(i, data))) else { //console.log(data); this.pdata[i] = data; /* Note: the added data is delayed from showing for the sake of * more decent transition animation */ path.transition().duration(750) .attr("d", graph.valueline(data)) .transition() .each(b.setHook()) .each("end", b.setNotifier(function(){})); } } } } LineGraph.prototype.setup = function(elem, d, i, b) { var graph = this; this.elem = elem; var margin = this.margin = {top: 10, right: 0, bottom: 50, left: 0}; var clippath_name = "clip" + d.jid; // Adds the svg canvas var svg = d3.select(this.elem) .append("svg") .attr("width", "100%") .attr("height", "100%") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); this.width = 500 - margin.left - margin.right; this.height = 250 - margin.top - margin.bottom; var mdata = this.parse_data(d); this.y = d3.scale.linear().range([this.height, 0]); if (mdata.length) this.recalc_y_domain(mdata); this.yAxis = d3.svg.axis().scale(this.y) .orient("left").ticks(5); // Add the Y Axis var ya = svg.append("g") .attr("class", "y axis") .call(this.yAxis); /* ya.selectAll("text").each(function() { if(this.getBBox().width > maxw) maxw = this.getBBox().width; }); */ var maxw = ya.node().getBBox().width; svg.attr("transform", "translate(" + (maxw + margin.left) + "," + margin.top + ")"); this.inner_width = this.width - maxw; this.x = d3.scale.linear().range([0, this.inner_width]); if (mdata.length) this.recalc_x_domain(mdata[0]); this.xAxis = d3.svg.axis().scale(this.x) .orient("bottom").ticks(5).tickFormat(d3.format("d")); // Define the line this.valueline = d3.svg.line() .x(function(d) { return this.x(d.x); }) .y(function(d) { return this.y(d.y); }); // Add the X Axis svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + this.height + ")") .call(this.xAxis); svg.append("defs").append("clipPath") .attr("id", clippath_name) .append("rect") .attr("width", this.inner_width) .attr("height", this.height); svg.append("rect") .attr("class", "overlay") .attr("width", this.inner_width) .attr("height", this.height) .on("mouseover", function() { //console.log("over"); for (var i = 0; i < graph.pdata.length; i++) graph.focus[i].style("display", null) // .transition().duration(300) .style("stroke-opacity", 1); }) .on("mouseout", function() { //console.log("out"); for (var i = 0; i < graph.pdata.length; i++) graph.focus[i] //.transition().duration(300) .style("stroke-opacity", 1e-6) .transition() .style("display", "none"); }) .on("mousemove", mousemove); function mousemove() { var x0 = graph.x.invert(d3.mouse(this)[0]); for (var i = 0; i < graph.pdata.length; i++) { var d = graph.pdata[i][Math.round(x0)]; var f = graph.focus[i]; f.transition().duration(100).attr("transform", "translate(" + graph.x(d.x) + "," + graph.y(d.y) + ")"); f.select("text").text(d.y); } } this.pdata = []; this.line = []; this.focus = []; d3.select(this.elem).transition() .duration(750) .style("height", this.height + this.margin.top + this.margin.bottom + "px") .each(b.setHook()) .each("end", b.setNotifier(function() { // Get the data for (var i = 0; i < mdata.length; i++) { var data = mdata[i]; var path = svg.append("g") .attr("clip-path", "url(#" + clippath_name + ")") .append("path"); path.attr("class", "line") .style("stroke", random_color) .attr("d", graph.valueline(data)); var totalLength = path.node().getTotalLength(); path.attr("stroke-dasharray", totalLength + " " + totalLength) .attr("stroke-dashoffset", totalLength) .transition() .duration(2000) .ease("linear") .attr("stroke-dashoffset", 0) .each(b.setHook()) .each("end", b.setNotifier(function(data, path) { return function() { path.attr("stroke-dasharray", ""); graph.line.push(path); }}(data, path))); } for (var i = 0; i < mdata.length; i++) { var f = svg.append("g") .attr("class", "focus") .style("display", "none"); f.append("circle") .attr("r", 4.5); f.append("text") .attr("x", 9) .attr("dy", ".35em"); graph.focus.push(f); } graph.pdata = mdata; })); } function ListGraph() {} ListGraph.prototype.parse_data = function(d) { var data = []; for (var i = 0; i < d.records.length; i++) { var rec = d.records[i]; data.push({rid: rec[0], rec: rec}); } return data; } ListGraph.prototype.setup = function(elem, d, i, b) { var data = this.parse_data(d); var div = d3.select(elem); this.elem = elem; /* div.append("div") .style("text-align", "center") .append("a") .style("font-size", "16px") .style("text-decoration", "none") .text(d.name); */ var table = d3.select(elem) .append("table") .attr("class", "listgraph table") .style("width", "100%") .style("height", "100%"); this.table = table; table.selectAll("tr").data(data) .enter().append("tr") .selectAll("td").data(function(d) { return d.rec; }) .enter().append("td") .style("opacity", 0) .transition().duration(750) .style("opacity", 1) .text(function(d) { return d; }) .each(b.setHook()) .each("end", b.setNotifier(function(){})); d3.select(elem) //.transition().duration(750) .style("height", null); } ListGraph.prototype.update = function(d, i, b) { var graph = this; var data = this.parse_data(d); var items = this.table.selectAll("tr").data(data, function(d) { return d.rid; }); b.setHook()(); var b0 = new Blocker(b.setNotifier(function() { items.order(); })); items.selectAll("td").text(function(d) { return d; }); items.enter() .append("tr") .selectAll("td") .data(function(d) { return d.rec; }) .enter() .append("td") .text(function(d) { return d; }); items.exit().remove(); b0.go(); } var graph_handler = {"linegraph": LineGraph, "listgraph": ListGraph}; function update_jobs(resp, after) { resp = Object.keys(resp).map(function(k) { return resp[k] }); var jobs = d3.select("#jobs").selectAll(".job_graph").data(resp, function(d) { return d.jid; }); var b = new Blocker(after); jobs.each(function(d, i) { d3.select(this).select(".job_content").node().handler.update(d, i, b); }); var outer = jobs.enter() .append("div") .attr("class", "job_graph panel panel-info") .style("width", "540px"); outer.append("div") .attr("class", "panel-heading") .text(function(d) { return d.name; }); outer.append("div") .attr("class", "job_content panel-body") .style("height", "0px") .each(function(d, i) { this.handler = new graph_handler[d.metadata.type](); this.handler.setup(this, d, i, b); }); jobs.exit() .style("height", function(d) { return d3.select(this).style("height"); }) .transition().duration(750) .style("height", "0px") .each(b.setHook()) .each("end",b.setNotifier(function(){})) .remove(); b.go(); } function graph_tick() { send_request(function(resp) { update_jobs(resp, function() { setTimeout(graph_tick, refresh_rate); }); }); } graph_tick();