Related
I am trying to add a hover effect without success. How to show data when hovering over datapoint?
https://plnkr.co/edit/Va1Dw3hg2D5jPoNGKVWp?p=preview&preview
<!DOCTYPE html>
svg {
font: 12px sans-serif;
text-anchor: middle;
}
rect {
stroke: lightgray;
stoke-width: 1px;
fill: none;
}
.y.axis path {
fill: none;
stroke: none;
}
</style>
d3.csv('data.csv', function (error, rows) {
var data = [];
rows.forEach(function (d) {
var x = d[''];
delete d[''];
for (prop in d) {
var y = prop,
value = d[prop];
data.push({
x: x,
y: y,
value: +value,
});
}
});
var margin = {
top: 25,
right: 80,
bottom: 25,
left: 25,
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
domain = d3
.set(
data.map(function (d) {
return d.x;
})
)
.values(),
num = Math.sqrt(data.length),
color = d3.scale
.linear()
.domain([-1, 0, 1])
.range(['#B22222', '#fff', '#000080']);
var x = d3.scale.ordinal().rangePoints([0, width]).domain(domain),
y = d3.scale.ordinal().rangePoints([0, height]).domain(domain),
xSpace = x.range()[1] - x.range()[0],
ySpace = y.range()[1] - y.range()[0];
var svg = d3
.select('body')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr(
'transform',
'translate(' + margin.left + ',' + margin.top + ')'
);
var cor = svg
.selectAll('.cor')
.data(data)
.enter()
.append('g')
.attr('class', 'cor')
.attr('transform', function (d) {
return 'translate(' + x(d.x) + ',' + y(d.y) + ')';
});
cor
.append('rect')
.attr('width', xSpace)
.attr('height', ySpace)
.attr('x', -xSpace / 2)
.attr('y', -ySpace / 2);
cor
.filter(function (d) {
var ypos = domain.indexOf(d.y);
var xpos = domain.indexOf(d.x);
for (var i = ypos + 1; i < num; i++) {
if (i === xpos) return false;
}
return true;
})
.append('text')
.attr('y', 5)
.text(function (d) {
if (d.x === d.y) {
return d.x;
} else {
return d.value.toFixed(2);
}
})
.style('fill', function (d) {
if (d.value === 1) {
return '#000';
} else {
return color(d.value);
}
});
cor
.filter(function (d) {
var ypos = domain.indexOf(d.y);
var xpos = domain.indexOf(d.x);
for (var i = ypos + 1; i < num; i++) {
if (i === xpos) return true;
}
return false;
})
.append('circle')
.attr('r', function (d) {
return (width / (num * 2)) * (Math.abs(d.value) + 0.1);
})
.style('fill', function (d) {
if (d.value === 1) {
return '#000';
} else {
return color(d.value);
}
});
var aS = d3.scale
.linear()
.range([-margin.top + 5, height + margin.bottom - 5])
.domain([1, -1]);
var yA = d3.svg.axis().orient('right').scale(aS).tickPadding(7);
var aG = svg
.append('g')
.attr('class', 'y axis')
.call(yA)
.attr(
'transform',
'translate(' + (width + margin.right / 2) + ' ,0)'
);
var iR = d3.range(-1, 1.01, 0.01);
var h = height / iR.length + 3;
iR.forEach(function (d) {
aG.append('rect')
.style('fill', color(d))
.style('stroke-width', 0)
.style('stoke', 'none')
.attr('height', h)
.attr('width', 10)
.attr('x', 0)
.attr('y', aS(d));
});
});
</script>
Create a new div and give it some styles. also set the opacity to 0.
var popupDiv = d3
.select("#root")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
Next, you need to add a mouseenter and mouseout to your desired elements
d3.append("div") // Change this to your element(s)
.on("mouseenter", function (d, i) {
popupDiv.transition().duration(200).style("opacity", 0.9);
popupDiv
.html(i.Description) // Example of display a data item description
.style("left", d.pageX + "px") // X offset can be added here
.style("top", d.pageY + "px"); // Y offset can be added here
})
.on("mouseout", function (d, i) {
popupDiv.transition().duration(200).style("opacity", 0);
})
Bear in mind the first parameter d to the mouseenter and mouseout functions is the mouse event itself which is being used to find out the x and y position of the mouse. The second parameter i is the data attached to your element if you wanted to use any of the data for specific descriptions or colours etc.
These are the base styles I used when creating a popup
div.tooltip {
position: absolute;
text-align: center;
width: auto;
height: auto;
max-width: 200px;
padding: 8px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
z-index: 10;
}
Here is a parallel coordinate with some sample data
http://plnkr.co/edit/dCNuBsaDNBwr7CrAJUBe?p=preview
Here the numerical axis like column1 , column3 have small number of data so it looks ok. But in practice, I have a big data (something like 600 entries), so numerical ticks in axis become clustered , overlapped.
Is there a way so that it looks good. Like Setting the number of ticks in parallel coordinate. For instance, if I want to change the number of ticks in column1 of this example from 10 to 5.
Any help would be appreciated.
Thanks
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ddd;
stroke-opacity: .4;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
stroke-opacity: .7;
}
.brush .extent {
fill-opacity: .3;
stroke: #fff;
shape-rendering: crispEdges;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
cursor: move;
}
</style>
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 600 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.scalePoint().rangeRound([0, width]).padding(1),
y = {},
dragging = {};
var line = d3.line(),
//axis = d3.axisLeft(x),
background,
foreground,
extents;
var container = d3.select("body").append("div")
.attr("class", "parcoords")
.style("width", width + margin.left + margin.right + "px")
.style("height", height + margin.top + margin.bottom + "px");
var svg = container.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var quant_p = function(v){return (parseFloat(v) == v) || (v == "")};
d3.json("convertcsv.json", function(error, cars) {
dimensions = d3.keys(cars[0]);
x.domain(dimensions);
dimensions.forEach(function(d) {
var vals = cars.map(function(p) {return p[d];});
if (vals.every(quant_p)){
y[d] = d3.scaleLinear()
.domain(d3.extent(cars, function(p) {
return +p[d]; }))
.range([height, 0])
console.log(y[d]);
}
else{
vals.sort();
y[d] = d3.scalePoint()
.domain(vals.filter(function(v, i) {return vals.indexOf(v) == i;}))
.range([height, 0],1);
}
})
extents = dimensions.map(function(p) { return [0,0]; });
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d) + ")"; })
.call(d3.drag()
.subject(function(d) { return {x: x(d)}; })
.on("start", function(d) {
dragging[d] = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(width, Math.max(0, d3.event.x));
foreground.attr("d", path);
dimensions.sort(function(a, b) { return position(a) - position(b); });
x.domain(dimensions);
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("end", function(d) {
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground).attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add an axis and title.
var g = svg.selectAll(".dimension");
g.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(d3.axisLeft(y[d]));})
//text does not show up because previous line breaks somehow
.append("text")
.attr("fill", "black")
.style("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d; });
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
if(y[d].name == 'r'){
// console.log(this);
d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [8,height]]).on("brush start", brushstart).on("brush", go_brush).on("brush", brush_parallel_chart).on("end", brush_end));
}
else if(y[d].name == 'n')
d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [15,height]]).on("brush start", brushstart).on("brush", go_brush).on("brush", brush_parallel).on("end", brush_end_ordinal));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
}); // closing
function position(d) {
var v = dragging[d];
return v == null ? x(d) : v;
}
function transition(g) {
return g.transition().duration(500);
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
}
function go_brush() {
d3.event.sourceEvent.stopPropagation();
}
invertExtent = function(y) {
return domain.filter(function(d, i) { return y === range[i]; });
};
function brushstart(selectionName) {
foreground.style("display", "none")
//console.log(selectionName);
var dimensionsIndex = dimensions.indexOf(selectionName);
//console.log(dimensionsIndex);
extents[dimensionsIndex] = [0, 0];
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
// Handles a brush event, toggling the display of foreground lines.
function brush_parallel_chart() {
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
//if (d3.event.sourceEvent.type === "brush") return;
extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);
}
}
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
function brush_end(){
if (!d3.event.sourceEvent) return; // Only transition after input.
if (!d3.event.selection) return; // Ignore empty selections.
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);
extents[i][0] = Math.round( extents[i][0] * 10 ) / 10;
extents[i][1] = Math.round( extents[i][1] * 10 ) / 10;
d3.select(this).transition().call(d3.event.target.move, extents[i].map(y[dimensions[i]]));
}
}
}
// brush for ordinal cases
function brush_parallel() {
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
var yScale = y[dimensions[i]];
var selected = yScale.domain().filter(function(d){
// var s = d3.event.target.extent();
var s = d3.event.selection;
return (s[0] <= yScale(d)) && (yScale(d) <= s[1])
});
var temp = selected.sort();
extents[i] = [temp[temp.length-1], temp[0]];
}
}
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
//var p_new = (y[p].ticks)?d[p]:y[p](d[p]);
//return extents[i][1] <= p_new && p_new <= extents[i][0];
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
function brush_end_ordinal(){
console.log("hhhhh");
if (!d3.event.sourceEvent) return; // Only transition after input.
if (!d3.event.selection) return; // Ignore empty selections.
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
var yScale = y[dimensions[i]];
var selected = yScale.domain().filter(function(d){
// var s = d3.event.target.extent();
var s = d3.event.selection;
return (s[0] <= yScale(d)) && (yScale(d) <= s[1])
});
var temp = selected.sort();
extents[i] = [temp[temp.length-1], temp[0]];
if(selected.length >1)
d3.select(this).transition().call(d3.event.target.move, extents[i].map(y[dimensions[i]]));
}
}
}
</script>
You can either use d3-axis.ticks() or d3-axis.tickValues([]) to specify custom number of ticks/tick values.
For example, for column1 using tickValues and column3 using ticks:
var g = svg.selectAll(".dimension");
g.append("g")
.attr("class", "axis")
.each(function(d) {
if(d === 'column1') {
d3.select(this).call(d3.axisLeft(y[d]).tickValues([.1, .2, .3, .5,.8]));
} else if(d === 'column3') {
d3.select(this).call(d3.axisLeft(y[d]).ticks(4));
} else {
d3.select(this).call(d3.axisLeft(y[d]));
}
})
A fork of your plunkr: TickValues and ticks DEMO
Hope this helps. Read the docs for more options.
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.
I am trying to combine two D3 Visualizations. I found a question before, but it did not really have a solution.
When I combine the two files the visualizations overlap and produce this:
the streamgraph component:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.chart {
background: #fff;
}
p {
font: 12px helvetica;
}
.axis path, .axis line {
fill: none;
stroke: #000;
stroke-width: 2px;
shape-rendering: crispEdges;
}
button {
position: absolute;
right: 50px;
top: 10px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<div class="chart">
</div>
<script>
chart("Data.csv", "blue");
var datearray = [];
var colorrange = [];
function chart(csvpath, color) {
if (color == "blue") {
colorrange = ["#045A8D", "#2B8CBE", "#74A9CF", "#A6BDDB", "#D0D1E6", "#F1EEF6"];
}
else if (color == "pink") {
colorrange = ["#980043", "#DD1C77", "#DF65B0", "#C994C7", "#D4B9DA", "#F1EEF6"];
}
else if (color == "orange") {
colorrange = ["#B30000", "#E34A33", "#FC8D59", "#FDBB84", "#FDD49E", "#FEF0D9"];
}
strokecolor = colorrange[0];
var format = d3.time.format("%m/%d/%y");
var margin = {top: 20, right: 40, bottom: 30, left: 50};
var width = document.body.clientWidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var tooltip = d3.select("body")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "20")
.style("visibility", "hidden")
.style("top", "30px")
.style("left", "75px");
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height-10, 0]);
var z = d3.scale.ordinal()
.range(colorrange);
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(x)
.ticks(d3.time.years, 10); //tick on every 10 years
/*.scale(x)
.orient("bottom")
.text(date)
//;*/
//. tickFormat(x)
//. tickValues(date)
//was already there but out of view -> changed the left margin
var yAxis = d3.svg.axis()
.scale(y);
var stack = d3.layout.stack()
.offset("silhouette")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.key; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var svg = d3.select(".chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
/* correct this function
var graph = d3.csv(csvpath, function(data) {
data.forEach(function(d) {
d.date = format.parse(d.date);
d.value = +d.value;
});*/
var graph = d3.csv(csvpath, function(raw) {
var data = [];
raw.forEach(function (d) {
data.push({
key: d.Country,
date : new Date(1980,0,1), //I had a bug in creating the right dates
value : parseInt(d['1980-1989'].replace(',','')) //get rid of the thousand separator
});
data.push({
key: d.Country,
date : new Date(1990,0,1),
value : parseInt(d['1990-1999'].replace(',',''))
});
data.push({
key: d.Country,
date : new Date(2000,0,1),
value : parseInt(d['2000-2009'].replace(',','') )
});
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return z(i); });
//adding .text causes axis to dissapear
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
//.text(date)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ", 0)")
//.text(value)
.call(yAxis.orient("right"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis.orient("left"));
var pro;
svg.selectAll(".layer")
.attr("opacity", 1)
.on("mouseover", function(d, i) {
svg.selectAll(".layer").transition()
.duration(250)
.attr("opacity", function(d, j) {
return j != i ? 0.6 : 1;
})})
.on("mousemove", function(d, i) {
var mousex = d3.mouse(this);
mousex = mousex[0];
var invertedx = x.invert(mousex);
//find the largest smaller element
var dd = d.values.filter(function(d) { return d.date <= invertedx; });
dd = dd[dd.length -1]; //use the last element
pro = dd.value;
d3.select(this)
.classed("hover", true)
.attr("stroke", strokecolor)
.attr("stroke-width", "0.5px");
tooltip.html( "<p>" + d.key + "<br>" + pro + "</p>" ).style("visibility", "visible");
})
.on("mouseout", function(d, i) {
svg.selectAll(".layer")
.transition()
.duration(250)
.attr("opacity", "1");
d3.select(this)
.classed("hover", false)
.attr("stroke-width", "0px");
tooltip.html( "<p>" + d.key + "<br>" + pro + "</p>" ).style("visibility", "hidden");
})
var vertical = d3.select(".chart")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "19")
.style("width", "1px")
.style("height", "380px")
.style("top", "10px")
.style("bottom", "30px")
.style("left", "0px")
.style("background", "#fff");
d3.select(".chart")
.on("mousemove", function(){
var mousex = d3.mouse(this);
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px" )})
.on("mouseover", function(){
var mousex = d3.mouse(this);
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px")});
});
}
</script>
Map component:
<!DOCTYPE html>
<meta charset="utf-8">
<title>U.S Immigration Data Visualization</title>
<style>
.country:hover{
stroke: #fff;
stroke-width: 1.5px;
}
.text{
font-size:10px;
text-transform:capitalize;
}
#container {
margin: 10px 10%;
border:2px solid #000;
border-radius: 5px;
height:100%;
overflow:hidden;
background: #e1eafe;
}
.hidden {
display: none;
}
div.tooltip {
color: #222;
background: #fff;
padding: .5em;
text-shadow: #f5f5f5 0 1px 0;
border-radius: 2px;
box-shadow: 0px 0px 2px 0px #a6a6a6;
opacity: 0.9;
position: absolute;
}
.graticule {
fill: none;
stroke: #bbb;
stroke-width: .5px;
stroke-opacity: .5;
}
.equator {
stroke: #ccc;
stroke-width: 1px;
}
</style>
</head>
<br>
<h1><center>U.S Immigration Data Visualization</center></h1>
<h2><b>Work in Progress</b></h2>
<h3><b>Ex-USSR countries included in Russia</b></h3>
<h3><b>Ex-Yugoslavia included in Macedonia</b></h3>
<div id="container"></div>
<script src="js/d3.min.js"></script>
<script src="js/topojson.v1.min.js"></script>
<script src="http://d3js.org/d3.geo.tile.v0.min.js"></script>
<script>
d3.select(window).on("resize", throttle);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 9])
.on("zoom", move);
var width = document.getElementById('container').offsetWidth;
var height = width / 2;
var topo,projection,path,svg,g;
var graticule = d3.geo.graticule();
var tooltip = d3.select("#container").append("div").attr("class", "tooltip hidden");
setup(width,height);
function setup(width,height){
projection = d3.geo.mercator()
.translate([(width/2), (height/2)])
.scale( width / 2 / Math.PI);
path = d3.geo.path().projection(projection);
svg = d3.select("#container").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
.on("click", click)
.append("g");
g = svg.append("g");
}
d3.json("data/world-topo-min.json", function(error, world) {
var countries = topojson.feature(world, world.objects.countries).features;
topo = countries;
draw(topo);
});
function draw(topo) {
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
g.append("path")
.datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]})
.attr("class", "equator")
.attr("d", path);
var country = g.selectAll(".country").data(topo);
country.enter().insert("path")
.attr("class", "country")
.attr("d", path)
.attr("id", function(d,i) { return d.id; })
.attr("title", function(d,i) { return d.properties.name; })
.style("fill", function(d, i) { return d.properties.color; });
//offsets for tooltips
var offsetL = document.getElementById('container').offsetLeft+20;
var offsetT = document.getElementById('container').offsetTop+10;
//tooltips
country
.on("mousemove", function(d,i) {
var mouse = d3.mouse(svg.node()).map( function(d) { return parseInt(d); } );
tooltip.classed("hidden", false)
.attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px")
.html(d.properties.name);
})
.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
});
//EXAMPLE: adding some capitals from external CSV file
d3.csv("Data.csv", function(err, capitals) {
capitals.forEach(function(i){
addpoint(i.CapitalLongitude, i.CapitalLatitude );
});
});
}
function redraw() {
width = document.getElementById('container').offsetWidth;
height = width / 2;
d3.select('svg').remove();
setup(width,height);
draw(topo);
}
function move() {
var t = d3.event.translate;
var s = d3.event.scale;
zscale = s;
var h = height/4;
t[0] = Math.min(
(width/height) * (s - 1),
Math.max( width * (1 - s), t[0] )
);
t[1] = Math.min(
h * (s - 1) + h * s,
Math.max(height * (1 - s) - h * s, t[1])
);
zoom.translate(t);
g.attr("transform", "translate(" + t + ")scale(" + s + ")");
//adjust the country hover stroke width based on zoom level
d3.selectAll(".country").style("stroke-width", 1.5 / s);
}
var throttleTimer;
function throttle() {
window.clearTimeout(throttleTimer);
throttleTimer = window.setTimeout(function() {
redraw();
}, 200);
}
//geo translation on mouse click in map
function click() {
var latlon = projection.invert(d3.mouse(this));
console.log(latlon);
}
//function to add points and text to the map (used in plotting capitals)
function addpoint(lat,lon,text) {
var gpoint = g.append("g").attr("class", "gpoint");
var x = projection([lat,lon])[0];
var y = projection([lat,lon])[1];
gpoint.append("svg:circle")
.attr("cx", x)
.attr("cy", y)
.attr("class","point")
.attr("r", 1);
//conditional in case a point has no associated text
//if(text.length>0){
// gpoint.append("text")
// .attr("x", x+2)
// .attr("y", y+2)
// .attr("class","text")
// .text(text);
//}
}
</script>
</body>
</html>
This is hard to answer without the code you actually use when you 'combine' the two SVG elements, and without the data or a working example.
What I've done is take the 2 basic components, the streamgraph (and svg node inside <div class="chart">) and the map (a separate svg node in <div id="container"></div>), and create working code that combines the 2:
http://plnkr.co/edit/WjlObRIasLYXOuEL4HDE?p=preview
This is the basic code:
<body>
<div class="chart">
</div>
<div id="container">
</div>
<script type="text/javascript">
var width = 300;
var height = width / 2;
// Equivalent of streamgraph code...
var svg_stream = d3.select(".chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("rect")
... // rect attributes
// Equivalent of map code...
var svg_map = d3.select("#container")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("circle")
... // circle attributes
</script>
</body>
What's inside each svg shouldn't make a difference to the positioning, so I've just used a rect to represent the streamgraph and a circle for the map. I've taken as much of the CSS and code from your snippets as it makes sense to. If you combine the two components as in the example above, you should not see any overlap. I'm not really able to correct your version as I don't know how you did it.
Note - You should also avoid defining duplicate variable names (like var svg for both SVGs) when combining the components
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)