if("undefined"!=typeof module&&"undefined"!=typeof process)var d3=require("d3");var FireGrapher=function(){"use strict";var e=function(e,i,c){function f(e,t){for(var a in t)"object"==typeof t[a]?(e[a]=e[a]?e[a]:{},f(e[a],t[a])):e[a]=e[a]?e[a]:t[a]}a(e),r(i),s(c),f(c,n());var p=document.querySelector(i);c.styles.size={width:p.clientWidth,height:p.clientHeight};var u;switch(c.type){case"line":case"scatter":case"bar":u=new o(c,i);break;case"map":u=new l(c,i);break;case"table":u=new d(c,i);break;default:throw new Error("Invalid config type: "+c.type)}u.init();var h=new t(e,c,u),g=[{pathArray:[],path:"/",params:{}}];h.parsePath(g,0)},t=function(e,t,r){function n(e,t){u.pathsToRecords.push(e),"undefined"==typeof g[e.pathArray.join()]&&(g[e.pathArray.join()]={}),g[e.pathArray.join()][t]=c.child(e.path).on(t,function(t){var a,r,s=t.val();switch(f.type){case"map":r={path:e.path+t.name(),label:s[f.marker.label],radius:s[f.marker.magnitude],latitude:parseFloat(s[f.marker.latitude]),longitude:parseFloat(s[f.marker.longitude])};break;case"table":r=[],f.columns.forEach(function(e){r.push("undefined"!=typeof s[e.value]?s[e.value].toString():"")});break;case"bar":a="$"===f.series[0]?e.params[f.series]:s[f.series],r={path:e.path+t.name(),series:a,value:parseInt(s[f.value])};break;case"line":case"scatter":a="$"===f.series[0]?e.params[f.series]:s[f.series];var i;i="undefined"!=typeof f.xCoord.stream&&f.xCoord.stream?p.data[a]?p.data[a].streamCount:0:parseInt(s[f.xCoord.value]),r={series:a,path:e.path+t.name(),xCoord:i,yCoord:parseInt(s[f.yCoord.value])}}p.addDataPoint(r)})}function o(e,t){switch(f.type){case"bar":delete p.data[e].aggregation;break;case"line":case"scatter":delete p.data[e].values}Object.keys(g[t.join()]).forEach(function(e){var a=g[t.join()][e];c.child(t.join("/")).off(e,a)}),p.draw()}function l(e){switch(f.type){case"map":c.child(e.path).on("child_removed",function(t){p.data.forEach(function(a,r){a.path===e.path+t.name()&&p.data.splice(r,1)}),p.draw()});break;case"table":break;case"bar":case"line":case"scatter":"undefined"==typeof g[e.pathArray.join()]&&(g[e.pathArray.join()]={}),g[e.pathArray.join()].child_removed=c.child(e.path).on("child_removed",function(t){var a="$"===f.series[0]?e.params[f.series]:t.val()[f.series];p.data[a].values.forEach(function(r,s){if(r.path===e.path+t.name()){var i=p.data[a].values.splice(s,1);"bar"===f.type&&(p.data[a].sum-=i)}}),p.draw()})}}function d(){}a(e),s(t),i(r);var c=e,f=t,p=r,u=this;this.pathsToRecords=[];var h=f.path.split("/"),g={};this.parsePath=function(e,t){if(t===h.length){var a="*"===h[h.length-1]?"child_added":"value";e.forEach(function(e){n(e,a),"child_added"===a&&(l(e),d(e))})}else{var r=h[t];if("*"===r[0]){if(t!==h.length-1)throw new Error('You can only use * as the last character in your "path"');this.parsePath(e,t+1)}else if("$"===r[0])e.forEach(function(e){c.child(e.path).on("child_added",function(a){var s={path:e.path+a.name()+"/",params:{}};s.pathArray=[].concat(e.pathArray),s.pathArray.push(a.name()),s.params[r]=a.name();for(var i in e.params)e.params.hasOwnnKey(i)&&(s.params[i]=e.params);this.parsePath([s],t+1)}.bind(this)),function(t){c.child(e.path).on("child_removed",function(e){var a=[].concat(t);a.push(e.name()),o(e.name(),a)})}(e.pathArray)}.bind(this));else{var s=[];e.forEach(function(e){var t={path:e.path+r+"/",params:e.params};t.pathArray=[].concat(e.pathArray),t.pathArray.push(r),s.push(t)}),this.parsePath(s,t+1)}}}},a=function(e){var t;if("undefined"==typeof e?t='no "firebaseRef" specified':e instanceof Firebase==!1&&(t='"firebaseRef" must be an instance of Firebase'),"undefined"!=typeof t)throw new Error("FireGrapher: "+t)},r=function(e){var t;if("undefined"==typeof e)t='no "cssSelector" specified';else if("string"!=typeof e)t='"cssSelector" must be a string';else{var a=document.querySelectorAll(e);0===a.length?t="no element matches the CSS selector '"+e+"'":a.length>1&&(t="multiple elements ("+a.length+" total) match the CSS selector '"+e+"'")}if("undefined"!=typeof t)throw new Error("FireGrapher: "+t)},s=function(e){var t;"undefined"==typeof e?t='no "config" specified':"object"!=typeof e&&(t='"config" must be an object');var a=["table","line","scatter","bar","map"];switch("undefined"==typeof e.type&&(t='no graph "type" specified. Must be "table", "line", or "scatter"'),-1===a.indexOf(e.type)&&(t='Invalid graph "type" specified. Must be "table", "line", or "scatter"'),"undefined"==typeof e.path&&(t='no "path" to individual record specified'),e.type){case"map":("undefined"==typeof e.marker||"undefined"==typeof e.marker.latitude||"undefined"==typeof e.marker.longitude||"undefined"==typeof e.marker.magnitude)&&(t='incomplete "marker" definition specified. \nExpected: '+JSON.stringify(n().marker)+"\nActual: "+JSON.stringify(e.marker));break;case"table":"undefined"==typeof e.columns&&(t='no table "columns" specified'),e.columns.forEach(function(e){"undefined"==typeof e.label&&(t='missing "columns" label'),"undefined"==typeof e.value&&(t='missing "columns" value')});break;case"line":"undefined"==typeof e.xCoord&&(t='no "xCoord" specified'),"undefined"==typeof e.yCoord&&(t='no "yCoord" specified.');break;case"bar":"undefined"==typeof e.value&&(t='no "value" specified.');break;case"scatter":}if("undefined"!=typeof t)throw new Error("FireGrapher: "+t)},i=function(e){var t;if(null===e||"object"!=typeof e?t='"grapher" must be an object':"function"!=typeof e.init?t='"grapher" must have an init() method':"function"!=typeof e.draw?t='"grapher" must have a draw() method':"function"!=typeof e.addDataPoint&&(t='"grapher" must have a addDataPoint() method'),"undefined"!=typeof t)throw new Error("FireGrapher: "+t)},n=function(){var e=["#1ABC9C","#E74C3C","#9B59B6","#3498DB","#F1C40F","#D35400","#2ECC71","#E67E22","#2C3E50","#C0392B"],t=["#28E1BC","#ED7469","#B07CC6","#5FAEE3","#F4D03F","#FF6607","#54D98B","#EB9850","#3E5771","#D65448"];return{styles:{fillColor:"#DDDDDD",fillOpacity:.3,outerStrokeColor:"#000000",outerStrokeWidth:2,innerStrokeColor:"#000000",innerStrokeWidth:1,axes:{x:{ticks:{fillColor:"#000000",fontSize:"14px"},label:{fillColor:"#000000",fontSize:"14px"}},y:{ticks:{fillColor:"#000000",fontSize:"14px"},label:{fillColor:"#000000",fontSize:"14px"}}},series:{strokeWidth:2,strokeColors:e},markers:{size:3.5,strokeWidth:2,style:"default",strokeColors:e,fillColors:t},legend:{fontSize:"16px",stroke:"#000000",strokeWidth:"2px",fill:"#AAAAAA",fillOpacity:.7}},xCoord:{label:""},yCoord:{label:""},marker:{label:"label",latitude:"latitude",longitude:"longitude",magnitude:"radius"}}},o=function(e,t){function a(e){var t=!1;"undefined"==typeof m.data[e.series]&&(t=!0,h+=1,m.data[e.series]={seriesIndex:h,values:[],aggregation:0},c.domain(Object.keys(m.data))),m.data[e.series].values.push(e.value);var a="median",r=0;switch(a){case"mean":for(var i=0;r<m.data[e.series].values.length;r++)i+=m.data[e.series].values[r];m.data[e.series].aggregation=i/m.data[e.series].values.length;break;case"median":var n=m.data[e.series].values.slice(0);n.sort(function(e,t){return e-t}),m.data[e.series].aggregation=n[Math.ceil(n.length/2)];break;case"min":for(m.data[e.series].aggregation=Number.MAX_VALUE;r<m.data[e.series].values.length;r++)m.data[e.series].values[r]<m.data[e.series].aggregation&&(m.data[e.series].aggregation=m.data[e.series].values[r]);break;case"max":for(m.data[e.series].aggregation=Number.MIN_VALUE;r<m.data[e.series].values.length;r++)m.data[e.series].values[r]>m.data[e.series].aggregation&&(m.data[e.series].aggregation=m.data[e.series].values[r]);break;case"sum":m.data[e.series].aggregation+=e.value;break;default:m.data[e.series].aggregation+=e.value}m.data[e.series].aggregation=m.data[e.series].aggregation?m.data[e.series].aggregation:0,t=t||s(null,[0,m.data[e.series].aggregation]),t?m.draw():l(h,e.series,m.data[e.series].aggregation)}function r(e){"undefined"==typeof m.data[e.series]&&(m.data[e.series]={seriesIndex:h,streamCount:0,values:[]},h+=1),m.data[e.series].streamCount+=1;var t=m.data[e.series].values;t.push(e),t.length>1&&e.xCoord<=t[t.length-2].xCoord&&t.sort(function(e,t){return t.xCoord-e.xCoord});var a=s(d3.extent(t,function(e){return e.xCoord}),d3.extent(t,function(e){return e.yCoord}));if(g.xCoord.limit&&t.length>g.xCoord.limit&&(t.shift(),c.domain(d3.extent(t,function(e){return e.xCoord})),a=!0),a)m.draw();else{var r=m.data[e.series].seriesIndex;switch(i(),g.type){case"line":n(r,t),o(r,t);break;case"scatter":o(r,t)}}}function s(e,t){var a=!1,r=!1;if(e&&e[0]<p.min&&(p.min=e[0],a=!0),e&&e[1]>p.max&&(p.max=e[1],a=!0),t&&t[0]<u.min&&(u.min=t[0],r=!0),t&&t[1]>u.max&&(u.max=t[1],r=!0),a&&c.domain([p.min,p.max]),r){var s=.1*(u.max-u.min);f.domain([u.min-s,u.max+s])}return a||r}function i(){var e=[];for(var t in m.data)"undefined"!==t&&e.push(t);if(d.selectAll("g.fg-legend").remove(),e.length>1){var a={top:5,bottom:5,left:5,right:5},r={right:5,bottom:15},s=50,i=20*e.length,n=g.styles.size.width-2*(s+a.left+a.right)-r.right,o=g.styles.size.height-2*(i+a.top+a.bottom)-r.bottom,l=d.append("g").attr("class","fg-legend");l.append("rect").attr("class","fg-legend-container").attr("x",n).attr("y",o).attr("width",s+a.left+a.right).attr("height",i+a.top+a.bottom).style("stroke",g.styles.legend.stroke).style("stroke-width",g.styles.legend.strokeWidth).style("fill",g.styles.legend.fill).style("fill-opacity",g.styles.legend.fillOpacity);var c=l.selectAll("text").data(e);c.enter().append("text").attr("class",function(e,t){return"fg-legend-series fg-series-"+t}).attr("x",n).attr("y",o).attr("dx",s).attr("dy",function(e,t){return 20*(t+1)}).style("text-anchor","end").style("stroke","none").style("fill",function(e,t){return g.styles.series.strokeColors[t]}).style("font-size",g.styles.legend.fontSize).text(function(e){return e})}}function n(e,t){var a={top:20,bottom:30,left:60,right:20},r=g.styles.size.height-2*a.bottom-a.top,s=d3.svg.area().defined(function(e){return null!==e}).x(function(e){return c(e.xCoord)}).y0(r).y1(function(e){return f(e.yCoord)});if(t){var i=d.selectAll("path.fg-area-"+e);i.empty()&&(i=d.append("path").attr("class","fg-area fg-area-"+e).attr("stroke",g.styles.series.strokeColors[e]).attr("stroke-width",g.styles.series.strokeWidth).attr("fill",g.styles.series.strokeColors[e]).attr("fill-opacity",.5)),t.length>0&&i.attr("d",s(t))}else d.selectAll("path.fg-area-"+e).remove()}function o(e,t){if(t){var a=d.selectAll("circle.fg-series-"+e).data(t);a.enter().append("circle").attr("class","fg-marker fg-series-"+e).attr("stroke",g.styles.markers.strokeColors[e]).attr("stroke-width",g.styles.markers.strokeWidth).attr("fill",g.styles.markers.fillColors[e]),a.exit().remove(),a.attr("cx",function(e){return c(e.xCoord)}).attr("cy",function(e){return f(e.yCoord)}).attr("r",g.styles.markers.size)}else d.selectAll("circle.fg-series-"+e).remove()}function l(e,t,a){"undefined"!=typeof a?d.selectAll(".fg-bar .fg-series-"+e).empty()?d.append("rect").attr("class","fg-bar fg-series-"+e):d.select(".fg-bar .fg-series-"+e).datum(a).attr("x",function(){return c(t)}).attr("width",c.rangeBand()).attr("y",function(e){return f(e)}).attr("height",function(e){return f.range()[0]-f(e)}):d.selectAll(".fg-bar .fg-series-"+e).remove()}var d,c,f,p,u,h,g=e,y=t,m=this;this.init=function(){this.data={},p={min:0,max:0},u={min:0,max:0},h=0,c="bar"===g.type?d3.scale.ordinal():d3.scale.linear().domain([p.min,p.max]),c.range([0,g.styles.size.width]),f=d3.scale.linear().domain([u.min,u.max]).range([g.styles.size.height,0])},this.draw=function(){var e={top:20,bottom:30,left:60,right:20},t=g.styles.size.height-e.bottom-e.top,a=g.styles.size.width-e.left-e.right;(!d||d3.selectAll(y+" svg").empty())&&(d=d3.select(y).append("svg").attr("class","fg-"+g.type).attr("width",g.styles.size.width).attr("height",g.styles.size.height).append("g").attr("transform","translate("+e.left+", "+e.bottom+")"),g.title&&d.append("text").attr("class","fg-title").attr("x",a/2).attr("y",0-e.top/2).attr("text-anchor","middle").style("font-size","16px").style("text-decoration","underline").text(g.title)),f.range([t-e.bottom,0]),"bar"===g.type?c.rangeRoundBands([0,a],.1,1):c.range([0,a]);var r=d3.svg.axis().orient("bottom").scale(c).ticks(Math.floor(.035*a)).tickSize(-t+e.bottom,-t+e.bottom),s=d3.svg.axis().orient("left").scale(f).ticks(Math.floor(.035*t)).tickSize(-a,-a);d.selectAll("g.fg-x-axis").empty()?(d.append("g").attr("class","fg-axis fg-x-axis").attr("transform","translate(0,"+(t-e.bottom)+")").attr("shape-rendering","crispEdges").call(r),d.append("text").attr("class","fg-axis-label fg-x-axis-label").attr("transform","translate("+a+", "+(t+e.bottom/2)+")").attr("fill",g.styles.axes.x.label.fillColor).attr("font-size",g.styles.axes.x.label.fontSize).attr("font-weight","bold").style("text-anchor","end").text(g.xCoord.label)):d.selectAll("g.fg-x-axis").call(r),d.selectAll("g.fg-y-axis").empty()?(d.append("g").attr("class","fg-axis fg-y-axis").attr("shape-rendering","crispEdges").call(s),d.append("text").attr("class","fg-axis-label fg-y-axis-label").attr("transform","rotate(-90)").attr("fill",g.styles.axes.y.label.fillColor).attr("font-size",g.styles.axes.y.label.fontSize).attr("font-weight","bold").attr("dy",-e.left+16).style("text-anchor","end").text(g.yCoord.label)):d.selectAll("g.fg-y-axis").call(s),d.selectAll(".domain").attr("stroke",g.styles.outerStrokeColor).attr("stroke-width",g.styles.outerStrokeWidth).attr("fill",g.styles.fillColor).attr("fill-opacity",g.styles.fillOpacity),d.selectAll(".fg-x-axis .tick").attr("stroke",g.styles.innerStrokeColor).attr("stroke-width","bar"===g.type?0:g.styles.innerStrokeWidth),d.selectAll(".fg-x-axis text").attr("stroke","none").attr("fill",g.styles.axes.x.ticks.fillColor).attr("font-size",g.styles.axes.x.ticks.fontSize),d.selectAll(".fg-y-axis .tick").attr("stroke",g.styles.innerStrokeColor).attr("stroke-width",g.styles.innerStrokeWidth),d.selectAll(".fg-y-axis text").attr("stroke","none").attr("fill",g.styles.axes.y.ticks.fillColor).attr("font-size",g.styles.axes.y.ticks.fontSize);for(var p in m.data)if(m.data.hasOwnProperty(p)){var u=m.data[p].seriesIndex,h=m.data[p].values;switch(g.type){case"line":i(),n(u,h),o(u,h);break;case"scatter":i(),o(u,h);break;case"bar":l(u,p,m.data[p].aggregation)}"undefined"==typeof h&&(delete m.data[p],"bar"===g.type&&(c.domain(Object.keys(m.data)),m.draw()))}},this.addDataPoint=function(e){switch(g.type){case"bar":a(e);break;case"line":case"scatter":r(e)}}},l=function(e,t){var a,r=t,s=this;this.init=function(){this.data=[];var e=new google.maps.Map(d3.select(r).node(),{zoom:8,center:new google.maps.LatLng(37.76487,-122.41948),mapTypeId:google.maps.MapTypeId.TERRAIN});a=new google.maps.OverlayView,a.onAdd=function(){function e(e){return e=new google.maps.LatLng(e.latitude,e.longitude),e=r.fromLatLngToDivPixel(e),d3.select(s).style("left",e.x-i+"px").style("top",e.y-i+"px")}var t=d3.select(a.getPanes().overlayLayer).append("div").attr("class","stations"),r=a.getProjection(),i=10;a.draw=function(){if(0!==s.data.length){var a=t.selectAll("svg").data(s.data).each(e).enter().append("svg:svg").each(e).attr("class","marker");a.append("svg:circle").attr("r",function(e){return e.radius}).attr("cx",function(e){return e.radius+i}).attr("cy",function(e){return e.radius+i}),a.append("svg:text").attr("x",i+7).attr("y",i).attr("dy",".31em").text(function(e){return e.label})}else t.selectAll("svg").remove()}},a.setMap(e)},this.draw=function(){a.draw()},this.addDataPoint=function(e){s.data.push(e),s.drawMap()}},d=function(e,t){function a(e){var t=0,a=!0;r.append("div").attr("class","fg-table-row").selectAll("div.header").data(e).enter().append("div").attr("class","fg-table-header").attr("style","font-weight: bold;").attr("width",function(e){return e.width}).text(function(e){return e.label}).on("click",function(s){for(var i=0,n=0;n<e.length;n++)e[n]===s&&(i=n);t!==i?(t=i,a=!0):a=!a,r.selectAll("div.data").sort(function(e,r){return e[t]===r[t]?0:a?e[t]>r[t]:e[t]<r[t]})})}var r,s=e,i=t,n=this;this.init=function(){this.data=[],r=d3.select(i).append("div").attr("class","fg-table").attr("style","display: inline-block;"),a(s.columns)},this.draw=function(){},this.addDataPoint=function(e){n.data.push(e),r.selectAll("div.fg-table-row").data(n.data).enter().append("div").attr("class","fg-table-row").attr("style","display: block; text-align: center; border-left: solid 3px #000; border-top: solid 3px #000;").selectAll("div.cell").data(function(e){return e}).enter().append("div").attr("class","gf-table-cell").attr("style","float: left; width: 100px; border-right: solid 3px black; padding: 5px;").attr("width",function(e,t){return s.columns[t].width}).text(function(e){return e})}};return e}();"undefined"!=typeof module&&"undefined"!=typeof process&&(module.exports=FireGrapher);
function getYScale(index) { return d3.scale.linear() .domain([0, nodes[index].length]) .range([0, graphConstants.HEIGHT - graphConstants.HEADER_HEIGHT]); }
function CompareTimeseries (params) { $.extend(this, params); this.colors = params.colors || d3.scale.category10(); }
return function histogram() { var margin = {top: 20, right: 20, bottom: 20, left: 50}; var width = 760 - margin.left - margin.right; var height = 120 - margin.top - margin.bottom; var color = d3.scale.category20c(); var xValue = function (d) { return d[0]; }; var yValue = function (d) { return d[1]; }; var offset = "zero"; var xAxis = d3.svg.axis().orient("bottom").ticks(5); var yAxis = d3.svg.axis().orient("left"); var xScale = d3.time.scale.utc().range([0, width]); var yScale = d3.scale.linear().range([height, 0]).nice(); var xDomain = function (data) { return d3.extent(data, function (d) { return d[0]; }); }; var yDomain = function (data) { return [ Math.min(0, getYStackExtent.call(data, "min")), Math.max(0, getYStackExtent.call(data, "max")) ]; }; var dispatch = d3.dispatch("brush", "hover", "mouseover", "mouseout"); // Axis options var showXAxis = true; var showYAxis = true; var xAxisTitle = ""; var yAxisTitle = ""; // Histogram options var stackClass; var barClass; var barFill; function chart(selection) { selection.each(function (data) { var stack = d3.layout.stack().x(xValue).y(yValue).offset(offset || "zero"); var layers = stack(data); var n = layers.length; // number of layers var svg; var g; var stackLayer; var bars; layers = layers.map(function (d) { return d.map(function (e, i) { return [xValue.call(d, e, i), yValue.call(d, e, i), y0.call(d, e, i)]; }); }); xScale.domain(xDomain.call(d3.merge(layers))); yScale.domain(yDomain.call(d3.merge(layers))); svg = d3.select(this).append("svg") .data([layers]) .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom); g = svg.append("g") .attr("transform", "translate(" + margin.left + ", " + margin.top + ")"); stackLayer = g.selectAll(".layer") .data(function (d) { return d; }) .enter().append("g") .attr("class", stackClass); // Enter bars = stackLayer.selectAll("rect") .data(function (d) { return d; }) .enter().append("rect") .attr("class", barClass) .attr("fill", barFill); // Update bars.attr("x", function (d, i, j) { if (offset === "grouped") { return xScale(d[0]) + xScale.rangeBand() / n * j; } return xScale(d[0]); }) .attr("width", function () { if (offset === "grouped") { return xScale.rangeBand() / n; } return xScale.rangeBand(); }) .attr("y", function (d) { if (offset === "grouped") { return yScale(d.y); } return yScale(d.y0) - yScale(d.y0 + d.y); }) .attr("height", function (d) { if (offset === "grouped") { return height - yScale(d.y); } return yScale(d.y0) - yScale(d.y0 + d.y); }); // Exit bars.exit().remove(); g.append("g").attr("class", "x axis"); g.append("g").attr("class", "y axis"); if (showXAxis) { g.select(".x.axis") .attr("transform", "translate(0," + yScale.range()[0] + ")") .call(xAxis.scale(xScale)) .append("text") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text(xAxisTitle); } if (showYAxis) { g.select(".y.axis") .call(yAxis.scale(yScale)) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text(yAxisTitle); } }); } function y0(d) { return d.y0; } function Y(d) { return yScale(d.y0 + d.y); } function getYStackExtent(data, extent) { return d3[extent](data, function (d) { return d3[extent](d, Y); }); } chart.margin = function (_) { if (!arguments.length) { return margin; } margin.top = typeof _.top !== "undefined" ? _.top : margin.top; margin.right = typeof _.right !== "undefined" ? _.right : margin.right; margin.bottom = typeof _.bottom !== "undefined" ? _.bottom : margin.bottom; margin.left = typeof _.left !== "undefined" ? _.left : margin.left; return chart; }; chart.width = function (_) { if (!arguments.length) { return width; } width = _; return chart; }; chart.height = function (_) { if (!arguments.length) { return height; } height = _; return chart; }; chart.color = function (_) { if (!arguments.length) { return color; } color = _; return chart; }; chart.x = function (_) { if (!arguments.length) { return xValue; } xValue = _; return chart; }; chart.y = function (_) { if (!arguments.length) { return yValue; } yValue = _; return chart; }; chart.offset = function (_) { if (!arguments.length) { return offset; } offset = _; return chart; }; chart.xAxis = function (_) { if (!arguments.length) { return xAxis; } xAxis = _; return chart; }; chart.yAxis = function (_) { if (!arguments.length) { return yAxis; } yAxis = _; return chart; }; chart.xScale = function (_) { if (!arguments.length) { return xScale; } xScale = _; return chart; }; chart.yScale = function (_) { if (!arguments.length) { return yScale; } yScale = _; return chart; }; chart.xDomain = function (_) { if (!arguments.length) { return xDomain; } xDomain = _; return chart; }; chart.yDomain = function (_) { if (!arguments.length) { return yDomain; } yDomain = _; return chart; }; chart.dispatch = function (_) { if (!arguments.length) { return dispatch; } dispatch = _; return chart; }; chart.showXAxis = function (_) { if (!arguments.length) { return showXAxis; } showXAxis = _; return chart; }; chart.showYAxis = function (_) { if (!arguments.length) { return showYAxis; } showYAxis = _; return chart; }; chart.xAxisTitle = function (_) { if (!arguments.length) { return xAxisTitle; } xAxisTitle = _; return chart; }; chart.yAxisTitle = function (_) { if (!arguments.length) { return yAxisTitle; } yAxisTitle = _; return chart; }; return chart; };
function link (scope, element, attrs) { let nbSteps, margin, width, height, color, data, $e nbSteps = attrs.steps || 4 margin = { top: attrs.showAxis && attrs.axisOrientation !== 'bottom' ? 20 : 0, right: 20, bottom: attrs.showAxis && attrs.axisOrientation === 'bottom' ? 20 : 0, left: 20 } width = getContainerWidth() - margin.left - margin.right height = attrs.height || 70 - margin.top - margin.bottom + (attrs.showAxis ? 20 : 0) color = attrs.color || 'darkgreen' const x = d3 .time .scale() .range([0, width]) const y = d3 .scale.linear() .range([height, 0]) const xAxis = d3 .svg .axis() .scale(x) .orient(attrs.axisOrientation || 'top') const line = d3 .svg .line() .x(function (d) { return x(d.date) }) .y(function (d) { return y(d.value) }) const bisectDate = d3 .bisector(function (d) { return d.date }).left const svg = d3 .select(element[0]) .append('svg') .attr('height', height + margin.top + margin.bottom) .attr('width', width + margin.left + margin.right) .append('g') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') // Tooltip const focus = svg .append('g') .attr('class', 'focus') .style('display', 'none') focus .append('text') .attr('x', 9) .attr('dy', '.35em') svg .append('rect') .attr('class', 'overlay') .attr('height', height) .attr('width', width) .style({fill: 'none', 'pointer-events': 'all'}) .on('mousemove', mousemove) const hover = svg .append('g') .attr('class', 'hover-container') .style('pointer-events', 'none') hover .append('line') .attr('y1', 0) .attr('y2', height) .style('stroke-width', 2) .style('stroke-dasharray', '5 5') .style('stroke', 'red') .style('fill', 'none') hover .append('text') .style({fill: 'black'}) function getContainerWidth () { $e = $(element[0]).parent() while ($e.width() === 0 && $e.parent()) { $e = $e.parent() } return $e.width() } function mousemove () { const x0 = x.invert(d3.mouse(this)[0]) const index = bisectDate(data, x0, 1) const d0 = data[index - 1] const d1 = data[index] const d = x0 - d0.date > d1.date - x0 ? d1 : d0 scope.$apply(function () { scope.selected = d.date }) } function hoverDate (date) { const val = find(data, {date: date}) if (val) { const value = val.textValue || Math.round(100 * val.value) / 100 hover .select('line') .attr('x1', x(date)) .attr('x2', x(date)) hover .select('text') .attr('dx', x(date) + 5) .attr('dy', height / 2) .text(value) } } d3.selection.prototype.moveToFront = function () { return this.each(function () { this.parentNode.appendChild(this) }) } function redraw () { let splittedData, counter, extent width = getContainerWidth() - margin.left - margin.right x.range([0, width]) d3 .select(element[0]) .select('svg') .attr('width', width + margin.left + margin.right) .select('rect') .attr('width', width) extent = scope.extent || d3 .extent(data, function (d) { return d.value }) extent = [Math.min(0, extent[0]), Math.max(0, extent[1])] const intervalSize = (extent[1] - extent[0]) / nbSteps splittedData = [] for (counter = 0; counter < nbSteps; counter++) { splittedData[counter] = [] var d = new Date(data[0].date) splittedData[counter].push({ date: d, value: 0 }) } forEach(data, function (datum) { for (counter = 0; counter < nbSteps; counter++) { let d, value d = new Date(datum.date) value = Math.min(Math.max(0, datum.value - intervalSize * counter), intervalSize) splittedData[counter].push({ date: d, value: value, close: new Date(+d + 60 * 60 * 1000) }) } }) for (counter = 0; counter < nbSteps; counter++) { const d = new Date(data[data.length - 1].date) splittedData[counter].push({ date: d, value: 0 }) } x.domain(d3.extent(splittedData[0], function (d) { return d.date })) y.domain([extent[0], extent[1] / nbSteps]) svg.selectAll('.horizon-area').remove() if (label) { label.remove() } svg.selectAll('.axis').remove() if (attrs.showAxis) { var bottomaxis = svg .append('g') .attr('class', 'axis') .style({ fill: 'none', stroke: '#000', 'shape-rendering': 'crispEdges' }) .call(xAxis) if (attrs.axisOrientation === 'bottom') { bottomaxis.attr('transform', 'translate(0,' + height + ')') } bottomaxis.selectAll('text').style({fill: 'black', stroke: 'transparent', 'font-size': '80%'}) } forEach(splittedData, function (oneSplitted) { svg .append('path') .attr('class', 'horizon-area') .datum(oneSplitted) .style({ fill: color, stroke: 'transparent', 'fill-opacity': 0.25, 'stroke-opacity': 0.3 }) .attr('d', line) }) const label = svg.append('text') .style({ 'font-size': '125%' }) const bbox = label.node().getBBox() label.attr('dx', 5) .attr('dy', (height + margin.bottom - bbox.height) / 2) .text(attrs.label) svg.select('.overlay').moveToFront() svg.select('.hover-container').moveToFront() } scope.$watch('selected', function (newVal) { if (newVal) { hoverDate(newVal) } }) scope.$watch(function () { return scope.chartData }, function (newVal) { if (!newVal || !newVal.length) { return } data = newVal redraw() }) scope.$watch(function () { return scope.extent }, function (newVal) { redraw() }) $(window).resize(function () { redraw() }) }
selection.each(function(data) { var svg = d3.select(this); var offset = innerRadius + Math.ceil(data.length / numSegments) * segmentHeight; g = svg.append("g") .classed("circular-heat", true) .attr("transform", "translate(" + parseInt(margin.left + offset) + "," + parseInt(margin.top + offset) + ")"); var autoDomain = false; if (domain === null) { domain = d3.extent(data, accessor); autoDomain = true; } var color = d3.scale.linear().domain(domain).range(range); if(autoDomain) domain = null; g.selectAll("path").data(data) .enter().append("path") .attr("d", d3.svg.arc().innerRadius(ir).outerRadius(or).startAngle(sa).endAngle(ea)) .attr("fill", function(d) {return color(accessor(d));}); // Unique id so that the text path defs are unique - is there a better way to do this? var id = d3.selectAll(".circular-heat")[0].length; //Radial labels var lsa = 0.01; //Label start angle var labels = svg.append("g") .classed("labels", true) .classed("radial", true) .attr("transform", "translate(" + parseInt(margin.left + offset) + "," + parseInt(margin.top + offset) + ")"); labels.selectAll("def") .data(radialLabels).enter() .append("def") .append("path") .attr("id", function(d, i) {return "radial-label-path-"+id+"-"+i;}) .attr("d", function(d, i) { var r = innerRadius + ((i + 0.2) * segmentHeight); return "m" + r * Math.sin(lsa) + " -" + r * Math.cos(lsa) + " a" + r + " " + r + " 0 1 1 -1 0"; }); labels.selectAll("text") .data(radialLabels).enter() .append("text") .append("textPath") .attr("xlink:href", function(d, i) {return "#radial-label-path-"+id+"-"+i;}) .style("font-size", 0.6 * segmentHeight + 'px') .text(function(d) {return d;}); //Segment labels var segmentLabelOffset = 2; var r = innerRadius + Math.ceil(data.length / numSegments) * segmentHeight + segmentLabelOffset; labels = svg.append("g") .classed("labels", true) .classed("segment", true) .attr("transform", "translate(" + parseInt(margin.left + offset) + "," + parseInt(margin.top + offset) + ")"); labels.append("def") .append("path") .attr("id", "segment-label-path-"+id) .attr("d", "m0 -" + r + " a" + r + " " + r + " 0 1 1 -1 0"); labels.selectAll("text") .data(segmentLabels).enter() .append("text") .append("textPath") .attr("xlink:href", "#segment-label-path-"+id) .attr("startOffset", function(d, i) {return i * 100 / numSegments + "%";}) .text(function(d) {return d;}); });
chart.colorbar.svg.attr("width", width + options.cb_margin.left + options.cb_margin.right) .attr("height", height + options.cb_margin.top + options.cb_margin.bottom); chart.colorbar.svg.selectAll("g.z") .attr("transform", "translate(" + width + "," + options.cb_margin.top + ")"); _redraw_main = true; } chart.type = "heatmap_2d"; return chart } var jet_colormap = d3.scale.linear() .domain([0, 31, 63, 95, 127, 159, 191, 223, 255]) /* Jet: #00007F: dark blue #0000FF: blue #007FFF: azure #00FFFF: cyan #7FFF7F: light green #FFFF00: yellow #FF7F00: orange #FF0000: red #7F0000: dark red #00000000: transparent for overflow */ .range(["#00007F", "#0000FF","#007FFF", "#00FFFF","#7FFF7F","#FFFF00","#FF7F00","#FF0000","#7F0000"]);
BarPlot.prototype.rangeX = function(){ var barWidth = this.calculateBarWidth(); var x = d3.scale.linear().range([0, barWidth]); x.domain([this.parent.data.min,this.parent.maxInData()]); return x; }
function link(scope, element) { // Key dimensions for the viz and constituent charts. let svgWidth = angular.element('.results-container').width(); const focusZoomPanelHeight = 25; const focusChartHeight = 310; const focusHeight = focusZoomPanelHeight + focusChartHeight; const contextChartHeight = 60; const contextChartLineTopMargin = 3; const chartSpacing = 25; const swimlaneHeight = 30; const margin = { top: 20, right: 10, bottom: 15, left: 40 }; const svgHeight = focusHeight + contextChartHeight + swimlaneHeight + chartSpacing + margin.top + margin.bottom; let vizWidth = svgWidth - margin.left - margin.right; const FOCUS_CHART_ANOMALY_RADIUS = 7; const SCHEDULED_EVENT_MARKER_HEIGHT = 5; const ZOOM_INTERVAL_OPTIONS = [ { duration: moment.duration(1, 'h'), label: '1h' }, { duration: moment.duration(12, 'h'), label: '12h' }, { duration: moment.duration(1, 'd'), label: '1d' }, { duration: moment.duration(1, 'w'), label: '1w' }, { duration: moment.duration(2, 'w'), label: '2w' }, { duration: moment.duration(1, 'M'), label: '1M' }]; // Set up the color scale to use for indicating score. const anomalyColorScale = d3.scale.threshold() .domain([3, 25, 50, 75, 100]) .range(['#d2e9f7', '#8bc8fb', '#ffdd00', '#ff7e00', '#fe5050']); // Create a gray-toned version of the color scale to use under the context chart mask. const anomalyGrayScale = d3.scale.threshold() .domain([3, 25, 50, 75, 100]) .range(['#dce7ed', '#b0c5d6', '#b1a34e', '#b17f4e', '#c88686']); const focusXScale = d3.time.scale().range([0, vizWidth]); const focusYScale = d3.scale.linear().range([focusHeight, focusZoomPanelHeight]); const focusXAxis = d3.svg.axis().scale(focusXScale).orient('bottom') .innerTickSize(-focusChartHeight).outerTickSize(0).tickPadding(10); const focusYAxis = d3.svg.axis().scale(focusYScale).orient('left') .innerTickSize(-vizWidth).outerTickSize(0).tickPadding(10); const focusValuesLine = d3.svg.line() .x(function (d) { return focusXScale(d.date); }) .y(function (d) { return focusYScale(d.value); }) .defined(d => d.value !== null); const focusBoundedArea = d3.svg.area() .x (function (d) { return focusXScale(d.date) || 1; }) .y0(function (d) { return focusYScale(d.upper); }) .y1(function (d) { return focusYScale(d.lower); }) .defined(d => (d.lower !== null && d.upper !== null)); let contextXScale = d3.time.scale().range([0, vizWidth]); let contextYScale = d3.scale.linear().range([contextChartHeight, contextChartLineTopMargin]); let fieldFormat = undefined; const brush = d3.svg.brush(); let mask; scope.$on('render', () => { fieldFormat = mlFieldFormatService.getFieldFormat(scope.selectedJob.job_id, scope.detectorIndex); render(); drawContextChartSelection(); }); scope.$watchCollection('focusForecastData', renderFocusChart); scope.$watchCollection('focusChartData', renderFocusChart); scope.$watchGroup(['showModelBounds', 'showForecast'], renderFocusChart); // Redraw the charts when the container is resize. const resizeChecker = new ResizeChecker(angular.element('.ml-timeseries-chart')); resizeChecker.on('resize', () => { scope.$evalAsync(() => { // Wait a digest cycle before rendering to prevent // the underlying ResizeObserver going into an infinite loop. render(); drawContextChartSelection(); renderFocusChart(); }); }); // Listeners for mouseenter/leave events for rows in the table // to highlight the corresponding anomaly mark in the focus chart. const tableRecordMousenterListener = function (record) { highlightFocusChartAnomaly(record); }; const tableRecordMouseleaveListener = function (record) { unhighlightFocusChartAnomaly(record); }; mlAnomaliesTableService.anomalyRecordMouseenter.watch(tableRecordMousenterListener); mlAnomaliesTableService.anomalyRecordMouseleave.watch(tableRecordMouseleaveListener); element.on('$destroy', () => { mlAnomaliesTableService.anomalyRecordMouseenter.unwatch(tableRecordMousenterListener); mlAnomaliesTableService.anomalyRecordMouseleave.unwatch(tableRecordMouseleaveListener); resizeChecker.destroy(); scope.$destroy(); }); function render() { // Clear any existing elements from the visualization, // then build the svg elements for the bubble chart. const chartElement = d3.select(element.get(0)); chartElement.selectAll('*').remove(); if (scope.contextChartData === undefined) { return; } // Set the size of the components according to the width of the parent container at render time. svgWidth = Math.max(angular.element('.results-container').width(), 0); const svg = chartElement.append('svg') .attr('width', svgWidth) .attr('height', svgHeight); let contextDataMin; let contextDataMax; if (scope.modelPlotEnabled === true || (scope.contextForecastData !== undefined && scope.contextForecastData.length > 0)) { const combinedData = scope.contextForecastData === undefined ? scope.contextChartData : scope.contextChartData.concat(scope.contextForecastData); contextDataMin = d3.min(combinedData, d => Math.min(d.value, d.lower)); contextDataMax = d3.max(combinedData, d => Math.max(d.value, d.upper)); } else { contextDataMin = d3.min(scope.contextChartData, d => d.value); contextDataMax = d3.max(scope.contextChartData, d => d.value); } // Set the size of the left margin according to the width of the largest y axis tick label. // The min / max of the aggregated context chart data may be less than the min / max of the // data which is displayed in the focus chart which is likely to be plotted at a lower // aggregation interval. Therefore ceil the min / max with the higher absolute value to allow // for extra space for chart labels which may have higher values than the context data // e.g. aggregated max may be 9500, whereas focus plot max may be 11234. const ceiledMax = contextDataMax > 0 ? Math.pow(10, Math.ceil(Math.log10(Math.abs(contextDataMax)))) : contextDataMax; const flooredMin = contextDataMin >= 0 ? contextDataMin : -1 * Math.pow(10, Math.ceil(Math.log10(Math.abs(contextDataMin)))); // Temporarily set the domain of the focus y axis to the min / max of the full context chart // data range so that we can measure the maximum tick label width on temporary text elements. focusYScale.domain([flooredMin, ceiledMax]); let maxYAxisLabelWidth = 0; const tempLabelText = svg.append('g') .attr('class', 'temp-axis-label tick'); tempLabelText.selectAll('text.temp.axis').data(focusYScale.ticks()) .enter() .append('text') .text((d) => { if (fieldFormat !== undefined) { return fieldFormat.convert(d, 'text'); } else { return focusYScale.tickFormat()(d); } }) .each(function () { maxYAxisLabelWidth = Math.max(this.getBBox().width + focusYAxis.tickPadding(), maxYAxisLabelWidth); }) .remove(); d3.select('.temp-axis-label').remove(); margin.left = (Math.max(maxYAxisLabelWidth, 40)); vizWidth = Math.max(svgWidth - margin.left - margin.right, 0); focusXScale.range([0, vizWidth]); focusYAxis.innerTickSize(-vizWidth); const focus = svg.append('g') .attr('class', 'focus-chart') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); const context = svg.append('g') .attr('class', 'context-chart') .attr('transform', 'translate(' + margin.left + ',' + (focusHeight + margin.top + chartSpacing) + ')'); // Draw each of the component elements. createFocusChart(focus, vizWidth, focusHeight); drawContextElements(context, vizWidth, contextChartHeight, swimlaneHeight); } function drawContextChartSelection() { if (scope.contextChartData === undefined) { return; } // Make appropriate selection in the context chart to trigger loading of the focus chart. let focusLoadFrom; let focusLoadTo; const contextXMin = contextXScale.domain()[0].getTime(); const contextXMax = contextXScale.domain()[1].getTime(); let combinedData = scope.contextChartData; if (scope.contextForecastData !== undefined) { combinedData = combinedData.concat(scope.contextForecastData); } if (scope.zoomFrom) { focusLoadFrom = scope.zoomFrom.getTime(); } else { focusLoadFrom = _.reduce(combinedData, (memo, point) => Math.min(memo, point.date.getTime()), new Date(2099, 12, 31).getTime()); } focusLoadFrom = Math.max(focusLoadFrom, contextXMin); if (scope.zoomTo) { focusLoadTo = scope.zoomTo.getTime(); } else { focusLoadTo = _.reduce(combinedData, (memo, point) => Math.max(memo, point.date.getTime()), 0); } focusLoadTo = Math.min(focusLoadTo, contextXMax); if ((focusLoadFrom !== contextXMin) || (focusLoadTo !== contextXMax)) { setContextBrushExtent(new Date(focusLoadFrom), new Date(focusLoadTo), true); } else { // Don't set the brush if the selection is the full context chart domain. setBrushVisibility(false); const selectedBounds = contextXScale.domain(); scope.selectedBounds = { min: moment(new Date(selectedBounds[0])), max: moment(selectedBounds[1]) }; scope.$root.$broadcast('contextChartSelected', { from: selectedBounds[0], to: selectedBounds[1] }); } } function createFocusChart(fcsGroup, fcsWidth, fcsHeight) { // Split out creation of the focus chart from the rendering, // as we want to re-render the paths and points when the zoom area changes. // Add a group at the top to display info on the chart aggregation interval // and links to set the brush span to 1h, 1d, 1w etc. const zoomGroup = fcsGroup.append('g') .attr('class', 'focus-zoom'); zoomGroup.append('rect') .attr('x', 0) .attr('y', 0) .attr('width', fcsWidth) .attr('height', focusZoomPanelHeight) .attr('class', 'chart-border'); createZoomInfoElements(zoomGroup, fcsWidth); // Add border round plot area. fcsGroup.append('rect') .attr('x', 0) .attr('y', focusZoomPanelHeight) .attr('width', fcsWidth) .attr('height', focusChartHeight) .attr('class', 'chart-border'); // Add background for x axis. const xAxisBg = fcsGroup.append('g') .attr('class', 'x-axis-background'); xAxisBg.append('rect') .attr('x', 0) .attr('y', fcsHeight) .attr('width', fcsWidth) .attr('height', chartSpacing); xAxisBg.append('line') .attr('x1', 0) .attr('y1', fcsHeight) .attr('x2', 0) .attr('y2', fcsHeight + chartSpacing); xAxisBg.append('line') .attr('x1', fcsWidth) .attr('y1', fcsHeight) .attr('x2', fcsWidth) .attr('y2', fcsHeight + chartSpacing); xAxisBg.append('line') .attr('x1', 0) .attr('y1', fcsHeight + chartSpacing) .attr('x2', fcsWidth) .attr('y2', fcsHeight + chartSpacing); const axes = fcsGroup.append('g'); axes.append('g') .attr('class', 'x axis') .attr('transform', 'translate(0,' + fcsHeight + ')'); axes.append('g') .attr('class', 'y axis'); // Create the elements for the metric value line and model bounds area. fcsGroup.append('path') .attr('class', 'area bounds'); fcsGroup.append('path') .attr('class', 'values-line'); fcsGroup.append('g') .attr('class', 'focus-chart-markers'); // Create the path elements for the forecast value line and bounds area. if (scope.contextForecastData) { fcsGroup.append('path') .attr('class', 'area forecast'); fcsGroup.append('path') .attr('class', 'values-line forecast'); fcsGroup.append('g') .attr('class', 'focus-chart-markers forecast'); } fcsGroup.append('rect') .attr('x', 0) .attr('y', 0) .attr('width', fcsWidth) .attr('height', fcsHeight + 24) .attr('class', 'chart-border chart-border-highlight'); } function renderFocusChart() { if (scope.focusChartData === undefined) { return; } const data = scope.focusChartData; const focusChart = d3.select('.focus-chart'); // Update the plot interval labels. const focusAggInt = scope.focusAggregationInterval.expression; const bucketSpan = scope.selectedJob.analysis_config.bucket_span; angular.element('.zoom-aggregation-interval').text( `(aggregation interval: ${focusAggInt}, bucket span: ${bucketSpan})`); // Render the axes. // Calculate the x axis domain. // Elasticsearch aggregation returns points at start of bucket, // so set the x-axis min to the start of the first aggregation interval, // and the x-axis max to the end of the last aggregation interval. const bounds = scope.selectedBounds; const aggMs = scope.focusAggregationInterval.asMilliseconds(); const earliest = moment(Math.floor((bounds.min.valueOf()) / aggMs) * aggMs); const latest = moment(Math.ceil((bounds.max.valueOf()) / aggMs) * aggMs); focusXScale.domain([earliest.toDate(), latest.toDate()]); // Calculate the y-axis domain. if (scope.focusChartData.length > 0 || (scope.focusForecastData !== undefined && scope.focusForecastData.length > 0)) { if (fieldFormat !== undefined) { focusYAxis.tickFormat(d => fieldFormat.convert(d, 'text')); } else { // Use default tick formatter. focusYAxis.tickFormat(null); } // Calculate the min/max of the metric data and the forecast data. let yMin = 0; let yMax = 0; let combinedData = data; if (scope.focusForecastData !== undefined && scope.focusForecastData.length > 0) { combinedData = data.concat(scope.focusForecastData); } yMin = d3.min(combinedData, (d) => { return d.lower !== undefined ? Math.min(d.value, d.lower) : d.value; }); yMax = d3.max(combinedData, (d) => { return d.upper !== undefined ? Math.max(d.value, d.upper) : d.value; }); if (yMax === yMin) { if ( contextYScale.domain()[0] !== contextYScale.domain()[1] && yMin >= contextYScale.domain()[0] && yMax <= contextYScale.domain()[1] ) { // Set the focus chart limits to be the same as the context chart. yMin = contextYScale.domain()[0]; yMax = contextYScale.domain()[1]; } else { yMin -= (yMin * 0.05); yMax += (yMax * 0.05); } } focusYScale.domain([yMin, yMax]); } else { // Display 10 unlabelled ticks. focusYScale.domain([0, 10]); focusYAxis.tickFormat(''); } // Get the scaled date format to use for x axis tick labels. const timeBuckets = new TimeBuckets(); timeBuckets.setInterval('auto'); timeBuckets.setBounds(bounds); const xAxisTickFormat = timeBuckets.getScaledDateFormat(); focusChart.select('.x.axis') .call(focusXAxis.ticks(numTicksForDateFormat(vizWidth), xAxisTickFormat) .tickFormat((d) => { return moment(d).format(xAxisTickFormat); })); focusChart.select('.y.axis') .call(focusYAxis); filterAxisLabels(focusChart.select('.x.axis'), vizWidth); // Render the bounds area and values line. if (scope.modelPlotEnabled === true) { focusChart.select('.area.bounds') .attr('d', focusBoundedArea(data)) .classed('hidden', !scope.showModelBounds); } focusChart.select('.values-line') .attr('d', focusValuesLine(data)); drawLineChartDots(data, focusChart, focusValuesLine); // Render circle markers for the points. // These are used for displaying tooltips on mouseover. // Don't render dots where value=null (data gaps) const dots = d3.select('.focus-chart-markers').selectAll('.metric-value') .data(data.filter(d => d.value !== null)); // Remove dots that are no longer needed i.e. if number of chart points has decreased. dots.exit().remove(); // Create any new dots that are needed i.e. if number of chart points has increased. dots.enter().append('circle') .attr('r', FOCUS_CHART_ANOMALY_RADIUS) .on('mouseover', function (d) { showFocusChartTooltip(d, this); }) .on('mouseout', () => mlChartTooltipService.hide()); // Update all dots to new positions. dots.attr('cx', (d) => { return focusXScale(d.date); }) .attr('cy', (d) => { return focusYScale(d.value); }) .attr('class', (d) => { let markerClass = 'metric-value'; if (_.has(d, 'anomalyScore')) { markerClass += ' anomaly-marker '; markerClass += getSeverityWithLow(d.anomalyScore); } return markerClass; }); // Add rectangular markers for any scheduled events. const scheduledEventMarkers = d3.select('.focus-chart-markers').selectAll('.scheduled-event-marker') .data(data.filter(d => d.scheduledEvents !== undefined)); // Remove markers that are no longer needed i.e. if number of chart points has decreased. scheduledEventMarkers.exit().remove(); // Create any new markers that are needed i.e. if number of chart points has increased. scheduledEventMarkers.enter().append('rect') .attr('width', FOCUS_CHART_ANOMALY_RADIUS * 2) .attr('height', SCHEDULED_EVENT_MARKER_HEIGHT) .attr('class', 'scheduled-event-marker') .attr('rx', 1) .attr('ry', 1); // Update all markers to new positions. scheduledEventMarkers.attr('x', (d) => focusXScale(d.date) - FOCUS_CHART_ANOMALY_RADIUS) .attr('y', (d) => focusYScale(d.value) - 3); // Plot any forecast data in scope. if (scope.focusForecastData !== undefined) { focusChart.select('.area.forecast') .attr('d', focusBoundedArea(scope.focusForecastData)) .classed('hidden', !scope.showForecast); focusChart.select('.values-line.forecast') .attr('d', focusValuesLine(scope.focusForecastData)) .classed('hidden', !scope.showForecast); const forecastDots = d3.select('.focus-chart-markers.forecast').selectAll('.metric-value') .data(scope.focusForecastData); // Remove dots that are no longer needed i.e. if number of forecast points has decreased. forecastDots.exit().remove(); // Create any new dots that are needed i.e. if number of forecast points has increased. forecastDots.enter().append('circle') .attr('r', FOCUS_CHART_ANOMALY_RADIUS) .on('mouseover', function (d) { showFocusChartTooltip(d, this); }) .on('mouseout', () => mlChartTooltipService.hide()); // Update all dots to new positions. forecastDots.attr('cx', (d) => { return focusXScale(d.date); }) .attr('cy', (d) => { return focusYScale(d.value); }) .attr('class', 'metric-value') .classed('hidden', !scope.showForecast); } } function createZoomInfoElements(zoomGroup, fcsWidth) { // Create zoom duration links applicable for the current time span. // Don't add links for any durations which would give a brush extent less than 10px. const bounds = timefilter.getActiveBounds(); const boundsSecs = bounds.max.unix() - bounds.min.unix(); const minSecs = (10 / vizWidth) * boundsSecs; let xPos = 10; const zoomLabel = zoomGroup.append('text') .attr('x', xPos) .attr('y', 17) .attr('class', 'zoom-info-text') .text('Zoom:'); const zoomOptions = [{ durationMs: scope.autoZoomDuration, label: 'auto' }]; _.each(ZOOM_INTERVAL_OPTIONS, (option) => { if (option.duration.asSeconds() > minSecs && option.duration.asSeconds() < boundsSecs) { zoomOptions.push({ durationMs: option.duration.asMilliseconds(), label: option.label }); } }); xPos += (zoomLabel.node().getBBox().width + 4); _.each(zoomOptions, (option) => { const text = zoomGroup.append('a') .attr('data-ms', option.durationMs) .attr('href', '') .append('text') .attr('x', xPos) .attr('y', 17) .attr('class', 'zoom-info-text') .text(option.label); xPos += (text.node().getBBox().width + 4); }); zoomGroup.append('text') .attr('x', (xPos + 6)) .attr('y', 17) .attr('class', 'zoom-info-text zoom-aggregation-interval') .text('(aggregation interval: , bucket span: )'); if (scope.modelPlotEnabled === false) { const modelPlotLabel = zoomGroup.append('text') .attr('x', 300) .attr('y', 17) .attr('class', 'zoom-info-text') .text('Model bounds are not available'); modelPlotLabel.attr('x', (fcsWidth - (modelPlotLabel.node().getBBox().width + 10))); } $('.focus-zoom a').click(function (e) { e.preventDefault(); setZoomInterval($(this).attr('data-ms')); }); } function drawContextElements(cxtGroup, cxtWidth, cxtChartHeight, swlHeight) { const data = scope.contextChartData; contextXScale = d3.time.scale().range([0, cxtWidth]) .domain(calculateContextXAxisDomain()); const combinedData = scope.contextForecastData === undefined ? data : data.concat(scope.contextForecastData); const valuesRange = { min: Number.MAX_VALUE, max: Number.MIN_VALUE }; _.each(combinedData, (item) => { valuesRange.min = Math.min(item.value, valuesRange.min); valuesRange.max = Math.max(item.value, valuesRange.max); }); let dataMin = valuesRange.min; let dataMax = valuesRange.max; const chartLimits = { min: dataMin, max: dataMax }; if (scope.modelPlotEnabled === true || (scope.contextForecastData !== undefined && scope.contextForecastData.length > 0)) { const boundsRange = { min: Number.MAX_VALUE, max: Number.MIN_VALUE }; _.each(combinedData, (item) => { boundsRange.min = Math.min(item.lower, boundsRange.min); boundsRange.max = Math.max(item.upper, boundsRange.max); }); dataMin = Math.min(dataMin, boundsRange.min); dataMax = Math.max(dataMax, boundsRange.max); // Set the y axis domain so that the range of actual values takes up at least 50% of the full range. if ((valuesRange.max - valuesRange.min) < 0.5 * (dataMax - dataMin)) { if (valuesRange.min > dataMin) { chartLimits.min = valuesRange.min - (0.5 * (valuesRange.max - valuesRange.min)); } if (valuesRange.max < dataMax) { chartLimits.max = valuesRange.max + (0.5 * (valuesRange.max - valuesRange.min)); } } } contextYScale = d3.scale.linear().range([cxtChartHeight, contextChartLineTopMargin]) .domain([chartLimits.min, chartLimits.max]); const borders = cxtGroup.append('g') .attr('class', 'axis'); // Add borders left and right. borders.append('line') .attr('x1', 0) .attr('y1', 0) .attr('x2', 0) .attr('y2', cxtChartHeight + swlHeight); borders.append('line') .attr('x1', cxtWidth) .attr('y1', 0) .attr('x2', cxtWidth) .attr('y2', cxtChartHeight + swlHeight); // Add x axis. const bounds = timefilter.getActiveBounds(); const timeBuckets = new TimeBuckets(); timeBuckets.setInterval('auto'); timeBuckets.setBounds(bounds); const xAxisTickFormat = timeBuckets.getScaledDateFormat(); const xAxis = d3.svg.axis().scale(contextXScale) .orient('top') .innerTickSize(-cxtChartHeight) .outerTickSize(0) .tickPadding(0) .ticks(numTicksForDateFormat(cxtWidth, xAxisTickFormat)) .tickFormat((d) => { return moment(d).format(xAxisTickFormat); }); cxtGroup.datum(data); const contextBoundsArea = d3.svg.area() .x((d) => { return contextXScale(d.date); }) .y0((d) => { return contextYScale(Math.min(chartLimits.max, Math.max(d.lower, chartLimits.min))); }) .y1((d) => { return contextYScale(Math.max(chartLimits.min, Math.min(d.upper, chartLimits.max))); }) .defined(d => (d.lower !== null && d.upper !== null)); if (scope.modelPlotEnabled === true) { cxtGroup.append('path') .datum(data) .attr('class', 'area context') .attr('d', contextBoundsArea); } const contextValuesLine = d3.svg.line() .x((d) => { return contextXScale(d.date); }) .y((d) => { return contextYScale(d.value); }) .defined(d => d.value !== null); cxtGroup.append('path') .datum(data) .attr('class', 'values-line') .attr('d', contextValuesLine); drawLineChartDots(data, cxtGroup, contextValuesLine, 1); // Create the path elements for the forecast value line and bounds area. if (scope.contextForecastData !== undefined) { cxtGroup.append('path') .datum(scope.contextForecastData) .attr('class', 'area forecast') .attr('d', contextBoundsArea); cxtGroup.append('path') .datum(scope.contextForecastData) .attr('class', 'values-line forecast') .attr('d', contextValuesLine); } // Create and draw the anomaly swimlane. const swimlane = cxtGroup.append('g') .attr('class', 'swimlane') .attr('transform', 'translate(0,' + cxtChartHeight + ')'); drawSwimlane(swimlane, cxtWidth, swlHeight); // Draw a mask over the sections of the context chart and swimlane // which fall outside of the zoom brush selection area. mask = new ContextChartMask(cxtGroup, scope.contextChartData, scope.modelPlotEnabled, swlHeight) .x(contextXScale) .y(contextYScale); // Draw the x axis on top of the mask so that the labels are visible. cxtGroup.append('g') .attr('class', 'x axis context-chart-axis') .call(xAxis); // Move the x axis labels up so that they are inside the contact chart area. cxtGroup.selectAll('.x.context-chart-axis text') .attr('dy', (cxtChartHeight - 5)); filterAxisLabels(cxtGroup.selectAll('.x.context-chart-axis'), cxtWidth); drawContextBrush(cxtGroup); } function drawContextBrush(contextGroup) { // Create the brush for zooming in to the focus area of interest. brush.x(contextXScale) .on('brush', brushing) .on('brushend', brushed); contextGroup.append('g') .attr('class', 'x brush') .call(brush) .selectAll('rect') .attr('y', -1) .attr('height', contextChartHeight + swimlaneHeight + 1); // move the left and right resize areas over to // be under the handles contextGroup.selectAll('.w rect') .attr('x', -10) .attr('width', 10); contextGroup.selectAll('.e rect') .attr('x', 0) .attr('width', 10); const topBorder = contextGroup.append('rect') .attr('class', 'top-border') .attr('y', -2) .attr('height', contextChartLineTopMargin); // Draw the brush handles using SVG foreignObject elements. // Note these are not supported on IE11 and below, so will not appear in IE. const leftHandle = contextGroup.append('foreignObject') .attr('width', 10) .attr('height', 90) .attr('class', 'brush-handle') .html('<div class="brush-handle-inner brush-handle-inner-left"><i class="fa fa-caret-left"></i></div>'); const rightHandle = contextGroup.append('foreignObject') .attr('width', 10) .attr('height', 90) .attr('class', 'brush-handle') .html('<div class="brush-handle-inner brush-handle-inner-right"><i class="fa fa-caret-right"></i></div>'); setBrushVisibility(!brush.empty()); function showBrush(show) { if (show === true) { const brushExtent = brush.extent(); mask.reveal(brushExtent); leftHandle.attr('x', contextXScale(brushExtent[0]) - 10); rightHandle.attr('x', contextXScale(brushExtent[1]) + 0); topBorder.attr('x', contextXScale(brushExtent[0]) + 1); topBorder.attr('width', contextXScale(brushExtent[1]) - contextXScale(brushExtent[0]) - 2); } setBrushVisibility(show); } function brushing() { const isEmpty = brush.empty(); showBrush(!isEmpty); } function brushed() { const isEmpty = brush.empty(); showBrush(!isEmpty); const selectedBounds = isEmpty ? contextXScale.domain() : brush.extent(); const selectionMin = selectedBounds[0].getTime(); const selectionMax = selectedBounds[1].getTime(); // Set the color of the swimlane cells according to whether they are inside the selection. contextGroup.selectAll('.swimlane-cell') .style('fill', (d) => { const cellMs = d.date.getTime(); if (cellMs < selectionMin || cellMs > selectionMax) { return anomalyGrayScale(d.score); } else { return anomalyColorScale(d.score); } }); scope.selectedBounds = { min: moment(selectionMin), max: moment(selectionMax) }; scope.$root.$broadcast('contextChartSelected', { from: selectedBounds[0], to: selectedBounds[1] }); } } function setBrushVisibility(show) { if (mask !== undefined) { const visibility = show ? 'visible' : 'hidden'; mask.style('visibility', visibility); d3.selectAll('.brush').style('visibility', visibility); const brushHandles = d3.selectAll('.brush-handle-inner'); brushHandles.style('visibility', visibility); const topBorder = d3.selectAll('.top-border'); topBorder.style('visibility', visibility); const border = d3.selectAll('.chart-border-highlight'); border.style('visibility', visibility); } } function drawSwimlane(swlGroup, swlWidth, swlHeight) { const data = scope.swimlaneData; // Calculate the x axis domain. // Elasticsearch aggregation returns points at start of bucket, so set the // x-axis min to the start of the aggregation interval. // Need to use the min(earliest) and max(earliest) of the context chart // aggregation to align the axes of the chart and swimlane elements. const xAxisDomain = calculateContextXAxisDomain(); const x = d3.time.scale().range([0, swlWidth]) .domain(xAxisDomain); const y = d3.scale.linear().range([swlHeight, 0]) .domain([0, swlHeight]); const xAxis = d3.svg.axis() .scale(x) .orient('bottom') .innerTickSize(-swlHeight) .outerTickSize(0); const yAxis = d3.svg.axis() .scale(y) .orient('left') .tickValues(y.domain()) .innerTickSize(-swlWidth) .outerTickSize(0); const axes = swlGroup.append('g'); axes.append('g') .attr('class', 'x axis') .attr('transform', 'translate(0,' + (swlHeight) + ')') .call(xAxis); axes.append('g') .attr('class', 'y axis') .call(yAxis); const earliest = xAxisDomain[0].getTime(); const latest = xAxisDomain[1].getTime(); const swimlaneAggMs = scope.contextAggregationInterval.asMilliseconds(); let cellWidth = swlWidth / ((latest - earliest) / swimlaneAggMs); if (cellWidth < 1) { cellWidth = 1; } const cells = swlGroup.append('g') .attr('class', 'swimlane-cells') .selectAll('cells') .data(data); cells.enter().append('rect') .attr('x', (d) => { return x(d.date); }) .attr('y', 0) .attr('rx', 0) .attr('ry', 0) .attr('class', (d) => { return d.score > 0 ? 'swimlane-cell' : 'swimlane-cell-hidden';}) .attr('width', cellWidth) .attr('height', swlHeight) .style('fill', (d) => { return anomalyColorScale(d.score);}); } function calculateContextXAxisDomain() { // Calculates the x axis domain for the context elements. // Elasticsearch aggregation returns points at start of bucket, // so set the x-axis min to the start of the first aggregation interval, // and the x-axis max to the end of the last aggregation interval. // Context chart and swimlane use the same aggregation interval. const bounds = timefilter.getActiveBounds(); let earliest = bounds.min.valueOf(); if (scope.swimlaneData !== undefined && scope.swimlaneData.length > 0) { // Adjust the earliest back to the time of the first swimlane point // if this is before the time filter minimum. earliest = Math.min(_.first(scope.swimlaneData).date.getTime(), bounds.min.valueOf()); } const contextAggMs = scope.contextAggregationInterval.asMilliseconds(); const earliestMs = Math.floor(earliest / contextAggMs) * contextAggMs; const latestMs = Math.ceil((bounds.max.valueOf()) / contextAggMs) * contextAggMs; return [new Date(earliestMs), new Date(latestMs)]; } // Sets the extent of the brush on the context chart to the // supplied from and to Date objects. function setContextBrushExtent(from, to, fireEvent) { brush.extent([from, to]); brush(d3.select('.brush')); if (fireEvent) { brush.event(d3.select('.brush')); } } function setZoomInterval(ms) { const bounds = timefilter.getActiveBounds(); const minBoundsMs = bounds.min.valueOf(); const maxBoundsMs = bounds.max.valueOf(); // Attempt to retain the same zoom end time. // If not, go back to the bounds start and add on the required millis. const millis = +ms; let to = scope.zoomTo.getTime(); let from = to - millis; if (from < minBoundsMs) { from = minBoundsMs; to = Math.min(minBoundsMs + millis, maxBoundsMs); } setContextBrushExtent(new Date(from), new Date(to), true); } function showFocusChartTooltip(marker, circle) { // Show the time and metric values in the tooltip. // Uses date, value, upper, lower and anomalyScore (optional) marker properties. const formattedDate = moment(marker.date).format('MMMM Do YYYY, HH:mm'); let contents = formattedDate + '<br/><hr/>'; if (_.has(marker, 'anomalyScore')) { const score = parseInt(marker.anomalyScore); const displayScore = (score > 0 ? score : '< 1'); contents += `anomaly score: ${displayScore}<br/>`; if (scope.modelPlotEnabled === false) { if (_.has(marker, 'actual')) { // Display the record actual in preference to the chart value, which may be // different depending on the aggregation interval of the chart. contents += `actual: ${formatValue(marker.actual, marker.function, fieldFormat)}`; contents += `<br/>typical: ${formatValue(marker.typical, marker.function, fieldFormat)}`; } else { contents += `value: ${formatValue(marker.value, marker.function, fieldFormat)}`; if (_.has(marker, 'byFieldName') && _.has(marker, 'numberOfCauses')) { const numberOfCauses = marker.numberOfCauses; const byFieldName = mlEscape(marker.byFieldName); if (numberOfCauses < 10) { // If numberOfCauses === 1, won't go into this block as actual/typical copied to top level fields. contents += `<br/> ${numberOfCauses} unusual ${byFieldName} values`; } else { // Maximum of 10 causes are stored in the record, so '10' may mean more than 10. contents += `<br/> ${numberOfCauses}+ unusual ${byFieldName} values`; } } } } else { contents += `value: ${formatValue(marker.value, marker.function, fieldFormat)}`; contents += `<br/>upper bounds: ${formatValue(marker.upper, marker.function, fieldFormat)}`; contents += `<br/>lower bounds: ${formatValue(marker.lower, marker.function, fieldFormat)}`; } } else { // TODO - need better formatting for small decimals. if (_.get(marker, 'isForecast', false) === true) { contents += `prediction: ${formatValue(marker.value, marker.function, fieldFormat)}`; } else { contents += `value: ${formatValue(marker.value, marker.function, fieldFormat)}`; } if (scope.modelPlotEnabled === true) { contents += `<br/>upper bounds: ${formatValue(marker.upper, marker.function, fieldFormat)}`; contents += `<br/>lower bounds: ${formatValue(marker.lower, marker.function, fieldFormat)}`; } } if (_.has(marker, 'scheduledEvents')) { contents += `<br/><hr/>Scheduled events:<br/>${marker.scheduledEvents.map(mlEscape).join('<br/>')}`; } mlChartTooltipService.show(contents, circle, { x: FOCUS_CHART_ANOMALY_RADIUS * 2, y: 0 }); } function highlightFocusChartAnomaly(record) { // Highlights the anomaly marker in the focus chart corresponding to the specified record. // Find the anomaly marker which is closest in time to the source record. // Depending on the way the chart is aggregated, there may not be // a point at exactly the same time as the record being highlighted. const anomalyTime = record.source.timestamp; const markerToSelect = findNearestChartPointToTime(scope.focusChartData, anomalyTime); // Render an additional highlighted anomaly marker on the focus chart. // TODO - plot anomaly markers for cases where there is an anomaly due // to the absence of data and model plot is enabled. if (markerToSelect !== undefined) { const selectedMarker = d3.select('.focus-chart-markers').selectAll('.focus-chart-highlighted-marker') .data([markerToSelect]); selectedMarker.enter().append('circle') .attr('r', FOCUS_CHART_ANOMALY_RADIUS); selectedMarker.attr('cx', (d) => { return focusXScale(d.date); }) .attr('cy', (d) => { return focusYScale(d.value); }) .attr('class', (d) => { let markerClass = 'metric-value anomaly-marker highlighted '; markerClass += getSeverityWithLow(d.anomalyScore); return markerClass; }); // Display the chart tooltip for this marker. // Note the values of the record and marker may differ depending on the levels of aggregation. const circle = $('.focus-chart-markers .anomaly-marker.highlighted'); if (circle.length) { showFocusChartTooltip(markerToSelect, circle[0]); } } } function unhighlightFocusChartAnomaly() { d3.select('.focus-chart-markers').selectAll('.anomaly-marker.highlighted').remove(); mlChartTooltipService.hide(); } }
RDFSChema.visualize = function(data){ var nodes = data.nodes; var links = data.links; var width = window.innerWidth; var height = window.innerHeight; var edges = []; links.forEach(function(e) { var sourceNode = nodes.filter(function(n) { return n.id === e.source; })[0], targetNode = nodes.filter(function(n) { return n.id === e.target; })[0]; edges.push({source: sourceNode, target: targetNode, label:e.label, uri: e.uri}); }); // set up data var graph = {}; graph.nodes = nodes; graph.links = edges; //Set up the colour scale var color = d3.scale.category20(); //Set up the force layout var force = d3.layout.force() .charge(-500) .theta(0.1) .gravity(0.7) .linkDistance(200) .size([width, height]); //Append a SVG to the body of the html page. Assign this SVG as an object to svg var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .attr('viewBox','0 0 '+Math.min(width,height)+' '+Math.min(width,height)) //Creates the graph data structure out of the json data force.nodes(graph.nodes) .links(graph.links) .start(); //Create all the line svgs but without locations yet var edges = svg.selectAll("line") .data(graph.links) .enter() .append("line") .attr('id', function(d, i){return 'edge' + i;}) .style("marker-end", "url(#arrowhead)") //Added .style('stroke-width', function(d){ return d.count; }) .style("pointer-events", "none"); //Set up tooltip var tip = d3.tip() .attr('class', 'd3-tip') .offset([-10, 0]) .html(function(d) { return "<strong>Uri:</strong> <span style='color:red'>" + d.uri + "</span>"; }) svg.call(tip); //Do the same with the circles for the nodes - no var node = svg.selectAll(".rect") .data(graph.nodes) .enter().append("g") .attr("class", "node") .call(force.drag) .on('click', tip.show) .on('mouseout', function() { d3.select(".d3-tip") .transition() .delay(100) .duration(1000) .style("opacity",0) .style('pointer-events', 'none') }) node.append('rect') .attr('height', font + rect_padding) .attr('width', function(d){ if(d.label){ return (d.label.length/2 * font) + 'px'; }else{ return font + 'px'; } }) .style('stroke-width', 1) .style('stroke', 'black') .style("fill", function (d) { if(d.label){ return 'green'; }else{ return 'red'; } }); node.append("text") //.attr('text-anchor', 'middle') .text(function(d){ //console.log(d) return d.label; }) .style('font-size',font + 'px') .style('color', 'black') .attr("dy", font + 'px'); var edgepaths = svg.selectAll(".edgepath") .data(graph.links) .enter() .append('path') .attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y}, 'class':'edgepath', 'fill-opacity':1, 'stroke-opacity':1, 'fill':'blue', 'stroke':'red', 'id':function(d,i) {return 'edgepath'+i}}) .style("pointer-events", "none"); var edgelabels = svg.selectAll(".edgelabel") .data(graph.links) .enter() .append('text') .style("pointer-events", "none") .attr({'class':'edgelabel', 'id':function(d,i){return 'edgelabel'+i}, 'dx':80, 'dy':0, 'font-size':10, 'fill':'#aaa'}); edgelabels.append('textPath') .attr('xlink:href',function(d,i) {return '#edgepath'+i}) .style("pointer-events", "none") .text(function(d,i){ return d.label }); svg.append('defs').append('marker') .attr({'id':'arrowhead', 'viewBox':'-0 -5 10 10', 'refX':25, 'refY':0, //'markerUnits':'strokeWidth', 'orient':'auto', 'markerWidth':10, 'markerHeight':10, 'xoverflow':'visible'}) .append('svg:path') .attr('d', 'M 0,-5 L 10 ,0 L 0,5') .attr('fill', '#ccc') .attr('stroke','#ccc'); /* //fisheye var fisheye = FE.fisheye.circular() .radius(120); svg.on("mousemove", function() { force.stop(); fisheye.focus(d3.mouse(this)); d3.selectAll("rect").each(function(d) { d.fisheye = fisheye(d); }) .attr("x", function(d) { return d.fisheye.x; }) .attr("y", function(d) { return d.fisheye.y; }) edges.attr("x1", function(d) { return d.source.fisheye.x; }) .attr("y1", function(d) { return d.source.fisheye.y; }) .attr("x2", function(d) { return d.target.fisheye.x; }) .attr("y2", function(d) { return d.target.fisheye.y; }); }); */ //Now we are giving the SVGs co-ordinates - the force layout is generating the co-ordinates which this code is using to update the attributes of the SVG elements force.on("tick", function () { edges.attr("x1", function (d) { return d.source.x; }) .attr("y1", function (d) { return d.source.y; }) .attr("x2", function (d) { return d.target.x; }) .attr("y2", function (d) { return d.target.y; }); d3.selectAll("rect").attr("x", function (d) { return d.x; }) .attr("y", function (d) { return d.y; }); d3.selectAll("text").attr("x", function (d) { return d.x; }) .attr("y", function (d) { return d.y; }); edgepaths.attr('d', function(d){ var path='M'+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y; return path; }); edgelabels.attr('transform',function(d,i){ if (d.target.x<d.source.x){ var bbox = this.getBBox(); var rx = bbox.x+bbox.width/2; var ry = bbox.y+bbox.height/2; return 'rotate(180 '+rx+' '+ry+')'; } else { return 'rotate(0)'; } }); node.each(collide(1)); //Added }); // collision detection var padding = 1, // separation between circles radius=8; function collide(alpha) { var quadtree = d3.geom.quadtree(graph.nodes); return function(d) { var rb = 2*radius + padding, nx1 = d.x - rb, nx2 = d.x + rb, ny1 = d.y - rb, ny2 = d.y + rb; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y); if (l < rb) { l = (l - rb) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } //Arrows svg.append("defs").selectAll("marker") .data(["suit", "licensing", "resolved"]) .enter().append("marker") .attr("id", function(d) { return d; }) .attr("viewBox", "0 -5 10 10") .attr("refX", 25) .attr("refY", 0) .attr("markerWidth", 6) .attr("markerHeight", 6) .attr("orient", "auto") .append("path") .attr("d", "M0,-5L10,0L0,5 L10,0 L0, -5") .style("stroke", "#4679BD") .style("opacity", "0.6"); //Toggle stores whether the highlighting is on var toggle = 0; //Create an array logging what is connected to what var linkedByIndex = {}; for (var i = 0; i < graph.nodes.length; i++) { linkedByIndex[i + "," + i] = 1; }; /*// search var optArray = []; for (var i = 0; i < graph.nodes.length - 1; i++) { optArray.push(graph.nodes[i].label); } optArray = optArray.sort(); $(function () { $("#search").autocomplete({ source: optArray }); }); function searchNode() { //find the node var selectedVal = document.getElementById('search').value; var node = svg.selectAll(".node"); if (selectedVal == "none") { node.style("stroke", "white").style("stroke-width", "1"); } else { var selected = node.filter(function (d, i) { return d.label != selectedVal; }); selected.style("opacity", "0"); var link = svg.selectAll(".link") link.style("opacity", "0"); d3.selectAll(".node, .link").transition() .duration(5000) .style("opacity", 1); } }*/ }
define(function(require){ // // L O A D T H E A S S E T S A N D L I B R A R I E S // -------------------------------------------------------------------------------- // // [ libraries ] var Backbone = require('backbone'), d3 = require('d3'), // // D E F I N E T H E S E T U P V A R I A B L E S // -------------------------------------------------------------------------------- // Url = "/api/fideicomisos", Definitions = TRUSTS_DATA.definitions, Categories = ["branch", "type", "scope", "theme", "unit", "settlor", "fiduciary"], Selected = [Categories[0], Categories[1], Categories[2]], Colors = d3.scale.category20(); // // D E F I N E T H E D 3 V A R I A B L E S // -------------------------------------------------------------------------------- // SVG = { width : 700, height : 700, margin : { top : 10, right : 10, bottom : 10, left : 10 }, }; // // C A C H E T H E C O M M O N E L E M E N T S // -------------------------------------------------------------------------------- // // // I N I T I A L I Z E T H E B A C K B O N E " V I E W " // -------------------------------------------------------------------------------- // var content = Backbone.View.extend({ // // [ DEFINE THE EVENTS ] // events :{ "change #pack-chart-controls input" : "set_categories" }, // // [ DEFINE THE ELEMENT ] // el : "#circle-pack", // // [ DEFINE THE TEMPLATES ] // // // [ THE INITIALIZE FUNCTION ] // // initialize : function(){ this.collection = new Backbone.Collection(); this.definitions = new Backbone.Collection(Definitions); this.get_data(); }, // // C O N T R O L F U N C T I O N S // ------------------------------------------------------------------------------ // set_categories : function(e){ if(e.currentTarget.checked){ Selected.push(Categories[+e.currentTarget.value]); } else{ Selected.splice(Selected.indexOf(Categories[+e.currentTarget.value]), 1); } this.update_pack(); }, // // R E N D E R F U N C T I O N S // ------------------------------------------------------------------------------ // // [ submit / ESC / call from controller ] // Genera el HTML del elemento seleccionado // render : function(e){ return this; }, // // // render_pack : function(){ var root = {collection : this.collection, name : "trusts", _class : "root"}, pack = d3.layout.pack() .sort(null) .size([SVG.width, SVG.height]) .value(function(d){ return d.collection ? d.collection.length : 1; }); root.children = this.generate_tree(root, 0); var svg = d3.select("#circle-pack"), chart = svg.append("svg:svg") .attr("width", SVG.width) .attr("height", SVG.height), enter = chart.selectAll("g").data(pack(root)).enter() .append("svg:g"); enter.append("svg:circle") .attr("r", function(d){ return d.r}) .attr("cx", function(d){ return d.x}) .attr("cy", function(d){ return d.y}) .attr("class", function(d){ return d._class}) //.attr("stroke", "black") //.attr("fill", "black") //.attr("stroke", "white") //.attr("stroke-width", 1); /* .attr("y", function(d){ return d.y}) .attr("width", function(d){ return d.dx}) .attr("height", function(d){ return d.dy}) .attr("fill", function(d,i){ return Colors(i)}); */ /* enter.append("svg:text") .text(function(d){ return d.value}) .attr("x", function (d) {return d.x+5;}) .attr("y", function (d) {return d.y+20;}) .attr("dy", ".35em") .attr("text-anchor", "middle"); */ }, update_pack : function(){ var root = {collection : this.collection, name : "trusts", _class : "root"}, pack = d3.layout.pack() .sort(null) .size([SVG.width, SVG.height]) .value(function(d){ return d.collection ? d.collection.length : 1; }); root.children = this.generate_tree(root, 0); var svg = d3.select("#circle-pack svg"), circles = svg.selectAll("g"); circles.remove(); circles = svg.selectAll("g").data(pack(root)); circles.enter().append("svg:g") .append("svg:circle") .attr("r", function(d){ return d.r}) .attr("cx", function(d){ return d.x}) .attr("cy", function(d){ return d.y}) .attr("class", function(d){ return d._class}); }, // // nodes for any level // generate_tree : function(parent, deep){ var category = Selected[deep], list = _.uniq(parent.collection.pluck(category)), search = {}, childrens = list.map(function(cat){ search[category] = cat; var child = { name : cat, collection : new Backbone.Collection(parent.collection.where(search)), _class : "category" }; child.value = child.collection.length; child.children = child.collection.map(function(ch){ return { name : ch.get("designation"), trust : ch, _class : "trust" }; }); return child; }, this); if(deep + 1 < Selected.length){ childrens.forEach(function(ch){ ch._class = "category"; ch.children = this.generate_tree(ch, deep+1); }, this); } return childrens; }, // // H E L P E R S // ------------------------------------------------------------------------------ // get_data : function(){ var that = this; $.get(Url, {}, function(d){ that.collection.reset(d); that.render_pack(); document.querySelector("#treemap-category").disabled = false; }, "json"); } }); // // R E T U R N T H E B A C K B O N E " C O N T R O L L E R " // -------------------------------------------------------------------------------- // return content; });
React.createElement(Controller, { athletes: this.state.athletes, line_type: this.state.line_type, add_athlete: this.add_athlete, set_athlete_state: this.set_athlete_state, set_line_type: this.set_line_type}), display ) ); }, set_line_type: function(type) { this.setState({line_type: type}); }, get_color: d3.scale.category10(), add_athlete: function(athlete) { var athletes = this.state.athletes.slice(); athlete.active = true; athlete.color = this.get_color(athletes.length); athletes.push(athlete); add_athlete_id_to_url(athlete.id); this.setState({athletes: athletes}); }, add_default_attributes_to_athletes: function(athletes) { return this.props.athletes.map(function(athlete, i) { athlete.active = true; athlete.color = this.get_color(i); return athlete;
chart: function(selection){ var _scale = d3.time.scale(); var lscale = d3.scale.linear(); var _X = function(d){ return xScale(xValue(d)); }; var _Y = function(d){ return yScale(yValue(d)); }; var $__0= Support.getProps(this, 'chart', { margin: Support.types.Object({ top: Support.types.Number(20), right: Support.types.Number(20), bottom: Support.types.Number(20), left: Support.types.Number(20) }), width: Support.types.Number(-1), height: Support.types.Number(120), style: Support.types.Object(false), xValue: Support.types.Function(function(d) { return d[0]; }), yValue: Support.types.Function(function(d) { return d[1]; }), xScale: Support.types.Function(_scale), yScale: Support.types.Function(lscale), xAxis: Support.types.Function(d3.svg.axis().scale(_scale).orient("bottom").tickSize(6, 0)) }),margin=$__0.margin,width=$__0.width,height=$__0.height,xValue=$__0.xValue,yValue=$__0.yValue,xScale=$__0.xScale,yScale=$__0.yScale,xAxis=$__0.xAxis,area=$__0.area,line=$__0.line,style=$__0.style; var area = d3.svg.area().x(_X).y1(_Y); var line = d3.svg.line().x(_X).y(_Y); selection.each(function(data) { var w = width===-1?this.offsetWidth:width; // Convert data to standard representation greedily; // this is needed for nondeterministic accessors. data = data.map(function(d, i) { return [xValue.call(data, d, i), yValue.call(data, d, i)]; }).sort(function(a, b){ return a[0]-b[0]; }); // Update the x-scale. xScale .domain(d3.extent(data, function(d) { return d[0]; })) .range([0, w - margin.left - margin.right]); // Update the y-scale. yScale .domain([0, d3.max(data, function(d) { return d[1]; })]) .range([height - margin.top - margin.bottom, 0]); // Select the svg element, if it exists. var svg = d3.select(this).selectAll("svg").data([data]); // Otherwise, create the skeletal chart. var gEnter = svg.enter().append("svg").append("g"); gEnter.append("path").attr("class", "area"); gEnter.append("path").attr("class", "line"); gEnter.append("g").attr("class", "x axis"); // Update the outer dimensions. svg .attr("width", w) .attr("height", height); // Update the inner dimensions. var g = svg.select("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // Update the area path. g.select(".area") .attr("d", area.y0(yScale.range()[0])); // Update the line path. g.select(".line") .attr("d", line); // Update the x-axis. g.select(".x.axis") .attr("transform", "translate(0," + yScale.range()[0] + ")") .call(xAxis); if(style){ Object.keys(style).forEach(function(key){ svg.selectAll(key).attr('style', style[key]); }); } }); },
function preparePieData(seriesList, options) { const { cellWidth, cellHeight, xPadding, yPadding, cellsInRow, hasX, } = calculateDimensions(seriesList, options); const formatNumber = createFormatter({ displayAs: 'number', numberFormat: options.numberFormat, }); const formatPercent = createFormatter({ displayAs: 'number', numberFormat: options.percentFormat, }); const formatText = options.textFormat === '' ? defaultFormatSeriesTextForPie : item => formatSimpleTemplate(options.textFormat, item); const hoverinfo = getPieHoverInfoPattern(options); // we will use this to assign colors for values that have not explicitly set color const getDefaultColor = d3.scale.ordinal().domain([]).range(ColorPaletteArray); const valuesColors = {}; each(options.valuesOptions, (item, key) => { if (isString(item.color) && (item.color !== '')) { valuesColors[key] = item.color; } }); return map(seriesList, (serie, index) => { const xPosition = (index % cellsInRow) * cellWidth; const yPosition = Math.floor(index / cellsInRow) * cellHeight; const sourceData = new Map(); const seriesTotal = reduce(serie.data, (result, row) => { const y = cleanNumber(row.y); return result + Math.abs(y); }, 0); each(serie.data, (row) => { const x = normalizeValue(row.x); const y = cleanNumber(row.y); sourceData.set(x, { x, y, yPercent: y / seriesTotal * 100, raw: extend({}, row.$raw, { // use custom display format - see also `updateSeriesText` '@@x': normalizeValue(row.x, options.xAxis.type, options.dateTimeFormat), }), }); }); return { values: map(serie.data, i => i.y), labels: map(serie.data, row => (hasX ? normalizeValue(row.x) : `Slice ${index}`)), type: 'pie', hole: 0.4, marker: { colors: map(serie.data, row => valuesColors[row.x] || getDefaultColor(row.x)), }, hoverinfo, text: [], textinfo: options.showDataLabels ? 'percent' : 'none', textposition: 'inside', textfont: { color: '#ffffff' }, name: serie.name, domain: { x: [xPosition, xPosition + cellWidth - xPadding], y: [yPosition, yPosition + cellHeight - yPadding], }, sourceData, formatNumber, formatPercent, formatText, }; }); }
export default function(element) { let selection = d3.select(element); let margin = {top: 20, bottom: 20, left: 80, right: 20}, width = 960 - margin.left - margin.right, height = 400 - margin.top - margin.bottom; let svgRoot = selection.append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom); let domainLimit = [0, 4*60*1e3]; let x = d3.scale.linear() .domain(domainLimit) .range([0, width]); let svg = svgRoot.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); let zoom = d3.behavior.zoom() .on("zoom", zoomed) .on("zoomstart", function() { d3.select(this).style("cursor", "move"); }) .on("zoomend", function() { d3.select(this).style("cursor", ""); }) .x(x); let timeBar = svgRoot.append("rect") .attr("class", "time-bar") .attr("x", 0) .attr("y", 15) .attr("height", height) .attr("width", 1) let interactionArea = svgRoot.append("rect") .attr("class", "interaction-area") .attr("x", 0) .attr("y", 0) .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .call(zoom); let formatTime = d3.time.format("%M:%S"), formatMinutes = (d) => formatTime(new Date(2012,0,1,0,0,0,d)); let xAxis = d3.svg.axis() .scale(x) .orient("top") .tickFormat(formatMinutes) .tickSize(-height); let xAxisElem = svg.append("g") .attr("class", "x axis") .attr("transform", `translate(0,${-margin.top + 10})`) .call(xAxis); let container = svg.append("g") .attr("class", "container"); let front = frontIR({ container, x, width, height: 80, offsetTop: 0 }); let side = sideIR({ container, x, width, height: 55, offsetTop: 110 }); let voltagesGraph = voltages({ container, x, width, height: 60, offsetTop: 195 }); function zoomed() { redraw(); } function updateDomain(extent) { x.domain(extent); zoom.x(x); redraw(); } function redraw() { front.redraw(); side.redraw(); voltagesGraph.redraw(); xAxisElem.call(xAxis); } return { frontIR: front, sideIR: side, voltages: voltagesGraph, onTimeChange: function(f) { svgRoot.on("mousemove", function() { let p = d3.mouse(svg.node()); timeBar.attr("transform", `translate(${p[0] + margin.left}, 0)`); f(x.invert(p[0])); }); }, domain: updateDomain }; }
function drawContextElements(cxtGroup, cxtWidth, cxtChartHeight, swlHeight) { const data = scope.contextChartData; contextXScale = d3.time.scale().range([0, cxtWidth]) .domain(calculateContextXAxisDomain()); const combinedData = scope.contextForecastData === undefined ? data : data.concat(scope.contextForecastData); const valuesRange = { min: Number.MAX_VALUE, max: Number.MIN_VALUE }; _.each(combinedData, (item) => { valuesRange.min = Math.min(item.value, valuesRange.min); valuesRange.max = Math.max(item.value, valuesRange.max); }); let dataMin = valuesRange.min; let dataMax = valuesRange.max; const chartLimits = { min: dataMin, max: dataMax }; if (scope.modelPlotEnabled === true || (scope.contextForecastData !== undefined && scope.contextForecastData.length > 0)) { const boundsRange = { min: Number.MAX_VALUE, max: Number.MIN_VALUE }; _.each(combinedData, (item) => { boundsRange.min = Math.min(item.lower, boundsRange.min); boundsRange.max = Math.max(item.upper, boundsRange.max); }); dataMin = Math.min(dataMin, boundsRange.min); dataMax = Math.max(dataMax, boundsRange.max); // Set the y axis domain so that the range of actual values takes up at least 50% of the full range. if ((valuesRange.max - valuesRange.min) < 0.5 * (dataMax - dataMin)) { if (valuesRange.min > dataMin) { chartLimits.min = valuesRange.min - (0.5 * (valuesRange.max - valuesRange.min)); } if (valuesRange.max < dataMax) { chartLimits.max = valuesRange.max + (0.5 * (valuesRange.max - valuesRange.min)); } } } contextYScale = d3.scale.linear().range([cxtChartHeight, contextChartLineTopMargin]) .domain([chartLimits.min, chartLimits.max]); const borders = cxtGroup.append('g') .attr('class', 'axis'); // Add borders left and right. borders.append('line') .attr('x1', 0) .attr('y1', 0) .attr('x2', 0) .attr('y2', cxtChartHeight + swlHeight); borders.append('line') .attr('x1', cxtWidth) .attr('y1', 0) .attr('x2', cxtWidth) .attr('y2', cxtChartHeight + swlHeight); // Add x axis. const bounds = timefilter.getActiveBounds(); const timeBuckets = new TimeBuckets(); timeBuckets.setInterval('auto'); timeBuckets.setBounds(bounds); const xAxisTickFormat = timeBuckets.getScaledDateFormat(); const xAxis = d3.svg.axis().scale(contextXScale) .orient('top') .innerTickSize(-cxtChartHeight) .outerTickSize(0) .tickPadding(0) .ticks(numTicksForDateFormat(cxtWidth, xAxisTickFormat)) .tickFormat((d) => { return moment(d).format(xAxisTickFormat); }); cxtGroup.datum(data); const contextBoundsArea = d3.svg.area() .x((d) => { return contextXScale(d.date); }) .y0((d) => { return contextYScale(Math.min(chartLimits.max, Math.max(d.lower, chartLimits.min))); }) .y1((d) => { return contextYScale(Math.max(chartLimits.min, Math.min(d.upper, chartLimits.max))); }) .defined(d => (d.lower !== null && d.upper !== null)); if (scope.modelPlotEnabled === true) { cxtGroup.append('path') .datum(data) .attr('class', 'area context') .attr('d', contextBoundsArea); } const contextValuesLine = d3.svg.line() .x((d) => { return contextXScale(d.date); }) .y((d) => { return contextYScale(d.value); }) .defined(d => d.value !== null); cxtGroup.append('path') .datum(data) .attr('class', 'values-line') .attr('d', contextValuesLine); drawLineChartDots(data, cxtGroup, contextValuesLine, 1); // Create the path elements for the forecast value line and bounds area. if (scope.contextForecastData !== undefined) { cxtGroup.append('path') .datum(scope.contextForecastData) .attr('class', 'area forecast') .attr('d', contextBoundsArea); cxtGroup.append('path') .datum(scope.contextForecastData) .attr('class', 'values-line forecast') .attr('d', contextValuesLine); } // Create and draw the anomaly swimlane. const swimlane = cxtGroup.append('g') .attr('class', 'swimlane') .attr('transform', 'translate(0,' + cxtChartHeight + ')'); drawSwimlane(swimlane, cxtWidth, swlHeight); // Draw a mask over the sections of the context chart and swimlane // which fall outside of the zoom brush selection area. mask = new ContextChartMask(cxtGroup, scope.contextChartData, scope.modelPlotEnabled, swlHeight) .x(contextXScale) .y(contextYScale); // Draw the x axis on top of the mask so that the labels are visible. cxtGroup.append('g') .attr('class', 'x axis context-chart-axis') .call(xAxis); // Move the x axis labels up so that they are inside the contact chart area. cxtGroup.selectAll('.x.context-chart-axis text') .attr('dy', (cxtChartHeight - 5)); filterAxisLabels(cxtGroup.selectAll('.x.context-chart-axis'), cxtWidth); drawContextBrush(cxtGroup); }
const d3 = require('d3') const viridis = require('../plasma') const depthScale = d3.scale.linear().domain([-100, 100]) // default range is [0, 1] .clamp(true) // Make sure the output is constrained to [0, 1] d3.selectAll('div', document.body) .data(d3.shuffle(d3.range(-100, 100, 0.1)).map(n => Math.random() * n)) .enter().append('div') .style({ background: d => viridis(depthScale(d)), width: '10px', height: '10px', float: 'left' })
function drawSwimlane(swlGroup, swlWidth, swlHeight) { const data = scope.swimlaneData; // Calculate the x axis domain. // Elasticsearch aggregation returns points at start of bucket, so set the // x-axis min to the start of the aggregation interval. // Need to use the min(earliest) and max(earliest) of the context chart // aggregation to align the axes of the chart and swimlane elements. const xAxisDomain = calculateContextXAxisDomain(); const x = d3.time.scale().range([0, swlWidth]) .domain(xAxisDomain); const y = d3.scale.linear().range([swlHeight, 0]) .domain([0, swlHeight]); const xAxis = d3.svg.axis() .scale(x) .orient('bottom') .innerTickSize(-swlHeight) .outerTickSize(0); const yAxis = d3.svg.axis() .scale(y) .orient('left') .tickValues(y.domain()) .innerTickSize(-swlWidth) .outerTickSize(0); const axes = swlGroup.append('g'); axes.append('g') .attr('class', 'x axis') .attr('transform', 'translate(0,' + (swlHeight) + ')') .call(xAxis); axes.append('g') .attr('class', 'y axis') .call(yAxis); const earliest = xAxisDomain[0].getTime(); const latest = xAxisDomain[1].getTime(); const swimlaneAggMs = scope.contextAggregationInterval.asMilliseconds(); let cellWidth = swlWidth / ((latest - earliest) / swimlaneAggMs); if (cellWidth < 1) { cellWidth = 1; } const cells = swlGroup.append('g') .attr('class', 'swimlane-cells') .selectAll('cells') .data(data); cells.enter().append('rect') .attr('x', (d) => { return x(d.date); }) .attr('y', 0) .attr('rx', 0) .attr('ry', 0) .attr('class', (d) => { return d.score > 0 ? 'swimlane-cell' : 'swimlane-cell-hidden';}) .attr('width', cellWidth) .attr('height', swlHeight) .style('fill', (d) => { return anomalyColorScale(d.score);}); }
function link(scope, el, attr){ // use this is to prevent rendering too quickly and creating watch issues var renderTimeout; // scales var xScale = d3.time.scale() .nice(d3.time.year); var yScale = d3.scale.linear() .nice(); // APPEND SVG TO ELEMENT // get the actual element not the jqlite wrapped object el = el[0]; var svg = d3.select(el) .append('svg'); // GENERATORS var xAxis = d3.svg.axis() .scale(xScale) .orient("bottom"); var yAxis = d3.svg.axis() .scale(yScale) .orient("left"); var line = d3.svg.line() .x(function(d) { return xScale(d.date); }) .y(function(d) { return yScale(d.close); }); _o.dataSource.getCSVData() .then( function (data) { data.forEach(function(d) { d.date = d3.time.format("%Y-%m-%d").parse(d.date); d.close = +d.close; }); xScale.domain(d3.extent(data, function(d) { return d.date; })); yScale.domain(d3.extent(data, function(d) { return d.close; })); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + _o.height + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("x", -(_o.margin.top) ) .attr("dy", ".6em") .attr('class', 'Viz_textVertical') .style("text-anchor", "end") .text("Price ($)"); var dataPerPixel = data.length/_o.width; var dataResampled = data.filter( function(d, i) { return i % Math.ceil(dataPerPixel) == 0; } ); svg.append("path") .data(dataResampled) .attr("class", "line") .attr("d", line); var firstRecord = data[data.length-1]; var lastRecord = data[0]; var first = svg.append("g") .attr("class", "first") .style("display", "none"); first.append("text") .attr("x", -8) .attr("y", 4) .attr("text-anchor", "end") .text("$" + firstRecord.close); first.append("circle") .attr("r", 4); var last = svg.append("g") .attr("class", "last") .style("display", "none"); last.append("text") .attr("x", 8) .attr("y", 4) .text("$" + lastRecord.close); last.append("circle") .attr("r", 4); function resize(){ svg.attr({ width: _o.width, height: _o.height }); xScale .range([ _o.margin.left, _o.width - _o.margin.left ]) .nice(d3.time.year); yScale .range([ _o.height - _o.margin.left, _o.margin.left ]) .nice(); if (_o.width < 300 || _o.height < 80) { svg.select('.x.axis').style("display", "none"); svg.select('.y.axis').style("display", "none"); svg.select(".first") .attr("transform", "translate(" + xScale(firstRecord.date) + "," + yScale(firstRecord.close) + ")") .style("display", "initial"); svg.select(".last") .attr("transform", "translate(" + xScale(lastRecord.date) + "," + yScale(lastRecord.close) + ")") .style("display", "initial"); } else { svg.select('.x.axis') .style("display", "initial"); svg.select('.y.axis') .style("display", "initial"); svg.select(".last") .style("display", "none"); svg.select(".first") .style("display", "none"); } yAxis.ticks(Math.max(_o.height/50, 2)); xAxis.ticks(Math.max(_o.width/50, 2)); svg .attr("width", _o.width + _o.margin.left) .attr("height", _o.height + _o.margin.left) svg.select('.x.axis') .attr('transform', 'translate(' + [ 0, yScale.range()[0] + 0.5 ] + ')') // WHY? // .attr("transform", "translate(0," + (_o.height - _o.margin.bottom) + ")") .call(xAxis); svg.select('.y.axis') .attr('transform', 'translate(' + [ xScale.range()[0], 0 ] + ')') // .attr("transform", "translate(" + _o.margin.left + ", 0)") .call(yAxis); dataPerPixel = data.length/_o.width; dataResampled = data.filter( function(d, i) { return i % Math.ceil(dataPerPixel) == 0; } ); svg.selectAll('.line') .datum(dataResampled) .attr("d", line); // xAxis.tickSize(-_o.height + 2 * _o.margin.left); // yAxis.tickSize(-_o.width + 2 * _o.margin.left); } function update(){ if (renderTimeout) { _o.$timeout.cancel(renderTimeout); renderTimeout = undefined; } renderTimeout = _o.$timeout( function () { resize(); }, 200 ); }; // REACTIVITY scope.$watch('data', update); // Responsive design extract this scope.$watch(function(){ _o.width = el.clientWidth; _o.height = el.clientHeight; return _o.width * _o.height; }, update ); // apply chart resize _o.$window.onresize = function() { scope.$apply(); }; }, function (error) { console.log('Failed to load content: ' + error); } ); }
export default function() { var _extent = [0,Math.PI * 2]; var _circularbrushDispatch = d3.dispatch('brushstart', 'brushend', 'brush'); var _arc = d3.svg.arc().innerRadius(50).outerRadius(100); var _brushData = [ {startAngle: _extent[0], endAngle: _extent[1], class: "extent"}, {startAngle: _extent[0] - .2, endAngle: _extent[0], class: "resize e"}, {startAngle: _extent[1], endAngle: _extent[1] + .2, class: "resize w"} ]; var _newBrushData = []; var d3_window = d3.select(window); var _origin; var _brushG; var _handleSize = .2; var _scale = d3.scale.linear().domain(_extent).range(_extent); var _tolerance = 0.00001; function _circularbrush(_container) { updateBrushData(); _brushG = _container .append("g") .attr("class", "circularbrush"); _brushG .selectAll("path.circularbrush") .data(_brushData) .enter() .insert("path", "path.resize") .attr("d", _arc) .attr("class", function(d) {return d.class + " circularbrush"}) _brushG.select("path.extent") .on("mousedown.brush", resizeDown) _brushG.selectAll("path.resize") .on("mousedown.brush", resizeDown) return _circularbrush; } _circularbrush.extent = function(_value) { var _d = _scale.domain(); var _r = _scale.range(); var _actualScale = d3.scale.linear() .domain([-_d[1],_d[0],_d[0],_d[1]]) .range([_r[0],_r[1],_r[0],_r[1]]) if (!arguments.length) return [_actualScale(_extent[0]),_actualScale(_extent[1])]; _extent = [_scale.invert(_value[0]),_scale.invert(_value[1])]; return this } _circularbrush.handleSize = function(_value) { if (!arguments.length) return _handleSize; _handleSize = _value; _brushData = [ {startAngle: _extent[0], endAngle: _extent[1], class: "extent"}, {startAngle: _extent[0] - _handleSize, endAngle: _extent[0], class: "resize e"}, {startAngle: _extent[1], endAngle: _extent[1] + _handleSize, class: "resize w"} ]; return this } _circularbrush.innerRadius = function(_value) { if (!arguments.length) return _arc.innerRadius(); _arc.innerRadius(_value); return this } _circularbrush.outerRadius = function(_value) { if (!arguments.length) return _arc.outerRadius(); _arc.outerRadius(_value); return this } _circularbrush.range = function(_value) { if (!arguments.length) return _scale.range(); _scale.range(_value); return this } _circularbrush.arc = function(_value) { if (!arguments.length) return _arc; _arc = _value; return this } _circularbrush.tolerance = function(_value) { if (!arguments.length) return _tolerance; _tolerance = _value; return this } _circularbrush.filter = function(_array, _accessor) { var data = _array.map(_accessor); var extent = _circularbrush.extent(); var start = extent[0]; var end = extent[1]; var firstPoint = _scale.range()[0]; var lastPoint = _scale.range()[1]; var filteredArray = []; var firstHalf = []; var secondHalf = []; if (Math.abs(start - end) < _tolerance) { return _array; } if (start < end) { filteredArray = _array.filter(function (d) { return _accessor(d) >= start && _accessor(d) <= end; }); } else { var firstHalf = _array.filter(function (d) { return (_accessor(d) >= start && _accessor(d) <= lastPoint); }); var secondHalf = _array.filter(function (d) { return (_accessor(d) <= end && _accessor(d) >= firstPoint); }); filteredArray = firstHalf.concat(secondHalf); } return filteredArray; } d3.rebind(_circularbrush, _circularbrushDispatch, "on"); return _circularbrush; function resizeDown(d) { var _mouse = d3.mouse(_brushG.node()); _originalBrushData = {startAngle: _brushData[0].startAngle, endAngle: _brushData[0].endAngle}; _origin = _mouse; if (d.class == "resize e") { d3_window .on("mousemove.brush", function() {resizeMove("e")}) .on("mouseup.brush", extentUp); } else if (d.class == "resize w") { d3_window .on("mousemove.brush", function() {resizeMove("w")}) .on("mouseup.brush", extentUp); } else { d3_window .on("mousemove.brush", function() {resizeMove("extent")}) .on("mouseup.brush", extentUp); } _circularbrushDispatch.brushstart(); } function resizeMove(_resize) { var _mouse = d3.mouse(_brushG.node()); var _current = Math.atan2(_mouse[1],_mouse[0]); var _start = Math.atan2(_origin[1],_origin[0]); if (_resize == "e") { var clampedAngle = Math.max(Math.min(_originalBrushData.startAngle + (_current - _start), _originalBrushData.endAngle), _originalBrushData.endAngle - (2 * Math.PI)); if (_originalBrushData.startAngle + (_current - _start) > _originalBrushData.endAngle) { clampedAngle = _originalBrushData.startAngle + (_current - _start) - (Math.PI * 2); } else if (_originalBrushData.startAngle + (_current - _start) < _originalBrushData.endAngle - (Math.PI * 2)) { clampedAngle = _originalBrushData.startAngle + (_current - _start) + (Math.PI * 2); } var _newStartAngle = clampedAngle; var _newEndAngle = _originalBrushData.endAngle; } else if (_resize == "w") { var clampedAngle = Math.min(Math.max(_originalBrushData.endAngle + (_current - _start), _originalBrushData.startAngle), _originalBrushData.startAngle + (2 * Math.PI)) if (_originalBrushData.endAngle + (_current - _start) < _originalBrushData.startAngle) { clampedAngle = _originalBrushData.endAngle + (_current - _start) + (Math.PI * 2); } else if (_originalBrushData.endAngle + (_current - _start) > _originalBrushData.startAngle + (Math.PI * 2)) { clampedAngle = _originalBrushData.endAngle + (_current - _start) - (Math.PI * 2); } var _newStartAngle = _originalBrushData.startAngle; var _newEndAngle = clampedAngle; } else { var _newStartAngle = _originalBrushData.startAngle + (_current - _start * 1); var _newEndAngle = _originalBrushData.endAngle + (_current - _start * 1); } _newBrushData = [ {startAngle: _newStartAngle, endAngle: _newEndAngle, class: "extent"}, {startAngle: _newStartAngle - _handleSize, endAngle: _newStartAngle, class: "resize e"}, {startAngle: _newEndAngle, endAngle: _newEndAngle + _handleSize, class: "resize w"} ] brushRefresh(); if (_newStartAngle > (Math.PI * 2)) { _newStartAngle = (_newStartAngle - (Math.PI * 2)); } else if (_newStartAngle < -(Math.PI * 2)) { _newStartAngle = (_newStartAngle + (Math.PI * 2)); } if (_newEndAngle > (Math.PI * 2)) { _newEndAngle = (_newEndAngle - (Math.PI * 2)); } else if (_newEndAngle < -(Math.PI * 2)) { _newEndAngle = (_newEndAngle + (Math.PI * 2)); } _extent = ([_newStartAngle,_newEndAngle]); _circularbrushDispatch.brush(); } function brushRefresh() { _brushG .selectAll("path.circularbrush") .data(_newBrushData) .attr("d", _arc) } function extentUp() { _brushData = _newBrushData; d3_window.on("mousemove.brush", null).on("mouseup.brush", null); _circularbrushDispatch.brushend(); } function updateBrushData() { _brushData = [ {startAngle: _extent[0], endAngle: _extent[1], class: "extent"}, {startAngle: _extent[0] - _handleSize, endAngle: _extent[0], class: "resize e"}, {startAngle: _extent[1], endAngle: _extent[1] + _handleSize, class: "resize w"} ]; } }
export default function heatChart(options_override) { var debug=false; var options_defaults = { margin: {top: 10, right: 10, bottom: 50, left: 50}, cb_margin: {top: 10, right: 50, bottom: 50, left: 10}, show_grid: true, show_colorbar: true, position_cursor: true, colorbar_width: 120, numberOfTicks: 4, aspect_ratio: null, autoscale: false, xlabel: "x-axis", ylabel: "y-axis", zlabel: "z-axis", ztransform: "linear", dims: { xmin: 0, xmax: 1, ymin: 0, ymax: 1, zmin: 1.0, zmax: 100.0 } } var options = jQuery.extend(true, {}, options_defaults); // copy jQuery.extend(true, options, options_override); // process any overrides from creation; //var zoomRect = false; var zoomScroll = false; var interactors = []; var plotdata, source_data; var z = d3.scale[options.ztransform](); var dims = options.dims; // create working copy of zmax and zmin, for zooming colorbar var zdims = {} var id = d3.id(); var x = d3.scale.linear(); var y = d3.scale.linear(); var xAxis = d3.svg.axis(); var yAxis = d3.svg.axis(); var zAxis = d3.svg.axis(); var xAxisGrid = d3.svg.axis(); var yAxisGrid = d3.svg.axis(); var colormap = jet_colormap; var zoomed = function() { _redraw_main = true; } var zoom = d3.behavior.zoom().on("zoom.heatmap", zoomed); var resetzoom = function() { zoom.translate([0,0]).scale(1); zoomed.call(this); } var cb_zoomed = function() { var svg = d3.select(this); svg.select(".z.axis").call(zAxis); zdims.zmax = Math.max.apply(Math, z.domain()); zdims.zmin = Math.min.apply(Math, z.domain()); _recalculate_main = true; //chart.redrawImage(); } var cb_zoom = d3.behavior.zoom() .on("zoom.colorbar", null) .on("zoom.colorbar", cb_zoomed); var cb_resetzoom = function() { cb_zoom.translate([0,0]).scale(1); cb_zoomed.call(this); } //var dispatch = d3.dispatch("update", "redrawImage"); //dispatch.on("redrawImage", function() { // _redraw_backing = true; // chart.redrawImage(); //}); // some private working variables var backing_canvas = document.createElement('canvas'); var backing_image; var colorbar_backing_canvas = document.createElement('canvas'); var _recalculate_main = false; var _redraw_main = false; var _redraw_backing = true; var _redraw_colorbar = true; var _colormap_array = []; function chart(selection) { selection.each(function(data) { var offset_right = (options.show_colorbar) ? options.colorbar_width + 5 : 0; var outercontainer = d3.select(this), innerwidth = outercontainer.node().clientWidth - offset_right, innerheight = outercontainer.node().clientHeight, width = innerwidth - options.margin.right - options.margin.left, height = innerheight - options.margin.top - options.margin.bottom; chart.outercontainer = outercontainer; source_data = data; //chart.update = function() { outercontainer.transition().call(chart); chart.colorbar.update(); }; if (options.autoscale) { var new_min_max = get_min_max(data, z); zdims.zmin = new_min_max.min; zdims.zmax = new_min_max.max; } else { zdims.zmin = dims.zmin; zdims.zmax = dims.zmax; } var limits = fixAspect(width, height); // Update the x-scale. x .domain([limits.xmin, limits.xmax]) .range([0, width]); // Update the y-scale. y .domain([limits.ymin, limits.ymax]) .range([height, 0]); z .domain([zdims.zmin, zdims.zmax]) make_plotdata(); xAxisGrid .scale(x) .orient("bottom") .ticks(options.numberOfTicks) .tickPadding(10) .tickSize(-height, 0, 0) .tickFormat(""); yAxisGrid .scale(y) .ticks(options.numberOfTicks) .tickPadding(10) .tickSubdivide(true) .orient("left") .tickSize(-width, 0, 0) .tickFormat("") xAxis .scale(x) .ticks(options.numberOfTicks) .tickPadding(10) .tickSubdivide(true) .orient("bottom"); yAxis .scale(y) .ticks(options.numberOfTicks) .tickPadding(10) .tickSubdivide(true) .orient("left"); // we will bind data to the container div, a slightly non-standard // arrangement. var container = d3.select(this).selectAll("div.heatmap-container").data([0]); zoom.x(x).y(y); // if inner container doesn't exist, build it. container.enter().append("div") .attr("class", "heatmap-container") .attr("width", innerwidth) .attr("height", innerheight) .style("display", "inline-block") .style("width", innerwidth + "px") .style("height", innerheight + "px"); var mainCanvas = container.selectAll("canvas.mainplot").data([0]); mainCanvas.enter().append("canvas"); mainCanvas .attr("width", width) .attr("height", height) .attr("class", "mainplot") .style("width", width + "px") .style("height", height + "px") .style("padding-left", options.margin.left + "px") .style("padding-right", options.margin.right + "px") .style("padding-top", options.margin.top + "px") .call(drawImage); chart.mainCanvas = mainCanvas; var svg = container.selectAll("svg.mainplot").data([0]); var esvg = svg.enter() .append("svg") .attr("class", "mainplot") .on("dblclick.resetzoom", resetzoom); esvg.append("g") .attr("class", "x axis") .append("text") .attr("class", "x axis-label") .attr("x", width/2.0) .attr("text-anchor", "middle") .attr("y", 35) esvg.append("g") .attr("class", "y axis") .append("text") .attr("class", "y axis-label") .attr("text-anchor", "middle") .attr("transform", "rotate(-90)") .attr("y", -35 ) .attr("x", -height/2) esvg.append("g") .attr("class", "x grid"); esvg.append("g") .attr("class", "y grid"); esvg.append("g") .attr("class", "y interactors") var mainview = esvg.append("g") .attr("class", "mainview") .attr("transform", "translate(" + options.margin.left + "," + options.margin.top + ")"); svg.select(".x.axis").call(xAxis); svg.select(".y.axis").call(yAxis); svg.select(".x.grid").call(xAxisGrid); svg.select(".y.grid").call(yAxisGrid); svg.select(".x.axis-label").text(options.xlabel); svg.select(".y.axis-label").text(options.ylabel); svg.attr("width", width + options.margin.left + options.margin.right) .attr("height", height + options.margin.top + options.margin.bottom); svg.selectAll("g.x") .attr("transform", "translate(" + options.margin.left + "," + (height + options.margin.top) + ")"); svg.selectAll("g.y") .attr("transform", "translate(" + options.margin.left + "," + options.margin.top + ")"); chart.svg = svg; //svg.call(zoom); // moved to zoomScroll function //************************************************************ // Position cursor (shows position of mouse in data coords) //************************************************************ if (options.position_cursor) { var position_cursor = mainview.selectAll(".position-cursor") .data([0]) position_cursor .enter().append("text") .attr("class", "position-cursor") .attr("x", width - 10) .attr("y", height + options.margin.bottom) .style("text-anchor", "end"); var follow = function (){ if (source_data == null || source_data[0] == null) { return } var mouse = d3.mouse(mainview.node()); var x_coord = x.invert(mouse[0]), y_coord = y.invert(mouse[1]), xdim = source_data[0].length, ydim = source_data.length; var x_bin = Math.floor((x_coord - dims.xmin) / (dims.xmax - dims.xmin) * xdim), y_bin = Math.floor((y_coord - dims.ymin) / (dims.ymax - dims.ymin) * ydim); var z_coord = (x_bin >= 0 && x_bin < xdim && y_bin >= 0 && y_bin < ydim) ? source_data[y_bin][x_bin] : NaN; position_cursor.text( x_coord.toPrecision(5) + ", " + y_coord.toPrecision(5) + ", " + z_coord.toPrecision(5)); } esvg .on("mousemove.position_cursor", null) .on("mouseover.position_cursor", null) .on("mousemove.position_cursor", follow) .on("mouseover.position_cursor", follow); } }); selection.call(chart.colorbar); } chart.colorbar = function(selection) { selection.each(function(data) { var outercontainer = d3.select(this), offset_left = 0, innerwidth = options.colorbar_width, innerheight = outercontainer.node().clientHeight, width = innerwidth - options.cb_margin.right, height = innerheight - options.cb_margin.top - options.cb_margin.bottom; //colorbar.name = "colorbar"; chart.colorbar.outercontainer = outercontainer; // update the z axis z.range([height, 0]); zAxis .scale(z) .ticks(options.numberOfTicks) .tickPadding(10) .tickSubdivide(true) .orient("right"); // we will bind data to the container div, a slightly non-standard // arrangement. var container = d3.select(this).selectAll("div.colorbar-container").data([0]); cb_zoom.y(z); chart.colorbar.resetzoom = cb_resetzoom; chart.colorbar.zoom = cb_zoom; // if inner container doesn't exist, build it. container.enter().append("div") .attr("class", "colorbar-container") .attr("width", innerwidth) .attr("height", innerheight) .style("display", "inline-block") .style("width", innerwidth + "px") .style("height", innerheight + "px"); var colorbarCanvas = container.selectAll("canvas.colorbar").data([0]); colorbarCanvas.enter().append("canvas"); colorbarCanvas .attr("width", width) .attr("height", height) .attr("class", "colorbar") .style("width", width + "px") .style("height", height + "px") .style("padding-left", offset_left + "px") .style("padding-right", options.cb_margin.right + "px") .style("padding-top", options.cb_margin.top + "px") .call(drawScale); chart.colorbar.colorbarCanvas = colorbarCanvas; var svg = container.selectAll("svg.colorbar").data([0]); var esvg = svg.enter() .append("svg") .attr("class", "colorbar") .call(cb_zoom) .on("dblclick.zoom", null) .on("dblclick.resetzoom", null) .on("dblclick.resetzoom", cb_resetzoom); esvg.append("g") .attr("class", "z axis"); svg.select(".z.axis").call(zAxis); svg.attr("width", width + options.cb_margin.left + options.cb_margin.right) .attr("height", height + options.cb_margin.top + options.cb_margin.bottom); svg.selectAll("g.z") .attr("transform", "translate(" + width + "," + options.cb_margin.top + ")"); chart.colorbar.svg = svg; }); } chart.colorbar.update = function() { this.outercontainer.call(chart.colorbar); }; chart.colormap=function(_) { if (!arguments.length) return colormap; colormap = _; _colormap_array = []; for (var i=0; i<256; i++) { _colormap_array[i] = d3.rgb(colormap(i)); _colormap_array[i].a = 255; } _colormap_array[256] = d3.rgb(0,0,0); _colormap_array[256].a = 0; _redraw_colorbar = true; return chart; }; // cache the colormap: chart.colormap(colormap); //chart.dispatch = dispatch; chart.redrawImage = function() { _redraw_backing = true; make_plotdata(this.source_data, dims, zdims, z); drawImage(this.mainCanvas); return chart; }; chart.redrawLoop = function() { if (_recalculate_main == true) { _recalculate_main = false; make_plotdata(); _redraw_backing = true; _redraw_main = true; //drawImage(chart.mainCanvas) //, plotdata); } if (_redraw_main == true) { _redraw_main = false; var svg = chart.svg; var canvas = chart.mainCanvas; var container = chart.outercontainer; svg.select(".x.axis").call(xAxis); svg.select(".y.axis").call(yAxis); svg.select(".grid.x").call(xAxisGrid); svg.select(".grid.y").call(yAxisGrid); chart.mainCanvas.call(drawImage); chart.interactors().forEach(function(d,i) { if (d.update) {d.update();}}); } window.requestAnimationFrame(chart.redrawLoop); }; window.requestAnimationFrame(chart.redrawLoop); chart.margin = function(_) { if (!arguments.length) return options.margin; options.margin = _; return chart; }; chart.show_grid = function(_) { if (!arguments.length) return options.show_grid; options.show_grid = _; chart.outercontainer.selectAll(".grid").style( "display", (options.show_grid == true || options.show_grid == "true") ? "inline" : "none" ); return chart; }; chart.ztransform = function(_) { if (!arguments.length) return options.ztransform; options.ztransform = _; var old_range = z.range(), old_domain = z.domain(); z = d3.scale[options.ztransform](); do_autoscale(); z.domain([zdims.zmin, zdims.zmax]).range(old_range); zAxis.scale(z); cb_zoom.y(z); cb_resetzoom.call(chart.colorbar.svg.node()); return chart; }; chart.xlabel = function(_) { if (!arguments.length) return options.xlabel; options.xlabel = _; if (options.axes && options.axes.xaxis) { options.axes.xaxis.label = _; } if (chart.svg && chart.svg.select) { chart.svg.select(".x.axis .x.axis-label").text(_); } return chart; } chart.ylabel = function(_) { if (!arguments.length) return options.ylabel; options.ylabel = _; if (options.axes && options.axes.yaxis) { options.axes.yaxis.label = _; } if (chart.svg && chart.svg.select) { chart.svg.select(".y.axis .y.axis-label").text(_); } return chart; } // drop all the other options into the chart namespace, // making objects update rather than overwrite for (var attr in options) { // ignore the ones we've already defined accessors for. if (!(attr in chart)) { chart[attr] = (function(attr) { var accessor = function(_) { if (!arguments.length) return options[attr]; if (jQuery.type(options[attr]) == "object") { jQuery.extend(options[attr], _); } else { options[attr] = _; } return chart; } return accessor })(attr); } } //chart.zoomRect = function(_) { // if (!arguments.length) return zoomRect; // zoomRect = _; // return chart; //}; chart.zoomScroll = function(_) { if (!arguments.length) return zoomScroll; zoomScroll = _; if (zoomScroll == true) { chart.svg.call(zoom).on("dblclick.zoom", null); } else if (zoomScroll == false) { chart.svg.on(".zoom", null); } return chart; }; chart.resetzoom = resetzoom; chart.x = function(_) { if (!arguments.length) return x; x = _; return chart; }; chart.y = function(_) { if (!arguments.length) return y; y = _; return chart; }; chart.z = function(_) { if (!arguments.length) return z; z = _; return chart; }; chart.source_data = function(_) { if (!arguments.length) return source_data; source_data = _; if (options.autoscale) { do_autoscale(); } _recalculate_main = true; }; chart.interactors = function(_) { if (!arguments.length) return interactors; chart.svg.select("g.interactors").call(_); _.x(x).y(y).update(); interactors.push(_); return chart; }; chart.destroy = function() { //delete backing_canvas; //delete colorbar_backing_canvas; var rs = this.outercontainer.selectAll("svg").remove(); for (var i in rs) {delete rs[i]}; var rd = this.outercontainer.selectAll("div").remove(); for (var i in rd) {delete rd[i]}; var rc = this.outercontainer.selectAll("canvas").remove(); for (var i in rc) {delete rc[i]}; }; var get_sxdx = function(){ var xdim = source_data[0].length, ydim = source_data.length; var delta_x = (dims.xmax - dims.xmin)/(xdim), delta_y = (dims.ymax - dims.ymin)/(ydim); var graph_xmax = Math.max.apply(Math, x.domain()), graph_xmin = Math.min.apply(Math, x.domain()), graph_ymax = Math.max.apply(Math, y.domain()), graph_ymin = Math.min.apply(Math, y.domain()); var xmin = Math.max(graph_xmin, dims.xmin), xmax = Math.min(graph_xmax, dims.xmax); var ymin = Math.max(graph_ymin, dims.ymin), ymax = Math.min(graph_ymax, dims.ymax); if (debug) { console.log('x', xmin,xmax, 'y', ymin,ymax, 'w', (xmax-xmin), 'h', (ymax-ymin)); console.log('dims', dims); } var sx = (xmin - dims.xmin)/delta_x, sy = (dims.ymax - ymax)/delta_y, sx2 = (xmax - dims.xmin)/delta_x, sy2 = (dims.ymax - ymin)/delta_y, sw = sx2 - sx, sh = sy2 - sy; if (debug) console.log('sx', sx, 'sy', sy, 'sw', sw, 'sh', sh, ' sx2 ', sx2, 'sy2 ', sy2); var dx = x(xmin), dy = y(ymax), dw = x(xmax) - dx, dh = y(ymin) - dy; if (debug) console.log('dx', dx, 'dy', dy, 'dw', dw, 'dh', dh); return {sx:sx, sy:sy, sw:sw, sh:sh, dx:dx, dy:dy, dw:dw, dh:dh} }; var fixAspect = function(width, height) { var aspect_ratio = options.aspect_ratio, xmin = dims.xmin, xmax = dims.xmax, ymin = dims.ymin, ymax = dims.ymax; if (aspect_ratio == null) { return {'xmin': xmin, 'xmax': xmax, 'ymin': ymin, 'ymax': ymax} } var yrange = (ymax - ymin); var ycenter = (ymax + ymin) / 2.0; var xrange = (xmax - xmin); var xcenter = (xmax + xmin) / 2.0; var graph_ratio = width / height; var ratio = yrange/xrange * graph_ratio; if (isNaN(ratio) || ratio == aspect_ratio) { return }; if (ratio < aspect_ratio) { // y-range is too small yrange = aspect_ratio * xrange / graph_ratio; } if (ratio > aspect_ratio) { xrange = yrange / aspect_ratio * graph_ratio; } var output = { 'xmin': xcenter - xrange/2.0, 'xmax': xcenter + xrange/2.0, 'ymin': ycenter - yrange/2.0, 'ymax': ycenter + yrange/2.0 } return output; }; // Compute the pixel colors; scaled by CSS. function drawImage(canvas) { // canvas is a d3 selection. //var plotdata = canvas.data()[0]; var maxColorIndex = 255, overflowIndex = 256, context = canvas.node().getContext("2d"), ctx = backing_canvas.getContext("2d"); if (_redraw_backing) { _redraw_backing = false; var height = source_data.length, width = source_data[0].length; if (backing_image == null || backing_canvas.width != width || backing_canvas.height != height) { backing_canvas.width = width; backing_canvas.height = height; backing_image = ctx.createImageData(width, height); } var data = backing_image.data; var yp, pp=0; for (var yt = 0, p = -1; yt < height; ++yt) { yp = dims.ydim - 1 - yt; // y-axis starts at the top! for (var xp = 0; xp < width; ++xp, pp++) { var c = _colormap_array[plotdata[pp]]; data[++p] = c.r; data[++p] = c.g; data[++p] = c.b; data[++p] = c.a; } } ctx.putImageData(backing_image, 0, 0); } //context.mozImageSmoothingEnabled = false; //context.webkitImageSmoothingEnabled = false; //context.msImageSmoothingEnabled = false; //context.imageSmoothingEnabled = false; context.clearRect(0,0, context.canvas.width, context.canvas.height); if (context.mozImageSmoothingEnabled) context.mozImageSmoothingEnabled = false; if (context.imageSmoothingEnabled) context.imageSmoothingEnabled = false; if (context.msImageSmoothingEnabled) context.msImageSmoothingEnabled = false; if (context.webkitImageSmoothingEnabled) context.webkitImageSmoothingEnabled = false; var sxdx = get_sxdx(); context.drawImage(ctx.canvas, sxdx.sx, sxdx.sy, sxdx.sw, sxdx.sh, sxdx.dx, sxdx.dy, sxdx.dw, sxdx.dh); } // Compute the pixel colors; scaled by CSS. function drawScale(canvas) { var maxColorIndex = 255, overflowIndex = 256, context = canvas.node().getContext("2d"), ctx = colorbar_backing_canvas.getContext("2d"); if (_redraw_colorbar) { _redraw_colorbar = false; colorbar_backing_canvas.width = 1; colorbar_backing_canvas.height = 256; var image = ctx.createImageData(1, 256); var data = image.data; for (var yp = 255, p = -1; yp >= 0; --yp) { var c = _colormap_array[yp]; data[++p] = c.r; data[++p] = c.g; data[++p] = c.b; data[++p] = c.a; } ctx.putImageData(image, 0, 0); } context.clearRect(0,0, context.canvas.width, context.canvas.height); if (context.mozImageSmoothingEnabled) context.mozImageSmoothingEnabled = false; if (context.imageSmoothingEnabled) context.imageSmoothingEnabled = false; context.drawImage(ctx.canvas, 0, 0, 1, 256, 0, 0, context.canvas.width, context.canvas.height); } // call after setting transform var make_plotdata = function() { // source_data is 2d array var plotz = z.copy().range([0,255]); //var crange = d3.range(256); //var lookups = crange.slice(0,255).map(plotz.invert); //var threshold = d3.scale.quantile().domain(lookups).range(crange); var height = source_data.length, width = source_data[0].length; // set the local plotdata: if (plotdata == null || plotdata.length != (width * height)) { plotdata = new Uint8ClampedArray(width*height); } // plotdata is stored in row-major order ("C"), where row is "y" var zz, r, c, dr, plotz, pp=0; for (r = height - 1; r >=0; r--) { dr = source_data[r]; for (c = 0; c < width; c++) { zz = dr[c]; plotdata[pp++] = plotz(zz); //plotdata[pp++] = threshold(zz); } } _redraw_backing = true; return }; function do_autoscale() { var new_min_max = get_min_max(source_data, z, Infinity, -Infinity); if (!isFinite(new_min_max.min) || !isFinite(new_min_max.max)) { new_min_max = {min: 1, max: 2}; // need to put something for invalid input scales. } zdims.zmin = new_min_max.min; zdims.zmax = new_min_max.max; z.domain([zdims.zmin, zdims.zmax]); cb_zoom.y(z); zAxis.scale(z); cb_zoomed.call(chart.colorbar.svg.node()); chart.colorbar.svg.select(".z.axis").call(zAxis); _recalculate_main = true; } chart.do_autoscale = do_autoscale; function get_min_max(array, transform, existing_min, existing_max) { var new_min_max = {min: existing_min, max: existing_max}; for (var i=0; i<array.length; i++) { var subarr = array[i]; if (subarr == null) { continue } if (!subarr.hasOwnProperty('length')) { var t_el = transform(subarr); if (isFinite(t_el)) { new_min_max = {min: subarr, max: subarr}; } } else { new_min_max = get_min_max(subarr, transform, existing_min, existing_max); } if (existing_min == undefined || new_min_max.min < existing_min) { var existing_min = new_min_max.min; } if (existing_max == undefined || new_min_max.max > existing_max) { var existing_max = new_min_max.max; } } return {min: existing_min, max: existing_max} }; function generate_cumsums() { //console.log('generating cumsum'); var height = source_data.length, width = source_data[0].length, data = source_data; var cumsum_x = [], cumsum_x_col; var cumsum_y = [], cumsum_y_col; var ysum = [], xsum; // initialize the y-sum: for (var r = 0; r<height; r++) ysum[r] = 0; cumsum_y[0] = ysum.slice(); for (var c = 0; c < width; c++) { cumsum_x_col = [0]; xsum = 0; cumsum_y_col = []; for (var r = 0; r < height; r++) { var z = data[r][c]; if (isFinite(z)) { xsum += z; ysum[r] += z; } cumsum_x_col[r] = xsum; cumsum_y_col[r] = ysum[r]; } cumsum_x[c] = cumsum_x_col; cumsum_y[c] = cumsum_y_col; } return {x: cumsum_x, y: cumsum_y} //this.cumsum_x = cumsum_x; //this.cumsum_y = cumsum_y; }; chart.generate_cumsums = generate_cumsums; chart.autofit = function() { var offset_right = (options.show_colorbar) ? options.colorbar_width + 5 : 0; var outercontainer = this.outercontainer, innerwidth = outercontainer.node().clientWidth - offset_right, innerheight = outercontainer.node().clientHeight, width = innerwidth - options.margin.right - options.margin.left, height = innerheight - options.margin.top - options.margin.bottom; var limits = fixAspect(width, height); // Update the x-scale. x .domain([limits.xmin, limits.xmax]) .range([0, width]); // Update the y-scale. y .domain([limits.ymin, limits.ymax]) .range([height, 0]); zoom.x(x).y(y); outercontainer.select(".heatmap-container") .attr("width", innerwidth) .attr("height", innerheight) .style("width", innerwidth + "px") .style("height", innerheight + "px"); outercontainer.select("canvas.mainplot") .attr("width", width) .attr("height", height) .style("width", width + "px") .style("height", height + "px") chart.svg.attr("width", width + options.margin.left + options.margin.right) .attr("height", height + options.margin.top + options.margin.bottom); chart.svg.selectAll("g.x") .attr("transform", "translate(" + options.margin.left + "," + (height + options.margin.top) + ")"); chart.svg.selectAll("g.y") .attr("transform", "translate(" + options.margin.left + "," + options.margin.top + ")"); chart.svg.selectAll("g.x.axis text").attr("x", width/2.0); chart.svg.selectAll("g.y.axis text").attr("x", -height/2.0); var innerwidth = options.colorbar_width, width = innerwidth - options.cb_margin.right, height = innerheight - options.cb_margin.top - options.cb_margin.bottom; z.range([height, 0]); outercontainer.select(".colorbar-container") .attr("width", innerwidth) .attr("height", innerheight) .style("width", innerwidth + "px") .style("height", innerheight + "px"); outercontainer.select("canvas.colorbar") .attr("width", width) .attr("height", height) .style("width", width + "px") .style("height", height + "px") .call(drawScale); chart.colorbar.svg.select(".z.axis").call(zAxis); chart.colorbar.svg.attr("width", width + options.cb_margin.left + options.cb_margin.right) .attr("height", height + options.cb_margin.top + options.cb_margin.bottom); chart.colorbar.svg.selectAll("g.z") .attr("transform", "translate(" + width + "," + options.cb_margin.top + ")"); _redraw_main = true; } chart.type = "heatmap_2d"; return chart }
var Rickshaw={namespace:function(a,b){var c=a.split("."),d=Rickshaw;for(var e=1,f=c.length;e<f;e++)currentPart=c[e],d[currentPart]=d[currentPart]||{},d=d[currentPart];return d},keys:function(a){var b=[];for(var c in a)b.push(c);return b},extend:function(a,b){for(var c in b)a[c]=b[c];return a}};if(typeof module!="undefined"&&module.exports){var d3=require("d3");module.exports=Rickshaw}(function(a){function j(a){return b.call(a)===i}function k(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function l(a){if(m(a)!==h)throw new TypeError;var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c);return b}function m(a){switch(a){case null:return c;case void 0:return d}var b=typeof a;switch(b){case"boolean":return e;case"number":return f;case"string":return g}return h}function n(a){return typeof a=="undefined"}function p(a){var b=a.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1].replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g,"").replace(/\s+/g,"").split(",");return b.length==1&&!b[0]?[]:b}function q(a,b){var c=a;return function(){var a=r([t(c,this)],arguments);return b.apply(this,a)}}function r(a,b){var c=a.length,d=b.length;while(d--)a[c+d]=b[d];return a}function s(a,b){return a=o.call(a,0),r(a,b)}function t(a,b){if(arguments.length<2&&n(arguments[0]))return this;var c=a,d=o.call(arguments,2);return function(){var a=s(d,arguments);return c.apply(b,a)}}var b=Object.prototype.toString,c="Null",d="Undefined",e="Boolean",f="Number",g="String",h="Object",i="[object Function]",o=Array.prototype.slice,u=function(){},v=function(){function b(){}function c(){function d(){this.initialize.apply(this,arguments)}var a=null,c=[].slice.apply(arguments);j(c[0])&&(a=c.shift()),k(d,v.Methods),d.superclass=a,d.subclasses=[];if(a){b.prototype=a.prototype,d.prototype=new b;try{a.subclasses.push(d)}catch(e){}}for(var f=0,g=c.length;f<g;f++)d.addMethods(c[f]);return d.prototype.initialize||(d.prototype.initialize=u),d.prototype.constructor=d,d}function d(b){var c=this.superclass&&this.superclass.prototype,d=l(b);a&&(b.toString!=Object.prototype.toString&&d.push("toString"),b.valueOf!=Object.prototype.valueOf&&d.push("valueOf"));for(var e=0,f=d.length;e<f;e++){var g=d[e],h=b[g];if(c&&j(h)&&p(h)[0]=="$super"){var i=h;h=q(function(a){return function(){return c[a].apply(this,arguments)}}(g),i),h.valueOf=t(i.valueOf,i),h.toString=t(i.toString,i)}this.prototype[g]=h}return this}var a=function(){for(var a in{toString:1})if(a==="toString")return!1;return!0}();return{create:c,Methods:{addMethods:d}}}();a.exports?a.exports.Class=v:a.Class=v})(Rickshaw),Rickshaw.namespace("Rickshaw.Compat.ClassList"),Rickshaw.Compat.ClassList=function(){typeof document!="undefined"&&!("classList"in document.createElement("a"))&&function(a){"use strict";var b="classList",c="prototype",d=(a.HTMLElement||a.Element)[c],e=Object,f=String[c].trim||function(){return this.replace(/^\s+|\s+$/g,"")},g=Array[c].indexOf||function(a){var b=0,c=this.length;for(;b<c;b++)if(b in this&&this[b]===a)return b;return-1},h=function(a,b){this.name=a,this.code=DOMException[a],this.message=b},i=function(a,b){if(b==="")throw new h("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(b))throw new h("INVALID_CHARACTER_ERR","String contains an invalid character");return g.call(a,b)},j=function(a){var b=f.call(a.className),c=b?b.split(/\s+/):[],d=0,e=c.length;for(;d<e;d++)this.push(c[d]);this._updateClassName=function(){a.className=this.toString()}},k=j[c]=[],l=function(){return new j(this)};h[c]=Error[c],k.item=function(a){return this[a]||null},k.contains=function(a){return a+="",i(this,a)!==-1},k.add=function(a){a+="",i(this,a)===-1&&(this.push(a),this._updateClassName())},k.remove=function(a){a+="";var b=i(this,a);b!==-1&&(this.splice(b,1),this._updateClassName())},k.toggle=function(a){a+="",i(this,a)===-1?this.add(a):this.remove(a)},k.toString=function(){return this.join(" ")};if(e.defineProperty){var m={get:l,enumerable:!0,configurable:!0};try{e.defineProperty(d,b,m)}catch(n){n.number===-2146823252&&(m.enumerable=!1,e.defineProperty(d,b,m))}}else e[c].__defineGetter__&&d.__defineGetter__(b,l)}(window)},(typeof RICKSHAW_NO_COMPAT!="undefined"&&!RICKSHAW_NO_COMPAT||typeof RICKSHAW_NO_COMPAT=="undefined")&&new Rickshaw.Compat.ClassList,Rickshaw.namespace("Rickshaw.Graph"),Rickshaw.Graph=function(a){this.element=a.element,this.series=a.series,this.defaults={interpolation:"cardinal",offset:"zero",min:undefined,max:undefined},Rickshaw.keys(this.defaults).forEach(function(b){this[b]=a[b]||this.defaults[b]},this),this.window={},this.updateCallbacks=[];var b=this;this.initialize=function(a){this.validateSeries(a.series),this.series.active=function(){return b.series.filter(function(a){return!a.disabled})},this.setSize({width:a.width,height:a.height}),this.element.classList.add("rickshaw_graph"),this.vis=d3.select(this.element).append("svg:svg").attr("width",this.width).attr("height",this.height);var c=[Rickshaw.Graph.Renderer.Stack,Rickshaw.Graph.Renderer.Line,Rickshaw.Graph.Renderer.Bar,Rickshaw.Graph.Renderer.Area,Rickshaw.Graph.Renderer.ScatterPlot];c.forEach(function(a){if(!a)return;b.registerRenderer(new a({graph:b}))}),this.setRenderer(a.renderer||"stack",a),this.discoverRange()},this.validateSeries=function(a){if(!(a instanceof Array||a instanceof Rickshaw.Series)){var b=Object.prototype.toString.apply(a);throw"series is not an array: "+b}var c;a.forEach(function(a){if(!(a instanceof Object))throw"series element is not an object: "+a;if(!a.data)throw"series has no data: "+JSON.stringify(a);if(!(a.data instanceof Array))throw"series data is not an array: "+JSON.stringify(a.data);c=c||a.data.length;if(c&&a.data.length!=c)throw"series cannot have differing numbers of points: "+c+" vs "+a.data.length+"; see Rickshaw.Series.zeroFill()";var b=typeof a.data[0].x,d=typeof a.data[0].y;if(b!="number"||d!="number")throw"x and y properties of points should be numbers instead of "+b+" and "+d})},this.dataDomain=function(){var a=this.series[0].data;return[a[0].x,a.slice(-1).shift().x]},this.discoverRange=function(){var a=this.renderer.domain();this.x=d3.scale.linear().domain(a.x).range([0,this.width]),this.y=d3.scale.linear().domain(a.y).range([this.height,0]),this.y.magnitude=d3.scale.linear().domain([a.y[0]-a.y[0],a.y[1]-a.y[0]]).range([0,this.height])},this.render=function(){var a=this.stackData();this.discoverRange(),this.renderer.render(),this.updateCallbacks.forEach(function(a){a()})},this.update=this.render,this.stackData=function(){var a=this.series.active().map(function(a){return a.data}).map(function(a){return a.filter(function(a){return this._slice(a)},this)},this);this.stackData.hooks.data.forEach(function(c){a=c.f.apply(b,[a])});var c=d3.layout.stack();c.offset(b.offset);var d=c(a);this.stackData.hooks.after.forEach(function(c){d=c.f.apply(b,[a])});var e=0;return this.series.forEach(function(a){if(a.disabled)return;a.stack=d[e++]}),this.stackedData=d,d},this.stackData.hooks={data:[],after:[]},this._slice=function(a){if(this.window.xMin||this.window.xMax){var b=!0;return this.window.xMin&&a.x<this.window.xMin&&(b=!1),this.window.xMax&&a.x>this.window.xMax&&(b=!1),b}return!0},this.onUpdate=function(a){this.updateCallbacks.push(a)},this.registerRenderer=function(a){this._renderers=this._renderers||{},this._renderers[a.name]=a},this.configure=function(a){(a.width||a.height)&&this.setSize(a),Rickshaw.keys(this.defaults).forEach(function(b){this[b]=b in a?a[b]:b in this?this[b]:this.defaults[b]},this),this.setRenderer(a.renderer||this.renderer.name,a)},this.setRenderer=function(a,b){if(!this._renderers[a])throw"couldn't find renderer "+a;this.renderer=this._renderers[a],typeof b=="object"&&this.renderer.configure(b)},this.setSize=function(a){a=a||{};if(typeof window!==undefined)var b=window.getComputedStyle(this.element,null),c=parseInt(b.getPropertyValue("width")),d=parseInt(b.getPropertyValue("height"));this.width=a.width||c||400,this.height=a.height||d||250,this.vis&&this.vis.attr("width",this.width).attr("height",this.height)},this.initialize(a)},Rickshaw.namespace("Rickshaw.Fixtures.Color"),Rickshaw.Fixtures.Color=function(){this.schemes={},this.schemes.spectrum14=["#ecb796","#dc8f70","#b2a470","#92875a","#716c49","#d2ed82","#bbe468","#a1d05d","#e7cbe6","#d8aad6","#a888c2","#9dc2d3","#649eb9","#387aa3"].reverse(),this.schemes.spectrum2000=["#57306f","#514c76","#646583","#738394","#6b9c7d","#84b665","#a7ca50","#bfe746","#e2f528","#fff726","#ecdd00","#d4b11d","#de8800","#de4800","#c91515","#9a0000","#7b0429","#580839","#31082b"],this.schemes.spectrum2001=["#2f243f","#3c2c55","#4a3768","#565270","#6b6b7c","#72957f","#86ad6e","#a1bc5e","#b8d954","#d3e04e","#ccad2a","#cc8412","#c1521d","#ad3821","#8a1010","#681717","#531e1e","#3d1818","#320a1b"],this.schemes.classic9=["#423d4f","#4a6860","#848f39","#a2b73c","#ddcb53","#c5a32f","#7d5836","#963b20","#7c2626","#491d37","#2f254a"].reverse(),this.schemes.httpStatus={503:"#ea5029",502:"#d23f14",500:"#bf3613",410:"#efacea",409:"#e291dc",403:"#f457e8",408:"#e121d2",401:"#b92dae",405:"#f47ceb",404:"#a82a9f",400:"#b263c6",301:"#6fa024",302:"#87c32b",307:"#a0d84c",304:"#28b55c",200:"#1a4f74",206:"#27839f",201:"#52adc9",202:"#7c979f",203:"#a5b8bd",204:"#c1cdd1"},this.schemes.colorwheel=["#b5b6a9","#858772","#785f43","#96557e","#4682b4","#65b9ac","#73c03a","#cb513a"].reverse(),this.schemes.cool=["#5e9d2f","#73c03a","#4682b4","#7bc3b8","#a9884e","#c1b266","#a47493","#c09fb5"],this.schemes.munin=["#00cc00","#0066b3","#ff8000","#ffcc00","#330099","#990099","#ccff00","#ff0000","#808080","#008f00","#00487d","#b35a00","#b38f00","#6b006b","#8fb300","#b30000","#bebebe","#80ff80","#80c9ff","#ffc080","#ffe680","#aa80ff","#ee00cc","#ff8080","#666600","#ffbfff","#00ffcc","#cc6699","#999900"]},Rickshaw.namespace("Rickshaw.Fixtures.RandomData"),Rickshaw.Fixtures.RandomData=function(a){var b;a=a||1;var c=200,d=Math.floor((new Date).getTime()/1e3);this.addData=function(b){var e=Math.random()*100+15+c,f=b[0].length,g=1;b.forEach(function(b){var c=Math.random()*20,h=e/25+g++ +(Math.cos(f*g*11/960)+2)*15+(Math.cos(f/7)+2)*7+(Math.cos(f/17)+2)*1;b.push({x:f*a+d,y:h+c})}),c=e*.85}},Rickshaw.namespace("Rickshaw.Fixtures.Time"),Rickshaw.Fixtures.Time=function(){var a=(new Date).getTimezoneOffset()*60,b=this;this.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],this.units=[{name:"decade",seconds:315576e3,formatter:function(a){return parseInt(a.getUTCFullYear()/10)*10}},{name:"year",seconds:31557600,formatter:function(a){return a.getUTCFullYear()}},{name:"month",seconds:2635200,formatter:function(a){return b.months[a.getUTCMonth()]}},{name:"week",seconds:604800,formatter:function(a){return b.formatDate(a)}},{name:"day",seconds:86400,formatter:function(a){return a.getUTCDate()}},{name:"6 hour",seconds:21600,formatter:function(a){return b.formatTime(a)}},{name:"hour",seconds:3600,formatter:function(a){return b.formatTime(a)}},{name:"15 minute",seconds:900,formatter:function(a){return b.formatTime(a)}},{name:"minute",seconds:60,formatter:function(a){return a.getUTCMinutes()}},{name:"15 second",seconds:15,formatter:function(a){return a.getUTCSeconds()+"s"}},{name:"second",seconds:1,formatter:function(a){return a.getUTCSeconds()+"s"}}],this.unit=function(a){return this.units.filter(function(b){return a==b.name}).shift()},this.formatDate=function(a){return a.toUTCString().match(/, (\w+ \w+ \w+)/)[1]},this.formatTime=function(a){return a.toUTCString().match(/(\d+:\d+):/)[1]},this.ceil=function(a,b){if(b.name=="month"){var c=new Date((a+b.seconds-1)*1e3),d=new Date(0);return d.setUTCFullYear(c.getUTCFullYear()),d.setUTCMonth(c.getUTCMonth()),d.setUTCDate(1),d.setUTCHours(0),d.setUTCMinutes(0),d.setUTCSeconds(0),d.setUTCMilliseconds(0),d.getTime()/1e3}if(b.name=="year"){var c=new Date((a+b.seconds-1)*1e3),d=new Date(0);return d.setUTCFullYear(c.getUTCFullYear()),d.setUTCMonth(0),d.setUTCDate(1),d.setUTCHours(0),d.setUTCMinutes(0),d.setUTCSeconds(0),d.setUTCMilliseconds(0),d.getTime()/1e3}return Math.ceil(a/b.seconds)*b.seconds}},Rickshaw.namespace("Rickshaw.Fixtures.Number"),Rickshaw.Fixtures.Number.formatKMBT=function(a){return a>=1e12?a/1e12+"T":a>=1e9?a/1e9+"B":a>=1e6?a/1e6+"M":a>=1e3?a/1e3+"K":a<1&&a>0?a.toFixed(2):a==0?"":a},Rickshaw.Fixtures.Number.formatBase1024KMGTP=function(a){return a>=0x4000000000000?a/0x4000000000000+"P":a>=1099511627776?a/1099511627776+"T":a>=1073741824?a/1073741824+"G":a>=1048576?a/1048576+"M":a>=1024?a/1024+"K":a<1&&a>0?a.toFixed(2):a==0?"":a},Rickshaw.namespace("Rickshaw.Color.Palette"),Rickshaw.Color.Palette=function(a){var b=new Rickshaw.Fixtures.Color;a=a||{},this.schemes={},this.scheme=b.schemes[a.scheme]||a.scheme||b.schemes.colorwheel,this.runningIndex=0,this.generatorIndex=0;if(a.interpolatedStopCount){var c=this.scheme.length-1,d,e,f=[];for(d=0;d<c;d++){f.push(this.scheme[d]);var g=d3.interpolateHsl(this.scheme[d],this.scheme[d+1]);for(e=1;e<a.interpolatedStopCount;e++)f.push(g(1/a.interpolatedStopCount*e))}f.push(this.scheme[this.scheme.length-1]),this.scheme=f}this.rotateCount=this.scheme.length,this.color=function(a){return this.scheme[a]||this.scheme[this.runningIndex++]||this.interpolateColor()||"#808080"},this.interpolateColor=function(){if(!Array.isArray(this.scheme))return;var a;return this.generatorIndex==this.rotateCount*2-1?(a=d3.interpolateHsl(this.scheme[this.generatorIndex],this.scheme[0])(.5),this.generatorIndex=0,this.rotateCount*=2):(a=d3.interpolateHsl(this.scheme[this.generatorIndex],this.scheme[this.generatorIndex+1])(.5),this.generatorIndex++),this.scheme.push(a),a}},Rickshaw.namespace("Rickshaw.Graph.Ajax"),Rickshaw.Graph.Ajax=Rickshaw.Class.create({initialize:function(a){this.dataURL=a.dataURL,this.onData=a.onData||function(a){return a},this.onComplete=a.onComplete||function(){},this.onError=a.onError||function(){},this.args=a,this.request()},request:function(){$.ajax({url:this.dataURL,dataType:"json",success:this.success.bind(this),error:this.error.bind(this)})},error:function(){console.log("error loading dataURL: "+this.dataURL),this.onError(this)},success:function(a,b){a=this.onData(a),this.args.series=this._splice({data:a,series:this.args.series}),this.graph=new Rickshaw.Graph(this.args),this.graph.render(),this.onComplete(this)},_splice:function(a){var b=a.data,c=a.series;return a.series?(c.forEach(function(a){var c=a.key||a.name;if(!c)throw"series needs a key or a name";b.forEach(function(b){var d=b.key||b.name;if(!d)throw"data needs a key or a name";if(c==d){var e=["color","name","data"];e.forEach(function(c){a[c]=a[c]||b[c]})}})}),c):b}}),Rickshaw.namespace("Rickshaw.Graph.Annotate"),Rickshaw.Graph.Annotate=function(a){var b=this.graph=a.graph;this.elements={timeline:a.element};var c=this;this.data={},this.elements.timeline.classList.add("rickshaw_annotation_timeline"),this.add=function(a,b,d){c.data[a]=c.data[a]||{boxes:[]},c.data[a].boxes.push({content:b,end:d})},this.update=function(){Rickshaw.keys(c.data).forEach(function(a){var b=c.data[a],d=c.graph.x(a);if(d<0||d>c.graph.x.range()[1]){b.element&&(b.line.classList.add("offscreen"),b.element.style.display="none"),b.boxes.forEach(function(a){a.rangeElement&&a.rangeElement.classList.add("offscreen")});return}if(!b.element){var e=b.element=document.createElement("div");e.classList.add("annotation"),this.elements.timeline.appendChild(e),e.addEventListener("click",function(a){e.classList.toggle("active"),b.line.classList.toggle("active"),b.boxes.forEach(function(a){a.rangeElement&&a.rangeElement.classList.toggle("active")})},!1)}b.element.style.left=d+"px",b.element.style.display="block",b.boxes.forEach(function(a){var e=a.element;e||(e=a.element=document.createElement("div"),e.classList.add("content"),e.innerHTML=a.content,b.element.appendChild(e),b.line=document.createElement("div"),b.line.classList.add("annotation_line"),c.graph.element.appendChild(b.line),a.end&&(a.rangeElement=document.createElement("div"),a.rangeElement.classList.add("annotation_range"),c.graph.element.appendChild(a.rangeElement)));if(a.end){var f=d,g=Math.min(c.graph.x(a.end),c.graph.x.range()[1]);f>g&&(g=d,f=Math.max(c.graph.x(a.end),c.graph.x.range()[0]));var h=g-f;a.rangeElement.style.left=f+"px",a.rangeElement.style.width=h+"px",a.rangeElement.classList.remove("offscreen")}b.line.classList.remove("offscreen"),b.line.style.left=d+"px"})},this)},this.graph.onUpdate(function(){c.update()})},Rickshaw.namespace("Rickshaw.Graph.Axis.Time"),Rickshaw.Graph.Axis.Time=function(a){var b=this;this.graph=a.graph,this.elements=[],this.ticksTreatment=a.ticksTreatment||"plain",this.fixedTimeUnit=a.timeUnit;var c=new Rickshaw.Fixtures.Time;this.appropriateTimeUnit=function(){var a,b=c.units,d=this.graph.x.domain(),e=d[1]-d[0];return b.forEach(function(b){Math.floor(e/b.seconds)>=2&&(a=a||b)}),a||c.units[c.units.length-1]},this.tickOffsets=function(){var a=this.graph.x.domain(),b=this.fixedTimeUnit||this.appropriateTimeUnit(),d=Math.ceil((a[1]-a[0])/b.seconds),e=a[0],f=[];for(var g=0;g<d;g++)tickValue=c.ceil(e,b),e=tickValue+b.seconds/2,f.push({value:tickValue,unit:b});return f},this.render=function(){this.elements.forEach(function(a){a.parentNode.removeChild(a)}),this.elements=[];var a=this.tickOffsets();a.forEach(function(a){if(b.graph.x(a.value)>b.graph.x.range()[1])return;var c=document.createElement("div");c.style.left=b.graph.x(a.value)+"px",c.classList.add("x_tick"),c.classList.add(b.ticksTreatment);var d=document.createElement("div");d.classList.add("title"),d.innerHTML=a.unit.formatter(new Date(a.value*1e3)),c.appendChild(d),b.graph.element.appendChild(c),b.elements.push(c)})},this.graph.onUpdate(function(){b.render()})},Rickshaw.namespace("Rickshaw.Graph.Axis.Y"),Rickshaw.Graph.Axis.Y=function(a){var b=this,c=.1;this.initialize=function(a){this.graph=a.graph,this.orientation=a.orientation||"right";var c=a.pixelsPerTick||75;this.ticks=a.ticks||Math.floor(this.graph.height/c),this.tickSize=a.tickSize||4,this.ticksTreatment=a.ticksTreatment||"plain",a.element?(this.element=a.element,this.vis=d3.select(a.element).append("svg:svg").attr("class","rickshaw_graph y_axis"),this.element=this.vis[0][0],this.element.style.position="relative",this.setSize({width:a.width,height:a.height})):this.vis=this.graph.vis,this.graph.onUpdate(function(){b.render()})},this.setSize=function(a){a=a||{};if(!this.element)return;if(typeof window!="undefined"){var b=window.getComputedStyle(this.element.parentNode,null),d=parseInt(b.getPropertyValue("width"));if(!a.auto)var e=parseInt(b.getPropertyValue("height"))}this.width=a.width||d||this.graph.width*c,this.height=a.height||e||this.graph.height,this.vis.attr("width",this.width).attr("height",this.height*(1+c));var f=this.height*c;this.element.style.top=-1*f+"px"},this.render=function(){this.graph.height!==this._renderHeight&&this.setSize({auto:!0});var b=d3.svg.axis().scale(this.graph.y).orient(this.orientation);b.tickFormat(a.tickFormat||function(a){return a});if(this.orientation=="left")var d=this.height*c,e="translate("+this.width+", "+d+")";this.element&&this.vis.selectAll("*").remove(),this.vis.append("svg:g").attr("class",["y_ticks",this.ticksTreatment].join(" ")).attr("transform",e).call(b.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));var f=(this.orientation=="right"?1:-1)*this.graph.width;this.graph.vis.append("svg:g").attr("class","y_grid").call(b.ticks(this.ticks).tickSubdivide(0).tickSize(f)),this._renderHeight=this.graph.height},this.initialize(a)},Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Highlight"),Rickshaw.Graph.Behavior.Series.Highlight=function(a){this.graph=a.graph,this.legend=a.legend;var b=this,c={};this.addHighlightEvents=function(a){a.element.addEventListener("mouseover",function(d){b.legend.lines.forEach(function(b){if(a===b)return;c[b.series.name]=c[b.series.name]||b.series.color,b.series.color=d3.interpolateRgb(b.series.color,d3.rgb("#d8d8d8"))(.8).toString()}),b.graph.update()},!1),a.element.addEventListener("mouseout",function(a){b.legend.lines.forEach(function(a){c[a.series.name]&&(a.series.color=c[a.series.name])}),b.graph.update()},!1)},this.legend&&this.legend.lines.forEach(function(a){b.addHighlightEvents(a)})},Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Order"),Rickshaw.Graph.Behavior.Series.Order=function(a){this.graph=a.graph,this.legend=a.legend;var b=this;$(function(){$(b.legend.list).sortable({containment:"parent",tolerance:"pointer",update:function(a,c){var d=[];$(b.legend.list).find("li").each(function(a,b){if(!b.series)return;d.push(b.series)});for(var e=b.graph.series.length-1;e>=0;e--)b.graph.series[e]=d.shift();b.graph.update()}}),$(b.legend.list).disableSelection()}),this.graph.onUpdate(function(){var a=window.getComputedStyle(b.legend.element).height;b.legend.element.style.height=a})},Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Toggle"),Rickshaw.Graph.Behavior.Series.Toggle=function(a){this.graph=a.graph,this.legend=a.legend;var b=this;this.addAnchor=function(a){var c=document.createElement("a");c.innerHTML="✔",c.classList.add("action"),a.element.insertBefore(c,a.element.firstChild),c.onclick=function(b){a.series.disabled?(a.series.enable(),a.element.classList.remove("disabled")):(a.series.disable(),a.element.classList.add("disabled"))};var d=a.element.getElementsByTagName("span")[0];d.onclick=function(c){var d=a.series.disabled;if(!d)for(var e=0;e<b.legend.lines.length;e++){var f=b.legend.lines[e];if(a.series!==f.series&&!f.series.disabled){d=!0;break}}d?(a.series.enable(),a.element.classList.remove("disabled"),b.legend.lines.forEach(function(b){a.series!==b.series&&(b.series.disable(),b.element.classList.add("disabled"))})):b.legend.lines.forEach(function(a){a.series.enable(),a.element.classList.remove("disabled")})}},this.legend&&($(this.legend.list).sortable({start:function(a,b){b.item.bind("no.onclick",function(a){a.preventDefault()})},stop:function(a,b){setTimeout(function(){b.item.unbind("no.onclick")},250)}}),this.legend.lines.forEach(function(a){b.addAnchor(a)})),this._addBehavior=function(){this.graph.series.forEach(function(a){a.disable=function(){if(b.graph.series.length<=1)throw"only one series left";a.disabled=!0,b.graph.update()},a.enable=function(){a.disabled=!1,b.graph.update()}})},this._addBehavior(),this.updateBehaviour=function(){this._addBehavior()}},Rickshaw.namespace("Rickshaw.Graph.HoverDetail"),Rickshaw.Graph.HoverDetail=Rickshaw.Class.create({initialize:function(a){var b=this.graph=a.graph;this.xFormatter=a.xFormatter||function(a){return(new Date(a*1e3)).toUTCString()},this.yFormatter=a.yFormatter||function(a){return a.toFixed(2)};var c=this.element=document.createElement("div");c.className="detail",this.visible=!0,b.element.appendChild(c),this.lastEvent=null,this._addListeners(),this.onShow=a.onShow,this.onHide=a.onHide,this.onRender=a.onRender,this.formatter=a.formatter||this.formatter},formatter:function(a,b,c,d,e,f){return a.name+": "+e},update:function(a){a=a||this.lastEvent;if(!a)return;this.lastEvent=a;if(!a.target.nodeName.match(/^(path|svg|rect)$/))return;var b=this.graph,c=a.offsetX||a.layerX,d=a.offsetY||a.layerY,e=b.x.invert(c),f=b.stackedData,g=f.slice(-1).shift(),h=d3.scale.linear().domain([g[0].x,g.slice(-1).shift().x]).range([0,g.length]),i=Math.floor(h(e)),j=Math.min(i||0,f[0].length-1);for(var k=i;k<f[0].length-1;){if(!f[0][k]||!f[0][k+1])break;if(f[0][k].x<=e&&f[0][k+1].x>e){j=k;break}f[0][k+1]<=e?k++:k--}var e=f[0][j].x,l=this.xFormatter(e),m=b.x(e),n=0,o=b.series.active().map(function(a){return{order:n++,series:a,name:a.name,value:a.stack[j]}}),p,q=function(a,b){return a.value.y0+a.value.y-(b.value.y0+b.value.y)},r=b.y.magnitude.invert(b.element.offsetHeight-d);o.sort(q).forEach(function(a){a.formattedYValue=this.yFormatter.constructor==Array?this.yFormatter[o.indexOf(a)](a.value.y):this.yFormatter(a.value.y),a.graphX=m,a.graphY=b.y(a.value.y0+a.value.y),r>a.value.y0&&r<a.value.y0+a.value.y&&!p&&(p=a,a.active=!0)},this),this.element.innerHTML="",this.element.style.left=b.x(e)+"px",this.visible&&this.render({detail:o,domainX:e,formattedXValue:l,mouseX:c,mouseY:d})},hide:function(){this.visible=!1,this.element.classList.add("inactive"),typeof this.onHide=="function"&&this.onHide()},show:function(){this.visible=!0,this.element.classList.remove("inactive"),typeof this.onShow=="function"&&this.onShow()},render:function(a){var b=a.detail,c=a.domainX,d=a.mouseX,e=a.mouseY,f=a.formattedXValue,g=document.createElement("div");g.className="x_label",g.innerHTML=f,this.element.appendChild(g),b.forEach(function(a){var b=document.createElement("div");b.className="item",b.innerHTML=this.formatter(a.series,c,a.value.y,f,a.formattedYValue,a),b.style.top=this.graph.y(a.value.y0+a.value.y)+"px",this.element.appendChild(b);var d=document.createElement("div");d.className="dot",d.style.top=b.style.top,d.style.borderColor=a.series.color,this.element.appendChild(d),a.active&&(b.className="item active",d.className="dot active")},this),this.show(),typeof this.onRender=="function"&&this.onRender(a)},_addListeners:function(){this.graph.element.addEventListener("mousemove",function(a){this.visible=!0,this.update(a)}.bind(this),!1),this.graph.onUpdate(function(){this.update()}.bind(this)),this.graph.element.addEventListener("mouseout",function(a){a.relatedTarget&&!(a.relatedTarget.compareDocumentPosition(this.graph.element)&Node.DOCUMENT_POSITION_CONTAINS)&&this.hide()}.bind(this),!1)}}),Rickshaw.namespace("Rickshaw.Graph.JSONP"),Rickshaw.Graph.JSONP=Rickshaw.Class.create(Rickshaw.Graph.Ajax,{request:function(){$.ajax({url:this.dataURL,dataType:"jsonp",success:this.success.bind(this),error:this.error.bind(this)})}}),Rickshaw.namespace("Rickshaw.Graph.Legend"),Rickshaw.Graph.Legend=function(a){var b=this.element=a.element,c=this.graph=a.graph,d=this;b.classList.add("rickshaw_legend");var e=this.list=document.createElement("ul");b.appendChild(e);var f=c.series.map(function(a){return a}).reverse();this.lines=[],this.addLine=function(a){var b=document.createElement("li");b.className="line";var c=document.createElement("div");c.className="swatch",c.style.backgroundColor=a.color,b.appendChild(c);var f=document.createElement("span");f.className="label",f.innerHTML=a.name,b.appendChild(f),e.appendChild(b),b.series=a,a.noLegend&&(b.style.display="none");var g={element:b,series:a};d.shelving&&(d.shelving.addAnchor(g),d.shelving.updateBehaviour()),d.highlighter&&d.highlighter.addHighlightEvents(g),d.lines.push(g)},f.forEach(function(a){d.addLine(a)}),c.onUpdate(function(){})},Rickshaw.namespace("Rickshaw.Graph.RangeSlider"),Rickshaw.Graph.RangeSlider=function(a){var b=this.element=a.element,c=this.graph=a.graph;$(function(){$(b).slider({range:!0,min:c.dataDomain()[0],max:c.dataDomain()[1],values:[c.dataDomain()[0],c.dataDomain()[1]],slide:function(a,b){c.window.xMin=b.values[0],c.window.xMax=b.values[1],c.update(),c.dataDomain()[0]==b.values[0]&&(c.window.xMin=undefined),c.dataDomain()[1]==b.values[1]&&(c.window.xMax=undefined)}})}),b[0].style.width=c.width+"px",c.onUpdate(function(){var a=$(b).slider("option","values");$(b).slider("option","min",c.dataDomain()[0]),$(b).slider("option","max",c.dataDomain()[1]),c.window.xMin==undefined&&(a[0]=c.dataDomain()[0]),c.window.xMax==undefined&&(a[1]=c.dataDomain()[1]),$(b).slider("option","values",a)})},Rickshaw.namespace("Rickshaw.Graph.Renderer"),Rickshaw.Graph.Renderer=Rickshaw.Class.create({initialize:function(a){this.graph=a.graph,this.tension=a.tension||this.tension,this.graph.unstacker=this.graph.unstacker||new Rickshaw.Graph.Unstacker({graph:this.graph}),this.configure(a)},seriesPathFactory:function(){},seriesStrokeFactory:function(){},defaults:function(){return{tension:.8,strokeWidth:2,unstack:!0,padding:{top:.01,right:0,bottom:.01,left:0},stroke:!1,fill:!1}},domain:function(){var a=[],b=this.graph.stackedData||this.graph.stackData(),c=this.unstack?b:[b.slice(-1).shift()];c.forEach(function(b){b.forEach(function(b){a.push(b.y+b.y0)})});var d=b[0][0].x,e=b[0][b[0].length-1].x;d-=(e-d)*this.padding.left,e+=(e-d)*this.padding.right;var f=this.graph.min==="auto"?d3.min(a):this.graph.min||0,g=this.graph.max||d3.max(a);if(this.graph.min==="auto"||f<0)f-=(g-f)*this.padding.bottom;return this.graph.max===undefined&&(g+=(g-f)*this.padding.top),{x:[d,e],y:[f,g]}},render:function(){var a=this.graph;a.vis.selectAll("*").remove();var b=a.vis.selectAll("path").data(this.graph.stackedData).enter().append("svg:path").attr("d",this.seriesPathFactory()),c=0;a.series.forEach(function(a){if(a.disabled)return;a.path=b[0][c++],this._styleSeries(a)},this)},_styleSeries:function(a){var b=this.fill?a.color:"none",c=this.stroke?a.color:"none";a.path.setAttribute("fill",b),a.path.setAttribute("stroke",c),a.path.setAttribute("stroke-width",this.strokeWidth),a.path.setAttribute("class",a.className)},configure:function(a){a=a||{},Rickshaw.keys(this.defaults()).forEach(function(b){if(!a.hasOwnProperty(b)){this[b]=this[b]||this.graph[b]||this.defaults()[b];return}typeof this.defaults()[b]=="object"?Rickshaw.keys(this.defaults()[b]).forEach(function(c){this[b][c]=a[b][c]!==undefined?a[b][c]:this[b][c]!==undefined?this[b][c]:this.defaults()[b][c]},this):this[b]=a[b]!==undefined?a[b]:this[b]!==undefined?this[b]:this.graph[b]!==undefined?this.graph[b]:this.defaults()[b]},this)},setStrokeWidth:function(a){a!==undefined&&(this.strokeWidth=a)},setTension:function(a){a!==undefined&&(this.tension=a)}}),Rickshaw.namespace("Rickshaw.Graph.Renderer.Line"),Rickshaw.Graph.Renderer.Line=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"line",defaults:function($super){return Rickshaw.extend($super(),{unstack:!0,fill:!1,stroke:!0})},seriesPathFactory:function(){var a=this.graph;return d3.svg.line().x(function(b){return a.x(b.x)}).y(function(b){return a.y(b.y)}).interpolate(this.graph.interpolation).tension(this.tension)}}),Rickshaw.namespace("Rickshaw.Graph.Renderer.Stack"),Rickshaw.Graph.Renderer.Stack=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"stack",defaults:function($super){return Rickshaw.extend($super(),{fill:!0,stroke:!1,unstack:!1})},seriesPathFactory:function(){var a=this.graph;return d3.svg.area().x(function(b){return a.x(b.x)}).y0(function(b){return a.y(b.y0)}).y1(function(b){return a.y(b.y+b.y0)}).interpolate(this.graph.interpolation).tension(this.tension)}}),Rickshaw.namespace("Rickshaw.Graph.Renderer.Bar"),Rickshaw.Graph.Renderer.Bar=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"bar",defaults:function($super){var a=Rickshaw.extend($super(),{gapSize:.05,unstack:!1});return delete a.tension,a},initialize:function($super,a){a=a||{},this.gapSize=a.gapSize||this.gapSize,$super(a)},domain:function($super){var a=$super(),b=this._frequentInterval();return a.x[1]+=parseInt(b.magnitude),a},barWidth:function(){var a=this.graph.stackedData||this.graph.stackData(),b=a.slice(-1).shift(),c=this._frequentInterval(),d=this.graph.x(b[0].x+c.magnitude*(1-this.gapSize));return d},render:function(){var a=this.graph;a.vis.selectAll("*").remove();var b=this.barWidth(),c=0,d=a.series.filter(function(a){return!a.disabled}).length,e=this.unstack?b/d:b,f=function(b){var c=[1,0,0,b.y<0?-1:1,0,b.y<0?a.y.magnitude(Math.abs(b.y))*2:0];return"matrix("+c.join(",")+")"};a.series.forEach(function(b){if(b.disabled)return;var d=a.vis.selectAll("path").data(b.stack).enter().append("svg:rect").attr("x",function(b){return a.x(b.x)+c}).attr("y",function(b){return a.y(b.y0+Math.abs(b.y))*(b.y<0?-1:1)}).attr("width",e).attr("height",function(b){return a.y.magnitude(Math.abs(b.y))}).attr("transform",f);Array.prototype.forEach.call(d[0],function(a){a.setAttribute("fill",b.color)}),this.unstack&&(c+=e)},this)},_frequentInterval:function(){var a=this.graph.stackedData||this.graph.stackData(),b=a.slice(-1).shift(),c={};for(var d=0;d<b.length-1;d++){var e=b[d+1].x-b[d].x;c[e]=c[e]||0,c[e]++}var f={count:0};return Rickshaw.keys(c).forEach(function(a){f.count<c[a]&&(f={count:c[a],magnitude:a})}),this._frequentInterval=function(){return f},f}}),Rickshaw.namespace("Rickshaw.Graph.Renderer.Area"),Rickshaw.Graph.Renderer.Area=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"area",defaults:function($super){return Rickshaw.extend($super(),{unstack:!1,fill:!1,stroke:!1})},seriesPathFactory:function(){var a=this.graph;return d3.svg.area().x(function(b){return a.x(b.x)}).y0(function(b){return a.y(b.y0)}).y1(function(b){return a.y(b.y+b.y0)}).interpolate(a.interpolation).tension(this.tension)},seriesStrokeFactory:function(){var a=this.graph;return d3.svg.line().x(function(b){return a.x(b.x)}).y(function(b){return a.y(b.y+b.y0)}).interpolate(a.interpolation).tension(this.tension)},render:function(){var a=this.graph;a.vis.selectAll("*").remove();var b=a.vis.selectAll("path").data(this.graph.stackedData).enter().insert("svg:g","g");b.append("svg:path").attr("d",
function mapRenderer() { return { restrict: 'E', template, link($scope, elm) { const colorScale = d3.scale.category10(); const map = L.map(elm[0].children[0].children[0], { scrollWheelZoom: false }); const mapControls = L.control.layers().addTo(map); const layers = {}; const tileLayer = L.tileLayer('//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors', }).addTo(map); function getBounds() { $scope.visualization.options.bounds = map.getBounds(); } function setBounds() { const b = $scope.visualization.options.bounds; if (b) { map.fitBounds([[b._southWest.lat, b._southWest.lng], [b._northEast.lat, b._northEast.lng]]); } else if (layers) { const allMarkers = _.flatten(_.map(_.values(layers), l => l.getLayers())); // eslint-disable-next-line new-cap const group = new L.featureGroup(allMarkers); map.fitBounds(group.getBounds()); } } map.on('focus', () => { map.on('moveend', getBounds); }); map.on('blur', () => { map.off('moveend', getBounds); }); function resize() { if (!map) return; map.invalidateSize(false); setBounds(); } const createMarker = (lat, lon) => L.marker([lat, lon]); const heatpoint = (lat, lon, color) => { const style = { fillColor: color, fillOpacity: 0.9, stroke: false, }; return L.circleMarker([lat, lon], style); }; function createDescription(latCol, lonCol, row) { const lat = row[latCol]; const lon = row[lonCol]; let description = '<ul style="list-style-type: none;padding-left: 0">'; description += `<li><strong>${lat}, ${lon}</strong>`; _.each(row, (v, k) => { if (!(k === latCol || k === lonCol)) { description += `<li>${k}: ${v}</li>`; } }); return description; } function removeLayer(layer) { if (layer) { mapControls.removeLayer(layer); map.removeLayer(layer); } } function addLayer(name, points) { const latCol = $scope.visualization.options.latColName || 'lat'; const lonCol = $scope.visualization.options.lonColName || 'lon'; const classify = $scope.visualization.options.classify; let markers; if ($scope.visualization.options.clusterMarkers) { const color = $scope.visualization.options.groups[name].color; const options = {}; if (classify) { options.iconCreateFunction = (cluster) => { const childCount = cluster.getChildCount(); let c = ' marker-cluster-'; if (childCount < 10) { c += 'small'; } else if (childCount < 100) { c += 'medium'; } else { c += 'large'; } c = ''; const style = `color: white; background-color: ${color};`; return L.divIcon({ html: `<div style="${style}"><span>${childCount}</span></div>`, className: `marker-cluster${c}`, iconSize: new L.Point(40, 40) }); }; } markers = L.markerClusterGroup(options); } else { markers = L.layerGroup(); } // create markers _.each(points, (row) => { let marker; const lat = row[latCol]; const lon = row[lonCol]; if (lat === null || lon === null) return; if (classify && classify !== 'none') { const groupColor = $scope.visualization.options.groups[name].color; marker = heatpoint(lat, lon, groupColor); } else { marker = createMarker(lat, lon); } marker.bindPopup(createDescription(latCol, lonCol, row)); markers.addLayer(marker); }); markers.addTo(map); layers[name] = markers; mapControls.addOverlay(markers, name); } function render() { const queryData = $scope.queryResult.getData(); const classify = $scope.visualization.options.classify; $scope.visualization.options.mapTileUrl = $scope.visualization.options.mapTileUrl || '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; tileLayer.setUrl($scope.visualization.options.mapTileUrl); if ($scope.visualization.options.clusterMarkers === undefined) { $scope.visualization.options.clusterMarkers = true; } if (queryData) { let pointGroups; if (classify && classify !== 'none') { pointGroups = _.groupBy(queryData, classify); } else { pointGroups = { All: queryData }; } const groupNames = _.keys(pointGroups); const options = _.map(groupNames, (group) => { if ($scope.visualization.options.groups && $scope.visualization.options.groups[group]) { return $scope.visualization.options.groups[group]; } return { color: colorScale(group) }; }); $scope.visualization.options.groups = _.object(groupNames, options); _.each(layers, (v) => { removeLayer(v); }); _.each(pointGroups, (v, k) => { addLayer(k, v); }); setBounds(); } } $scope.$watch('queryResult && queryResult.getData()', render); $scope.$watch('visualization.options', render, true); angular.element(window).on('resize', resize); $scope.$watch('visualization.options.height', resize); }, }; }
render = function(data, units) { var dateEnd, dateStart, daySpan, format, h, max, min, numdays, padb, padl, padr, padt, subs, ticks, timeFormat, vis, w, x, xAxis, y, yAxis, _ref; w = imgWidth; h = imgHeight; format = function(d) { return d + units; }; timeFormat = function(d) { if (d < 60) { return "" + d + "m"; } else { return "" + (d / 60) + "h"; } }; data = data.map(function(d, i) { return [new Date(d[0] * 1000), d[1]]; }).sort(function(a, b) { return d3.ascending(a[0], b[0]); }); numdays = data.length; _ref = [6, 70, 20, 8]; padt = _ref[0]; padl = _ref[1]; padr = _ref[2]; padb = _ref[3]; max = d3.max(data, function(d) { return d[1]; }); min = d3.min(data, function(d) { return d[1]; }); if (units === '%') { if (max === min) { max = 100; } if (!(min < 99)) { min = 99; } } // render chart dateStart = data[0][0]; dateEnd = data[data.length - 1][0]; daySpan = Math.round((dateEnd - dateStart) / (1000 * 60 * 60 * 24)); x = d3.time.scale() .domain([dateStart, dateEnd]) .range([0, w - padl - padr]); y = d3.scale.linear() .domain([min, max]) .range([h - padb - padt, 0]); if (daySpan === 1) { ticks = 3; subs = 6; } else if (daySpan === 7) { ticks = 4; subs = 1; } else { ticks = 4; subs = 6; } xAxis = d3.svg.axis().scale(x) .tickSize(1) .tickSubdivide(subs) .ticks(ticks) .orient("bottom") .tickFormat(function(d) { if (daySpan <= 1) { return d3.time .format('%H:%M')(d) .replace(/\s/, '') .replace(/^0/, ''); } else { return d3.time .format('%m/%d')(d) .replace(/\s/, '') .replace(/^0/, '') .replace(/\/0/, '/'); } }); yAxis = d3.svg.axis().scale(y) .tickSize(1) .ticks(2) .orient("left") .tickFormat(format); vis = d3.select(box) .attr('width', w) .attr('height', h + padt + padb) .append('svg:g') .attr('transform', "translate(" + padl + "," + padt + ")"); vis.append("svg:g") .attr("class", "x axis") .attr('transform', "translate(0, " + (h - padt - padb) + ")") .call(xAxis); vis.append("svg:g") .attr("transform", "translate(0, 0)") .attr("class", "y axis") .call(yAxis); var lineGen = d3.svg.line() .x(function(d) { return x(d[0]); }) .y(function(d) { return y(d[1]); }).interpolate("basis"); vis.append('svg:path') .attr('d', lineGen(data)) .attr('stroke', color) .attr('stroke-width', 2) .attr('fill', 'none'); }
getYScale() { return d3.scale.linear().range([this.height, 0]) .domain([this.yMin, this.yMax]); }
renderChart: function () { var t11 = this.model.get('team1-first-half-score'); var t13 = this.model.get('team1-total-score'); var t21 = this.model.get('team2-first-half-score'); var t23 = this.model.get('team2-total-score'); var team1 = [ {score: t11}, {score: t13} ]; var team2 = [ {score: t21}, {score: t23} ]; team1.unshift({score: 0}); // all games start with 0 points team2.unshift({score: 0}); // all games start with 0 points // need to walk thru each array and compare points to give // the higher point the top number on that circle _.each(team1, function (team1, i) { var team1Score = team1.score; var team2Score = team2[i].score; if(team2Score > team1Score){ team2[i].higher = true; } else { team1.higher = true; } }); var xAxisData = ['Start', '1st Half', 'Total']; var margin = 20; var xaxisMargin = 30; var aboveCricleMargin = 30; var width = 300 - margin - margin; var height = 150 - margin - margin; // define our X and Y scale functions var y = d3.scale.linear() .domain([0, d3.max(team1, function (t) { return t.score; })]) .range([0+xaxisMargin, height-aboveCricleMargin]); var x = d3.scale.linear() .domain([0, team1.length]) // don't know why I need to make the range extra big to get the // drawing to render correctly .range([0, width + margin*5]); // define our line function var line = d3.svg.line() .x(function (d, i) { return x(i); }) .y(function (d) { // the -1 is need since the axis system is currently flipped // which is the default in an svg element return -1 * y(d); }); //// RENDERING STEPS // define svg chart var svg = d3.select(this.$el.find('.chart')[0]) .append('svg') .attr('width', width + margin + margin) .attr('height', height + margin + margin) .append('g') .attr('transform', 'translate('+margin+','+margin+')'); // define a shifted group to make a more natural x,y coordinate system // out of the SVG area var shiftGroup = svg.append('g') .attr('transform', 'translate( 0,' + height + ')'); // draw dead simple xAxis line shiftGroup.append('line') .attr('class', 'x axis') .attr('x1', 0 - margin) .attr('y1', - xaxisMargin/2) .attr('x2', function () { return x(3) + margin; }) .attr('y2', - xaxisMargin/2); // add labels to xAxis shiftGroup.selectAll('text') .data(xAxisData) .enter() .append('text') .text(function(d){return d;}) .attr('y', 0) .attr('x', function (d, i) { return x(i); }) .attr('class', 'xAxisText'); // .style('text-anchor', 'middle'); //// CREATE G's FOR EACH TEAM // render winner last !!! var team2Group; var team1Group; if(this.model.get('winner') == 'team1'){ team2Group = shiftGroup.append('g').attr('class', 'team2'); team1Group = shiftGroup.append('g').attr('class', 'team1 winner'); } else { team1Group = shiftGroup.append('g').attr('class', 'team1'); team2Group = shiftGroup.append('g').attr('class', 'team2 winner'); } //// TEAM 1 & TEAM 2 PATHS createTeamPaths( team1Group, team1 ); createTeamPaths( team2Group, team2 ); function createTeamPaths (g, data) { g.append('path') .datum(data) .attr('class', 'scoreLine') .attr('d', function (data) { var score = []; _.each(data,function (datum) { score.push(datum.score); }); return line(score); }); } //// TEAM 1 & TEAM 2 POINTS createTeamCircles( team1Group, team1 ); createTeamCircles( team2Group, team2 ); function createTeamCircles ( g, data ) { var node = g.selectAll('.nodes') .data(data) .enter() .append('g') .attr('class', 'node'); // circle node.append('circle') .attr('class', 'scoreCircle') .attr('r', function (d) { // make the first zero nothing if (d.score===0) return 0; return 5; }) .attr('cx', function (d, i) { return x(i); }) .attr('cy', function (d) { return -1 * y(d.score); }); // text node.append('text') .attr('dx', function(d, i){ return x(i); }) .attr('dy', function (d) { var bump = -15; if(d.higher){ bump = 12; } return (-1 * y(d.score)) - bump; }) .text(function (d) { if(d.score===0) return ''; return d.score; }); } } // renderChart
export default function (svg, edgeLayers, nodes, hasTrainingData) { const xScale = d3.scale.linear() .domain([0, nodes.length - 1]) .range([ graphConstants.INPUT_LAYER_NODE_WIDTH, graphConstants.WIDTH - graphConstants.OUTPUT_LAYER_LABEL ]); function getYScale(index) { return d3.scale.linear() .domain([0, nodes[index].length]) .range([0, graphConstants.HEIGHT - graphConstants.HEADER_HEIGHT]); } const edgeBoxes = svg .selectAll(`.edge-layer`) .data(edgeLayers); edgeBoxes .enter() .append('g') .attr('class', 'edge-layer'); const edges = edgeBoxes .selectAll('.edge') .data(d => d); // Entering edges edges .enter() .append('line') .attr('class', `edge`); // Edge definitions edges .attr('x1', (d, i, layerIndex) => { if (layerIndex === 0) { return graphConstants.INPUT_LAYER_NODE_WIDTH; } if (hasTrainingData) { return xScale(layerIndex) + graphConstants.BIAS_LABEL_WIDTH - 5; } return xScale(layerIndex) + graphConstants.BIAS_LABEL_WIDTH / 2 - 5; }) .attr('x2', (d, i, layerIndex) => { const { BIAS_LABEL_WIDTH, OUTPUT_LAYER_NODE_WIDTH } = graphConstants; // Handle last layer with special case for simplicity if (layerIndex === edgeLayers.length - 1) { return xScale(layerIndex + 1) - BIAS_LABEL_WIDTH - OUTPUT_LAYER_NODE_WIDTH; } if (hasTrainingData) { return xScale(layerIndex + 1) - BIAS_LABEL_WIDTH; } return xScale(layerIndex + 1) - BIAS_LABEL_WIDTH / 2 - 5; }) .attr('y1', (d, i, layerIndex) => { return getYScale(layerIndex)(d.source.index) + getYScale(layerIndex)(1) / 2; }) .attr('y2', (d, i, layerIndex) => { return getYScale(layerIndex + 1)(d.target.index) + getYScale(layerIndex + 1)(1) / 2; }) .attr('stroke-width', (d, i, layerIndex) => { const thicken = edgeLayers[layerIndex].length < 1000; let zScore = Math.abs(d.zScore); if (zScore > 6) { zScore = 6; } if (hasTrainingData) { if (thicken) { return zScore * .8; // return '.9'; // const factor = d.isOn ? 1.2 : .7; // return zScore * factor; //return d.isOn ? '1.5' : '.6'; } return zScore * 0.08; } else { if (thicken) { return zScore * 0.6; } return zScore * 0.07; } }) .style('stroke', d => { if (hasTrainingData) { return d.isOn ? graphConstants.WITH_TRAINING_ON : graphConstants.WITH_TRAINING_OFF; } else { return d.weight > 0 ? graphConstants.NO_TRAINING_NEGATIVE : graphConstants.NO_TRAINING_POSITIVE; } }); // lines.exit().remove(); }
export default function() { // creates an array with four elements, representing the high, low, open and close // values of the given array function highLowOpenClose(data) { var xValueAccessor = sparkline.xValue(), yValueAccessor = sparkline.yValue(); var high = d3.max(data, yValueAccessor); var low = d3.min(data, yValueAccessor); function elementWithYValue(value) { return data.filter(function(d) { return yValueAccessor(d) === value; })[0]; } return [{ x: xValueAccessor(data[0]), y: yValueAccessor(data[0]) }, { x: xValueAccessor(elementWithYValue(high)), y: high }, { x: xValueAccessor(elementWithYValue(low)), y: low }, { x: xValueAccessor(data[data.length - 1]), y: yValueAccessor(data[data.length - 1]) }]; } var xScale = dateTime(); var yScale = d3.scale.linear(); var radius = 2; var line = _line(); // configure the point series to render the data from the // highLowOpenClose function var point = _point() .xValue(function(d) { return d.x; }) .yValue(function(d) { return d.y; }) .decorate(function(sel) { sel.attr('class', function(d, i) { switch (i) { case 0: return 'open'; case 1: return 'high'; case 2: return 'low'; case 3: return 'close'; } }); }); var multi = _multi() .series([line, point]) .mapping(function(series) { switch (series) { case point: return highLowOpenClose(this); default: return this; } }); var sparkline = function(selection) { point.size(radius * radius * Math.PI); selection.each(function(data) { var container = d3.select(this); var dimensions = innerDimensions(this); var margin = radius; xScale.range([margin, dimensions.width - margin]); yScale.range([dimensions.height - margin, margin]); multi.xScale(xScale) .yScale(yScale); container.call(multi); }); }; rebindAll(sparkline, xScale, include('discontinuityProvider', 'domain'), prefix('x')); rebindAll(sparkline, yScale, include('domain'), prefix('y')); rebindAll(sparkline, line, include('xValue', 'yValue')); sparkline.xScale = function() { return xScale; }; sparkline.yScale = function() { return yScale; }; sparkline.radius = function(x) { if (!arguments.length) { return radius; } radius = x; return sparkline; }; return sparkline; }
var _ = require('underscore'); var util = require('../util'); var d3 = require('d3'); var color = d3.scale.category20(); var locale = d3.locale({ "decimal": ".", "thousands": ",", "grouping": [3], "currency": ["$", ""], "dateTime": "%a %b %e %X %Y", "date": "%m/%d/%Y", "time": "%H:%M:%S", "periods": ["AM", "PM"], "days": ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], "shortDays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], "months": ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], "shortMonths": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] }) var data = [{ name: "http requests", dates: [new Date('2014/09/15 13:24:54'), new Date('2014/09/15 13:25:03'), new Date('2014/09/15 13:25:05')] }, { name: "SQL queries", dates: [new Date('2014/09/15 13:24:57'), new Date('2014/09/15 13:25:04'), new Date('2014/09/15 13:25:04')] }, { name: "cache invalidations", dates: [new Date('2014/09/15 13:25:12')] }];
done : function(errors, window) { function pick(key) { return function(d) { return d[key]; }; } function unitify(unit) { return function(d) { return '' + d + unit; }; } var x = 1280; var y = 1024; var thumbnailMargin = 20; var el = window.document.querySelector('#svg'); var svgimg = d3.select(el); svgimg.append("text").text(map.name) .style('font-weight', 'bold') .attr('y', 20) .attr('x', 40); var nodes = _.groupBy(map.nodes, 'componentId'); var _dependencyConnections = []; var _actionConnections = []; for(var i = 0; i < map.connections.length; i++){ if(map.connections[i].scope === 'Actions'){ _actionConnections.push(map.connections[i]); } else { _dependencyConnections.push(map.connections[i]); } } var dependencyConnections = _dependencyConnections.map(function(c) { return [ nodes[c.pageSourceId][0], nodes[c.pageTargetId][0] ]; }); var actionConnections = _actionConnections.map(function(c) { return [ nodes[c.pageSourceId][0], nodes[c.pageTargetId][0] ]; }); // basic size var margin = {top: (thumbnailMargin + 50), right: thumbnailMargin, bottom: thumbnailMargin, left: thumbnailMargin}; var width = x - margin.left - margin.right; var height = y - margin.top - margin.bottom; // scales var x = d3.scale.linear().range([0, width]); var y = d3.scale.linear().range([0, height]); var line = d3.svg.line() .x(_.compose(x, pick('positionX'))) .y(_.compose(y, pick('positionY'))); // setup viz container var mapViz = svgimg .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom) .append('g') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); // x legend var legendItems = ['Genesis', 'Custom built', 'Product(or rental)', 'Commodity/Utility']; var xLegend = d3.scale.ordinal() .domain(legendItems) .rangeBands([0, width], 0, 0.1); mapViz.append('g') .attr('class', 'x axis') .attr('transform', 'translate(0,' + height + ')') .call(function(el) { el.append('path') .attr('d', line([{positionX:0, positionY:0}, {positionX:1, positionY:0}])) .style('stroke','grey').style('stroke-width', '2px').style('marker-end', 'url(#arrow)'); el.selectAll('g.label') .data(legendItems) .enter() .append('g') .classed('label', true) .attr('transform', function(d) { return 'translate(' + xLegend(d) + ',15)'; }) .append('text') .text(_.identity); }); mapViz.append('g') .classed('evolution-marker', true) .selectAll('path') .data(_.tail(legendItems)) .enter() .append('path') .attr('transform', function(d) { return 'translate(' + (xLegend(d) - 40) + ',0)'; }) .attr('d', line([{positionX:0, positionY:1}, {positionX:0, positionY:0}])) .style('stroke','grey').style('stroke-width', '1px').style('stroke-dasharray', '1,5'); // y legend mapViz.append('g') .attr('class', 'y axis') .call(function(el) { el.append('path') .attr('d', line([{positionX:0, positionY:1}, {positionX:0, positionY:0}])) .style('stroke','grey').style('stroke-width', '2px').style('marker-end', 'url(#arrow)'); }); //action connections mapViz .selectAll('.connection') .data(actionConnections) .enter() .append('path') .classed('action', true) .attr('d', line) .style('stroke','green') .style('stroke-width', '2px') .style('marker-end', 'url(#actionarrow)'); // regular dependencies mapViz .selectAll('.connection') .data(dependencyConnections) .enter() .append('path') .classed('connection', true) .attr('d', line) .style('stroke','grey').style('stroke-width', '2px'); // nodes mapViz .append('g') .classed('nodes', true) .selectAll('node') .data(map.nodes) .enter() .append('g') .classed({ node: true, external: pick('external'), userneed: pick('userneed') }) .call(function(gnode) { var moveX = _.compose(x, pick('positionX')); var moveY = _.compose(y, pick('positionY')); gnode .append('circle') .attr({ r: '10px', 'cx': moveX, 'cy': moveY }) .style('fill','silver').style('stroke','black').style('stroke-width', '2px'); gnode.append('text') .style('filter', 'url(#drop-shadow)') .attr('transform', function(d) { return 'translate(' + (moveX(d) + 12) + ',' + (moveY(d) - 8) + ')'; }) .text(pick('name')); }); mapViz.selectAll('.userneed > circle').style('stroke-width', '4px'); mapViz.selectAll('.external > circle').style('fill', 'white'); var svgXML = (new xmldom.XMLSerializer()).serializeToString(el); res.setHeader('Content-Type', 'image/svg+xml'); res.send('<?xml version="1.0" encoding="UTF-8" standalone="no"?>' + svgXML); }