Reputation: 340
My task is to organize a timeline visualization.
I took the example from - http://bl.ocks.org/bunkat/2338034.
Making necessary modifications I notice that timeBegin
variable is equal to "0" and if I try to modify it by introducing:
d3.min(timeSegments, function (d) {
return d.segment_start;
})
The graph starts to enlarge horizontally. Only when the variable timeBegin
is 0 the graph is displaying in a correct manner. Where is my wrong minding in D3?
I found another example - https://codepen.io/manglass/pen/MvLBRz, but this cases are slightly complicated for me.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>RePLICA</title>
<style type="text/css">
.chart-table {
shape-rendering: crispEdges;
}
.graph-square text {
font: 10px sans-serif;
}
div.tooltip-donut {
position: absolute;
text-align: center;
padding: .5rem;
background: #FFFFFF;
color: #313639;
border: 1px solid #313639;
border-radius: 8px;
pointer-events: none;
font-size: 1.3rem;
}
.brush .extent {
stroke: gray;
fill: dodgerblue;
fill-opacity: .365;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script type="text/javascript">
//data
var lanes = ["Chinese", "Japanese", "Korean", "Moldova"],
laneLength = lanes.length,
timeSegments = [{
"lane": 0,
"id": "Qin",
"segment_start": 100,
"segment_end": 210,
"flag": false
},
{
"lane": 0,
"id": "Jin",
"segment_start": 210,
"segment_end": 420,
"flag": true
},
{
"lane": 0,
"id": "Sui",
"segment_start": 420,
"segment_end": 615,
"flag": false
},
{
"lane": 1,
"id": "Yamato",
"segment_start": 300,
"segment_end": 530,
"flag": false
},
{
"lane": 1,
"id": "Asuka",
"segment_start": 530,
"segment_end": 700,
"flag": true
},
{
"lane": 1,
"id": "Nara",
"segment_start": 710,
"segment_end": 800,
"flag": false
},
{
"lane": 1,
"id": "Heian",
"segment_start": 800,
"segment_end": 1180,
"flag": true
},
{
"lane": 2,
"id": "Three Kingdoms",
"segment_start": 100,
"segment_end": 670,
"flag": false
},
{
"lane": 2,
"id": "North and South States",
"segment_start": 670,
"segment_end": 900,
"flag": true
},
{
"lane": 3,
"id": "Chisinau",
"segment_start": 250,
"segment_end": 600,
"flag": false
},
{
"lane": 3,
"id": "Balti",
"segment_start": 600,
"segment_end": 900,
"flag": true
},
{
"lane": 3,
"id": "Ungheni",
"segment_start": 920,
"segment_end": 1380,
"flag": false
}
],
timeBegin = 0, // !!! d3.min(timeSegments, function (d) { return d.segment_start; }) ---- Does not work !!!
timeEnd = d3.max(timeSegments, function(d) {
return d.segment_end;
});
var widthTotal = 1300,
heightTotal = 500,
margin = {
top: 10,
right: 15,
bottom: 0,
left: 100
},
widthSVG = widthTotal - margin.right - margin.left,
heightSVG = heightTotal - margin.top - margin.bottom,
graphHeight = laneLength * 10 + heightTotal / 3; // - 3 just a coonstant
// scales
var scaleX = d3.scaleLinear()
.domain([timeBegin, timeEnd])
.range([0, widthSVG]);
var scaleY = d3.scaleLinear()
.domain([0, laneLength])
.range([0, graphHeight]);
var colorScale = d3.scaleOrdinal(d3.schemeCategory10);
var chart = d3.select("body")
.append("svg")
.attr("width", widthSVG + margin.right + margin.left)
.attr("height", heightSVG + margin.top + margin.bottom)
.attr("class", "chart-table");
var graph = chart.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("width", widthSVG)
.attr("height", graphHeight)
.attr("class", "graph-square");
// Draw the axis
chart.append("g")
.attr("transform", "translate(" + margin.left + ", " + (graphHeight + 20) + ")") // This controls the vertical position of the Axis
.call(d3.axisBottom(scaleX));
// Delimitation lines
graph.append("g").selectAll(".laneLines")
.data(timeSegments)
.enter().append("line")
.attr("x1", 0)
.attr("y1", function(d) {
return scaleY(d.lane);
})
.attr("x2", widthSVG)
.attr("y2", function(d) {
return scaleY(d.lane);
})
.attr("stroke", "lightgray");
// Lanes Names display
graph.append("g").selectAll(".laneText")
.data(lanes)
.enter().append("text")
.text(function(d) {
return d;
})
.attr("x", -margin.right)
.attr("y", function(d, i) {
return scaleY(i + .5);
})
.attr("dy", ".5ex")
.attr("text-anchor", "end")
.attr("class", "laneText");
// Add DIV for "hover_info"
var div = d3.select("body").append("div")
.attr("class", "tooltip-donut")
.style("opacity", 0);
// Graph item rects
graph.append("g").selectAll(".graphItem")
.data(timeSegments)
.enter().append("rect")
.attr("x", function(d) {
return scaleX(d.segment_start);
})
.attr("y", function(d) {
let shiftVertical = 9;
if (d.flag) {
shiftVertical = 0
};
return scaleY(d.lane + .5) - shiftVertical;
})
.attr("width", function(d) {
return scaleX(d.segment_end - d.segment_start);
})
.attr("height", 10)
.style("fill", function(d) {
return colorScale(d.lane);
})
// Hover effect
.on('mouseover', function(d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '.5');
div.transition()
.duration(50)
.style("opacity", 1);
let hover_info = ("id:" + d.id + "<br/>" + "start:" + d.segment_start + "<br/>" + "end:" + d.segment_end).toString();
//Makes the new div appear on hover:
div.html(hover_info)
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 15) + "px");
})
.on('mouseout', function(d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '1')
//Makes the new div disappear:
div.transition()
.duration('50')
.style("opacity", 0);
});
</script>
</body>
</html>
Upvotes: 1
Views: 160
Reputation: 102188
The d3.min
function you have does work, that's not the problem. The problem is the math you're using to calculate the width of the rectangles:
.attr("width", function(d) {
return scaleX(d.segment_end - d.segment_start);
})
As you can see, that only works if the scale starts at 0
. For a more dynamic scale, like the one you want, the width should be:
.attr("width", function(d) {
return scaleX(d.segment_end - d.segment_start + scaleX.domain()[0]);
})
That is, you add the first value of the domain to the number you pass to the scale.
Here is your code with that change only:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>RePLICA</title>
<style type="text/css">
.chart-table {
shape-rendering: crispEdges;
}
.graph-square text {
font: 10px sans-serif;
}
div.tooltip-donut {
position: absolute;
text-align: center;
padding: .5rem;
background: #FFFFFF;
color: #313639;
border: 1px solid #313639;
border-radius: 8px;
pointer-events: none;
font-size: 1.3rem;
}
.brush .extent {
stroke: gray;
fill: dodgerblue;
fill-opacity: .365;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script type="text/javascript">
//data
var lanes = ["Chinese", "Japanese", "Korean", "Moldova"],
laneLength = lanes.length,
timeSegments = [{
"lane": 0,
"id": "Qin",
"segment_start": 100,
"segment_end": 210,
"flag": false
},
{
"lane": 0,
"id": "Jin",
"segment_start": 210,
"segment_end": 420,
"flag": true
},
{
"lane": 0,
"id": "Sui",
"segment_start": 420,
"segment_end": 615,
"flag": false
},
{
"lane": 1,
"id": "Yamato",
"segment_start": 300,
"segment_end": 530,
"flag": false
},
{
"lane": 1,
"id": "Asuka",
"segment_start": 530,
"segment_end": 700,
"flag": true
},
{
"lane": 1,
"id": "Nara",
"segment_start": 710,
"segment_end": 800,
"flag": false
},
{
"lane": 1,
"id": "Heian",
"segment_start": 800,
"segment_end": 1180,
"flag": true
},
{
"lane": 2,
"id": "Three Kingdoms",
"segment_start": 100,
"segment_end": 670,
"flag": false
},
{
"lane": 2,
"id": "North and South States",
"segment_start": 670,
"segment_end": 900,
"flag": true
},
{
"lane": 3,
"id": "Chisinau",
"segment_start": 250,
"segment_end": 600,
"flag": false
},
{
"lane": 3,
"id": "Balti",
"segment_start": 600,
"segment_end": 900,
"flag": true
},
{
"lane": 3,
"id": "Ungheni",
"segment_start": 920,
"segment_end": 1380,
"flag": false
}
],
timeBegin = d3.min(timeSegments, function(d) {
return d.segment_start;
}),
timeEnd = d3.max(timeSegments, function(d) {
return d.segment_end;
});
var widthTotal = 1300,
heightTotal = 500,
margin = {
top: 10,
right: 15,
bottom: 0,
left: 100
},
widthSVG = widthTotal - margin.right - margin.left,
heightSVG = heightTotal - margin.top - margin.bottom,
graphHeight = laneLength * 10 + heightTotal / 3; // - 3 just a coonstant
// scales
var scaleX = d3.scaleLinear()
.domain([timeBegin, timeEnd])
.range([0, widthSVG]);
var scaleY = d3.scaleLinear()
.domain([0, laneLength])
.range([0, graphHeight]);
var colorScale = d3.scaleOrdinal(d3.schemeCategory10);
var chart = d3.select("body")
.append("svg")
.attr("width", widthSVG + margin.right + margin.left)
.attr("height", heightSVG + margin.top + margin.bottom)
.attr("class", "chart-table");
var graph = chart.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("width", widthSVG)
.attr("height", graphHeight)
.attr("class", "graph-square");
// Draw the axis
chart.append("g")
.attr("transform", "translate(" + margin.left + ", " + (graphHeight + 20) + ")") // This controls the vertical position of the Axis
.call(d3.axisBottom(scaleX));
// Delimitation lines
graph.append("g").selectAll(".laneLines")
.data(timeSegments)
.enter().append("line")
.attr("x1", 0)
.attr("y1", function(d) {
return scaleY(d.lane);
})
.attr("x2", widthSVG)
.attr("y2", function(d) {
return scaleY(d.lane);
})
.attr("stroke", "lightgray");
// Lanes Names display
graph.append("g").selectAll(".laneText")
.data(lanes)
.enter().append("text")
.text(function(d) {
return d;
})
.attr("x", -margin.right)
.attr("y", function(d, i) {
return scaleY(i + .5);
})
.attr("dy", ".5ex")
.attr("text-anchor", "end")
.attr("class", "laneText");
// Add DIV for "hover_info"
var div = d3.select("body").append("div")
.attr("class", "tooltip-donut")
.style("opacity", 0);
// Graph item rects
graph.append("g").selectAll(".graphItem")
.data(timeSegments)
.enter().append("rect")
.attr("x", function(d) {
return scaleX(d.segment_start);
})
.attr("y", function(d) {
let shiftVertical = 9;
if (d.flag) {
shiftVertical = 0
};
return scaleY(d.lane + .5) - shiftVertical;
})
.attr("width", function(d) {
return scaleX(d.segment_end - d.segment_start + scaleX.domain()[0]);
})
.attr("height", 10)
.style("fill", function(d) {
return colorScale(d.lane);
})
// Hover effect
.on('mouseover', function(d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '.5');
div.transition()
.duration(50)
.style("opacity", 1);
let hover_info = ("id:" + d.id + "<br/>" + "start:" + d.segment_start + "<br/>" + "end:" + d.segment_end).toString();
//Makes the new div appear on hover:
div.html(hover_info)
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 15) + "px");
})
.on('mouseout', function(d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '1')
//Makes the new div disappear:
div.transition()
.duration('50')
.style("opacity", 0);
});
</script>
</body>
</html>
Upvotes: 1