示例#1
0
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);
示例#2
0
 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();
 }
示例#4
0
  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;
  };
示例#5
0
文件: index.js 项目: acobat/xo-web
  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()
    })
  }
示例#6
0
        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);
	    }
	}*/
}
示例#11
0
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;
});
示例#12
0
文件: app.js 项目: CJStadler/Trackr
				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;
示例#13
0
  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]);
        });
      }
    });
  },
示例#14
0
文件: utils.js 项目: ariarijp/redash
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,
    };
  });
}
示例#15
0
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
  };

}
示例#16
0
    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);
    }
示例#17
0
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'
    })
示例#18
0
    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);});

    }
示例#19
0
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);
      }
    );


}
示例#20
0
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
  
}
示例#22
0
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="&#10004;",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+":&nbsp;"+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",
示例#23
0
文件: index.js 项目: kitsuyui/redash
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: '&copy; <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);
    },
  };
}
示例#24
0
文件: index.js 项目: yhyuan/hegel
		    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');
			}
示例#25
0
 getYScale() {
   return d3.scale.linear().range([this.height, 0])
            .domain([this.yMin, this.yMax]);
 }
示例#26
0
文件: popup.js 项目: cogell/brackets
    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
示例#27
0
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();
}
示例#28
0
文件: sparkline.js 项目: gabhi/d3fc
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;
}
示例#29
0
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')]
}];
示例#30
0
		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);
		}