Related
I saw this post last night and wanted to replicate it/make it better - because it interested me. However, I'm stuck on displaying the selections to an SVG rectangle. I thought by appending text to the function where the Lasso is called would work, however it doesn't. I've included the problematic code and would like to know where I've gone wrong for me to improve. Thanks.
If you need anymore code, holla.
var textBox = svg.append("g")
.attr("transform", "translate(5,385)");
textBox.append("rect")
.attr("height", 10)
.attr("width", 100)
.style("stroke", "black")
.style("fill", "none")
.style("stroke-width", 1);
var lasso_start = function() {
lasso.items()
.attr("r",7)
.classed("not_possible",true)
.classed("selected",false);
};
var lasso_draw = function() {
lasso.possibleItems()
.classed("not_possible",false)
.classed("possible",true);
lasso.notPossibleItems()
.classed("not_possible",true)
.classed("possible",false);
};
var lasso_end = function() {
lasso.items()
.classed("not_possible",false)
.classed("possible",false);
selected = lasso.selectedItems()
.classed("selected", true)
.attr("r", 13);
var selectedDots = selected.data().map(d=>d[0]);
};
var lasso = d3.lasso()
.closePathSelect(true)
.closePathDistance(100)
.items(circles)
.targetArea(area)
.on("start",lasso_start)
.on("draw",lasso_draw)
.on("end",function(d) {
textBox.append("text")
.attr("x", 10)
.attr("y", 20)
.text(lasso_end)
});
svg.call(lasso);
Modifying the block that I posted as an answer to this post to include the selections in a rect, here's a fork for the same:
https://bl.ocks.org/shashank2104/d58381c96971ff131c03e8832470085b/151651c9fa3e94fef46e243227924f4e4c90cf01
And a code snippet if that's convenient:
#import url('https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300');
#import url("https://fonts.googleapis.com/css?family=Lato:300");
text {
font-family: "Open Sans Condensed";
}
.axis path {
stroke: black;
}
.tick line {
visibility: hidden;
}
.border {
margin-top: 9px;
margin-left: 29px;
border: .5px solid black;
width: 325px;
height: 335px;
position: absolute;
}
.lasso path {
stroke: rgb(80,80,80);
stroke-width:2px;
}
.lasso .drawn {
fill-opacity:.05 ;
}
.lasso .loop_close {
fill:none;
stroke-dasharray: 4, 4;
}
.lasso .origin {
fill:#3399FF;
fill-opacity:.5;
}
.not_possible {
fill:rgb(200,200,200);
}
.textBox rect {
fill: steelblue;
}
text.selection {
font-size: 12px;
fill: #FFF;
letter-spacing: 0.1em;
font-weight: 300;
}
<!DOCTYPE html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("d3-selection"),require("d3-drag")):"function"==typeof define&&define.amd?define(["exports","d3-selection","d3-drag"],n):n(t.d3=t.d3||{},t.d3,t.d3)}(this,function(t,n,r){"use strict";function e(t,n){return n={exports:{}},t(n,n.exports),n.exports}function o(){function t(t){function u(){p=[],h="",_.attr("d",null),m.attr("d",null),r.nodes().forEach(function(t){t.__lasso.possible=!1,t.__lasso.selected=!1,t.__lasso.hoverSelect=!1,t.__lasso.loopSelect=!1;var n=t.getBoundingClientRect();t.__lasso.lassoPoint=[Math.round(n.left+n.width/2),Math.round(n.top+n.height/2)]}),s&&r.on("mouseover.lasso",function(){this.__lasso.hoverSelect=!0}),i.start()}function l(){var t,n;"touchmove"===d3.event.sourceEvent.type?(t=d3.event.sourceEvent.touches[0].clientX,n=d3.event.sourceEvent.touches[0].clientY):(t=d3.event.sourceEvent.clientX,n=d3.event.sourceEvent.clientY);var s=d3.mouse(this)[0],u=d3.mouse(this)[1];""===h?(h=h+"M "+s+" "+u,v=[t,n],d=[s,u],b.attr("cx",s).attr("cy",u).attr("r",7).attr("display",null)):h=h+" L "+s+" "+u,p.push([t,n]);var l=Math.sqrt(Math.pow(t-v[0],2)+Math.pow(n-v[1],2)),f="M "+s+" "+u+" L "+d[0]+" "+d[1];_.attr("d",h),m.attr("d",f),a=l<=e,a&&o?m.attr("display",null):m.attr("display","none"),r.nodes().forEach(function(t){t.__lasso.loopSelect=!(!a||!o)&&c(p,t.__lasso.lassoPoint)<1,t.__lasso.possible=t.__lasso.hoverSelect||t.__lasso.loopSelect}),i.draw()}function f(){r.on("mouseover.lasso",null),r.nodes().forEach(function(t){t.__lasso.selected=t.__lasso.possible,t.__lasso.possible=!1}),_.attr("d",null),m.attr("d",null),b.attr("display","none"),i.end()}var h,v,d,p,g=t.append("g").attr("class","lasso"),_=g.append("path").attr("class","drawn"),m=g.append("path").attr("class","loop_close"),b=g.append("circle").attr("class","origin"),M=d3.drag().on("start",u).on("drag",l).on("end",f);n.call(M)}var n,r=[],e=75,o=!0,a=!1,s=!0,i={start:function(){},draw:function(){},end:function(){}};return t.items=function(n){if(!arguments.length)return r;r=n;var e=r.nodes();return e.forEach(function(t){t.__lasso={possible:!1,selected:!1}}),t},t.possibleItems=function(){return r.filter(function(){return this.__lasso.possible})},t.selectedItems=function(){return r.filter(function(){return this.__lasso.selected})},t.notPossibleItems=function(){return r.filter(function(){return!this.__lasso.possible})},t.notSelectedItems=function(){return r.filter(function(){return!this.__lasso.selected})},t.closePathDistance=function(n){return arguments.length?(e=n,t):e},t.closePathSelect=function(n){return arguments.length?(o=n===!0,t):o},t.isPathClosed=function(n){return arguments.length?(a=n===!0,t):a},t.hoverSelect=function(n){return arguments.length?(s=n===!0,t):s},t.on=function(n,r){if(!arguments.length)return i;if(1===arguments.length)return i[n];var e=["start","draw","end"];return e.indexOf(n)>-1&&(i[n]=r),t},t.targetArea=function(r){return arguments.length?(n=r,t):n},t}var a=e(function(t){function n(t,n,e){var o=t*n,a=r*t,s=a-t,i=a-s,u=t-i,l=r*n,f=l-n,c=l-f,h=n-c,v=o-i*c,d=v-u*c,p=d-i*h,g=u*h-p;return e?(e[0]=g,e[1]=o,e):[g,o]}t.exports=n;var r=+(Math.pow(2,27)+1)}),s=e(function(t){function n(t,n){var r=t+n,e=r-t,o=r-e,a=n-e,s=t-o,i=s+a;return i?[i,r]:[r]}function r(t,r){var e=0|t.length,o=0|r.length;if(1===e&&1===o)return n(t[0],r[0]);var a,s,i=e+o,u=new Array(i),l=0,f=0,c=0,h=Math.abs,v=t[f],d=h(v),p=r[c],g=h(p);d<g?(s=v,f+=1,f<e&&(v=t[f],d=h(v))):(s=p,c+=1,c<o&&(p=r[c],g=h(p))),f<e&&d<g||c>=o?(a=v,f+=1,f<e&&(v=t[f],d=h(v))):(a=p,c+=1,c<o&&(p=r[c],g=h(p)));for(var _,m,b,M,y,w=a+s,x=w-a,j=s-x,E=j,A=w;f<e&&c<o;)d<g?(a=v,f+=1,f<e&&(v=t[f],d=h(v))):(a=p,c+=1,c<o&&(p=r[c],g=h(p))),s=E,w=a+s,x=w-a,j=s-x,j&&(u[l++]=j),_=A+w,m=_-A,b=_-m,M=w-m,y=A-b,E=y+M,A=_;for(;f<e;)a=v,s=E,w=a+s,x=w-a,j=s-x,j&&(u[l++]=j),_=A+w,m=_-A,b=_-m,M=w-m,y=A-b,E=y+M,A=_,f+=1,f<e&&(v=t[f]);for(;c<o;)a=p,s=E,w=a+s,x=w-a,j=s-x,j&&(u[l++]=j),_=A+w,m=_-A,b=_-m,M=w-m,y=A-b,E=y+M,A=_,c+=1,c<o&&(p=r[c]);return E&&(u[l++]=E),A&&(u[l++]=A),l||(u[l++]=0),u.length=l,u}t.exports=r}),i=e(function(t){function n(t,n,r){var e=t+n,o=e-t,a=e-o,s=n-o,i=t-a;return r?(r[0]=i+s,r[1]=e,r):[i+s,e]}t.exports=n}),u=e(function(t){function n(t,n){var o=t.length;if(1===o){var a=r(t[0],n);return a[0]?a:[a[1]]}var s=new Array(2*o),i=[.1,.1],u=[.1,.1],l=0;r(t[0],n,i),i[0]&&(s[l++]=i[0]);for(var f=1;f<o;++f){r(t[f],n,u);var c=i[1];e(c,u[0],i),i[0]&&(s[l++]=i[0]);var h=u[1],v=i[1],d=h+v,p=d-h,g=v-p;i[1]=d,g&&(s[l++]=g)}return i[1]&&(s[l++]=i[1]),0===l&&(s[l++]=0),s.length=l,s}var r=a,e=i;t.exports=n}),l=e(function(t){function n(t,n){var r=t+n,e=r-t,o=r-e,a=n-e,s=t-o,i=s+a;return i?[i,r]:[r]}function r(t,r){var e=0|t.length,o=0|r.length;if(1===e&&1===o)return n(t[0],-r[0]);var a,s,i=e+o,u=new Array(i),l=0,f=0,c=0,h=Math.abs,v=t[f],d=h(v),p=-r[c],g=h(p);d<g?(s=v,f+=1,f<e&&(v=t[f],d=h(v))):(s=p,c+=1,c<o&&(p=-r[c],g=h(p))),f<e&&d<g||c>=o?(a=v,f+=1,f<e&&(v=t[f],d=h(v))):(a=p,c+=1,c<o&&(p=-r[c],g=h(p)));for(var _,m,b,M,y,w=a+s,x=w-a,j=s-x,E=j,A=w;f<e&&c<o;)d<g?(a=v,f+=1,f<e&&(v=t[f],d=h(v))):(a=p,c+=1,c<o&&(p=-r[c],g=h(p))),s=E,w=a+s,x=w-a,j=s-x,j&&(u[l++]=j),_=A+w,m=_-A,b=_-m,M=w-m,y=A-b,E=y+M,A=_;for(;f<e;)a=v,s=E,w=a+s,x=w-a,j=s-x,j&&(u[l++]=j),_=A+w,m=_-A,b=_-m,M=w-m,y=A-b,E=y+M,A=_,f+=1,f<e&&(v=t[f]);for(;c<o;)a=p,s=E,w=a+s,x=w-a,j=s-x,j&&(u[l++]=j),_=A+w,m=_-A,b=_-m,M=w-m,y=A-b,E=y+M,A=_,c+=1,c<o&&(p=-r[c]);return E&&(u[l++]=E),A&&(u[l++]=A),l||(u[l++]=0),u.length=l,u}t.exports=r}),f=e(function(t){function n(t,n){for(var r=new Array(t.length-1),e=1;e<t.length;++e)for(var o=r[e-1]=new Array(t.length-1),a=0,s=0;a<t.length;++a)a!==n&&(o[s++]=t[e][a]);return r}function r(t){for(var n=new Array(t),r=0;r<t;++r){n[r]=new Array(t);for(var e=0;e<t;++e)n[r][e]=["m",e,"[",t-r-1,"]"].join("")}return n}function e(t){return 1&t?"-":""}function o(t){if(1===t.length)return t[0];if(2===t.length)return["sum(",t[0],",",t[1],")"].join("");var n=t.length>>1;return["sum(",o(t.slice(0,n)),",",o(t.slice(n)),")"].join("")}function i(t){if(2===t.length)return[["sum(prod(",t[0][0],",",t[1][1],"),prod(-",t[0][1],",",t[1][0],"))"].join("")];for(var r=[],a=0;a<t.length;++a)r.push(["scale(",o(i(n(t,a))),",",e(a),t[0][a],")"].join(""));return r}function f(t){for(var e=[],a=[],s=r(t),u=[],l=0;l<t;++l)0===(1&l)?e.push.apply(e,i(n(s,l))):a.push.apply(a,i(n(s,l))),u.push("m"+l);var f=o(e),c=o(a),h="orientation"+t+"Exact",_=["function ",h,"(",u.join(),"){var p=",f,",n=",c,",d=sub(p,n);return d[d.length-1];};return ",h].join(""),m=new Function("sum","prod","scale","sub",_);return m(d,v,p,g)}function c(t){var n=x[t.length];return n||(n=x[t.length]=f(t.length)),n.apply(void 0,t)}function h(){for(;x.length<=_;)x.push(f(x.length));for(var n=[],r=["slow"],e=0;e<=_;++e)n.push("a"+e),r.push("o"+e);for(var o=["function getOrientation(",n.join(),"){switch(arguments.length){case 0:case 1:return 0;"],e=2;e<=_;++e)o.push("case ",e,":return o",e,"(",n.slice(0,e).join(),");");o.push("}var s=new Array(arguments.length);for(var i=0;i<arguments.length;++i){s[i]=arguments[i]};return slow(s);}return getOrientation"),r.push(o.join(""));var a=Function.apply(void 0,r);t.exports=a.apply(void 0,[c].concat(x));for(var e=0;e<=_;++e)t.exports[e]=x[e]}var v=a,d=s,p=u,g=l,_=5,m=1.1102230246251565e-16,b=(3+16*m)*m,M=(7+56*m)*m,y=f(3),w=f(4),x=[function(){return 0},function(){return 0},function(t,n){return n[0]-t[0]},function(t,n,r){var e,o=(t[1]-r[1])*(n[0]-r[0]),a=(t[0]-r[0])*(n[1]-r[1]),s=o-a;if(o>0){if(a<=0)return s;e=o+a}else{if(!(o<0))return s;if(a>=0)return s;e=-(o+a)}var i=b*e;return s>=i||s<=-i?s:y(t,n,r)},function(t,n,r,e){var o=t[0]-e[0],a=n[0]-e[0],s=r[0]-e[0],i=t[1]-e[1],u=n[1]-e[1],l=r[1]-e[1],f=t[2]-e[2],c=n[2]-e[2],h=r[2]-e[2],v=a*l,d=s*u,p=s*i,g=o*l,_=o*u,m=a*i,b=f*(v-d)+c*(p-g)+h*(_-m),y=(Math.abs(v)+Math.abs(d))*Math.abs(f)+(Math.abs(p)+Math.abs(g))*Math.abs(c)+(Math.abs(_)+Math.abs(m))*Math.abs(h),x=M*y;return b>x||-b>x?b:w(t,n,r,e)}];h()}),c=e(function(t){function n(t,n){for(var e=n[0],o=n[1],a=t.length,s=1,i=a,u=0,l=a-1;u<i;l=u++){var f=t[u],c=t[l],h=f[1],v=c[1];if(v<h){if(v<o&&o<h){var d=r(f,c,n);if(0===d)return 0;s^=0<d|0}else if(o===h){var p=t[(u+1)%a],g=p[1];if(h<g){var d=r(f,c,n);if(0===d)return 0;s^=0<d|0}}}else if(h<v){if(h<o&&o<v){var d=r(f,c,n);if(0===d)return 0;s^=d<0|0}else if(o===h){var p=t[(u+1)%a],g=p[1];if(g<h){var d=r(f,c,n);if(0===d)return 0;s^=d<0|0}}}else if(o===h){var _=Math.min(f[0],c[0]),m=Math.max(f[0],c[0]);if(0===u){for(;l>0;){var b=(l+a-1)%a,M=t[b];if(M[1]!==o)break;var y=M[0];_=Math.min(_,y),m=Math.max(m,y),l=b}if(0===l)return _<=e&&e<=m?0:1;i=l+1}for(var w=t[(l+a-1)%a][1];u+1<i;){var M=t[u+1];if(M[1]!==o)break;var y=M[0];_=Math.min(_,y),m=Math.max(m,y),u+=1}if(_<=e&&e<=m)return 0;var x=t[(u+1)%a][1];e<_&&w<o!=x<o&&(s^=1)}}return 2*s-1}t.exports=n;var r=f});t.lasso=o,Object.defineProperty(t,"__esModule",{value:!0})});</script>
</head>
<body>
<script>
var data = [["Arsenal",-0.0032967741593940836, 0.30399753945657115],["Chelsea", 0.2752159801936051, -0.0389675484210763], ["Liverpool",-0.005096951348655329, 0.026678627680541075], ["Manchester City",-0.004715381791104284, -0.12338379196523988], ["Manchester United",0.06877966010653305, -0.0850615090351779], ["Tottenham",-0.3379518099485709, -0.09933664174939877]];
var selectedTeams = [];
const colours = d3.scaleOrdinal()
.domain(data)
.range(["#F8B195", "#F67280", "#C06C84", "#6C5B7B", "#355C7D", "#2A363B"]);
var canvasW = 675;
var canvasH = 600;
var w = 365;
var h = 365;
var xPadding = 30;
var yPadding = 20;
var padding = 10;
var border = 0.5;
var bordercolor = 'black';
var xScale = d3.scaleLinear()
.range([xPadding, w - padding])
.domain([-1, 1]);
var yScale = d3.scaleLinear()
.range([h - yPadding, padding])
.domain([-1, 1]);
var svg = d3.select('body')
.append("svg")
.attr('width', canvasW)
.attr('height', canvasH);
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 7)
.attr("cx", function(d) { return xScale(d[1]); })
.attr("cy", function(d) { return yScale(d[2]); })
.attr("fill", function(d) {
var result = null;
if (data.indexOf(d) >= 0) {
result = colours(d);
} else {
result = "white";
}
return result;
});
var lasso_start = function() {
lasso.items()
.attr("r",7)
.classed("not_possible",true)
.classed("selected",false);
};
var lasso_draw = function() {
lasso.possibleItems()
.classed("not_possible",false)
.classed("possible",true);
lasso.notPossibleItems()
.classed("not_possible",true)
.classed("possible",false);
};
var modifyTextBox = function (selected) {
if(selected.length) {
var widths = [], textHeight = 0;
var texts = textBox.selectAll('text.selection')
.data(selected);
texts.exit().remove();
texts
.enter().append('text').attr('class', 'selection').attr('x', '3')
.merge(texts)
.attr('y', function (d, i) {
return i * 13 + 12;
}).text(function (d) { return d; });
textBox.selectAll('text.selection')
.each(function (d) {
widths.push(d3.select(this).node().getBBox().width);
textHeight = d3.select(this).node().getBBox().height;
});
var maxW = selected.length ? d3.max(widths) : 0;
textBox.style('display', null).select('rect').attr('width', maxW+10).attr('height', selected.length*textHeight);
} else {
textBox.style('display', 'none');
}
};
var lasso_end = function() {
lasso.items()
.classed("not_possible",false)
.classed("possible",false);
var selected = lasso.selectedItems()
.classed("selected", true)
.attr("r", 13);
var selectedDots = selected.data().map(d=>d[0]);
modifyTextBox(selectedDots);
};
var textBox = svg.append("g").classed('textBox', true).style('display', 'none');
textBox.append("rect")
.attr("height", 10)
.attr("width", 100)
.style("stroke-width", 1);
var area = svg.append("rect")
.attr("width", w)
.attr("height", h)
.style("opacity", 0);
var lasso = d3.lasso()
.closePathSelect(true)
.closePathDistance(100)
.items(circles)
.targetArea(area)
.on("start",lasso_start)
.on("draw",lasso_draw)
.on("end",lasso_end);
var legend = svg.selectAll(".legend")
.data(colours.domain())
.enter()
.append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 29 + ")"; });
legend.append("rect")
.attr("x", canvasW - 184)
.attr("y", 11)
.attr("width", 18)
.attr("height", 18)
.style("fill", colours);
legend.append("text")
.attr("x", canvasW - 194)
.attr("y", 20)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d[0];})
svg.call(lasso);
</script>
Relevant changes:
Based on the selections, I've added a function that adds texts to the textBox with an enter/exit/merge logic and assigns widths to the rect based on the maximum length of the text.
var modifyTextBox = function (selected) {
if(selected.length) {
var widths = [], textHeight = 0;
var texts = textBox.selectAll('text.selection')
.data(selected);
texts.exit().remove();
texts
.enter().append('text').attr('class', 'selection').attr('x', '3')
.merge(texts)
.attr('y', function (d, i) {
return i * 13 + 12;
}).text(function (d) { return d; });
textBox.selectAll('text.selection')
.each(function (d) {
widths.push(d3.select(this).node().getBBox().width);
textHeight = d3.select(this).node().getBBox().height;
});
var maxW = selected.length ? d3.max(widths) : 0;
textBox.style('display', null).select('rect').attr('width', maxW+10).attr('height', selected.length*textHeight);
} else {
textBox.style('display', 'none');
}
};
And calling this in the lasso_end function as: modifyTextBox(selectedDots);
And I've added some CSS for this textBox and the texts within it:
.textBox rect {
fill: steelblue;
}
text.selection {
font-size: 12px;
fill: #FFF;
letter-spacing: 0.1em;
font-weight: 300;
}
Feel free to play around with this function to position the rect (probably based on the mouse).
Hope this helps.
Here is a plunkr of a matrix scatterplot in d3 V4
http://plnkr.co/edit/7meh4sMhxItcQtCaAtZP?p=preview
(It may take some time to load and display)
I am looking forward to zoom the plot in order to see the cluttered points in a convenient way.
I have used d3.zoom() to rescale the axis but the circles are not being plotted accordingly.
Here is the problem description:
Initially, all the axis are set with different ranges (like top y-axis is having range from 4.5-7.5 , below that y-axis range is from 2.0-4.0 .. )
Now after zooming it, (i.e when scrolling mouse over the circles), all the axis are set in the same ranges that leads to all circles oriented diagonally.
Is there a workaround for that so that we can zoom the axis accordingly and visualize it nicely !!
Thanks. Any help would highly appreciated.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
padding: 10px;
}
.axis,
.frame {
shape-rendering: crispEdges;
}
.axis line {
stroke: #ddd;
}
.axis path {
display: none;
}
.cell text {
font-weight: bold;
text-transform: capitalize;
}
.frame {
fill: none;
stroke: #aaa;
}
circle {
fill-opacity: .7;
}
circle.hidden {
fill: #ccc !important;
}
.extent {
fill: #000;
fill-opacity: .125;
stroke: #fff;
}
div.tooltip {
position: absolute;
text-align: center;
width: 100px;
height: 80px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var tooltipDiv;
d3.helper = {};
var zoomable = true;
var new_xScale;
var new_yScale;
d3.helper.tooltip = function (param1, param2) {
var bodyNode = d3.select('body').node();
function tooltip(selection) {
selection.on('mouseover.tooltip', function (point) {
// Clean up lost tooltips
d3.select('body').selectAll('div.tooltip').remove();
// Append tooltip
tooltipDiv = d3.select('body')
.append('div')
.attr('class', 'tooltip');
var absoluteMousePos = d3.mouse(bodyNode);
//console.log('absoluteMousePos', absoluteMousePos);
tooltipDiv
.style('left', (absoluteMousePos[0] + 10) + 'px')
.style('top', (absoluteMousePos[1] - 30) + 'px');
var line = '';
//var temp_key = d3.keys(point);
var temp_key = [param1, param2];
// _.each(d3.keys(point), function (key, index) {
temp_key.forEach(function (key, index) {
if (index != temp_key.length - 1) {
line += key + ': ' + point[key] + '</br>';
} else {
line += key + ': ' + point[key];
}
});
tooltipDiv.html(line);
})
.on('mousemove.tooltip', function () {
// Move tooltip
var absoluteMousePos = d3.mouse(bodyNode);
tooltipDiv
.style("left", (absoluteMousePos[0] + 10) + 'px')
.style("top", absoluteMousePos[1] < 80 ? absoluteMousePos[1] + 10 :(absoluteMousePos[1] - 70) + 'px');
})
.on('mouseout.tooltip', function () {
// Remove tooltip
tooltipDiv.remove();
});
}
tooltip.attr = function (_x) {
if (!arguments.length) return attrs;
attrs = _x;
return this;
};
tooltip.style = function (_x) {
if (!arguments.length) return styles;
styles = _x;
return this;
};
return tooltip;
};
var width = 500,
size = 150,
padding = 20;
var x = d3.scaleLinear().rangeRound([padding / 2, size - padding / 2]);
var y = d3.scaleLinear().rangeRound([size - padding / 2, padding / 2]);
var xAxis = d3.axisBottom()
.scale(x)
.ticks(7);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(7);
var color = d3.scaleOrdinal(d3.schemeCategory10);
d3.json("flowers.json", function (data) {
var attr2Domain = {},
attrs= ['sepal length', 'sepal width', 'petal length','petal width']
n = attrs.length;
attrs.forEach(function (attr) {
attr2Domain[attr] = d3.extent(data, function (ele) {
return ele[attr];
});
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var svg = d3.select("body")
.append("svg")
.attr("width", size * n + padding)
.attr("height", size * n + padding)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
svg.selectAll(".x.axis")
.data(attrs)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function (d, i) {
return "translate(" + (n - i - 1) * size + ",0)";
})
.each(function (d) {
x.domain(attr2Domain[d]);
d3.select(this).call(xAxis);
});
svg.selectAll(".y.axis")
.data(attrs)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function (d, i) {
return "translate(0," + i * size + ")";
})
.each(function (d) {
y.domain(attr2Domain[d]);
d3.select(this).call(yAxis);
});
var cell = svg.selectAll(".cell")
.data(cross(attrs, attrs))
.enter()
.append("g")
.attr("class", "cell")
.attr("classx", function(d){ return d.x; })
.attr("classy", function(d){ return d.y; })
.attr("transform", function (d) {
return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")";
});
// Titles for the diagonal.
cell.filter(function (d) {
return d.i === d.j;
})
.append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.style("fill", "black")
.text(function (d) {
return d.x;
});
cell.each(plot);
// plot each cell
function plot(p) {
cell = d3.select(this);
x.domain(attr2Domain[p.x]);
y.domain(attr2Domain[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.style("pointer-events", "none")
.attr("height", size - padding);
var circles = cell.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d) {
return x(d[p.x]);
})
.attr("cy", function (d) {
return y(d[p.y]);
})
.attr("r", 4)
//.style("fill", "green");
.style("fill", function (d) {
return color(d.species);
});
circles.on('mousemove', function(){
var param1 = d3.select(this.parentNode).attr("classx");
var param2 = d3.select(this.parentNode).attr("classy");
circles.call(d3.helper.tooltip(param1, param2));
});
}
//--------------------------------------------- applying zoom----------
applyZoom();
function applyZoom() {
if (zoomable) {
var zoom = d3.zoom()
.on("zoom", zoomed);
svg.call(zoom).on("dblclick.zoom", null);
}
}
function zoomed() {
console.log('zoomed');
new_xScale = d3.event.transform.rescaleX(x);
new_yScale = d3.event.transform.rescaleY(y);
console.log("new_xScale", x);
svg.selectAll(".x.axis")
.each(function (d) {
x.domain(attr2Domain[d]);
d3.select(this).call(xAxis.scale(new_xScale));
});
svg.selectAll(".y.axis")
.each(function (d) {
y.domain(attr2Domain[d]);
d3.select(this).call(yAxis.scale(new_yScale));
});
cell.each(plotly);
}
function plotly(p) {
console.log("plotly", p);
//return x(d[p.x])
svg.selectAll("circle")
.attr("cx", function (d) {
return new_xScale(d[p.x]);
})
.attr("cy", function (d) {
return new_yScale(d[p.y]);
});
}
});
function cross(a, b) {
var c = [], n = a.length, m = b.length, i, j;
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++) {
c.push({x: a[i], i: i, y: b[j], j: j});
}
}
return c;
}
</script>
I think move'new_xScale = d3.event.transform.rescaleX(x)' after 'x.domain(attr2Domain[d])' inside each function should work. The same as y.
Thanks
Consider the following:
var dataAsCsv = `date,col_1,col_2
11/1/2012,1977652,1802851
12/1/2012,1128739,948687
1/1/2013,1201944,1514667
2/1/2013,1863148,1834006
3/1/2013,1314851,1906060
4/1/2013,1283943,1978702
5/1/2013,1127964,1195606
6/1/2013,1773254,977214
7/1/2013,1929574,1127450
8/1/2013,1980411,1808161
9/1/2013,1405691,1182788
10/1/2013,1336790,937890
11/1/2013,1851053,1358400
12/1/2013,1472623,1214610
1/1/2014,1155116,1757052
2/1/2014,1571611,1935038
3/1/2014,1898348,1320348
4/1/2014,1444838,1934789
5/1/2014,1235087,950194
6/1/2014,1272040,1580656
7/1/2014,980781,1680164
8/1/2014,1391291,1115999
9/1/2014,1211125,1542148
10/1/2014,1020824,1782795
11/1/2014,1685081,926612
12/1/2014,1469254,1767071
1/1/2015,1168523,935897
2/1/2015,1602610,1450541
3/1/2015,1830278,1354876
4/1/2015,1275158,1412555
5/1/2015,1560961,1839718
6/1/2015,949948,1587130
7/1/2015,1413765,1494446
8/1/2015,1166141,1305105
9/1/2015,958975,1202219
10/1/2015,902696,1023987
11/1/2015,961441,1865628
12/1/2015,1363145,1954046
1/1/2016,1862878,1470741
2/1/2016,1723891,1042760
3/1/2016,1906747,1169012
4/1/2016,1963364,1927063
5/1/2016,1899735,1936915
6/1/2016,1300369,1430697
7/1/2016,1777108,1401210
8/1/2016,1597045,1566763
9/1/2016,1558287,1140057
10/1/2016,1965665,1953595
11/1/2016,1800438,937551
12/1/2016,1689152,1221895
1/1/2017,1607824,1963282
2/1/2017,1878431,1415658
3/1/2017,1730296,1947106
4/1/2017,1956756,1696780
5/1/2017,1746673,1662892
6/1/2017,989702,1537646
7/1/2017,1098812,1592064
8/1/2017,1861973,1892987
9/1/2017,1129596,1406514
10/1/2017,1528632,1725020
11/1/2017,925850,1795575`;
var margin = {
top: 50,
right: 20,
bottom: 50,
left: 80
},
width = 1400 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// parse the date / time
// look at the .csv in Notepad! DO NOT LOOK AT EXCEL!
var parseDate = d3.timeParse("%m/%d/%Y");
var sessions_desktop_col = "#CE1126",
sessions_mobile_col = "#00B6D0";
var x = d3.scaleTime()
.range([0, width - margin.left - margin.right]);
var y = d3.scaleLinear().range([height, 0]);
var z = d3.scaleOrdinal()
.range([sessions_desktop_col, sessions_mobile_col]); // red and blue
var xMonthAxis = d3.axisBottom(x)
.ticks(d3.timeMonth.every(1))
.tickFormat(d3.timeFormat("%b")); // label every month
var xYearAxis = d3.axisBottom(x)
.ticks(d3.timeYear.every(1))
.tickFormat(d3.timeFormat("%Y")); // label every year
var yAxis = d3.axisLeft(y).tickFormat(d3.format('.2s')).tickSize(-width + margin.left + margin.right);
var formatNum = d3.format(",");
var barPad = 8;
var data = d3.csvParse(dataAsCsv, function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
});
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var keys = data.columns.slice(1);
data.sort(function(a, b) {
return b.date - a.date;
});
x.domain(d3.extent(data, function(d) {
return d.date
}));
var max = x.domain()[1];
var min = x.domain()[0];
var datePlusOneMonth = d3.timeDay.offset(d3.timeMonth.offset(max, 1), -1); // last day of current month: move up one month, back one day
x.domain([min, datePlusOneMonth]);
y.domain([0, d3.max(data, function(d) {
return d.total;
})]).nice();
z.domain(keys);
// x-axis
var monthAxis = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xMonthAxis);
var defaultSliderValue = 25;
// calculate offset date based on initial slider value
var offsetDate = defaultSliderValue ? d3.timeDay.offset(d3.timeMonth.offset((new Date(datePlusOneMonth.getFullYear(), datePlusOneMonth.getMonth() - defaultSliderValue)), 1), -1) : min
// set x domain and re-render xAxis
x.domain([offsetDate, datePlusOneMonth]);
g.select('.x.axis').call(xMonthAxis);
var filteredData = data.filter(function(d) {
return d.date >= offsetDate;
});
barWidth = (width - margin.right - margin.left) / (filteredData.length + barPad);
// the bars
g.append("g").classed('bars', true)
.selectAll("g")
.data(d3.stack().keys(keys)(filteredData))
.enter().append("g")
.attr('class', function(d) {
return d.key;
})
.attr("fill", function(d) {
return z(d.key);
})
.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.data.date);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("height", function(d) {
return y(d[0]) - y(d[1]);
})
.attr("width", barWidth)
.on("mousemove", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
var yPosition = parseFloat(d3.mouse(this)[1]) + 50;
var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values
var totalCount = d.data["total"];
var valuePercent = value / totalCount;
var valueClass = d3.select(this.parentNode).attr('class').replace("col_1", "Column 1").replace("col_2", "Column 2");
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
d3.select("#tooltip")
.select("#count")
.text(formatNum(value)); // return the value
d3.select("#tooltip")
.select("#totalCount")
.text(formatNum(totalCount)); // return the value
d3.select("#tooltip")
.select("#percent")
.text((valuePercent * 100).toFixed(1) + "%"); // return the value
d3.select("#tooltip")
.select("#month")
.text(d3.timeFormat("%B %Y")(d.data.date)); // return the value
d3.select("#tooltip")
.select("#sessionLabel")
.text(valueClass); // return the class
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
g.selectAll("bars")
.attr("transform", "translate(0,300)");
const firstDataYear = x.domain()[0];
if (firstDataYear.getMonth() == 11) { // When .getmonth() == 11, this shows two years overlapping with each other, making the label look ugly
const firstDataYearOffset = d3.timeDay.offset(firstDataYear, 1);
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length && firstDataYearOffset.getFullYear() !== tickValues[1].getFullYear()) {
tickValues = [firstDataYearOffset].concat(tickValues);
} else {
tickValues = [firstDataYearOffset];
}
} else {
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length && firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {
tickValues = [firstDataYear].concat(tickValues);
} else {
tickValues = [firstDataYear];
}
}
xYearAxis.tickValues(tickValues);
var yearAxis = g.append("g")
.attr("class", "yearaxis axis")
.attr("transform", "translate(0," + (height + 25) + ")")
.call(xYearAxis);
var valueAxis = g.append("g")
.attr("class", "y axis grid")
.call(yAxis);
monthAxis.selectAll("g").select("text")
.attr("transform", "translate(" + barWidth / 2 + ",0)");
var options = d3.keys(data[0]).filter(function(key) {
return key !== "date";
}).reverse();
var legend = svg.selectAll(".legend")
.data(options.slice().filter(function(type) {
return type != "total"
}))
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.attr('class', function(d) {
return d;
})
.style("fill", z)
.on("mouseover", function(d) {
if (d == 'col_2') {
d3.selectAll(".col_2").style("fill", '#008394');
}
if (d == 'col_1') {
d3.selectAll(".col_1").style("fill", '#80061b');
};
})
.on("mouseout", function() {
d3.selectAll(".col_2").style("fill", col_2_col);
d3.selectAll(".col_1").style("fill", col_1_col);
});
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return capitalizeFirstLetter(d.replace("col_1", "Column 1").replace("col_2", "Column 2"));
})
.on("mouseover", function(d) {
if (d == 'col_2') {
d3.selectAll(".col_2").style("fill", '#008394');
}
if (d == 'col_1') {
d3.selectAll(".col_1").style("fill", '#80061b');
};
})
.on("mouseout", function() {
d3.selectAll(".col_2").style("fill", col_2_col);
d3.selectAll(".col_1").style("fill", col_1_col);
});;
// Initialize jQuery slider
$('div#month-slider').slider({
min: 1,
max: 25,
value: defaultSliderValue,
create: function() {
// add value to the handle on slider creation
$(this).find('.ui-slider-handle').html($(this).slider("value"));
},
slide: function(e, ui) {
// change values on slider handle and label based on slider value
$(e.target).find('.ui-slider-handle').html(ui.value);
// calculate offset date based on slider value
var offsetDate = ui.value ? d3.timeDay.offset(d3.timeMonth.offset((new Date(datePlusOneMonth.getFullYear(), datePlusOneMonth.getMonth() - ui.value)), 1), -1) : min
// set x domain and re-render xAxis
x.domain([offsetDate, datePlusOneMonth]);
g.select('.x.axis').call(xMonthAxis);
const firstDataYear = x.domain()[0];
if (firstDataYear.getMonth() == 11) { // When .getmonth() == 11, this shows two years overlapping with each other, making the label look ugly
const firstDataYearOffset = d3.timeDay.offset(firstDataYear, 1);
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length == 2 && firstDataYearOffset.getFullYear() !== tickValues[1].getFullYear()) {
tickValues = [firstDataYearOffset].concat(tickValues);
} else {
tickValues = [firstDataYearOffset];
}
} else {
var tickValues = x.ticks().filter(function(d) {
return !d.getMonth()
});
if (tickValues.length && firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {
tickValues = [firstDataYear].concat(tickValues);
} else {
tickValues = [firstDataYear];
}
}
xYearAxis.tickValues(tickValues);
g.select('.yearaxis.axis').call(xYearAxis);
// calculate filtered data based on new offset date, set y axis domain and re-render y axis
var filteredData = data.filter(function(d) {
return d.date >= offsetDate;
});
y.domain([0, d3.max(filteredData, function(d) {
return d.total;
})]).nice();
g.select('.y.axis').transition().duration(200).call(yAxis);
// re-render the bars based on new filtered data
// the bars
var bars = g.select("g.bars")
.selectAll("g")
.data(d3.stack().keys(keys)(filteredData));
var barRects = bars.enter().append("g").merge(bars)
.attr('class', function(d) {
return d.key;
})
.attr("fill", function(d) {
return z(d.key);
})
.selectAll("rect")
.data(function(d) {
return d;
});
barRects.exit().remove();
barRects.enter()
.append("rect");
barWidth = (width - margin.right - margin.left) / (filteredData.length + barPad);
monthAxis.selectAll("g").select("text")
.attr("transform", "translate(" + barWidth / 2 + ",0)");
g.select("g.bars").selectAll('g rect')
.attr("x", function(d) {
return x(d.data.date);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("height", function(d) {
return y(d[0]) - y(d[1]);
})
.attr("width", barWidth)
.on("mousemove", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
var yPosition = parseFloat(d3.mouse(this)[1]) + 50;
var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values
var totalCount = d.data["total"];
var valuePercent = value / totalCount;
var valueClass = d3.select(this.parentNode).attr('class').replace("col_1", "Column 1").replace("col_2", "Column 2");
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
d3.select("#tooltip")
.select("#count")
.text(formatNum(value)); // return the value
d3.select("#tooltip")
.select("#totalCount")
.text(formatNum(totalCount)); // return the value
d3.select("#tooltip")
.select("#percent")
.text((valuePercent * 100).toFixed(1) + "%"); // return the value
d3.select("#tooltip")
.select("#month")
.text(d3.timeFormat("%B %Y")(d.data.date)); // return the value
d3.select("#tooltip")
.select("#sessionLabel")
.text(valueClass); // return the class
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
}
});
g.append("text")
.attr("class", "title")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.text("Title");
#tooltip {
position: absolute;
width: 290px;
z-index: 2;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
.legend {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 60%;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
g[class="col_1"] rect:hover {
fill: #80061b;
}
g[class="col_2"] rect:hover {
fill: #008394;
}
div.slider-container {
margin: 20px 20px 20px 80px;
}
div#month-slider {
width: 50%;
margin: 0px 0px 0px 330px;
background: #e3eff4;
}
div#month-slider .ui-slider .ui-slider-handle {
width: 25%;
}
div#month-slider .ui-slider-handle {
text-align: center;
}
.title {
font-size: 30px;
font-weight: bold;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
pointer-events: none;
}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js" integrity="sha256-eGE6blurk5sHj+rmkfsGYeKyZx3M4bG+ZlFyA7Kns7E=" crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.7.0/d3-legend.min.js"></script>
<body>
<div id="tooltip" class="hidden">
<p><strong>Month: </strong><span id="month"></span>
<p>
<p><strong><span id="sessionLabel"></span>: </strong><span id="count"></span> (<span id="percent"></span>)</p>
<p><strong>Total: </strong><span id="totalCount"></span></p>
</div>
<div class="slider-container" style='inline-block;'>
<span style='margin-left:6em;display:inline-block;position:relative;top:15px;'><strong>Number of Months Displayed:</strong></span>
<div id="month-slider" style='inline-block;'></div>
</div>
I would like the title to be above the slider in this. My understanding for why this isn't working is because the slider is in a div completely separate from the svg element. The thing that makes the most sense here, then, is to insert the div with class slider-container into the svg element. How can this be done?
Here's your options:
Use a forigenObject svg node to insert your html slider but this is poorly supported under IE.
Remove the title from your svg make it html.
Create an svg slider like here.
Need help in showing attendence value in each cell of D3 Calender visualization. I am new to D3 and kind of stuck in this step. Appreciate as much help on this.
Sample Date used for visualization
Date Station Group Attendance Absent PTO
2010-07-19 C GH 57 10 0
2010-07-20 C EF 58 10 1
2010-09-21 A GH 6 1 1
2010-09-20 A IJ 6 1 2
2010-09-17 A AB 6 1 3
HTML page
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
.day {
fill: #fff;
stroke: #ccc;
}
.month {
fill: none;
stroke: #000;
stroke-width: 2px;
}
.RdYlGn .q10-11{fill:rgb(165,0,38)}
.RdYlGn .q9-11{fill:rgb(215,48,39)}
.RdYlGn .q8-11{fill:rgb(244,109,67)}
.RdYlGn .q7-11{fill:rgb(253,174,97)}
.RdYlGn .q6-11{fill:rgb(254,224,139)}
.RdYlGn .q5-11{fill:rgb(255,255,191)}
.RdYlGn .q4-11{fill:rgb(217,239,139)}
.RdYlGn .q3-11{fill:rgb(166,217,106)}
.RdYlGn .q2-11{fill:rgb(102,189,99)}
.RdYlGn .q1-11{fill:rgb(26,152,80)}
.RdYlGn .q0-11{fill:rgb(0,104,55)}
.chart rect {
fill: steelblue;
}
.chart text {
fill: white;
font: 10px sans-serif;
text-anchor: end;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/d3-time-format.v2.min.js"></script>
<script>
//For Selecting Distinct Year from Date field for drop down
d3.csv("station.csv", function(error, csv) {
if (error) throw error;
var data = d3.nest()
.key(function(d) { return d.Date.split("-")[0]; })
.sortKeys(d3.descending)
.rollup(function(v) { return {
total: d3.sum(v, function(d) { return d.Attendance; })
}; })
.entries(csv);
var dropdown = d3.select('body')
.append('select')
.attr('class','select')
.attr('id','year_dropdown')
.on('change', Year_change_Del);
var options = dropdown.selectAll('option')
.data(data)
.enter()
.append('option')
.text(function (d) { return d.key; });
});
//For Selecting Distinct Year from Date field for drop down
d3.csv("station.csv", function(error, csv) {
if (error) throw error;
var data = d3.nest()
.key(function(d) { return d.Station; })
.sortKeys(d3.ascending)
.rollup(function(v) { return {
total: d3.sum(v, function(d) { return d.Attendance; })
}; })
.entries(csv);
var dropdown = d3.select('body')
.append('select')
.attr('class','select')
.attr('id','station_dropdown')
.on('change', Year_change_Del);
var options = dropdown.selectAll('option')
.data(data)
.enter()
.append('option')
.text(function (d) { return d.key; });
});
var width = 1450,
height = 190,
cellSize = 25; // cell size
var percent = d3.format(".1%"),
format = d3.time.format("%Y-%m-%d");
var color = d3.scale.quantize()
.domain([-.05, .50])
.range(d3.range(11).map(function(d) { return "q" + d + "-11"; }));
var month = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var week_days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
function Year_change_Del(){
d3.selectAll('svg').remove();
d3.select(".day").remove();
Year_Change_Create();
};
function Year_Change_Create() {
var station_selection = d3.select('#station_dropdown').property('value');
var year_selection = eval(d3.select('#year_dropdown').property('value')),
year_start = eval(d3.select('#year_dropdown').property('value')) -2 ,
year_end = eval(d3.select('#year_dropdown').property('value')) +1 ;
//alert(year_end);
var svg = d3.select("body").selectAll("svg")
.data(d3.range(year_start, year_end).reverse())
.enter().append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "RdYlGn")
.append("g")
.attr("transform", "translate(" + ((width - cellSize * 53) / 2) + "," + (height - cellSize * 7 - 1) + ")");
svg.append("text")
.attr("transform", "translate(-45," + cellSize * 3.5 + ")rotate(-90)")
.style("text-anchor", "middle")
.text(function(d) { return d; });
for (var i=0; i<7; i++)
{
svg.append("text")
.attr("transform", "translate(-15," + cellSize*(i+1) + ")")
.style("text-anchor", "end")
.attr("dy", "-.25em")
.text(function(d) { return week_days[i]; });
}
var rect = svg.selectAll(".day")
.data(function(d) { return d3.time.days(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter().append("g");
var newrect = rect.append("rect")
.attr("id",function(d) { return d.getFullYear(d) + "," + d3.time.weekOfYear(d) * cellSize + "," + d.getDay() * cellSize; })
.attr("class", "day")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("x", function(d) { return d3.time.weekOfYear(d) * cellSize; })
.attr("y", function(d) { return d.getDay() * cellSize; })
.datum(format);
newrect.append("title")
.text(function(d) { return d ; });
var newrect1 = rect.append("text")
.attr("class", "day")
.attr("x", function(d) { return d3.time.weekOfYear(d) * cellSize ; })
.attr("y", function(d) { return d.getDay() * cellSize; })
.attr("dy", "15px")
.attr("dx", "5px")
.style({"font-size":"8px","z-index":"999999999"})
.style("text-anchor", "left")
.text(function(d) { return d ; });
// to add month name on top of graph - month name not getting aligned properly
var legend = svg.selectAll(".legend")
.data(month)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(" + (((i+1) * ((width -50)/12))) + ",0)"; });
legend.append("text")
.attr("class", function(d,i){ return month[i] })
.style("text-anchor", "end")
.attr("dy", "-.45em")
.text(function(d,i){ return month[i] });
svg.selectAll(".month")
.data(function(d) { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter().append("path")
.attr("class", "month")
.attr("d", monthPath);
d3.csv("station.csv", function(csv) {
data = csv;
dataset = datafilter(data);
data=d3.nest()
.key(function(d) { return d.Date; })
.rollup(function(d) { return (d[0].Absent * 100) / d[0].Attendance / 100; })
.map(dataset);//console.log(JSON.stringify(data));
newrect.filter(function(d) { return d in data; })
.attr("class", function(d) { return "day " + color(data[d]); })
.select("title")
.text(function(d) { return d + ": " + percent(data[d]); });
function datafilter(d){
data = data.filter(function(d) { return d.Station == station_selection;});
return data;};
});
d3.csv("station.csv", function(csv) {
data = csv;
dataset = datafilter(data);
d3.csv("station.csv", function(csv) {
data = csv;
dataset = datafilter(data);
data=d3.nest()
.key(function(d) { return d.Date; })
.rollup(function(d) { return (d[0].Absent * 100) / d[0].Attendance / 100; })
.entries(dataset);//console.log(JSON.stringify(data));
rect.filter(function(d) { return d in data; })
.select("text")
.text(function(d) { return d + ": " + percent(data[d]); });
function datafilter(d){
data = data.filter(function(d) { return d.Station == station_selection;});
return data;};
});
function monthPath(t0) {
var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
d0 = t0.getDay(), w0 = d3.time.weekOfYear(t0),
d1 = t1.getDay(), w1 = d3.time.weekOfYear(t1);
return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize
+ "H" + w0 * cellSize + "V" + 7 * cellSize
+ "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
+ "H" + (w1 + 1) * cellSize + "V" + 0
+ "H" + (w0 + 1) * cellSize + "Z";
}
};
var formatTime = function(input){
var dateParse = d3.time.format("%Y-%m-%d").parse;
var dateFormat = d3.time.format("%Y");
return dateFormat(dateParse(input));
};
var formatTime = function(input){
var dateParse = d3.time.format("%Y-%m-%d").parse;
var dateFormat = d3.time.format("%u");
return dateFormat(dateParse(input));
};
</Script>
The first D3.csv creates the chart and does the coloring and even the tooltip is working. I am trying to get the data to be inserted into each cell from the second D3.csv. But this is not working. Can someone guide me on this.
d3.csv("station.csv", function(csv) {
data = csv;
dataset = datafilter(data);
data=d3.nest()
.key(function(d) { return d.Date; })
.rollup(function(d) { return (d[0].Absent * 100) / d[0].Attendance / 100; })
.map(dataset);//console.log(JSON.stringify(data));
newrect.filter(function(d) { return d in data; })
.attr("class", function(d) { return "day " + color(data[d]); })
.select("title")
.text(function(d) { return d + ": " + percent(data[d]); });
function datafilter(d){
data = data.filter(function(d) { return d.Station == station_selection;});
return data;};
});
d3.csv("station.csv", function(csv) {
data = csv;
dataset = datafilter(data);
d3.csv("station.csv", function(csv) {
data = csv;
dataset = datafilter(data);
data=d3.nest()
.key(function(d) { return d.Date; })
.rollup(function(d) { return (d[0].Absent * 100) / d[0].Attendance / 100; })
.entries(dataset);//console.log(JSON.stringify(data));
rect.filter(function(d) { return d in data; })
.select("text")
.text(function(d) { return d + ": " + percent(data[d]); });
function datafilter(d){
data = data.filter(function(d) { return d.Station == station_selection;});
return data;};
});
Who wants the d3 calendar with dates?
Here is an example fiddle created by me.
var cellSize = 17, // cell size
width = cellSize * 8,
height = cellSize * 9,
firstDayOfEachMonth = getFirstDayOfEachMonth(2013);
var day = d3.time.format("%w"),
myday = d3.time.format("%d"),
week = d3.time.format("%U"),
monthName = d3.time.format("%B");
percent = d3.format(".1%"),
format = d3.time.format("%Y-%m-%d");
var svg = d3.select("#calendars").selectAll("svg")
.data(firstDayOfEachMonth)
.enter().append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
// Just centering the month here
.attr("transform", "translate(" + (width - cellSize * 7 - 1) + "," + ((height - cellSize * 6) / 2) + ")");
svg.append("text")
.attr("transform", "translate(" + cellSize * 3.5 + ", -6)")
.style("text-anchor", "middle")
.text(function(d) { return monthName(d); });
var rect = svg.selectAll("g")
.data(function(d) { return getDaysInMonth(d); })
.enter().append("g")
.attr("class", "mygroup")
//.attr("width", cellSize)
//.attr("height", cellSize)
//.attr("x", function(d) { return day(d) * cellSize; })
//.attr("y", function(d) { return (week(d) - getFirstWeekOfMonth(d)) * cellSize; })
//.on("mouseover", function(){d3.select(this).attr("class", "hovered")})
//.on("mouseout", function(){d3.select(this).style("background-color", "white")})
//.on("mouseout", function(){d3.select(this).attr("class", "")})
// format is a function - a formatter configured to use the format specified above (format = d3.time.format("%Y-%m-%d");)
// datum will execute the function to set the data for each item in the dataset - in this case, every day in the year.
//.datum(format);
rect.append("rect")
.data(function(d) { return getDaysInMonth(d); })
//.enter().append("rect")
.attr("class", "day")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("x", function(d) { return day(d) * cellSize; })
.attr("y", function(d) { return (week(d) - getFirstWeekOfMonth(d)) * cellSize; })
.on("mouseover", function(){d3.select(this).attr("class", "hovered")})
//.on("mouseout", function(){d3.select(this).style("background-color", "white")})
.on("mouseout", function(){d3.select(this).attr("class", "")})
// format is a function - a formatter configured to use the format specified above (format = d3.time.format("%Y-%m-%d");)
// datum will execute the function to set the data for each item in the dataset - in this case, every day in the year.
.datum(format);
rect.append("text")
//.text(function(d) { return d; })
.text(function(d) { return myday(d)})
.attr("x", function(d) { return day(d) * cellSize + 4; })
.attr("y", function(d) { return (week(d) - getFirstWeekOfMonth(d)) * cellSize + 12; })
rect.append("title")
// This will display the formatted date for each day in the year when hovered (title attribute)
.text(function(d) { return d; });
d3.select(self.frameElement).style("height", "2910px");
function getDaysInYear(year) {
return d3.time.days(new Date(year, 0, 1), new Date(year + 1, 0, 1));
}
function getFirstDayOfEachMonth(year) {
return d3.time.months(new Date(year, 0, 1), new Date(year + 1, 0, 1));
}
function getDaysInMonth(firstDayOfMonth) {
return d3.time.days(firstDayOfMonth, new Date(firstDayOfMonth.getFullYear(), firstDayOfMonth.getMonth() + 1, 1));
}
function getFirstWeekOfMonth(day) {
// TODO: stop using this. It's wasteful because it gets executed for every day of every month.
// We should only do this once per month. The problem is with referencing data throughout a month.
// Perhaps use a closure - http://stackoverflow.com/questions/13076553/combining-parent-and-nested-data-with-d3-js
// also, the "each" reference in - http://nelsonslog.wordpress.com/2011/04/03/d3-selections/
return week(new Date(day.getFullYear(), day.getMonth(), 1));
}
body {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
.day {
fill: #fff;
stroke: #ccc;
}
.month {
fill: none;
stroke: #000;
stroke-width: 2px;
}
#calendars rect {
fill: #ffffff;
stroke: #bbbbbb;
}
#calendars rect.hovered {
fill: #f0f8ff;
}
<div id="calendars"></div>
I've tried to use the following code to scale my visualization:
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
-ms-transform: scale(1.5,1.5);
-webkit-transform: scale(1.5,1.5);
transform: scale(1.5,1.5);
}
However, this causes the d3.behavior.drag() to misbehave, as the drag will no longer follow the mouse, but move too quickly. How can I scale while still maintaining the correct behaviour of the drag mechanics? Any kind of code (CSS/javascript/whatever) is welcome as long as it achieves the purpose.
Below is my code and data. Feel free to comment on potential improvements on my code as it is my first D3 visualization.
timeline.js
// Some code used from http://codepen.io/idan/pen/xejuD, which uses the MIT license (see https://blog.codepen.io/legal/licensing/).
/* The MIT License (MIT)
Copyright (c) Idan Gazit
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall
be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. */
// visualization parameters
var text_minutes = 20;
var axisOpacity = 0.6;
var large_tick_size = 24;
var rectHourSize = graphWidth / (7 * 8);
var daylabel_displacement = 12;
var half_a_week_in_min = 5040;
// slider variables
var sliderscale, startWeek, endWeek;
/* var leftWidth, rightWidth, leftBorder, rightBorder; */
var slidervalue_left, slidervalue_right;
var sliderborder_translate = 0.5;
var sliderborder_area_width = 8;
/* var slidercenter_pixels; */
// window
var margin = {
top : 20,
right : 10,
bottom : 30,
left : 35
};
var graphWidth = 700;
var graphHeight = 160;
var width = graphWidth + margin.left + margin.right;
var height = graphHeight + margin.top + margin.bottom;
var yaxisHeight = 100;
// kernel density plot
var bandwidth = 5;
var granularity = 1;
var kernelHeight = 30;
// visualization global variables
var daysLabelsAxis, daysTickmarksAxis, hoursAxis, hoursTickSpacing, hoursg, start, end, svg, weekscale, shownTicks, records;
var recordTypes = d3.map();
var readyForInput = false;
var y = d3.scale.ordinal()
.rangeRoundBands([yaxisHeight, 0], 0.3);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.outerTickSize(0)
.tickFormat(function (d, i) {
return recordTypes.get(d).label;
});
var colorOf = d3.scale.category10();
var parseDate = d3.time.format("%d-%m-%Y %H:%M").parse;
var results = Papa.parse("timeseries.csv", {
header : true,
download : true,
dynamicTyping : true,
delimiter : ",",
skipEmptyLines : true,
complete : function (results) {
records = results.data;
records.forEach(function (d) {
d.time = parseDate(d.time);
d.time = moment(d.time);
});
initializeStaticWindow();
defineScales();
drawKernelDensity();
drawGraphOutline();
initializeScaler();
updateWeekScale();
prepareAxis();
visualizeData();
readyForInput = true;
}
});
function initializeStaticWindow() {
svg = d3.select("body").append("svg")
.attr("id", "svgID")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// set types of records for y-axis
recordTypes.set("inc_call", {
label : "in",
color : "#438DCA",
order : 1
});
recordTypes.set("out_call", {
label : "out",
color : "#245A76",
order : 2
});
recordTypes.set("inc_text", {
label : "in",
color : "#70C05A",
order : 3
});
recordTypes.set("out_text", {
label : "out",
color : "#326B26",
order : 4
});
records = records.sort(function (a, b) {
return recordTypes.get(b.type).order - recordTypes.get(a.type).order;
});
y.domain(records.map(function (d) {
return d.type;
}));
}
function defineScales() {
records.forEach(function (d, i) {
if (i == 0) {
start = d.time;
end = d.time;
} else {
start = moment.min(start, d.time);
end = moment.max(end, d.time);
}
});
start = moment(start).startOf('day');
end = moment(end).startOf('day').add(1, 'days');
// Turn into pure dates
var daysNum = dayDiff(start, end);
// kernel slider
var slider_week_width = (7 / daysNum) * graphWidth;
sliderscale = d3.time.scale().nice(d3.time.day).domain([start.toDate(), end.toDate()]).range([0, graphWidth]).clamp(true);
records.forEach(function (d) {
d.time_from_start = hoursFromStart(d.time)
});
slidervalue_left = sliderscale(moment(start));
slidervalue_right = sliderscale(moment(start).add(7, 'days'));
}
function drawKernelDensity() {
// extract only timestamps
timestamps = pluck(records, 'time_from_start');
// create kernel
kde = science.stats.kde().sample(timestamps);
var kernel_output = kde.bandwidth(bandwidth)(d3.range(0, hoursFromStart(end), granularity));
kernel_output = pluck(kernel_output, 1)
var kernel_max = ss.quantile(kernel_output, 0.975);
// scales to transform data
kernel_scale_x = d3.scale.linear().domain([0, hoursFromStart(end)]).range([0, graphWidth]);
kernel_scale_y = d3.scale.linear().domain([0, kernel_max]).range([0, kernelHeight]).clamp(true);
var line = d3.svg.line()
.x(function (d) {
return kernel_scale_x(d[0]);
})
.y(function (d) {
return graphHeight - kernel_scale_y(d[1]);
});
svg.selectAll("kde")
.data([bandwidth])
.enter().append("path")
.attr("d", line(kde.bandwidth(bandwidth)(d3.range(0, hoursFromStart(end), granularity))))
.attr("stroke", "#438DCA");
}
function initializeScaler() {
/* updateBorderWidth();
moveBorders(); */
var whiteSelectionData = [{
x : slidervalue_left,
width : slidervalue_right - slidervalue_left
}
]
svg.selectAll("whiteSelection")
.data(whiteSelectionData)
.enter().append('rect')
.attr("x", function (d) {
return d.x;
})
.attr("y", graphHeight - kernelHeight - 1)
.attr("width", function (d) {
return d.width;
})
.attr("height", kernelHeight + 2)
.attr("class", "whiteSelectionArea")
.call(dragSelection);
var greyAreaData = [{
x : 0,
width : 0.01,
side : "leftGrey",
visibility : "visible"
}, {
x : slidervalue_right,
width : graphWidth - slidervalue_right,
side : "rightGrey",
visibility : "visible"
}
]
svg.selectAll("greyArea")
.data(greyAreaData)
.enter().append('rect')
.attr("x", function (d) {
return d.x;
})
.attr("y", graphHeight - kernelHeight - 1)
.attr("width", function (d) {
return d.width;
})
.attr("visibility", function (d) {
return d.visibility;
})
.attr("height", kernelHeight + 2)
.attr("class", function (d) {
return d.side;
});
var borderLineData = [{
x : 0 + sliderborder_translate,
side : "leftBorderLine"
}, {
x : slidervalue_right + sliderborder_translate,
side : "rightBorderLine"
}
]
svg.selectAll("borderLine")
.data(borderLineData)
.enter().append('line')
.attr("x1", function (d) {
return d.x;
})
.attr("x2", function (d) {
return d.x;
})
.attr("y1", graphHeight - kernelHeight - 1)
.attr("y2", graphHeight + 1)
.attr("class", function (d) {
return d.side;
});
var borderEllipseData = [{
cx : 0 + sliderborder_translate,
side : "leftBorderEllipse"
}, {
cx : slidervalue_right + sliderborder_translate,
side : "rightBorderEllipse"
}
]
svg.selectAll("ellipse")
.data(borderEllipseData)
.enter().append('ellipse')
.attr("cx", function (d) {
return d.cx;
})
.attr("cy", graphHeight - kernelHeight / 2)
.attr("rx", 5)
.attr("ry", 8)
.attr("class", function (d) {
return d.side;
});
var borderRectData = [{
cx : 0 + sliderborder_translate,
side : "leftBorderRect",
width : 3,
height : 5
}, {
cx : slidervalue_right + sliderborder_translate,
side : "rightBorderRect",
width : 3,
height : 5
}
]
svg.selectAll("borderRect")
.data(borderRectData)
.enter().append('rect')
.attr("x", function (d) {
return d.cx - d.width / 2;
})
.attr("y", function (d) {
return graphHeight - kernelHeight / 2 - d.height / 2;
})
.attr("width", function (d) {
return d.width;
})
.attr("height", function (d) {
return d.height;
})
.attr("class", function (d) {
return d.side;
});
var borderAreaData = [{
x : slidervalue_left - sliderborder_area_width / 2,
width : sliderborder_area_width,
side : "leftBorderArea",
visibility : "visible"
}, {
x : slidervalue_right - sliderborder_area_width / 2,
width : sliderborder_area_width,
side : "rightBorderArea",
visibility : "visible"
}
]
svg.selectAll("borderSelectArea")
.data(borderAreaData)
.enter().append('rect')
.attr("x", function (d) {
return d.x;
})
.attr("y", graphHeight - kernelHeight - 1)
.attr("width", function (d) {
return d.width;
})
.attr("visibility", function (d) {
return d.visibility;
})
.attr("height", kernelHeight + 2)
.attr("class", function (d) {
return d.side;
}).call(dragBorder);
}
function updateWeekScale() {
startWeek = sliderscale.invert(slidervalue_left);
endWeek = sliderscale.invert(slidervalue_right);
shownDays = dayDiff(startWeek, endWeek)
weekscale = d3.time.scale().nice(d3.time.day).domain([startWeek, endWeek]).range([0, graphWidth]);
}
function prepareAxis() {
function diffMinutesFromLeftSide(d) {
var textMinutes = d.getMinutes();
var startWeek
}
// Labels without tickmarks to describe the date
daysLabelsAxis = d3.svg.axis().scale(weekscale).orient('bottom').ticks(d3.time.hour, 12).tickSize(0).tickPadding(daylabel_displacement).tickFormat(function (d) {
var formatter;
if (minuteDiff(startWeek, moment(d)) > 180 && minuteDiff(moment(d), endWeek) > 180) { // remove text crossing the edges
if (d.getHours() === 12) {
if ((d.getDate() === 1 || moment(d).isSame(start, 'day')) && shownDays < 16) {
// if the month changed or it's the first label, show the month
formatter = d3.time.format.utc('%a %d %b');
} else {
// else no month
formatter = d3.time.format.utc('%a %d');
}
return formatter(d);
} else {
return null;
}
}
});
// small tickmarks every three hours, but only label 6a and 6p
hoursAxis = d3.svg.axis().scale(weekscale).orient('bottom').ticks(d3.time.hour, 3).tickPadding(6).tickSize(8).tickFormat(function (d) {
var hours;
hours = d.getHours();
if (hours === 6) {
return null;
/* return sun; */
} else if (hours === 18) {
return null;
/* return moon; */
} else {
return null;
}
});
// draw axis below data
hoursg = svg.append('g').classed('axis', true).classed('hours', true).classed('labeled', true).attr("transform", "translate(0.5," + yaxisHeight + ")").call(hoursAxis).style("opacity", axisOpacity);
// Need the pixel dimensions between each tick e.g. three hours.
hoursTickSpacing = weekscale(moment(start).add(3, 'hours').toDate()) - weekscale(start.toDate());
// add day/night shading by adding elements to the dom for every tickmark in the hours axis.
var hourTicks = hoursg.selectAll('g.tick');
//hourTicks.filter(':not(:last-child)').insert('rect', ':first-child').attr('class', function (d, i) {
//hourTicks.insert('rect', ':not(:last-child)').attr('class', function (d, i) {
hourTicks.insert('rect', ':first-child').attr('class', function (d, i) {
var hours;
hours = d.getHours();
if (hours < 6 || hours >= 18) {
return 'nighttime';
} else {
return 'daytime';
}
}).attr('x', 0).attr('width', hoursTickSpacing).attr('height', 8);
/* function (d, i) {
return i != daysNum * 8 ? 8 : 0; // remove last tick in week..
}); */
// Larger tickmarks to denote midnights without labels
daysTickmarksAxis = d3.svg.axis().scale(weekscale).orient('bottom').ticks(d3.time.day, 1).tickFormat('').tickSize(large_tick_size).tickPadding(6);
// draw axes below data
svg.append('g').classed('axis', true).classed('days', true).attr("transform", "translate(0.5," + (yaxisHeight) + ")").call(daysTickmarksAxis);
svg.append('g').classed('axis', true).classed('days', true).classed('labeled', true).attr("transform", "translate(0.5," + (yaxisHeight) + ")").call(daysLabelsAxis).style("opacity", axisOpacity);
// draw y-axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0.5,0.0)")
.style("opacity", axisOpacity)
.call(yAxis);
svg.selectAll("y_axis_vertical")
.data(['call', 'text'])
.enter().append("text")
.attr("y", function (d, i) {
return 4 + i * 45;
}) // function(d,i) { return i+"em"})
.attr("x", "-32")
.style("writing-mode", "tb")
.style("glyph-orientation-vertical", 0)
.style("letter-spacing", -1)
.style("opacity", axisOpacity)
.style("font-family", "FontAwesome")
.text(function (d, i) {
return d;
});
}
function visualizeData() {
svg.selectAll(".bar")
.data(records)
.enter().append("rect")
.attr("class", "bar")
.attr("y", function (d) {
return y(d.type);
})
.attr("height", y.rangeBand())
.attr("x", function (d) {
return weekscale(d.time);
})
.attr("width", function (d) {
// set texts to a fixed length (and make calls minimum length)
length = d.type == "inc_text" || d.type == "out_text" ? text_minutes : d.call_duration/60;
if (length < text_minutes) {
length = text_minutes;
}
// find length of call on time scale by finding length between start and start + call_duration
var rect_width = weekscale(moment(start).add(length, 'minutes').toDate()) - weekscale(start.toDate());
return rect_width;
})
.style("fill", function (d) {
return recordTypes.get(d.type).color;
})
// clip sides
.style("opacity", function (d) {
return (weekscale(d.time) < 0 || weekscale(d.time) > graphWidth) ? 0.0 : 1.0;
});
}
function drawGraphOutline() {
svg.append("line")
.attr("x1", 0.5)
.attr("x2", 0.5)
.attr("y1", 0)
.attr("y2", graphHeight + 1)
.attr("stroke-width", 1)
.attr("stroke", "black");
svg.append("line")
.attr("x1", graphWidth + 0.5)
.attr("x2", graphWidth + 0.5)
.attr("y1", 0)
.attr("y2", graphHeight + 1)
.attr("stroke-width", 1)
.attr("stroke", "black");
}
function updateVisualization() {
if (readyForInput) {
cleanAxisWindow();
updateWeekScale();
prepareAxis();
visualizeData();
}
}
function cleanAxisWindow() {
svg.selectAll(".axis").remove()
svg.selectAll("text").remove()
svg.selectAll(".bar").remove()
svg.selectAll(".handle").remove()
}
function updateBorder(x, whichBorder) {
svg.selectAll(whichBorder.concat("Area"))
.each(function (d) {
d.x = x - sliderborder_area_width / 2
})
.attr("x", x - sliderborder_area_width / 2);
// translate visuals slightly to fit graph
x = x + sliderborder_translate
svg.selectAll(whichBorder.concat("Line"))
.attr("x1", x)
.attr("x2", x);
svg.selectAll(whichBorder.concat("Ellipse"))
.attr("cx", x);
svg.selectAll(whichBorder.concat("Rect"))
.attr("x", function (d) {
return x - d.width / 2
});
}
function updateGreyArea(x, width, whichGrey) {
width = Math.max(width, 0.01);
svg.selectAll(whichGrey)
.attr("x", x)
.attr("width", width)
.each(function (d) {
d.x = x
})
.each(function (d) {
d.width = width
});
}
// Function for grabbing a specific property from an array
pluck = function (ary, prop) {
return ary.map(function (x) {
return x[prop]
});
}
function checkBordersMoreThanWeekApart(d, slider_drag) {
if (d.side == "leftBorderArea") {
temp_left = d.x + slider_drag + sliderborder_area_width / 2;
temp_right = slidervalue_right;
} else {
temp_left = slidervalue_left;
temp_right = d.x + slider_drag + sliderborder_area_width / 2;
}
left_date = sliderscale.invert(temp_left);
right_date = sliderscale.invert(temp_right);
num_days = dayDiff(left_date, right_date);
return (num_days >= 4 && num_days <= 18);
}
var dragBorder = d3.behavior.drag()
.on('drag', function (d) {
slider_drag = d3.event.dx;
/* Hitting edges of graph? */
if (d.x + sliderborder_area_width / 2 + slider_drag < 0) {
d.x = -sliderborder_area_width / 2;
} else if (d.x + sliderborder_area_width / 2 + slider_drag > graphWidth) {
d.x = graphWidth - sliderborder_area_width / 2;
} else {
/* Borders too close to each other? */
if (checkBordersMoreThanWeekApart(d, slider_drag))
d.x += slider_drag;
}
if (d.side == "leftBorderArea") {
slidervalue_left = d.x + sliderborder_area_width / 2;
} else {
slidervalue_right = d.x + sliderborder_area_width / 2;
}
updateScalerObjects();
updateVisualization();
});
var dragSelection = d3.behavior.drag()
.on('drag', function (d) {
slider_drag = d3.event.dx;
/* Hitting edges of graph? */
if (d.x + slider_drag < 0) {
d.x = 0;
} else if (d.x + d.width + slider_drag > graphWidth) {
d.x = graphWidth - d.width;
} else {
d.x += slider_drag;
}
slidervalue_left = d.x;
slidervalue_right = d.x + d.width;
updateScalerObjects();
updateVisualization();
});
function updateScalerObjects() {
svg.selectAll(".whiteSelectionArea")
.attr("width", slidervalue_right - slidervalue_left)
.attr("x", slidervalue_left)
.each(function (d) {
d.x = slidervalue_left
})
.each(function (d) {
d.width = slidervalue_right - slidervalue_left
});
updateGreyArea(0, slidervalue_left, ".leftGrey");
updateBorder(slidervalue_left, ".leftBorder");
updateGreyArea(slidervalue_right, graphWidth - slidervalue_right, ".rightGrey");
updateBorder(slidervalue_right, ".rightBorder");
}
function inputData(d) {
d.time = parseDate(d.time);
return d;
}
function dayDiff(date_start, date_end) {
var dr = moment.range(date_start, date_end);
return dr.diff('days');
}
function hourDiff(date_start, date_end) {
var dr = moment.range(date_start, date_end);
return dr.diff('hours');
}
function minuteDiff(date_start, date_end) {
var dr = moment.range(date_start, date_end);
return dr.diff('minutes');
}
function hoursFromStart(moment) {
return hourDiff(start, moment);
}
function minFromStart(moment) {
return minuteDiff(start, moment);
}
axis.css
html, body {
min-width: 100%;
min-height: 100vh;
padding: 0;
margin: 0;
font-family: 'FontAwesome'; /* "Source Sans Pro"; */
font-weight: 300;
font-size: 10px;
cursor: default;
}
svg rect.background {
cursor: default !important;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
opacity: 1.0;
}
.x.axis path {
display: none;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
-ms-transform: scale(1.5,1.5);
-webkit-transform: scale(1.5,1.5);
transform: scale(1.5,1.5);
}
// Labels
text {
opacity: 0;
}
&.labeled text {
opacity: 1;
}
&.hours text {
fill: #ccc;
font-size: 1rem;
font-family: "FontAwesome";
}
&.days text {
fill: #999;
font-size: 0.9rem;
text-transform: uppercase;
}
// strokes for the two days axes
&.days {
path, line { stroke: #ccc; }
// don't draw strokes for the labeled axis
&.labeled {
path, line { stroke: none; }
}
}
.nighttime {
fill: darken(#2980b9, 10%);
fill-opacity: 0.15;
}
.daytime {
fill: #f1c40f;
fill-opacity: 0.25;
} /**/
.leftGrey {
fill: darken(#2980b9, 10%);
fill-opacity: 0.07;
cursor: default !important;
}
.rightGrey {
fill: darken(#2980b9, 10%);
fill-opacity: 0.07;
cursor: default !important;
}
.whiteSelectionArea {
fill-opacity: 0;
cursor: move !important;
}
.leftBorderLine{
stroke-width: 2;
stroke: #727272 ;
}
.rightBorderLine{
stroke-width: 2;
stroke: #727272 ;
}
.leftBorderArea {
fill: darken(#2980b9, 10%);
fill-opacity: 0.01;
cursor: col-resize !important;
}
.rightBorderArea {
fill: darken(#2980b9, 10%);
fill-opacity: 0.01;
cursor: col-resize !important;
}
.leftBorderEllipse {
fill: white;
stroke: #727272 ;
}
.rightBorderEllipse {
fill: white;
stroke: #727272 ;
}
.leftBorderRect {
fill: #727272;
stroke-width: 0;
}
.rightBorderRect {
fill: #727272;
stroke-width: 0;
}
path{
fill: none;
stroke: #438DCA;
stroke-width: 1;
stroke-opacity: 0.6;
shape-rendering: crispEdges;
}
}
index.html
<!DOCTYPE html>
<meta charset = "utf-8">
<body>
<script type="text/javascript" src="d3.js"></script>
<script type="text/javascript" src="moment.js"></script>
<script type="text/javascript" src="moment-range.js"></script>
<script type="text/javascript" src="papaparse.js"></script>
<script type="text/javascript" src="science.v1.js"></script>
<script type="text/javascript" src="simple_statistics.js"></script>
<link href = "axis.css" rel = "stylesheet" type = "text/css"/>
<script type="text/javascript" src="timeline.js"></script>
</body>
timeseries.csv: http://tny.cz/38e361e1 (too long to be allowed to paste it here)