diff --git a/js/metrics-graphics.js b/js/metrics-graphics.js index 2a9d4deb..b29f4ba8 100644 --- a/js/metrics-graphics.js +++ b/js/metrics-graphics.js @@ -79,10 +79,11 @@ function moz_chart() { moz.defaults.point = { ls: false, lowess: false, + point_size: 2.5, size_accessor: null, color_accessor: null, - size_range: null,//[1,5] - color_range: null,//['blue', 'red'] + size_range: null, // when we set a size_accessor option, this array determines the size range, e.g. [1,5] + color_range: null, // e.g. ['blue', 'red'] to color different groups of points size_domain: null, color_domain: null } @@ -441,40 +442,42 @@ function x_axis(args) { return args.scales.X(di[args.x_accessor]); } - if (args.chart_type=='point'){ + if (args.chart_type == 'point') { // figure out var min_size, max_size, min_color, max_color, size_range, color_range, size_domain, color_domain; - if (args.color_accessor!=null){ - if (args.color_domain==null){ + if (args.color_accessor != null){ + if (args.color_domain == null){ - min_color=d3.min(args.data[0], function(d){return d[args.color_accessor]}); - max_color=d3.max(args.data[0], function(d){return d[args.color_accessor]}); + min_color = d3.min(args.data[0], function(d){return d[args.color_accessor]}); + max_color = d3.max(args.data[0], function(d){return d[args.color_accessor]}); color_domain = [min_color, max_color]; } else { color_domain = args.color_domain; } - if (args.color_range==null){ + + if (args.color_range == null){ color_range = ['blue', 'red']; } else { color_range = args.color_range; } - args.scales.color=d3.scale.linear().domain(color_domain).range(color_range).clamp(true); + args.scales.color = d3.scale.linear().domain(color_domain).range(color_range).clamp(true); - args.scalefns.color=function(di){ + args.scalefns.color = function(di){ return args.scales.color(di[args.color_accessor]); }; } - if (args.size_accessor!=null){ - if (args.size_domain==null){ - min_size=d3.min(args.data[0], function(d){return d[args.size_accessor]}); - max_size=d3.max(args.data[0], function(d){return d[args.size_accessor]}); + if (args.size_accessor != null) { + + if (args.size_domain == null){ + min_size = d3.min(args.data[0], function(d){return d[args.size_accessor]}); + max_size = d3.max(args.data[0], function(d){return d[args.size_accessor]}); size_domain = [min_size, max_size]; } else { size_domain = args.size_domain; } - if (args.size_range==null){ + if (args.size_range == null){ size_range = [1,5];//args.size_domain; } else { size_range = args.size_range; @@ -482,7 +485,7 @@ function x_axis(args) { args.scales.size=d3.scale.linear().domain(size_domain).range(size_range).clamp(true); - args.scalefns.size=function(di){ + args.scalefns.size = function(di){ return args.scales.size(di[args.size_accessor]); }; } @@ -1591,12 +1594,10 @@ charts.point = function(args) { this.markers = function() { markers(args); - if (args.least_squares){ + if (args.least_squares) { add_ls(args); } - // if (args.lowess){ - // add_lowess(args); - // } + return this } @@ -1611,48 +1612,61 @@ charts.point = function(args) { var pts = g.selectAll('circle') .data(args.data[0]) .enter().append('svg:circle') + .attr('class', function(d, i) { return 'path-' + i; }) .attr('cx', args.scalefns.xf) .attr('cy', args.scalefns.yf); - if (args.color_accessor!=null){ + //are we coloring our points, or just using the default color? + if (args.color_accessor!=null) { pts.attr('fill', args.scalefns.color); pts.attr('stroke', args.scalefns.color); - } else { - pts.attr('fill', '#0000ff'); - pts.attr('stroke', '#0000ff'); } - if (args.size_accessor!=null){ + else { + pts.classed('points-mono', true); + } + + if (args.size_accessor != null) { pts.attr('r', args.scalefns.size); - } else { - pts.attr('r', 2); } + else { + pts.attr('r', args.point_size); + } + + //are we adding rug plots? var rug; - if (args.x_rug){ - //var data = args.data[0].map(function(d){return d[args.x_accessor]}); - rug=g.selectAll('line.x_rug').data(args.data[0]).enter().append('svg:line') - .attr('x1', args.scalefns.xf) - .attr('x2', args.scalefns.xf) - .attr('y1', args.height-args.top+args.buffer) - .attr('y2', args.height-args.top) - .attr('opacity', .3); - if (args.color_accessor){ + if (args.x_rug) { + rug = g.selectAll('line.x_rug').data(args.data[0]) + .enter().append('svg:line') + .attr('x1', args.scalefns.xf) + .attr('x2', args.scalefns.xf) + .attr('y1', args.height-args.top+args.buffer) + .attr('y2', args.height-args.top) + .attr('class', 'x-rug') + .attr('opacity', 0.3); + + if (args.color_accessor) { rug.attr('stroke', args.scalefns.color); - } else { - rug.attr('stroke', 'black'); + } + else { + rug.classed('x-rug-mono', true); } } - if (args.y_rug){ - rug=g.selectAll('line.y_rug').data(args.data[0]).enter().append('svg:line') - .attr('x1', args.left+1) - .attr('x2', args.left+args.buffer) - .attr('y1', args.scalefns.yf) - .attr('y2', args.scalefns.yf) - .attr('stroke', 'black') - .attr('opacity', .2); - if (args.color_accessor){ + + if (args.y_rug) { + rug = g.selectAll('line.y_rug').data(args.data[0]) + .enter().append('svg:line') + .attr('x1', args.left+1) + .attr('x2', args.left+args.buffer) + .attr('y1', args.scalefns.yf) + .attr('y2', args.scalefns.yf) + .attr('class', 'y-rug') + .attr('opacity', 0.3); + + if (args.color_accessor) { rug.attr('stroke', args.scalefns.color); - } else { - rug.attr('stroke', 'black'); + } + else { + rug.classed('y-rug-mono', true); } } @@ -1662,12 +1676,6 @@ charts.point = function(args) { this.rollover = function() { var svg = d3.select(args.target + ' svg'); - var clips = svg.append('g') - .attr('id', 'point-clips'); - - var paths = svg.append('g') - .attr('id', 'point-paths'); - //remove rollover text if it already exists if($(args.target + ' svg .active_datapoint').length > 0) { $(args.target + ' svg .active_datapoint').remove(); @@ -1680,32 +1688,26 @@ charts.point = function(args) { .attr('x', args.width - args.right) .attr('y', args.top / 2) .attr('text-anchor', 'end'); - - clips.selectAll('clipPath') - .data(args.data[0]) - .enter().append('clipPath') - .attr('id', function(d, i) { return 'clip-'+i;}) - .append('circle') - .attr('cx', args.scalefns.xf) - .attr('cy', args.scalefns.yf) - .attr('r', 20); + //add rollover paths var voronoi = d3.geom.voronoi() .x(args.scalefns.xf) - .y(args.scalefns.yf); + .y(args.scalefns.yf) + .clipExtent([[args.buffer, args.buffer], [args.width - args.buffer, args.height - args.buffer]]); + + var paths = svg.append('g') + .attr('class', 'voronoi'); paths.selectAll('path') .data(voronoi(args.data[0])) .enter().append('path') - .attr('d', function(d) { + .attr('d', function(d) { + if(d == undefined) return; return 'M' + d.join(',') + 'Z'; }) - .attr('id', function(d,i) { + .attr('class', function(d,i) { return 'path-' + i; }) - .attr('clip-path', function(d,i) { - return 'url(#clip-'+i+')'; - }) .style('fill-opacity', 0) .on('mouseover', this.rolloverOn(args)) .on('mouseout', this.rolloverOff(args)); @@ -1716,12 +1718,34 @@ charts.point = function(args) { this.rolloverOn = function(args) { var svg = d3.select(args.target + ' svg'); - return function(d, i){ + return function(d, i) { svg.selectAll('.points circle') - .classed('unselected', true); + .classed('selected', false); + + //highlight active point + var pts = svg.selectAll('.points circle.path-' + i) + .classed('selected', true); + + if (args.size_accessor) { + pts.attr('r', function(di) { + return args.scalefns.size(di) + 1 + }); + } else { + pts.attr('r', args.point_size); + } + + //trigger mouseover on all points for this class name in .linked charts + if(args.linked && !globals.link) { + globals.link = true; + + //trigger mouseover on matching point in .linked charts + d3.selectAll('.voronoi .path-' + i) + .each(function() { + d3.select(this).on('mouseover')(d,i); + }) + } var fmt = d3.time.format('%b %e, %Y'); - if (args.format == 'count') { var num = function(d_) { var is_float = d_ % 1 != 0; @@ -1738,18 +1762,6 @@ charts.point = function(args) { } } - //highlight active point - var pts = svg.selectAll('.points circle') - .filter(function(g,j){return i == j}) - .classed('unselected', false) - .classed('selected', true); - - if (args.size_accessor){ - pts.attr('r', function(di){return args.scalefns.size(di)+1}); - } else { - pts.attr('r', 3); - } - //update rollover text if (args.show_rollover_text) { svg.select('.active_datapoint') @@ -1768,22 +1780,36 @@ charts.point = function(args) { } }); } + + if(args.rollover_callback) { + args.rollover_callback(d, i); + } } } this.rolloverOff = function(args) { var svg = d3.select(args.target + ' svg'); - return function(d,i){ + return function(d,i) { + if(args.linked && globals.link) { + globals.link = false; + + d3.selectAll('.voronoi .path-' + i) + .each(function() { + d3.select(this).on('mouseout')(d,i); + }) + } + //reset active point var pts = svg.selectAll('.points circle') .classed('unselected', false) .classed('selected', false); - if (args.size_accessor){ + if (args.size_accessor) { pts.attr('r', args.scalefns.size); - } else { - pts.attr('r', 2); + } + else { + pts.attr('r', args.point_size); } //reset active data point text @@ -2238,8 +2264,7 @@ function add_ls(args){ .attr('x2', args.scales.X(max_x)) .attr('y1', args.scales.Y(args.ls_line.fit(min_x)) ) .attr('y2', args.scales.Y(args.ls_line.fit(max_x)) ) - .attr('stroke-width', 1) - .attr('stroke', 'red'); + .attr('class', 'least-squares-line') } function add_lowess(args){ @@ -2250,10 +2275,10 @@ function add_lowess(args){ .x(function(d){return args.scales.X(d.x)}) .y(function(d){return args.scales.Y(d.y)}) .interpolate(args.interpolate); + svg.append('path') .attr('d', line(lowess)) - .attr('stroke', 'red') - .attr('fill', 'none'); + .attr('class', 'lowess-line') } function lowess_robust(x, y, alpha, inc){ @@ -2304,14 +2329,29 @@ function lowess(x, y, alpha, inc){ } -function least_squares(x, y) { - var xi, yi, +function least_squares(x_, y_) { + var x, y, xi, yi, _x = 0, _y = 0, _xy = 0, _xx = 0; - var n = x.length; + var n = x_.length; + if (x_[0] instanceof Date){ + x = x_.map(function(d){ + return d.getTime(); + }); + } else { + x = x_; + }; + + if (y_[0] instanceof Date){ + y = y_.map(function(d){ + return d.getTime(); + }); + } else { + y = y_; + }; var xhat = d3.mean(x); var yhat = d3.mean(y); @@ -2327,7 +2367,6 @@ function least_squares(x, y) { var beta = numerator / denominator; var x0 = yhat - beta * xhat; - return { x0:x0, beta:beta, diff --git a/js/metrics-graphics.min.js b/js/metrics-graphics.min.js index e55f124c..21ec61e3 100644 --- a/js/metrics-graphics.min.js +++ b/js/metrics-graphics.min.js @@ -1,5 +1,5 @@ 'use strict';var charts={};var globals={};globals.link=false;globals.version="0.6";function moz_chart(){var moz={};moz.defaults={};moz.defaults.all={missing_is_zero:false,legend:'',legend_target:'',error:'',animate_on_load:false,top:40,bottom:30,right:10,left:50,buffer:8,width:350,height:220,small_height_threshold:120,small_width_threshold:160,small_text:false,xax_count:6,xax_tick:5,yax_count:5,yax_tick:5,x_extended_ticks:false,y_extended_ticks:false,y_scale_type:'linear',max_x:null,max_y:null,min_x:null,min_y:null,point_size:2.5,x_accessor:'date',xax_units:'',x_label:'',x_axis:true,y_axis:true,y_accessor:'value',y_label:'',yax_units:'',transition_on_update:true,rollover_callback:null,show_rollover_text:true,show_confidence_band:null,xax_format:function(d){var df=d3.time.format('%b %d');var pf=d3.formatPrefix(d);return(this.x_accessor=='date')?df(d):pf.scale(d)+pf.symbol;},area:true,chart_type:'line',data:[],decimals:2,format:'count',inflator:10/9,linked:false,list:false,baselines:null,markers:null,scalefns:{},scales:{},show_years:true,target:'#viz',interpolate:'cardinal',custom_line_color_map:[],max_data_size:null} -moz.defaults.point={ls:false,lowess:false,size_accessor:null,color_accessor:null,size_range:null,color_range:null,size_domain:null,color_domain:null} +moz.defaults.point={ls:false,lowess:false,point_size:2.5,size_accessor:null,color_accessor:null,size_range:null,color_range:null,size_domain:null,color_domain:null} moz.defaults.histogram={rollover_callback:function(d,i){$('#histogram svg .active_datapoint').html('Frequency Count: '+d.y);},binned:false,bins:null,processed_x_accessor:'x',processed_y_accessor:'y',processed_dx_accessor:'dx',bar_margin:1} moz.defaults.bar={y_accessor:'factor',x_accessor:'value',binned:false,padding_percentage:.1,outer_padding_percentage:.1,height:500,top:20} moz.defaults.missing={top:0,bottom:0,right:0,left:0,width:350,height:220} @@ -166,22 +166,30 @@ this.init(args);return this;} charts.point=function(args){this.args=args;this.init=function(args){raw_data_transformation(args);process_point(args);init(args);x_axis(args);y_axis(args);return this;} this.markers=function(){markers(args);if(args.least_squares){add_ls(args);} return this} -this.mainPlot=function(){var svg=d3.select(args.target+' svg');var g;g=svg.append('g').classed('points',true);var pts=g.selectAll('circle').data(args.data[0]).enter().append('svg:circle').attr('cx',args.scalefns.xf).attr('cy',args.scalefns.yf);if(args.color_accessor!=null){pts.attr('fill',args.scalefns.color);pts.attr('stroke',args.scalefns.color);}else{pts.attr('fill','#0000ff');pts.attr('stroke','#0000ff');} -if(args.size_accessor!=null){pts.attr('r',args.scalefns.size);}else{pts.attr('r',2);} -var rug;if(args.x_rug){rug=g.selectAll('line.x_rug').data(args.data[0]).enter().append('svg:line').attr('x1',args.scalefns.xf).attr('x2',args.scalefns.xf).attr('y1',args.height-args.top+args.buffer).attr('y2',args.height-args.top).attr('opacity',.3);if(args.color_accessor){rug.attr('stroke',args.scalefns.color);}else{rug.attr('stroke','black');}} -if(args.y_rug){rug=g.selectAll('line.y_rug').data(args.data[0]).enter().append('svg:line').attr('x1',args.left+1).attr('x2',args.left+args.buffer).attr('y1',args.scalefns.yf).attr('y2',args.scalefns.yf).attr('stroke','black').attr('opacity',.2);if(args.color_accessor){rug.attr('stroke',args.scalefns.color);}else{rug.attr('stroke','black');}} +this.mainPlot=function(){var svg=d3.select(args.target+' svg');var g;g=svg.append('g').classed('points',true);var pts=g.selectAll('circle').data(args.data[0]).enter().append('svg:circle').attr('class',function(d,i){return'path-'+i;}).attr('cx',args.scalefns.xf).attr('cy',args.scalefns.yf);if(args.color_accessor!=null){pts.attr('fill',args.scalefns.color);pts.attr('stroke',args.scalefns.color);} +else{pts.classed('points-mono',true);} +if(args.size_accessor!=null){pts.attr('r',args.scalefns.size);} +else{pts.attr('r',args.point_size);} +var rug;if(args.x_rug){rug=g.selectAll('line.x_rug').data(args.data[0]).enter().append('svg:line').attr('x1',args.scalefns.xf).attr('x2',args.scalefns.xf).attr('y1',args.height-args.top+args.buffer).attr('y2',args.height-args.top).attr('class','x-rug').attr('opacity',0.3);if(args.color_accessor){rug.attr('stroke',args.scalefns.color);} +else{rug.classed('x-rug-mono',true);}} +if(args.y_rug){rug=g.selectAll('line.y_rug').data(args.data[0]).enter().append('svg:line').attr('x1',args.left+1).attr('x2',args.left+args.buffer).attr('y1',args.scalefns.yf).attr('y2',args.scalefns.yf).attr('class','y-rug').attr('opacity',0.3);if(args.color_accessor){rug.attr('stroke',args.scalefns.color);} +else{rug.classed('y-rug-mono',true);}} return this;} -this.rollover=function(){var svg=d3.select(args.target+' svg');var clips=svg.append('g').attr('id','point-clips');var paths=svg.append('g').attr('id','point-paths');if($(args.target+' svg .active_datapoint').length>0){$(args.target+' svg .active_datapoint').remove();} -svg.append('text').attr('class','active_datapoint').attr('xml:space','preserve').attr('x',args.width-args.right).attr('y',args.top/2).attr('text-anchor','end');clips.selectAll('clipPath').data(args.data[0]).enter().append('clipPath').attr('id',function(d,i){return'clip-'+i;}).append('circle').attr('cx',args.scalefns.xf).attr('cy',args.scalefns.yf).attr('r',20);var voronoi=d3.geom.voronoi().x(args.scalefns.xf).y(args.scalefns.yf);paths.selectAll('path').data(voronoi(args.data[0])).enter().append('path').attr('d',function(d){return'M'+d.join(',')+'Z';}).attr('id',function(d,i){return'path-'+i;}).attr('clip-path',function(d,i){return'url(#clip-'+i+')';}).style('fill-opacity',0).on('mouseover',this.rolloverOn(args)).on('mouseout',this.rolloverOff(args));return this;} -this.rolloverOn=function(args){var svg=d3.select(args.target+' svg');return function(d,i){svg.selectAll('.points circle').classed('unselected',true);var fmt=d3.time.format('%b %e, %Y');if(args.format=='count'){var num=function(d_){var is_float=d_%1!=0;var n=d3.format("0,000");d_=is_float?d3.round(d_,args.decimals):d_;return n(d_);}} +this.rollover=function(){var svg=d3.select(args.target+' svg');if($(args.target+' svg .active_datapoint').length>0){$(args.target+' svg .active_datapoint').remove();} +svg.append('text').attr('class','active_datapoint').attr('xml:space','preserve').attr('x',args.width-args.right).attr('y',args.top/2).attr('text-anchor','end');var voronoi=d3.geom.voronoi().x(args.scalefns.xf).y(args.scalefns.yf).clipExtent([[args.buffer,args.buffer],[args.width-args.buffer,args.height-args.buffer]]);var paths=svg.append('g').attr('class','voronoi');paths.selectAll('path').data(voronoi(args.data[0])).enter().append('path').attr('d',function(d){if(d==undefined)return;return'M'+d.join(',')+'Z';}).attr('class',function(d,i){return'path-'+i;}).style('fill-opacity',0).on('mouseover',this.rolloverOn(args)).on('mouseout',this.rolloverOff(args));return this;} +this.rolloverOn=function(args){var svg=d3.select(args.target+' svg');return function(d,i){svg.selectAll('.points circle').classed('selected',false);var pts=svg.selectAll('.points circle.path-'+i).classed('selected',true);if(args.size_accessor){pts.attr('r',function(di){return args.scalefns.size(di)+1});}else{pts.attr('r',args.point_size);} +if(args.linked&&!globals.link){globals.link=true;d3.selectAll('.voronoi .path-'+i).each(function(){d3.select(this).on('mouseover')(d,i);})} +var fmt=d3.time.format('%b %e, %Y');if(args.format=='count'){var num=function(d_){var is_float=d_%1!=0;var n=d3.format("0,000");d_=is_float?d3.round(d_,args.decimals):d_;return n(d_);}} else{var num=function(d_){var fmt_string=(args.decimals?'.'+args.decimals:'')+'%';var n=d3.format(fmt_string);return n(d_);}} -var pts=svg.selectAll('.points circle').filter(function(g,j){return i==j}).classed('unselected',false).classed('selected',true);if(args.size_accessor){pts.attr('r',function(di){return args.scalefns.size(di)+1});}else{pts.attr('r',3);} if(args.show_rollover_text){svg.select('.active_datapoint').text(function(){if(args.time_series){var dd=new Date(+d['point'][args.x_accessor]);dd.setDate(dd.getDate());return fmt(dd)+' '+args.yax_units +num(d['point'][args.y_accessor]);} else{return args.x_accessor+': '+num(d['point'][args.x_accessor]) +', '+args.y_accessor+': '+args.yax_units -+num(d['point'][args.y_accessor]);}});}}} -this.rolloverOff=function(args){var svg=d3.select(args.target+' svg');return function(d,i){var pts=svg.selectAll('.points circle').classed('unselected',false).classed('selected',false);if(args.size_accessor){pts.attr('r',args.scalefns.size);}else{pts.attr('r',2);} ++num(d['point'][args.y_accessor]);}});} +if(args.rollover_callback){args.rollover_callback(d,i);}}} +this.rolloverOff=function(args){var svg=d3.select(args.target+' svg');return function(d,i){if(args.linked&&globals.link){globals.link=false;d3.selectAll('.voronoi .path-'+i).each(function(){d3.select(this).on('mouseout')(d,i);})} +var pts=svg.selectAll('.points circle').classed('unselected',false).classed('selected',false);if(args.size_accessor){pts.attr('r',args.scalefns.size);} +else{pts.attr('r',args.point_size);} svg.select('.active_datapoint').text('');}} this.update=function(args){return this;} this.init(args);return this;} @@ -228,8 +236,8 @@ if(!processed_data.hasOwnProperty(this_dp))processed_data[this_dp]=0;processed_d processed_data=Object.keys(processed_data).map(function(d){var obj={};obj[args.x_accessor]=processed_data[d];obj[args.y_accessor]=d;return obj;})}else{processed_data=our_data;} args.data=[processed_data];return this;} function process_point(args){var data=args.data[0];var x=data.map(function(d){return d[args.x_accessor]});var y=data.map(function(d){return d[args.y_accessor]});if(args.least_squares){args.ls_line=least_squares(x,y);};return this;} -function add_ls(args){var svg=d3.select(args.target+' svg');var data=args.data[0];var min_x=args.scales.X.ticks(args.xax_count)[0];var max_x=args.scales.X.ticks(args.xax_count)[args.scales.X.ticks(args.xax_count).length-1];svg.append('svg:line').attr('x1',args.scales.X(min_x)).attr('x2',args.scales.X(max_x)).attr('y1',args.scales.Y(args.ls_line.fit(min_x))).attr('y2',args.scales.Y(args.ls_line.fit(max_x))).attr('stroke-width',1).attr('stroke','red');} -function add_lowess(args){var svg=d3.select(args.target+' svg');var lowess=args.lowess_line;var line=d3.svg.line().x(function(d){return args.scales.X(d.x)}).y(function(d){return args.scales.Y(d.y)}).interpolate(args.interpolate);svg.append('path').attr('d',line(lowess)).attr('stroke','red').attr('fill','none');} +function add_ls(args){var svg=d3.select(args.target+' svg');var data=args.data[0];var min_x=args.scales.X.ticks(args.xax_count)[0];var max_x=args.scales.X.ticks(args.xax_count)[args.scales.X.ticks(args.xax_count).length-1];svg.append('svg:line').attr('x1',args.scales.X(min_x)).attr('x2',args.scales.X(max_x)).attr('y1',args.scales.Y(args.ls_line.fit(min_x))).attr('y2',args.scales.Y(args.ls_line.fit(max_x))).attr('class','least-squares-line')} +function add_lowess(args){var svg=d3.select(args.target+' svg');var lowess=args.lowess_line;var line=d3.svg.line().x(function(d){return args.scales.X(d.x)}).y(function(d){return args.scales.Y(d.y)}).interpolate(args.interpolate);svg.append('path').attr('d',line(lowess)).attr('class','lowess-line')} function lowess_robust(x,y,alpha,inc){var _l;var r=[];var yhat=d3.mean(y);for(var i=0;i=0&&u<=1){return Math.pow(1-Math.pow(u,w),w)}else{return 0}} function _bisquare_weight(u){return _pow_weight(u,2);}