Related
I would like to color a rectangle by the value of a dataset.
Here is an example where I plot a sine-wave and color the line by the y-value (from red to blue as the y-value changes from 1 to -1).
What I would like is to have a bar that is colored by that y-value.
Here is a fiddle: https://jsfiddle.net/sanandak/m2jryr22/11/
(apologies for my prior post where the fiddle was missing!)
var svg = d3.select('body').append('svg')
svg.attr('width', 300).attr('height', 300)
data = d3.range(0, 2 * Math.PI, 0.1)
.map(function(t) {
return {
x: t,
y: Math.sin(t)
};
});
var xScale = d3.scaleLinear()
.domain([0, 2 * Math.PI])
.range([10, 290])
var yScale = d3.scaleLinear()
.domain([-1, 1])
.range([150, 10])
var line = d3.line()
.x(function(d, i) {
return xScale(d.x);
})
.y(function(d, i) {
return yScale(d.y);
});
svg.append("linearGradient")
.attr("id", "line-gradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0).attr("y1", yScale(-1))
.attr("x2", 0).attr("y2", yScale(1))
.selectAll("stop")
.data([{
"offset": "0%",
color: "blue"
}, {
"offset": "100%",
color: "red"
}])
.enter()
.append("stop")
.attr("offset", function(d) {
return d.offset;
})
.attr("stop-color", function(d) {
return d.color;
})
svg.append('g')
.datum(data)
.append('path')
.attr('d', line)
.attr('class', 'line')
.attr('stroke', 'url(#line-gradient)')
svg.append('g')
.datum(data)
.append('rect')
.attr('x', 10)
.attr('y', 160)
.attr('width', 280)
.attr('height', 20)
.attr('fill', 'url(#line-gradient)')
I understand now. I would define a complex gradient matching your y values across the x domain:
var c = d3.scaleLinear()
.domain([-1,1])
.range(['blue', 'red'])
svg.append("linearGradient")
.attr("id", "line-gradient2")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 10).attr("y1", 0)
.attr("x2", 280).attr("y2", 0)
.selectAll("stop")
.data(data)
.enter()
.append('stop')
.attr('stop-color', function(d){
return c(d.y);
})
.attr('offset',function(d){
return (d.x/xScale.domain()[1] * 100) + '%';
});
Here's it is in action:
var svg = d3.select('body').append('svg')
svg.attr('width', 300).attr('height', 300)
data = d3.range(0, 2 * Math.PI, 0.1)
.map(function(t) {
return {
x: t,
y: Math.sin(t)
};
});
var xScale = d3.scaleLinear()
.domain([0, 2 * Math.PI])
.range([10, 290])
var yScale = d3.scaleLinear()
.domain([-1, 1])
.range([150, 10])
var line = d3.line()
.x(function(d, i) {
return xScale(d.x);
})
.y(function(d, i) {
return yScale(d.y);
});
svg.append("linearGradient")
.attr("id", "line-gradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0).attr("y1", yScale(-1))
.attr("x2", 0).attr("y2", yScale(1))
.selectAll("stop")
.data([{
"offset": "0%",
color: "blue"
}, {
"offset": "100%",
color: "red"
}])
.enter()
.append("stop")
.attr("offset", function(d) {
return d.offset;
})
.attr("stop-color", function(d) {
return d.color;
})
var c = d3.scaleLinear()
.domain([-1,1])
.range(['blue', 'red'])
svg.append("linearGradient")
.attr("id", "line-gradient2")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 10).attr("y1", 0)
.attr("x2", 280).attr("y2", 0)
.selectAll("stop")
.data(data)
.enter()
.append('stop')
.attr('stop-color', function(d){
return c(d.y);
})
.attr('offset',function(d){
return (d.x/xScale.domain()[1] * 100) + '%';
});
svg.append('g')
.datum(data)
.append('path')
.attr('d', line)
.attr('class', 'line')
.attr('stroke', 'url(#line-gradient)')
svg.append('g')
.datum(data)
.append('rect')
.attr('x', 10)
.attr('y', 160)
.attr('width', 280)
.attr('height', 20)
.attr('fill', 'url(#line-gradient2)')
var rWidth = 280 / (data.length -1);
svg.append('g')
.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', function(d) {return xScale(d.x);})
.attr('y', 200)
.attr('width', rWidth)
.attr('height', 20)
.attr('stroke', 'none')
.attr('fill', function(d) {return c(d.y);})
.line {
fill: none;
stroke-width: 2;
}
<script src="//d3js.org/d3.v4.min.js"></script>
I'm working on d3.js v4 and im trying to load external data from data.tsv file but its not loading, here is my code:
var bardata = [];
d3.tsv('data.tsv', function(){
for (key in data){
bardata.push(data[key].year.value)
}
var margin = {top:30, right:30, bottom:40, left:50}
var height = 400 - margin.top - margin.bottom,
width = 400 - margin.left - margin.right,
barWidth = 50,
barOffset = 5;
var tempColor;
var yScale = d3.scaleLinear()
.domain([0, d3.max(bardata)])
.range([0, height]);
var xScale = d3.scaleBand()
.domain(d3.range(0, bardata.length))
.padding(0.1)
.range([0, width]);
var tooltip = d3.select('body').append('div')
.style('position', 'absolute')
.style('padding', '0 10px')
.style('background', 'white')
.style('opacity', 0)
var myChart = d3.select('#chart').append('svg')
.style('background', '#E7E0CB')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate('+ margin.left +', '+ margin.top +')')
.style('background', '#C9D7D6')
.selectAll('rect').data(bardata)
.enter().append('rect')
.style('fill', '#C61C6F')
.attr('width', xScale.bandwidth())
.attr('x', function(d, i) {
return xScale(i);
})
.attr('height', 0)
.attr('y', height)
.on('mouseover', function(d){
d3.select(this)
.style('opacity', 0.5)
})
.on('mouseleave', function(d){
d3.select(this)
.style('opacity', 1)
})
.on('mouseover', function(d){
tooltip.transition()
.style('opacity', 0.9)
tooltip.html(d)
.style('left', (d3.event.pageX - 35) + 'px')
.style('top', (d3.event.pageY - 30) + 'px')
tempColor = this.style.fill;
d3.select(this)
.style('opacity', 0.5)
.style('fill', 'yellow')
})
.on('mouseleave', function(d){
tempColor = this.style.fill;
d3.select(this)
.style('opacity', 1)
.style('fill', '#C61C6F')
})
myChart.transition()
.attr('height', function(d){
return yScale(d);
})
.attr('y', function(d){
return height - yScale(d);
})
.delay(function(d, i){
return i * 20;
})
.duration(1000)
.ease(d3.easeElastic)
var vGuideScale = d3.scaleLinear()
.domain([0, d3.max(bardata)])
.range([0, height]);
var vAxis = d3.axisLeft(vGuideScale).ticks(10)
var vGuide = d3.select('svg').append('g')
vAxis(vGuide)
vGuide.attr('transform', 'translate('+ margin.left +', '+ margin.top +')')
var hAxis = d3.axisBottom(xScale).tickValues(xScale.domain().filter(function(d,i){
return !(i % (bardata.length/5))
}))
var hGuide = d3.select('svg').append('g')
hAxis(hGuide)
hGuide.attr('transform', 'translate('+ margin.left +', '+ (height + margin.top) +')')
});
<!DOCTYPE html>
<html>
<head>
<title>Bar Chart</title>
<meta charset="8-UTF">
<link rel="stylesheet" src="css/style.css"> </head>
<body>
<div class="container">
<h2>Bar Chart</h2>
<div id="chart"></div>
</div>
<script src="js/d3.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
and here is the data in my tsv file
year value
2010 23,499,201
2011 22,544,175
2012 24,061,655
2013 23,413,369
2014 27,144,494
2015 26,812,665
2016 30,377,832
its has no errors but nothing appears on the chart
The way you're trying to create your bar chart right now, getting rid of the nice array of objects d3.tsv creates for you and relying in a simple array of numbers, is not the adequate let alone the most convenient way to do it. For any seasoned D3 coder, it makes little sense. However, it's not my place giving lectures here.
That being said, I'll address only the current problems:
d3.tsv needs a parameter in the callback:
d3.tsv('data.tsv', function(data){
//parameter here -------------^
Don't put comma in your numbers. This is not accepted in any programming language I'm aware of:
23,499,201 ---> 23499201
Remember, as I said in your last question, to avoid the columns property:
for (key in data) {
if (key != "columns") bardata.push(data[key].value)
}
Convert the strings to numbers:
bardata.push(+data[key].value)
All together, this is your working code:
var data = d3.tsvParse(d3.select("#tsv").text());
var bardata = [];
for (key in data){
if(key != "columns") bardata.push(+data[key].value)
}
var margin = {top:30, right:30, bottom:40, left:50}
var height = 400 - margin.top - margin.bottom,
width = 400 - margin.left - margin.right,
barWidth = 50,
barOffset = 5;
var tempColor;
var yScale = d3.scaleLinear()
.domain([0, d3.max(bardata)])
.range([0, height]);
var xScale = d3.scaleBand()
.domain(d3.range(0, bardata.length))
.padding(0.1)
.range([0, width]);
var tooltip = d3.select('body').append('div')
.style('position', 'absolute')
.style('padding', '0 10px')
.style('background', 'white')
.style('opacity', 0)
var myChart = d3.select('body').append('svg')
.style('background', '#E7E0CB')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate('+ margin.left +', '+ margin.top +')')
.style('background', '#C9D7D6')
.selectAll('rect').data(bardata)
.enter().append('rect')
.style('fill', '#C61C6F')
.attr('width', xScale.bandwidth())
.attr('x', function(d, i) {
return xScale(i);
})
.attr('height', 0)
.attr('y', height)
.on('mouseover', function(d){
d3.select(this)
.style('opacity', 0.5)
})
.on('mouseleave', function(d){
d3.select(this)
.style('opacity', 1)
})
.on('mouseover', function(d){
tooltip.transition()
.style('opacity', 0.9)
tooltip.html(d)
.style('left', (d3.event.pageX - 35) + 'px')
.style('top', (d3.event.pageY - 30) + 'px')
tempColor = this.style.fill;
d3.select(this)
.style('opacity', 0.5)
.style('fill', 'yellow')
})
.on('mouseleave', function(d){
tempColor = this.style.fill;
d3.select(this)
.style('opacity', 1)
.style('fill', '#C61C6F')
})
myChart.transition()
.attr('height', function(d){
return yScale(d);
})
.attr('y', function(d){
return height - yScale(d);
})
.delay(function(d, i){
return i * 20;
})
.duration(1000)
.ease(d3.easeElastic)
var vGuideScale = d3.scaleLinear()
.domain([0, d3.max(bardata)])
.range([0, height]);
var vAxis = d3.axisLeft(vGuideScale).ticks(10)
var vGuide = d3.select('svg').append('g')
vAxis(vGuide)
vGuide.attr('transform', 'translate('+ margin.left +', '+ margin.top +')')
var hAxis = d3.axisBottom(xScale).tickValues(xScale.domain().filter(function(d,i){
return !(i % (bardata.length/5))
}))
var hGuide = d3.select('svg').append('g')
hAxis(hGuide)
hGuide.attr('transform', 'translate('+ margin.left +', '+ (height + margin.top) +')')
pre{
display: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<pre id="tsv">year value
2010 23499201
2011 22544175
2012 24061655
2013 23413369
2014 27144494
2015 26812665
2016 30377832
</pre>
Again: I'm using a <pre> element to store the data. Don't simply copy/paste my snippet, as you did last time.
I'm new to d3.js. I wanted to drag a line chart using its points. It is working fine without the x and y axes.
I have used this example as reference: https://bl.ocks.org/mbostock/4342190
With the axes to the line chart the line is not plotting correcly. Please have a look into the snippet below.
Thanks in advance.
<!DOCTYPE html>
<svg width="500" height="350"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
let points = d3.range(1, 10).map(function(i) {
return [i * width / 10, 50 + Math.random() * (height - 100)];
});
var x = d3.scaleLinear()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y);
var line = d3.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); });
let drag = d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended);
svg.append('rect')
.attr('class', 'zoom')
.attr('cursor', 'move')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
var focus = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(points, function(d) { return d[0]; }));
y.domain(d3.extent(points, function(d) { return d[1]; }));
focus.append("path")
.datum(points)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
focus.selectAll('circle')
.data(points)
.enter()
.append('circle')
.attr('r', 5.0)
.attr('cx', function(d) { return x(d[0]); })
.attr('cy', function(d) { return y(d[1]); })
.style('cursor', 'pointer')
.style('fill', 'steelblue');
focus.selectAll('circle')
.call(drag);
focus.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
focus.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
function dragstarted(d) {
d3.select(this).raise().classed('active', true);
}
function dragged(d) {
d3.select(this)
.attr('cx', d[0] = d3.event.x)
.attr('cy', d[1] = d3.event.y)
focus.select('path').attr('d', line);
}
function dragended(d) {
d3.select(this).classed('active', false);
}
</script>
d3.event is holding pixel positions, but your plot is driven by user-space coordinates. So, you need to convert those pixel positions to user-space. You can use your scales invert method to do so.
function dragged(d) {
d[0] = x.invert(d3.event.x); // convert to user-space
d[1] = y.invert(d3.event.y);
d3.select(this)
.attr('cx', x(d[0])) // back to pixels
.attr('cy', y(d[1]))
focus.select('path').attr('d', line); //line does pixel conversion too
}
Running code:
<!DOCTYPE html>
<svg width="500" height="350"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
let points = d3.range(1, 10).map(function(i) {
return [i * width / 10, 50 + Math.random() * (height - 100)];
});
var x = d3.scaleLinear()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y);
var line = d3.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); });
let drag = d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended);
svg.append('rect')
.attr('class', 'zoom')
.attr('cursor', 'move')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
var focus = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(points, function(d) { return d[0]; }));
y.domain(d3.extent(points, function(d) { return d[1]; }));
focus.append("path")
.datum(points)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
focus.selectAll('circle')
.data(points)
.enter()
.append('circle')
.attr('r', 5.0)
.attr('cx', function(d) { return x(d[0]); })
.attr('cy', function(d) { return y(d[1]); })
.style('cursor', 'pointer')
.style('fill', 'steelblue');
focus.selectAll('circle')
.call(drag);
focus.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
focus.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
function dragstarted(d) {
d3.select(this).raise().classed('active', true);
}
function dragged(d) {
d[0] = x.invert(d3.event.x);
d[1] = y.invert(d3.event.y);
d3.select(this)
.attr('cx', x(d[0]))
.attr('cy', y(d[1]))
focus.select('path').attr('d', line);
}
function dragended(d) {
d3.select(this).classed('active', false);
}
</script>
I have the following code.
On click of a button it adds a circle , on hovering the circle a red square is shown and on mouseout it will be hidden. For one circle it works perfectly. But since i'm using d3.selectAll , when there are multiple circle it shows and hides all red rectangles when hovered on one circle.
Is there a way to select the rectangle associated to circle that is hovered using the d3.select or d3.selectAll ?
For demonstrating the problem in the code i've added 3 circles can be properly added
$(document).ready(function() {
var width = 560,
height = 500;
var i = -1;
valArray = [100, 200, 300, 400];
var svg = d3.select('#canvas')
.append('svg')
.attr('width', width)
.attr('height', height);
$("#add").click(function() {
i++;
var g = svg.append('svg:g')
.attr('x', valArray[i])
.attr('y', valArray[i]);
var yesDecision = g.append('svg:rect')
.style('fill', "#D64541")
.attr("width", "50")
.attr("height", "50")
.attr("id", "yesDecision")
.attr("class", "hoverNode")
.attr("x", valArray[i])
.attr("y", valArray[i]);
g.append('svg:text')
.attr('x', valArray[i])
.attr('y', valArray[i])
.attr('class', 'id hoverNode')
.text(function(d) {
return "Yes";
});
g.append('svg:circle')
.attr('class', 'node')
.attr('r', 40)
.attr('cx', valArray[i])
.attr('cy', valArray[i])
.style('fill', function(d) {
return ("#ccc");
}).on('mouseover', function(d) {
d3.selectAll(".hoverNode").style("visibility", "visible")
})
.on('mouseout', function(d) {
d3.selectAll(".hoverNode").style("visibility", "hidden")
})
});
});
.hoverNode {
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="add">Add element</button>
<div id="canvas">
</div>
One solution is setting a specific class for each rectangle, based on i:
var yesDecision = g.append('rect')
.attr("class", "hoverNode" + i)
And do the same for the circles:
g.append('circle')
.attr('class', 'node' + i)
Then, inside your mouseover and mousemove, you select the rectangle based on the circle class:
.on('mouseover', function(d) {
var elementID = d3.select(this).attr("class").slice(-1);
d3.selectAll(".hoverNode" + elementID).style("visibility", "visible")
})
.on('mouseout', function(d) {
var elementID = d3.select(this).attr("class").slice(-1);
d3.selectAll(".hoverNode" + elementID).style("visibility", "hidden")
})
Here is the working code (click "run code snippet"):
var width = 560,
height = 500;
var i = -1;
valArray = [50, 120, 190, 260];
var svg = d3.select("#chart")
.append("svg")
.attr('width', width)
.attr('height', height);
d3.select("#add").on("click", function() {
i++;
var g = svg.append('g')
.attr('x', valArray[i])
.attr('y', valArray[i]);
var yesDecision = g.append('rect')
.style('fill', "#D64541")
.attr("width", "50")
.attr("height", "50")
.attr("id", "yesDecision")
.style("visibility", "hidden")
.attr("class", "hoverNode" + i)
.attr("x", valArray[i])
.attr("y", valArray[i]);
g.append('circle')
.attr('class', 'node' + i)
.attr('r', 40)
.attr('cx', valArray[i])
.attr('cy', valArray[i])
.style('fill', function(d) {
return ("#ccc");
}).on('mouseover', function(d) {
var elementID = d3.select(this).attr("class").slice(-1);
d3.selectAll(".hoverNode" + elementID).style("visibility", "visible")
})
.on('mouseout', function(d) {
var elementID = d3.select(this).attr("class").slice(-1);
d3.selectAll(".hoverNode" + elementID).style("visibility", "hidden")
})
g.append('text')
.attr("x", valArray[i])
.attr("y", valArray[i])
.style("visibility", "hidden")
.attr("pointer-events", "none")
.attr('class', 'id hoverNode' + i)
.text("Yes");
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<button id="add">Add</button>
<div id="chart"></div>
You could bind rect as data to circle and then get access to rect in mouse events:
g.append('svg:circle')
.data([yesDecision])
.attr('class', 'node')
.attr('r', 40)
.attr('cx', valArray[i])
.attr('cy', valArray[i])
.style('fill', function(d) {
return ("#ccc");
}).on('mouseover', function(d) {
d.style("visibility", "visible")
})
.on('mouseout', function(d) {
d.style("visibility", "hidden")
})
Here is your edited code:
$(document).ready(function() {
var width = 560,
height = 500;
var i = -1;
valArray = [100, 200, 300, 400];
var svg = d3.select('#canvas')
.append('svg')
.attr('width', width)
.attr('height', height);
$("#add").click(function() {
i++;
var g = svg.append('svg:g')
.attr('x', valArray[i])
.attr('y', valArray[i]);
var yesDecision = g.append('svg:rect')
.style('fill', "#D64541")
.attr("width", "50")
.attr("height", "50")
.attr("id", "yesDecision")
.attr("class", "hoverNode")
.attr("x", valArray[i])
.attr("y", valArray[i]);
g.append('svg:text')
.attr('x', valArray[i])
.attr('y', valArray[i])
.attr('class', 'id hoverNode')
.text(function(d) {
return "Yes";
});
g.append('svg:circle')
.data([yesDecision])
.attr('class', 'node')
.attr('r', 40)
.attr('cx', valArray[i])
.attr('cy', valArray[i])
.style('fill', function(d) {
return ("#ccc");
}).on('mouseover', function(d) {
d.style("visibility", "visible")
})
.on('mouseout', function(d) {
d.style("visibility", "hidden")
})
});
});
.hoverNode {
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="add">Add element</button>
<div id="canvas">
</div>
I have implemented a d3 line graph which reads data from a CSV file, and then plots multiple lines which react to mouseover events. It works fine with pan and zoom using the following code (sorry that it is so long and slightly untidy but I felt it best to display the full code):
d3.csv("ijisb.csv", function(error, data) {
var margin = {top: 50, right: 120, bottom: 50, left: 70},
width = 1200 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
var parseDate = d3.time.format("%m%d").parse;
var color = d3.scale.category20();
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var sources = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, temperature: +d[name]};
})
};
});
var x = d3.time.scale()
.range([0, width])
.domain([
d3.min(data, function(d) { return d.date; }),
d3.max(data, function(d) { return d.date; })
]);
var y = d3.scale.linear()
.range([height, 0])
.domain([
0,
d3.max(sources, function(c) { return d3.max(c.values, function(v) { return v.temperature; }); })
]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(12)
.tickFormat(d3.time.format("%b %d"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var line = d3.svg.line()
.defined(function(d) { return d.temperature >= 0; })
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.temperature); });
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 8])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", "0 0 1200 800")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
var rect = svg.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("class", "plot");
var make_x_axis = function () {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(12)
.tickFormat(d3.time.format("%b %d"));
};
var make_y_axis = function () {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
};
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat(""));
svg.append("g")
.attr("class", "y grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat(""));
var source = svg.selectAll(".source")
.data(sources)
.enter().append("g")
.attr("class", "source");
var clip = svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.append("text");
source.append("path")
.data(sources)
.attr("class", "line")
.attr("clip-path", "url(#clip)")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) {return color(d.name);})
.style("opacity", 0.8)
.on("mouseover", function(d){
d3.select(this)
.style("stroke",function(d) {return color(d.name);})
.style("opacity", 1.0)
.style("stroke-width", 2.5);
this.parentNode.parentNode.appendChild(this.parentNode);
d3.select('#text-' + d.name)
.style("fill",function(d) {return color(d.name);})
.style("font-weight", 700);
})
.on("mouseout", function(d) {
d3.select(this)
.transition()
.duration(250)
.style("stroke", function(d) {return color(d.name);})
.style("stroke-width", 1.5)
.style("opacity", 0.8);
d3.select('#text-' + d.name)
.transition()
.duration(250)
.style("fill", function(d) {return color(d.name);})
.style("font-weight", 400);
})
.attr("id", function(d, i) { return "path-" + d.name; });
source.append("text")
.datum(function(d) { return {name: d.name}; })
.attr("x", function(d, i) { return width+10; })
.attr("y", function(d, i) { return (i*(height/16)); })
.style("fill", function(d) {return color(d.name);})
.on("mouseover", function(d){
d3.select('#path-' + d.name)
.style("stroke",function(d) {return color(d.name);})
.style("opacity", 1.0)
.style("stroke-width", 2.5);
this.parentNode.parentNode.appendChild(this.parentNode);
d3.select(this)
.style("fill",function(d) {return color(d.name);})
.style("font-weight", 700);
})
.on("mouseout", function(d) {
d3.select('#path-' + d.name)
.transition()
.duration(250)
.style("stroke", function(d) {return color(d.name);})
.style("stroke-width", 1.5)
.style("opacity", 0.8);
d3.select(this)
.transition()
.duration(250)
.style("fill", function(d) {return color(d.name);})
.style("font-weight", 400);
})
.text(function(d) { return d.name; })
.attr("font-family","sans-serif")
.attr("font-size","14px")
.attr("id", function(d, i) { return "text-" + d.name; }
);
var minT = new Date('01/01/1900'), maxT = new Date('01/01/2002'), w = $(window).width();
function zoomed() {
d3.event.translate;
d3.event.scale;
svg.select(".x.axis")
.call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.select(".x.grid")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat(""));
svg.select(".y.grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat(""));
source.select(".line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) {return color(d.name);});
}
});
The problem I have is that I want to limit the panning and zooming so that it is not possible for the graph to be zoomed or panned out offscreen. I can do this by setting the scaleExtent on the zoom (which is implemented in the above example) and changing the zoomed function to:
function zoomed() {
var t = zoom.translate(),
tx = t[0];
ty = t[1];
tx = Math.min(tx, 0);
zoom.translate([tx, ty]);
d3.event.translate;
d3.event.scale;
svg.select(".x.axis")
.call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.select(".x.grid")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat(""));
svg.select(".y.grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat(""));
source.select(".line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) {return color(d.name);});
}
This limits the x-axis minimum to zero. However hard I try however, I cannot limit the maximum value of the x-axis, which means the graph can still pan too far to the right.
Any help? I hope this makes sense!
Nick
Thanks for the help, I did it by the following code in the end:
var t = zoom.translate(),
s = zoom.scale();
tx = Math.min(0, Math.max(width * (1 - s), t[0]));
ty = Math.min(0, Math.max(height * (1 - s), t[1]));
zoom.translate([tx, ty]);
The difficulty was in bounding the graph at various zoom levels, which this now solves. The d3.event.translate and d3.event.scale statements were also unnecessary, and should have been removed.
You can simply check the value manually and reset it if it is too high:
if(tx > threshold) { tx = threshold; }
Also, the statements
d3.event.translate;
d3.event.scale;
in your code have no effect.