d3: clipPath don't clips - javascript

clipPath in a d3-project don't clips: the graph overflows the chart window on the left.
I followed several examples available online, but I don't see, why my clipPath don't works.
Working example can be found here: https://phinetwork.ch/wolf/dynamicgraph.html
Code:
<style>
.area {
fill: darkorange;
}
.bgrect {
background-color:#999;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
background-color:grey;
}
.grid line{
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid text {
display:none;
}
.grid path, .xgrid path{
stroke-width: 0;
}
</style>
<svg width="900" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 0, bottom: 110, left: 40},
margin2 = {top: 430, right: 0, 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]),
x2 = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
y2 = d3.scaleLinear().range([height2, 0]);
var xAxis = d3.axisBottom(x)
.ticks(10),
yAxis = d3.axisLeft(y)
.ticks(7),
xgAxis = d3.axisBottom(x)
.ticks(10)
.tickSize(-height),
ygAxis = d3.axisLeft(x)
.ticks(10)
.tickSize(-width - margin.left),
xAxis2 = d3.axisBottom(x2);
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.rz); });
var area2 = d3.area()
.curve(d3.curveMonotoneX)
.x(function(d) { return x2(d.date); })
.y0(height2)
.y1(function(d) { return y2(d.rz); });
//svg.append("defs").append("clipPath")
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", 40)
.attr("y", 0)
.attr("width", width-margin.left)
.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("files/institute/testdat.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.rz; })]);
x2.domain(x.domain());
y2.domain(y.domain());
focus.append("rect")
.attr("class", "bgrect")
.attr("width", width)
.attr("height", height)
//.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
focus.append("path")
.datum(data)
.attr("class", "area")
.attr("clip-path", "url(#clip)")
.attr("d", area);
focus.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "axis grid")
.attr("transform", "translate(0," + height + ")")
.call(xgAxis);
focus.append("g")
.attr("class", "axis grid")
.attr("transform", "translate(0," + -margin.top + ")")
.call(ygAxis);
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());
svg.append("rect")
.attr("class", "zoom")
.attr("width", width - margin.left)
.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);
focus.select(".grid").call(xgAxis);
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.select(".grid").call(xgAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
function type(d) {
d.date = parseDate(d.date);
d.rz = +d.rz;
return d;
}
</script>
Any suggestions for this problem?

Related

Is it possible to add zoom and tooltip on the same line chart in d3js?

Currently I am learning d3js, one of the feature i like to implement is showing tooltip and zooming horizontally. I figured out how to add zooming in the chart (working fiddle) but feeling little complex in adding tooltip when hover over the points. Is it possible in d3js. Because when zooming we are adding rect element on the svg element. if we add the rect element in the chart means how to make this tooltip works. Need some help from d3 ninjas.
var data = [{
date: "10:30:00",
price: 36000
},
{
date: "11:00:20",
price: 40000
},
{
date: "12:00:00",
price: 38000
},
{
date: "14:20:00",
price: 50400
}
];
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("%H:%M:%S"); //"%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.line()
//.curve(d3.curveMonotoneX)
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.price);
});
var area2 = d3.line()
.curve(d3.curveMonotoneX)
.x(function(d) {
return x2(d.date);
})
.y(function(d) {
return y2(d.price);
});
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 + ")");
function update() {
for (var k in data) {
type(data[k]);
}
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.price;
})]);
x2.domain(x.domain());
y2.domain(y.domain());
focus.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
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.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "circle")
.attr("r", 5)
.style("fill", 'orange')
.style("stroke", 'red')
.style("stroke-width", "2")
.attr("cx", function(d) {
return x(d.date)
})
.attr("cy", function(d) {
return y(d.price);
});
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.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "circle")
.attr("r", 1)
.style("fill", 'blue')
.style("stroke", 'red')
.style("stroke-width", "2")
.attr("cx", function(d) {
return x(d.date)
})
.attr("cy", function(d) {
return y(d.price);
});
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);
}
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.selectAll('.circle')
.attr("cx", function(d) {
return x(d.date)
})
.attr("cy", function(d) {
return y(d.price);
});
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.selectAll('.circle')
.attr("cx", function(d) {
return x(d.date)
})
.attr("cy", function(d) {
return y(d.price);
});
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
context.selectAll('.circle')
.attr("cx", function(d) {
return x(d.date)
})
.attr("cy", function(d) {
return y(d.price);
});
}
function type(d) {
d.date = parseDate(d.date);
d.price = +d.price;
return d;
}
update();
.area {
fill: none;
stroke: #a2dced;
stroke-width: 2;
clip-path: url(#clip);
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
rect.selection {
fill: green;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="500"></svg>
Of course it's possible to add a tooltip in d3, there are a lot of examples and even a dedicated package for older versions.
You can choose to show a tooltip inside the SVG (as a rect with text) or outside, as a div. The benefit of outside is that the tooltip can overflow the SVG, the downside is that positioning can be more difficult, especially with scrolling.
I show a very simple implementation below, using a DIV tooltip. I positioned the .zoom rect behind the circles, so they would catch the mouse events instead, and added on mouseenter and mouseleave event listeners.
var data = [{
date: "10:30:00",
price: 36000
},
{
date: "11:00:20",
price: 40000
},
{
date: "12:00:00",
price: 38000
},
{
date: "14:20:00",
price: 50400
}
];
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("%H:%M:%S"); //"%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.line()
//.curve(d3.curveMonotoneX)
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.price);
});
var area2 = d3.line()
.curve(d3.curveMonotoneX)
.x(function(d) {
return x2(d.date);
})
.y(function(d) {
return y2(d.price);
});
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 + ")");
var tooltip = d3.select('body')
.append('div')
.attr('id', 'tooltip')
.style("transform", "translate(" + margin.left + "px," + margin.top + "px)")
.classed('hide', true);
function update() {
for (var k in data) {
type(data[k]);
}
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.price;
})]);
x2.domain(x.domain());
y2.domain(y.domain());
focus.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
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.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "circle")
.attr("r", 5)
.style("fill", 'orange')
.style("stroke", 'red')
.style("stroke-width", "2")
.attr("cx", function(d) {
return x(d.date)
})
.attr("cy", function(d) {
return y(d.price);
})
.on("mouseenter", function(d) {
// Show the tooltip and position it correctly
tooltip.classed('hide', false)
.style('left', x(d.date).toString() + 'px')
.style('top', y(d.price).toString() + 'px')
.html("<p>Price: " + d.price + "</p>");
})
.on("mouseleave", function() {
tooltip.classed('hide', true);
});
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.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "circle")
.attr("r", 1)
.style("fill", 'blue')
.style("stroke", 'red')
.style("stroke-width", "2")
.attr("cx", function(d) {
return x(d.date)
})
.attr("cy", function(d) {
return y(d.price);
});
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
// Insert the zoom rect *before* the circles, so the circles
// are drawn in front of the recrt
focus.insert("rect", "circle")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.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.selectAll('.circle')
.attr("cx", function(d) {
return x(d.date)
})
.attr("cy", function(d) {
return y(d.price);
});
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.selectAll('.circle')
.attr("cx", function(d) {
return x(d.date)
})
.attr("cy", function(d) {
return y(d.price);
});
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
context.selectAll('.circle')
.attr("cx", function(d) {
return x(d.date)
})
.attr("cy", function(d) {
return y(d.price);
});
}
function type(d) {
d.date = parseDate(d.date);
d.price = +d.price;
return d;
}
update();
.area {
fill: none;
stroke: #a2dced;
stroke-width: 2;
clip-path: url(#clip);
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
rect.selection {
fill: green;
}
#tooltip {
position: absolute;
border: solid 1px black;
background: white;
margin: 20px;
}
.hide {
opacity: 0;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="500"></svg>

Build d3 line chart based on javascript json variable

Edited: added zooming.
I'm getting data from php function (just sql request) to js variable, so js variable is json data. I need to build zoomable d3 line chart based on this json.
Json variable looks like this:
[{temp: "5", created_at: "2017-03-19"},
{temp: "5", created_at: "2017-03-19"},
{temp: "26", created_at: "2018-04-20"}]
Error is: Error: <path> attribute d: Expected number, "MNaN,NaNLNaN,NaNL…"
Line_chart.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line); ///One error is on this line
context.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line2); ///Second error is on this line
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]),
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 line = d3.line()
.x(function (d) { return x(d.Date); })
.y(function (d) { return y(d.Air_Temp); });
var line2 = d3.line()
.x(function (d) { return x2(d.Date); })
.y(function (d) { return y2(d.Air_Temp); });
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 + ")");
var data = jsonfile
x.domain(d3.extent(data, function(d) { return d.created_at; }));
y.domain([0, d3.max(data, function (d) { return d.temp; })]);
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")
.attr("d", 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);
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);
console.log(data);
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));
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; // ignore zoom-by-brush
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.created_at);
d.Air_Temp = +d.temp;
return d;
}
jsonfile in d3.json is this json variable.
I have problem with dislaying data. I have x and y axis but no displayed data.
---------Reason of the error is incorrectly parsed data, still don't know where code goes wrong.-----------
I added: after var data = jsonfile
data.forEach(function(d){
d.created_at = parseDate(d.created_at);
d.temp = +d.temp;
});
And it worked. Graph builds using console. Don't why and where json strings parsed incorrectly.

D3 V4 How to move dots according to zoom in linechart?

Small problems are difficult to identify. I am using example to create a linechart using d3 v4. I'm using d3.line() rather than d3.area(). Also drawing circles on the line peak points. Things are working fine.
Problem is with the drawn circles. They are not moving to the correct position when i'm zooming the graph.
Here is the complete code:
.area {
fill: none;
stroke: #a2dced;
stroke-width: 2;
clip-path: url(#clip);
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
rect.selection
{
fill:green;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="500"></svg>
<script>
var data = [{date: "10:30:00", price: 36000},
{date: "11:00:20", price: 40000},
{date: "12:00:00", price: 38000},
{date: "14:20:00", price: 50400}];
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("%H:%M:%S");//"%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.line()
//.curve(d3.curveMonotoneX)
.x(function (d) {
return x(d.date);
})
.y(function (d) {
return y(d.price);
});
var area2 = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d) {
return x2(d.date);
})
.y(function (d) {
return y2(d.price);
});
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 + ")");
function update(){
for(var k in data)
{
type(data[k]);
}
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.price; })]);
x2.domain(x.domain());
y2.domain(y.domain());
focus.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
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.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class","circle")
.attr("r", 5)
.style("fill", 'orange')
.style("stroke", 'red')
.style("stroke-width", "2")
.attr("cx", function(d) { return x(d.date) })
.attr("cy", function(d) { return y(d.price); });
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.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class","circle")
.attr("r", 1)
.style("fill", 'blue')
.style("stroke", 'red')
.style("stroke-width", "2")
.attr("cx", function(d) { return x(d.date) })
.attr("cy", function(d) { return y(d.price); });
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);
}
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.selectAll('.circle').attr("transform", function(d) {
return "translate(" + x(d.date) + "," + y(d.price) + ")";
});
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.selectAll('.circle').attr("transform", function(d) {
return "translate(" + x(d.date) + "," + y(d.price) + ")";
});
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
context.selectAll('.circle').attr("transform", function(d) {
return "translate(" + x2(d.date) + "," + y2(d.price) + ")";
});
}
function type(d) {
d.date = parseDate(d.date);
d.price = +d.price;
return d;
}
update();
</script>
JSFiddle link.
I updated you jsfiddle by replacing all transformattributes by changing it to cx and cy, e.g.:
before
focus.selectAll('.circle').attr("transform", function(d) {
return "translate(" + x(d.date) + "," + y(d.price) + ")";
});
after
focus.selectAll('.circle')
.attr("cx", function(d) { return x(d.date) })
.attr("cy", function(d) { return y(d.price); });
Otherwise, you will translate with your other coordinates, which might lead to hard to find problems

D3.js Mouseover and Focus + Context issue

I am new to D3.js
I've gone through some tutorials and have straight up jumped into my first project. I was hoping to combine the following with slight tweaks according to my needs. Currently I am having two issues
Focus+Context via Brushing
and
X-Value Mouseover
The Mouseover is wrongly displayed. It renders to the left of the chart. Could be a very small issue but I cant seem to find it.
I cant seem to figure out a way to display the "Safe Value" text outside the chart right next to the line. EDIT 2 - I've figured this out
Any help would be much appreciated.
Here is the CSS
body {
font: 10px sans-serif;
}
svg {
font: 10px sans-serif;
}
.line {
fill: none;
stroke: steelBlue;
stroke-width: 1.5px;
/*clip-path: url(#clip);*/
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
}
.overlay {
fill: none;
pointer-events: all;
}
.xy circle {
fill: steelblue;
stroke: black;
}
JS
var margin = {top: 10, right: 15, bottom: 100, left: 60},
margin2 = {top: 430, right: 15, bottom: 20, left: 60},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
height2 = 500 - margin2.top - margin2.bottom;
var parseDate = d3.time.format("%Y-%m-%dT%H:%M:%SZ").parse,
bisectDate = d3.bisector(function(d) { return d.date; }).left,
formatValue = d3.format(",.2f"),
formatData = function(d) { return formatValue(d) + " %"; };
var x = d3.time.scale().range([0, width]),
x2 = d3.time.scale().range([0, width]),
y = d3.scale.linear().range([height, 0]),
y2 = d3.scale.linear().range([height2, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(d3.time.months, 1).tickFormat(d3.time.format("%m/%y")),
xAxis2 = d3.svg.axis().scale(x2).orient("bottom").ticks(d3.time.months, 1).tickFormat(d3.time.format("%m/%y")),
yAxis = d3.svg.axis().scale(y).orient("left");
var brush = d3.svg.brush()
.x(x2)
.on("brush", brushed);
var line = d3.svg.line()
.interpolate("monotone")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); });
var line2 = d3.svg.line()
.interpolate("monotone")
.x(function(d) { return x2(d.date); })
.y(function(d) { return y2(d.value); });
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 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.csv", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
data.sort(function(a, b) {
return a.date - b.date;
});
x.domain(d3.extent(data.map(function(d) { return d.date; })));
y.domain([0, d3.max(data.map(function(d) { return d.value; }))]);
x2.domain(x.domain());
y2.domain(y.domain());
focus.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
focus.append("line")
.attr("x1",x(data[0].date))
.attr("y1",y(83))
.attr("x2",x(data[data.length - 1].date))
.attr("y2",y(83))
.attr("stroke","orangered");
svg.append("text")
.attr("transform", "translate(" + (width+3) + "," + y(83) + ")")
.attr("dy", ".35em")
.attr("text-anchor", "start")
.style("fill", "orangered")
.text(function(d) { return "Safe Value = 83" });
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("x", 0)
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(-50,"+ height/2 + ") rotate(-90)")
.text("Dissolved Oxygen (%)");
context.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line2);
context.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", height2 + 7);
var xy = svg.append("g")
.attr("class", "xy")
.style("display", "none");
xy.append("circle")
.attr("r", 4.5);
xy.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.on("mouseover", function() { xy.style("display", null); })
.on("mouseout", function() { xy.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
console.log(x0);
xy.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
xy.select("text").text(formatData(d.value));
}
});
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.select(".line").attr("d", line);
focus.select(".x.axis").call(xAxis);
}
Plunker Code
(Please refer to the code at Plunker, since I have updated a few things over there.) Thanks
Image1
Image2
For your problem #2, the code for the text is placing it out of the visible area. Just adjust your arguments to translate to something like the following:
.attr("transform", "translate(" + (width - 35) + ",30" + ")")
or something else that you prefer - note the minus on the x.

Adding two paths to d3

I would appreciate any help on this! I am currently attempting to add two paths from two different csv files to code adapted from this Focus+Context via Brushing d3 visualization: http://bl.ocks.org/mbostock/1667367
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.area {
fill: none;
stroke: #000;
clip-path: url(#clip);
}
.area_ {
fill: none;
stroke: #a0f2;
clip-path: url(#clip);
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
}
.brush_ .extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 10, right: 10, bottom: 100, left: 40},
margin2 = {top: 430, right: 10, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
height2 = 500 - margin2.top - margin2.bottom;
var parseDate = d3.time.format("%b %Y").parse;
var x = d3.time.scale().range([0, width]),
x2 = d3.time.scale().range([0, width]),
y = d3.scale.linear().range([height, 0]),
y2 = d3.scale.linear().range([height2, 0]),
x_ = d3.time.scale().range([0, width]),
x2_ = d3.time.scale().range([0, width]),
y_ = d3.scale.linear().range([height, 0]),
y2_ = d3.scale.linear().range([height2, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom"),
xAxis2 = d3.svg.axis().scale(x2).orient("bottom"),
xAxis2_ = d3.svg.axis().scale(x2_).orient("bottom"),
xAxis_ = d3.svg.axis().scale(x_).orient("bottom"),
yAxis = d3.svg.axis().scale(y).orient("left"),
yAxis_ = d3.svg.axis().scale(y_).orient("left");
var brush = d3.svg.brush()
.x(x2)
.on("brush", brushed);
var brush_ = d3.svg.brush()
.x(x2_)
.on("brush", brushed);
var area = d3.svg.area()
.interpolate("monotone")
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.price); });
var area_ = d3.svg.area()
.interpolate("monotone")
.x(function(d) { return x_(d.date); })
.y0(height)
.y1(function(d) { return y_(d.price); });
var area2 = d3.svg.area()
.interpolate("monotone")
.x(function(d) { return x2(d.date); })
.y0(height2)
.y1(function(d) { return y2(d.price); });
var area2_ = d3.svg.area()
.interpolate("monotone")
.x(function(d) { return x2_(d.date); })
.y0(height2)
.y1(function(d) { return y2_(d.price); });
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 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("sp500.csv", type, function(error, data) {
x.domain(d3.extent(data.map(function(d) { return d.date; })));
y.domain([0, d3.max(data.map(function(d) { return d.price; }))]);
x2.domain(x.domain());
y2.domain(y.domain());
focus.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "y axis")
.call(yAxis);
context.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area2);
context.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", height2 + 7);
});
d3.csv("academia_score.csv", type, function(error, data) {
x_.domain(d3.extent(data.map(function(d) { return d.date; })));
y_.domain([0, d3.max(data.map(function(d) { return d.price; }))]);
x2_.domain(x_.domain());
y2_.domain(y_.domain());
focus.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area_);
context.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area2_);
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", height2 + 7);
});
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.select(".area").attr("d", area);
focus.select(".area_").attr("d", area_);
focus.select(".x.axis").call(xAxis);
}
function type(d) {
d.date = parseDate(d.date);
d.price = +d.price;
return d;
}
</script>
However, the brush zoom is not working on my second path. The output can be viewed on this page: http://researchiq.net/comp.html Why isn't the zoom working on the second path??? Thanks!
Because d3.select() only selects a single element, not multiple, so focus.select(".area") is incorrect. Switch it to focus.selectAll(".area") and, assuming everything else was done properly, you'll be good to go.

Categories

Resources