lib/scout_realtime/web/javascripts/d3.linechart.js in scout_realtime-0.5.3 vs lib/scout_realtime/web/javascripts/d3.linechart.js in scout_realtime-0.5.4

- old
+ new

@@ -1,19 +1,28 @@ function lineChart(params) { this.data = params.data; this.yScaleMax = params.yScaleMax; this.element = params.element; this.metadata = params.metadata; - var line, path, x, y, barWidth, height, selection, chartInfo, latestValue, chartId; + // charts can plot multiple metrics. if so, we assume units are the same. fetch that data from the first metric. + this.shared_metadata = ('units' in this.metadata ? this.metadata : this.metadata[Object.keys(this.metadata)[0]]); + var line, path, x, y, barWidth, height, selection, chartInfo, latestValue, chartId, xAxis, yAxis; + var type = params.type; + if(type == 'overview') { + var margin = { top: 20, right: 0, bottom: 20, left: 30 }; + } else { + var margin = { top: 0, right: 0, bottom: 0, left: 0 }; + } + this.draw = function() { var _this = this; selection = d3.select(this.element); chartId = $(selection[0]).data('collector') + $(selection[0]).data('instance-name'); // get the width and height form the selection - var width = selection.node().clientWidth || parseInt($(selection.node()).css('width')); - height = selection.node().clientHeight || parseInt($(selection.node()).css('height')); + var width = (selection.node().clientWidth || parseInt($(selection.node()).css('width'))) - margin.right; // don't take left margin into account for smoothness' sake + height = (selection.node().clientHeight || parseInt($(selection.node()).css('height'))) - margin.bottom - margin.top; y = d3.scale.linear().range([height, 0]); x = d3.time.scale().range([0, width]); now = new Date(); x.domain([new Date(now - 60 * refreshInterval), now]); var currentYMax = d3.max([this.yScaleMax, yMax(this.data)]); @@ -30,18 +39,31 @@ selection.append("defs").append("clipPath") .attr("id", "clip-" + chartId) .attr("class", "clip-path") .append("rect") .attr("width", width - (barWidth * 2)) // new data points are drawn outside of clip area - .attr("height", height); + .attr("height", height) + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); line = d3.svg.line() .interpolate("basis") .x(function(d, i) { return x(d.time); }) .y(function(d, i) { return y(d.value[0]); }); + if(type == 'overview') { + xAxis = selection.append("g") + .attr("class", 'x axis') + .attr("transform", "translate(" + margin.left + "," + (height + margin.top) + ")") + .call(x.axis = d3.svg.axis().scale(x).ticks(d3.time.seconds, 15).tickFormat(d3.time.format("%I:%M:%S")).orient("bottom")); + + yAxis = selection.append("g") + .attr("class", 'y axis') + .attr("transform", "translate(" + margin.left + "," + margin.top + ")") + .call(y.axis = d3.svg.axis().scale(y).ticks(1).orient("right")); + } + chartInfo = $(selection[0]).parent().siblings('.chart_info'); latestValue = $(selection[0]).parent().siblings('.latest_value'); latestValue.html(this.format(this.data[this.data.length - 1].value[0])); chartSection = selection.selectAll("rect") @@ -50,17 +72,26 @@ chartSection.enter() .append("rect") .attr("class", "chart_section") .attr("width", barWidth) .attr("height", height) - .attr("transform", function(d, i) { return "translate(" + (x(d.time) - barWidth) + ",0)"; }) + .attr("transform", function(d, i) { return "translate(" + ((x(d.time) - barWidth) + margin.left) + "," + margin.top + ")"; }) + // for overview charts, the latest value stays visible and the mouseover values appear beneath the chart. + // because of space constraints, the latest value is hidden in process charts and the mouseover value takes its place. .on("mouseover", function(d) { - //latestValue.html(_this.format(d.value[0])); - chartInfo.html(_this.tooltip(d)); + chartInfo.html(_this.tooltip(d)) + if (chartInfo.css('display') == 'none') { + latestValue.hide(); + chartInfo.show(); + } }) .on("mouseout", function(d) { - chartInfo.html(""); + chartInfo.html("") + if (latestValue.css('display') == 'none') { + latestValue.show(); + chartInfo.hide(); + } }); path = selection.append("g") .attr("clip-path", "url(#clip-" + chartId + ")") .append("path") @@ -85,41 +116,53 @@ } y.domain([currentYMin, currentYMax]); latestValue.html(this.format(this.data[this.data.length - 1].value[0])); + var translateTarget = x(x.domain()[0] - refreshInterval) + margin.left; + path // update the line - this isn't part of the transition. new points will be off to the right. .attr("d", line) - .attr("transform", null) + .attr("transform", "translate(" + margin.left + "," + margin.top + ")") // slide the path to the left 1 second .transition() .duration(refreshInterval - 50) // duration is the same as the update interval...if it equals the update interval, it gets canceled? .ease("linear") - .attr("transform", "translate(" + x(x.domain()[0] - refreshInterval) + ")") + .attr("transform", "translate(" + translateTarget + "," + margin.top + ")") .each("end", function() { _this.refresh(); }); + if(type == 'overview') { + xAxis.transition() + .duration(refreshInterval - 50) + .ease("linear") + .call(x.axis); + + yAxis.transition() + .call(y.axis); + } + chartSection = selection.selectAll("rect") .data(this.data); chartSection.enter() .append("rect") .attr("class", "chart_section") .attr("width", barWidth) .attr("height", height) - .attr("transform", function(d, i) { return "translate(" + (x(d.time) - barWidth) + ",0)"; }) + .attr("transform", function(d, i) { return "translate(" + ((x(d.time) - barWidth) + margin.left) + "," + margin.top + ")"; }) .on("mouseover", function(d) { chartInfo.html(_this.tooltip(d)); }) .on("mouseout", function(d) { chartInfo.html(""); }) .transition() .duration(0) - .attr("transform", function(d, i) { return "translate(" + (x(d.time) - barWidth) + ",0)"; }) + .attr("transform", function(d, i) { return "translate(" + ((x(d.time) - barWidth) + margin.left) + "," + margin.top + ")"; }) } else { setTimeout(function() { _this.refresh() }, refreshInterval); } } @@ -146,21 +189,26 @@ }); return dataMin; } this.format = function(value) { - var formattedUnits = ( this.metadata.units == '%' ? '%' : ' ' + this.metadata.units ) - return(value.toFixed(this.metadata.precision) + formattedUnits); + var formattedUnits = ( this.shared_metadata.units == '%' ? '%' : '&nbsp;' + this.shared_metadata.units ) + return(value.toFixed(this.shared_metadata.precision) + formattedUnits); } this.tooltip = function(data) { var tooltipStr = ''; - if(data.valueBreakdown) { + // if multiple values (ex: io wait, system, user), display the metric label + value. otherwise, + // just display the value. + if(data.valueBreakdown && Object.keys(data.valueBreakdown).length > 1) { for(var key in data.valueBreakdown) { - tooltipStr += key + ': ' + this.format(data.valueBreakdown[key]) + '; '; + var label = ('units' in this.metadata ? this.metadata.label : this.metadata[key].label); + if (!label) { label = key }; + // metrics aren't guarenteed to have a label. use it if they do. + tooltipStr += '<span>'+label + '<span>' + this.format(data.valueBreakdown[key]) + '</span></span>'; } } else { - tooltipStr = this.format(data.value[0]); + tooltipStr = '<span><span>'+this.format(data.value[0])+'</span></span>'; } return tooltipStr; } return this;