/* requires D3 + https://github.com/Caged/d3-tip */ d3.scatterplotwithlineplot = function() { function scatterplotwithlineplot(selection) { selection.each(function(d, i) { //options var data = (typeof(data) === "function" ? data(d) : d.data), lines = (typeof(lines) === "function" ? lines(d) : d.lines), margin = (typeof(margin) === "function" ? margin(d) : d.margin), axes = (typeof(axes) === "function" ? axes(d) : d.axes), minmax = (typeof(minmax) === "function" ? minmax(d) : d.minmax), size = (typeof(size) === "function" ? size(d) : d.size); // chart sizes var width = size['width'] - margin.left - margin.right, height = size['height'] - margin.top - margin.bottom; //scales var xScale = d3.scale.linear() .domain([minmax['x']['min'], minmax['x']['max']]) .range([0, width]) var yScale = d3.scale.linear() .domain([minmax['y']['min'], minmax['y']['max']]) .range([height, 0]) var rScale = d3.scale.linear() .domain([minmax['r']['min'],minmax['r']['max']]) .range([minmax['rrange']['min'],minmax['rrange']['max']]); //axes var xAxis = d3.svg.axis() .scale(xScale) .orient("bottom"); //.ticks(5); //.tickSize(16, 0); var yAxis = d3.svg.axis() .scale(yScale) .orient("left"); //.ticks(5); var element = d3.select(this); //Create X axis element.append("g") .attr("class", "axis x-axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); //Create Y axis element.append("g") .attr("class", "axis y-axis") .call(yAxis); limits = {"x":[minmax["x"]["min"],minmax["x"]["max"]],"y":[minmax["y"]["min"],minmax["y"]["max"]]} for (k in lines) { ps = linecross(lines[k],limits); if (ps.length == 2) { lines[k].x1 = ps[0][0]; lines[k].y1 = ps[0][1]; lines[k].x2 = ps[1][0]; lines[k].y2 = ps[1][1]; } way = get_sign(lines[k].b,lines[k].n1,lines[k].n2); lines[k].path =[corners(lines[k],limits,way),corners(lines[k],limits,-1*way)]; } var line = element.selectAll ('.line') .data(lines) .enter() .append("line") .attr("x1",function(d) {return xScale(d.x1)}) .attr("y1",function(d) {return yScale(d.y1)}) .attr("x2",function(d) {return xScale(d.x2)}) .attr("y2",function(d) {return yScale(d.y2)}) .attr("id", function (d, i) {return "q-" + i;}) .attr("class", function(d) { if (typeof(d['class'] != 'undefined')) return d['class']; else return 'line'; }) .on('mouseover', tip.show) .on('mouseout', tip.hide); element.selectAll(".circle") .data(data) .enter() .append("circle") .attr("cx", function(d) { return xScale(d.x); }) .attr("cy", function(d) { return yScale(d.y); }) .attr("r", function(d) { return rScale(1); }) .attr("class", function(d) { if (typeof(d['class'] != 'undefined')) return d['class']; else return 'circle'; }) .on('mouseover', tip.show) .on('mouseout', tip.hide); //axis labels element.append("text") .attr("class", "x-label label") .attr("text-anchor", "end") .attr("x", width) .attr("y", height-5) .text(axes['labels']['x']); element.append("text") .attr("class", "y label") .attr("text-anchor", "end") .attr("y", 5) .attr("x", 0) .attr("dy", ".75em") .attr("transform", "rotate(-90)") .text(axes['labels']['y']); }); } scatterplotwithlineplot.data = function(value) { if (!arguments.length) return value; data = value; return scatterplotwithlineplot; }; scatterplotwithlineplot.lines = function(value) { if (!arguments.length) return value; lines = value; return scatterplotwithlineplot; }; scatterplotwithlineplot.margin = function(value) { if (!arguments.length) return value; margin = value; return scatterplotwithlineplot; }; scatterplotwithlineplot.axes = function(value) { if (!arguments.length) return value; axes = value; return scatterplotwithlineplot; }; scatterplotwithlineplot.minmax = function(value) { if (!arguments.length) return value; minmax = value; return scatterplotwithlineplot; }; scatterplotwithlineplot.size = function(value) { if (!arguments.length) return value; size = value; return scatterplotwithlineplot; }; return scatterplotwithlineplot; function corners(l,e,o) { //l = {"a":0,"b":1} //line, a and slope, i.e., y=a+bx //e = {"x": [-10,10], "y": [-10,10]} //limits //o = 1 //orientation -1 or 1 //crossing x0, x1 //crossing y0, y1 outp = linecross (l,e); out = []; //vertices for (i=0;i<=1;i++){ for (j=0;j<=1;j++){ if (o*(l.a+l.b*e.x[i]-e.y[j]) > 0) outp.push([e.x[i],e.y[j]]); } } //sort the outps, anticlockwise if (outp.length > 0) { mid = [0,0]; for (i in outp) { mid[0] += outp[i][0]; mid[1] += outp[i][1]; } mid[0] = mid[0] / outp.length; mid[1] = mid[1] / outp.length; for (i in outp) { p = outp[i][1] - mid[1]; q = outp[i][0] - mid[0]; if (q != 0) outp[i][2] = Math.atan(p/q) + (q<0 ? Math.PI : 0); else outp[i][2] = Math.PI/2 + Math.PI*sign(p); } outp = outp.sort(function(w,z) { return w[2] > z[2]; }); for (i in outp) { outp[i].splice(2,1); out.push({"x":outp[i][0],"y":outp[i][1]}); } } return out; } function linecross (l,e) { out = []; //crossing x0, x1 for (i=0;i<=1;i++){ Y = l.a + l.b*e.x[i]; if ((Y > e.y[0]) && (Y < e.y[1])) out.push([e.x[i],Y]); } //crossing y0, y1 for (j=0;j<=1;j++){ if (l.b != 0) { X = (e.y[j] - l.a)/l.b; if ((X > e.x[0]) && (X < e.x[1])) out.push([X,e.y[j]]); } } return out; } function get_sign(b,d1,d2) { t = b*d1-d2; if (t > 0) return 1; if (t < 0) return -1; return 0; } }