I'm new to Javascript, and through exploring various websites I have created a draggable points line chart. The data points are movable and the line connecting the dots is made invisible. The data points will move vertically only. I want to have grid lines with this chart. I tried but have not able to achieve it. Can someone help me with adding gridlines to this chart? I have attached the code that I have build.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.0/d3.min.js"></script>
<!DOCTYPE html>
<style>
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
</style>
<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;
function make_x_axis() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
}
function make_y_axis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
}
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 + ')')
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat("")
)
svg.append("g")
.attr("class", "grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat("")
)
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", "white")
.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>
The part you're using to append the grid is from v3 of d3.js. d3v4 added d3.axisLeft() and d3.axisBottom(), etc. See this post.
Also, the translate coordinates you're using for the grid is not correct, I've adjusted some of the values so it fits the graph.
Here's a working example:
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.0/d3.min.js"></script>-->
<!DOCTYPE html>
<style>
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
</style>
<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;
function make_x_axis() {
return d3.axisBottom(x)
// .scale(x)
// .orient("bottom")
.ticks(5)
}
function make_y_axis() {
return d3.axisLeft(y)
// .scale(y)
// .orient("left")
.ticks(5)
}
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 + ')')
// svg.append("g")
// .attr("class", "grid")
// .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
// .call(make_x_axis()
// .tickSize(-height, 0, 0)
// .tickFormat("")
// )
//
// svg.append("g")
// .attr("class", "grid")
// .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
// // .attr("transform", "translate(0," + (height + margin.top) + ")")
// .call(make_y_axis()
// .tickSize(-width, 0, 0)
// .tickFormat("")
// )
svg.append("g")
.attr("class", "grid")
.attr("transform", `translate(${margin.left}, ${height + margin.top})`)
.call(make_x_axis()
.tickSize(-height)
.tickFormat("")
)
// add the Y gridlines
svg.append("g")
.attr("class", "grid")
.attr("transform", `translate(${margin.left}, ${margin.top})`)
.call(make_y_axis()
.tickSize(-width)
.tickFormat("")
)
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", "white")
.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>
In case you're wondering why the lines are "broken", it's because of the white best-fitting line overlapping with the grid.
Line revealed:
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.0/d3.min.js"></script>-->
<!DOCTYPE html>
<style>
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
</style>
<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;
function make_x_axis() {
return d3.axisBottom(x)
// .scale(x)
// .orient("bottom")
.ticks(5)
}
function make_y_axis() {
return d3.axisLeft(y)
// .scale(y)
// .orient("left")
.ticks(5)
}
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 + ')')
// svg.append("g")
// .attr("class", "grid")
// .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
// .call(make_x_axis()
// .tickSize(-height, 0, 0)
// .tickFormat("")
// )
//
// svg.append("g")
// .attr("class", "grid")
// .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
// // .attr("transform", "translate(0," + (height + margin.top) + ")")
// .call(make_y_axis()
// .tickSize(-width, 0, 0)
// .tickFormat("")
// )
svg.append("g")
.attr("class", "grid")
.attr("transform", `translate(${margin.left}, ${height + margin.top})`)
.call(make_x_axis()
.tickSize(-height)
.tickFormat("")
)
// add the Y gridlines
svg.append("g")
.attr("class", "grid")
.attr("transform", `translate(${margin.left}, ${margin.top})`)
.call(make_y_axis()
.tickSize(-width)
.tickFormat("")
)
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", "black") // modified
.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>
Related
I would like to add a tooltip to the line chart, where each data point displays a text box upon hover, as follows:
-----------------|
x-coordinate: ## |
y-coordinate: ## |
-----------------|
The working snippet for the working graph is posted below. But I will comment out the tooltip block to plot the chart.
Thanks.
var margin = {top: 50, right: 50, bottom: 50, left: 50}
, width = window.innerWidth - margin.left - margin.right
, height = window.innerHeight - margin.top - margin.bottom;
//labels
var labels = ['Mon','Tue','Thur','Frid'];
var yvals = [12,11,0,18];
// X scale
var xScale = d3.scalePoint()
.domain(labels) // input
.range([0, width-1]); // output
// Y scale
var yScale = d3.scaleLinear()
.domain([0, 20])
.range([height,0]);
var line = d3.line()
.x(function(d, i) { return xScale(labels[i]); })
.y(function(d) { return yScale(d.y); })
.curve(d3.curveMonotoneX)
var dataset = d3.range(yvals.length).map(function(d,i) { return {"y": yvals[i]} })
//Tooltip
//var tip = d3.select('body')
//.append('div')
//.attr('class', 'tip')
//.html('number:'+ function(d,i) return {data[data.i]})
// .style('border', '1px solid steelblue')
// .style('padding', '5px')
//.style('position', 'absolute')
// .style('display', 'none')
//.on('mouseover', function(d, i) {
// tip.transition().duration(0);
// })
// .on('mouseout', function(d, i) {
// tip.style('display', 'none');
// });
// SVGs
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 + ")");
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "white");
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x axis call
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
//.call(d3.axisBottom(xScale));
.call(d3.axisBottom(xScale));
// y axis call
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale));
svg.append("path")
.datum(dataset)
.attr("class", "line")
.attr("d", line);
// 12. Appends a circle for each datapoint
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d, i) { return xScale(labels[i]) })
.attr("cy", function(d,i) { return yScale(yvals[i]) })
.attr("r", 3);
//.on('mouseover', function(d, i) {
// tip.transition().duration(0);
// })
svg.append("text")
.attr("class", "title")
.attr("x", width/2)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.text("Testing");
.line {
fill: none;
stroke: orange;
stroke-width: 1;
}
.dot {
fill: brown;
stroke: #fff;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
</style>
<body>
</body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
</script>
I have just made a few changes to the mousemove event.
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 50
},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - margin.top - margin.bottom;
//labels
var labels = ['Mon', 'Tue', 'Thur', 'Frid'];
var yvals = [12, 11, 0, 18];
// X scale
var xScale = d3.scalePoint()
.domain(labels) // input
.range([0, width - 1]); // output
// Y scale
var yScale = d3.scaleLinear()
.domain([0, 20])
.range([height, 0]);
var line = d3.line()
.x(function(d, i) {
return xScale(labels[i]);
})
.y(function(d) {
return yScale(d.y);
})
.curve(d3.curveMonotoneX)
var dataset = d3.range(yvals.length).map(function(d, i) {
return {
"y": yvals[i]
}
})
var tip = d3.select('body').append("div")
.attr("class", "tip");
// SVGs
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 + ")");
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "white");
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x axis call
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
//.call(d3.axisBottom(xScale));
.call(d3.axisBottom(xScale));
// y axis call
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale));
svg.append("path")
.datum(dataset)
.attr("class", "line")
.attr("d", line);
// 12. Appends a circle for each datapoint
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d, i) {
return xScale(labels[i])
})
.attr("cy", function(d, i) {
return yScale(yvals[i])
})
.attr("r", 3)
.on("mouseover", function() {
tip.style("display", null);
})
.on("mouseout", function() {
tip.style("display", "none");
})
.on("mousemove", function(d) {
return tip
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY + 10 + "px")
.style("visibility", "visible")
.html(function() {
return '<div style="border:1px solid #ccc;">' +
'<p style="font-weight:bold;">' + d.y + '</p>' +
'</div>';
})
})
svg.append("text")
.attr("class", "title")
.attr("x", width / 2)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.text("Testing");
.line {
fill: none;
stroke: orange;
stroke-width: 1;
}
.dot {
fill: brown;
stroke: #fff;
}
.tip {
position: absolute;
border: 1px solid steelblue;
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
Here is the working jsFiddle
Hope it helps :)
am working on multi line chart with two lines and with brush and zoom in d3 v4, when i brush my only one line is moving my another line remains constant. Since i have just started learning this on my own i don't have idea on what changes i have to make so that my both lines moves when i brush. Any kind of suggestions would be of a great help.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 110, left: 40},
margin2 = {top: 430, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
height2 = +svg.attr("height") - margin2.top - margin2.bottom;
var parseDate = d3.timeParse("%Y-%m-%d");
var x = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
//brush
x2 = d3.scaleTime().range([0, width]),
y2 = d3.scaleLinear().range([height2, 0]);
var xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y),
//brush
xAxis2 = d3.axisBottom(x2);
//slider that grey selection one
var brush = d3.brushX()
.extent([[0, 0], [width, height2]])
.on("brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
var line = d3.line()
.x(function (d) { return x(d.date); })
.y(function (d) { return y(d.extra); });
var line1 = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.Speed); });
//slider line
var line2 = d3.line()
.x(function (d) { return x2(d.date); })
.y(function (d) { return y2(d.Speed); });
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
var Line_chart = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("clip-path", "url(#clip)");
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
d3.csv("data/morley.csv", type, function (error, data) {
if (error) throw error;
data.sort(function(a,b){
return a.date-b.date
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) {
return Math.max(d.Speed, d.extra); })]);
//slider
x2.domain(x.domain());
y2.domain(y.domain());
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);
Line_chart.append("path")
.datum(data)
.attr("class", "line")
.style("stroke", "green")
.attr("d", line);
Line_chart.append("path")
.datum(data)
.attr("class", "line")
.style("stroke", "blue")
.attr("d", line1);
//slider line
context.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line2);
context.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
//slider
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
});
//selection in slider
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return;
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
Line_chart.select(".line").attr("d", line);
focus.select(".axis--x").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return;
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
Line_chart.select(".line").attr("d", line);
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
function type(d) {
d.date = parseDate(d.date);
d.extra = +d.extra;
d.Speed=+d.Speed
return d;
}
</script>
In the brushed() and zoomed() functions, you are only updating the green line (extra VS. date). You have to identify each line (for example with a specific class), and update both using their respective d3.line functions:
Line_chart.append("path")
.datum(data)
.attr("class", "line line-extra") // <-- class added here
.style("stroke", "green")
.attr("d", line);
Line_chart.append("path")
.datum(data)
.attr("class", "line line-speed") // <-- and here
.style("stroke", "blue")
.attr("d", line1);
[...]
function updateLines() {
Line_chart.select(".line-extra").attr("d", line);
Line_chart.select(".line-speed").attr("d", line1);
}
function brushed() {
[...]
updateLines();
}
function zoomed() {
[...]
updateLines();
}
Here is the template for my Django where I am visualizing training using D3:
.line {
fill: none;
stroke: gray;
stroke-width: 2px;
}
<meta charset="utf-8">
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var real = {{values.real0|safe}}, pred = {{values.got0|safe}};
var margin = {top: 20, right: 20, bottom: 110, left: 50},
margin2 = {top: 430, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
height2 = 500 - margin2.top - margin2.bottom;
var x = d3.scaleLinear().range([0, width]).domain([0, Object.keys(real).length]),
x2 = d3.scaleLinear().range([0, width]).domain([0, Object.keys(real).length]),
y = d3.scaleLinear().range([height, 0]).domain([0, 1]),
y2 = d3.scaleLinear().range([height2, 0]).domain([0, 1]);
var xAxis = d3.axisBottom(x),
xAxis2 = d3.axisBottom(x2),
yAxis = d3.axisLeft(y);
var brush = d3.brushX()
.extent([[0, 0], [width, height2]])
.on("brush", brushed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var formain = d3.line()
.x(function(d,i) { return x(i); })
.y(function(d) { return y(d); });
var forbrush = d3.line()
.x(function(d,i) { return x2(i); })
.y(function(d) { return y2(d); });
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
// Real starts
var color = d3.scaleLinear()
.domain([0, 0.5, 1])
.range(["red", "dodgerblue", "lime"]);
// x.domain(d3.extent(data, function(d) { return d.date; }));
// y.domain([0, d3.max(data, function(d) { return d.price; })+200]);
// x2.domain(x.domain());
// y2.domain(y.domain());
// append scatter plot to main chart area
var dots = focus.append("g");
dots.attr("clip-path", "url(#clip)");
dots.selectAll("dot")
.data(real)
.enter().append("circle")
.attr('class', 'dot')
.attr("r",5)
.style("opacity", .5)
.attr("cx", function(d,i) { return x(i); })
.attr("cy", function(d) { return y(d); })
.attr("fill",(function (d) { return color(d) }));
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);
focus.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text(Object.keys({{values|safe}}));
// console.log(Object.keys({{values|safe}}));
svg.append("text")
.attr("transform",
"translate(" + ((width + margin.right + margin.left)/2) + " ," +
(height + margin.top + margin.bottom) + ")")
.style("text-anchor", "middle")
.text("index");
// append scatter plot to brush chart area
var dots = context.append("g");
dots.attr("clip-path", "url(#clip)");
dots.selectAll("dot")
.data(real)
.enter().append("circle")
.attr('class', 'dotContext')
.attr("r",3)
.style("opacity", .5)
.attr("cx", function(d,i) { return x2(i); })
.attr("cy", function(d) { return y2(d); })
.attr("fill",(function (d) { return color(d) }));
context.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
focus.append("path")
.data([real])
.attr("class", "line")
.attr("d", formain);
context.append("path")
.data([real])
.attr("class", "line")
.attr("d", forbrush);
//create brush function redraw scatterplot with selection
function brushed() {
var selection = d3.event.selection;
x.domain(selection.map(x2.invert, x2));
focus.selectAll(".dot")
.attr("cx", function(d,i) { return x(i); })
.attr("cy", function(d) { return y(d); });
context.selectAll(".line")
.attr("cx", function(d,i) { return x(i); })
.attr("cy", function(d) { return y(d); });
focus.select(".axis--x").call(xAxis);
context.select(".axis--x").call(xAxis2);
}
</script>
The output I received is something like the following:
What I want is the the magnifier focus should display the respective contents of the line and the dots. Plus I want to have the line in the background and dots on the foreground.
Please help me modify the sample for my use. There is some attribute I guess I am missing.
The sample csv need is : Sample Csv
Try to change the few thing and it will work. see below:
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
focus.append("path")
.data([real])
.attr("class", "line")
.attr("d", formain);
context.append("path")
.data([real])
.attr("class", "line")
.attr("d", forbrush);
Place it just as mentioned.
Change the brushed() function like the following:
function brushed() {
var selection = d3.event.selection;
x.domain(selection.map(x2.invert, x2));
focus.selectAll(".dot")
.attr("cx", function(d,i) { return x(i); })
.attr("cy", function(d) { return y(d); });
focus.selectAll(".line")
.attr("d",formain)
}
See the output of mine. It worked.:
Hope this will help you.
I currently trying to learn d3, and wanted to do a small project but was running into an issue. I have been using this block as a base: Brush & Zoom
and replace the area fill with a scatterplot data I had. I was able to get the points to render but when moving the zoom/brush on the second axis, the x-axis for the .focus chart transforms but my scatterplots didn't. I tried a number of ways including to see if I was appending it to the right svg element but in my limited understanding of D3, I'm not too sure what is wrong. My sample code is as follows:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.area {
fill: #FDB827;
clip-path: url(#clip);
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 110, left: 40},
margin2 = {top: 430, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
height2 = +svg.attr("height") - margin2.top - margin2.bottom;
var parseDate = d3.timeParse("%b %Y");
var x = d3.scaleTime().range([0, width]),
x2 = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
y2 = d3.scaleLinear().range([height2, 0]);
var xAxis = d3.axisBottom(x),
xAxis2 = d3.axisBottom(x2),
yAxis = d3.axisLeft(y);
var brush = d3.brushX()
.extent([[0, 0], [width, height2]])
.on("brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
var area = d3.area()
.curve(d3.curveMonotoneX)
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.PTS); });
var area2 = d3.area()
.curve(d3.curveMonotoneX)
.x(function(d) { return x2(d.date); })
.y0(height2)
.y1(function(d) { return y2(d.PTS); });
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
d3.csv("./data/kobe-playoff1.csv", type, function(error, data) {
if (error) throw error;
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.PTS; })]);
x2.domain(x.domain());
y2.domain(y.domain());
let fixedData ={};
let arr = [];
data.forEach((d)=>{
fixedData["date"] = d.date;
fixedData["PTS"] = d.PTS;
arr.push(fixedData);
fixedData = {};
})
//
focus.append("path")
.datum(arr)
.attr("class", "area")
.attr("d", area);
// draw dots
focus.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "brush")
// .attr("class", "brush")
.attr("r", (d)=>{return (d.PTS)})
.attr("cx", (d)=>{return x(d.date)})
.attr("cy", (d)=>{return y(d.PTS)})
.style("fill", "#552583");
// .on("mouseover", function(d) {
// tooltip.transition()
// .duration(200)
// .style("opacity", .9);
// tooltip.html(d["date"] + "<br/> (" + xValue(d)
// + ", " + yValue(d) + ")")
// .style("left", (d3.event.pageX + 5) + "px")
// .style("top", (d3.event.pageY - 28) + "px");
// })
// .on("mouseout", function(d) {
// tooltip.transition()
// .duration(500)
// .style("opacity", 0);
// });
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);
context.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area2);
context.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
focus.append("g")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
});
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
function type(d) {
// d.date = parseDate(d.date);
// d.price = +d.price;
// d.date = new Date(d.Date);
d.date = Date.parse(d.Date);
d.PTS = +d.PTS;
return d;
}
</script>
The sample data I am using is as follows:
Rk,G,Date,Age,Tm,,Opp,,GS,MP,FG,FGA,FG%,3P,3PA,3P%,FT,FTA,FT%,ORB,DRB,TRB,AST,STL,BLK,TOV,PF,PTS,GmSc
1,1,1997-04-25,18-245,LAL,,POR,W (+18),0,1:00,1,1,1.000,0,0,,0,0,,0,0,0,0,0,0,0,0,2,1.7
2,2,1997-04-27,18-247,LAL,,POR,W (+14),0,5:00,1,3,.333,0,1,.000,4,4,1.000,0,0,0,0,0,0,0,1,6,3.9
3,3,1997-04-30,18-250,LAL,#,POR,L (-8),0,27:00,7,13,.538,2,3,.667,6,8,.750,0,4,4,2,1,0,4,5,22,12.5
4,4,1997-05-02,18-252,LAL,#,POR,W (+4),0,6:00,0,0,,0,0,,0,0,,0,0,0,0,0,0,0,0,0,0.0
5,5,1997-05-04,18-254,LAL,#,UTA,L (-16),0,14:00,1,7,.143,1,5,.200,0,1,.000,1,1,2,3,0,1,0,5,3,-0.1
6,6,1997-05-06,18-256,LAL,#,UTA,L (-2),0,4:00,1,1,1.000,0,0,,0,0,,0,0,0,1,0,0,1,2,2,0.6
7,7,1997-05-08,18-258,LAL,,UTA,W (+20),0,19:00,3,7,.429,0,2,.000,13,14,.929,0,1,1,3,1,1,3,3,19,14.8
8,8,1997-05-10,18-260,LAL,,UTA,L (-15),0,28:00,3,9,.333,3,6,.500,0,0,,0,2,2,0,0,0,5,4,9,-2.1
9,9,1997-05-12,18-262,LAL,#,UTA,L (-5),0,29:00,4,14,.286,0,6,.000,3,3,1.000,0,2,2,2,1,0,1,3,11,3.6
Any help would be greatly appreciated! Thank you.
In your brushed and zoomed functions, you need to select your circles and apply dynamic attributes. But before you do that, you need to rename the class for your circles. Something like:
// draw dots
focus.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "circle")
...
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
focus.selectAll(".circle")
.attr("r", (d)=>{return (d.PTS)})
.attr("cx", (d)=>{return x(d.date)})
.attr("cy", (d)=>{return y(d.PTS)})
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
focus.selectAll(".circle")
.attr("r", (d)=>{return (d.PTS)})
.attr("cx", (d)=>{return x(d.date)})
.attr("cy", (d)=>{return y(d.PTS)})
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
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>