Im using d3 line chart and created cross hair with value on it.
Facing issue when zoom in.
Cross hair not updated with new zoomed value. its have the old value and shows the value as first.
How do i fix this, and make cross hair with zoomed values
Here is my code:
var self = this;
this.margin = { top: 0, right: 45, bottom: 20, left: 0 };
this.size = {
width: $('#realtimechart').width() - 40,
height: 300
};
$('#realtimechart').empty();
this.x = d3.time.scale.utc()
.domain(d3.extent(self.plots, function (d) {
return d.date;
}))
.range([0, this.size.width])
.clamp(true);
this.y = d3.scale.linear()
.domain([0, d3.max(self.plots, function (d) {
return d.close;
})])
.range([this.size.height, 0])
.clamp(true);
this.line = d3.svg.line()
.x(function (d) {
return self.x(d.date);
})
.y(function (d) {
return self.y(d.close);
});
this.xAxis = d3.svg.axis().scale(this.x).ticks(12).orient('bottom');
this.yAxis = d3.svg.axis().scale(this.y).ticks(8).orient('right');
this.zoom = d3.behavior.zoom().x(this.x).scaleExtent([1,10]).on('zoom', this.redraw());
this.zoom.scale(currentZoom).translate([0, 0]);
this.svg = d3.select('#realtimechart').append('svg')
.attr('width', this.size.width + this.margin.left + this.margin.right)
.attr('height', this.size.height + this.margin.top + this.margin.bottom)
.append('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')')
.call(this.zoom);
this.makeX = function () {
return d3.svg.axis()
.scale(self.x)
.orient('bottom')
.ticks(12);
};
this.makeY = function () {
return d3.svg.axis()
.scale(self.y)
.orient('left')
.ticks(8);
};
this.svg.append('rect')
.attr('width', this.size.width)
.attr('height', this.size.height);
var focus = this.svg.append("g")
.attr("class","focus")
.style("display", "none");
focus.append("line")
.attr({
"x1": -this.size.width,
"y1": 0,
"x2": this.size.width,
"y2": 0
});
focus.append("line")
.attr({
"x1": 0,
"y1": -this.size.height,
"x2": 0,
"y2": this.size.height
});
var tipValue = this.svg.append("g")
.attr("class","focus")
.style("display", "none");
tipValue.append("text")
.attr("x", -10);
var bisectDate = d3.bisector(function(d) { return d.date; }).left;
var formatValue = d3.format(",.2f");
var formatCurrency = function(d) { return "$" + formatValue(d); };
var xDomain = d3.extent(self.chartData, function (d, i){ return d.date; });
var yMin = d3.min(self.chartData, function(d){ return Math.min(d.close); });
var yMax = d3.max(self.chartData, function(d){ return Math.max(d.close); });
var x = d3.time.scale()
.range([0, this.size.width]);
var y = d3.scale.linear()
.range([this.size.height, 0]);
x.domain([self.chartData[0].date, self.chartData[self.chartData.length - 1].date]);
y.domain(d3.extent(self.chartData, function(d) { return d.close; }));
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(self.chartData, x0, 1),
d0 = self.chartData[i - 1],
d1 = self.chartData[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
tipValue.attr("transform", "translate(10, 10)");
tipValue.select("text").text(formatCurrency(d.close));
}
this.svg.on('mouseover', function() {
focus.style("display", null);
tipValue.style("display", null);
}).on('mouseout', function() {
focus.style("display", "none");
tipValue.style("display", "none");
}).on('mousemove', mousemove);
this.svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + this.size.height + ')')
.call(this.xAxis);
this.svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + this.size.width + ', 0)')
.call(this.yAxis);
this.svg.append('g')
.attr('class', 'x grid')
.attr('transform', 'translate(0,' + this.size.height + ')')
.call(this.makeX()
.tickSize(-this.size.height, 0, 0)
.tickFormat(''));
this.svg.append('g')
.attr('class', 'y grid')
.call(this.makeY()
.tickSize(-this.size.width, 0, 0)
.tickFormat(''));
this.clip = this.svg.append('svg:clipPath')
.attr('id', 'clip')
.append('svg:rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', this.size.width)
.attr('height', this.size.height);
this.chartBody = this.svg.append('g')
.attr('clip-path', 'url(#clip)')
.append('svg:path')
.datum(self.plots)
.attr('class', 'line')
.attr('d', this.line);
}
Chart.prototype.redraw = function() {
var self = this;
return function() {
currentZoom = d3.event.scale;
self.svg.select('.x.axis').call(self.xAxis);
self.svg.select('.y.axis').call(self.yAxis);
self.svg.select('.x.grid')
.call(self.makeX()
.tickSize(-self.size.height, 0, 0)
.tickFormat(''));
self.svg.select('.y.grid')
.call(self.makeY()
.tickSize(-self.size.width, 0, 0)
.tickFormat(''));
self.svg.select('.line')
.attr('class', 'line')
.attr('d', self.line);
};
Related
I am trying to create a divergent bar chart, which shows the buy and sell volume on a particular date.
The graph has both zoom and brush working on it. I am having trouble selecting the whole bar when hovering/clicking on the orange(Sell) color bar, the Blue(Buy) color bar works on mouseover and click. I have created same class name for a stack but when I try to select the lower stack the stroke width does not change and same is the case with click. In both the functions, upper bar works but not the lower one. Also any suggestion on how to deactivate the click when an event is made on the body of the svg would be appreciated.
working image :
Problem:
Here is the code for the chart
<!DOCtyPE html>
<meta charset="utf-8">
<style type="text/css">
body {
font-family: avenir next, sans-serif;
font-size: 12px;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
#tooltip {
background-color: rgba(187, 187, 187, 0.7);
border-radius: 5px;
height: 18px;
opacity: 0;
pointer-events: none;
position: absolute;
text-align: center;
}
</style>
<body>
</body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3fc#13.0.1"></script>
<script>
function randomData(samples) {
var data = [],
random = d3.randomNormal();
for (i = 0; i < samples; i++) {
data.push({
Buy: Math.floor(Math.abs(Math.random()*1000000)),
Sell: Math.floor(Math.abs(Math.random()*1000000)),
Date: new Date(2013,01,i)
});
}
return data;
}
var data = randomData(100)
data.forEach(d => {
d["Date"] = new Date(d["Date"]);
d["Buy"] = +d["Buy"];
d["Sell"] = +d["Sell"]*(-1);
})
var margin = {top: 20, right: 20, bottom: 230, left: 70},
margin2 = {top: 250, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 900 - margin.top - margin.bottom,
height2 = 350 - margin2.top - margin2.bottom;
var series = d3.stack()
.keys(["Buy", "Sell"])
.offset(d3.stackOffsetDiverging)
(data);
console.log(series)
var Ctx = d3.scaleTime().rangeRound([0, width]),
x = fc.scaleDiscontinuous(d3.scaleTime().rangeRound([0, width])).discontinuityProvider(fc.discontinuitySkipWeekends()),
y = d3.scaleLinear().range([height ,0]),
Cty = d3.scaleLinear().rangeRound([height2, 0]),
x3 = d3.scaleTime().rangeRound([0, width]);
x.domain(d3.extent(data,function(d) { return d["Date"]; }))
y.domain([d3.min(series, stackMin), d3.max(series, stackMax)])
Ctx.domain(x.domain());
Cty.domain(y.domain());
let formatValue = d3.format(".2s");
function stackMin(serie) {
return d3.min(serie, function(d) { return d[0]; });
}
function stackMax(serie) {
return d3.max(serie, function(d) { return d[1]; });
}
var z = d3.scaleOrdinal(d3.schemeCategory10)
var xAxis = d3.axisBottom(x).tickSize(0),
xAxis3 = d3.axisBottom(x3).ticks(0).tickSize(0),
xAxis2 = d3.axisBottom(Ctx).tickSize(0),
yAxis = d3.axisLeft(y).tickFormat(function(d) { return formatValue(d).replace('G', 'B');});
var brush = d3.brushX()
.extent([[0, 0], [width, height2]])
.on("brush", brushed);
var zoom = d3.zoom()
.scaleExtent([1, 50])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
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 + "," + (height+80)+ ")");
var bars = focus.append("g");
bars.attr("clip-path", "url(#clip)")
.attr("cursor","pointer")
.attr("height",height)
.attr("width",width);
focus
.append("g")
.attr("class", "axis x-axis")
.attr("transform", `translate(0,${height+5})`)
.call(xAxis);
focus.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
bars.selectAll("g")
.data(series)
.enter().append("g")
.attr("fill", function(d) { return z(d.key) })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr('class', (d, i) => `bar bar-${i}`)
.transition()
.duration(1000)
.attr("width",10)
.attr("x", function(d) { return x(d.data["Date"])})
.attr("y", function(d) { return y(d[1])})
.attr("height", function(d) { return y(d[0]) - y(d[1])});
var toggleSelected = true;
focus.selectAll(".bar")
.on('mouseover', function (d, i, elements) {
d3.selectAll(`.bar-${i}`)
.style('stroke', 'black')
.style('stroke-width', '2')
tooltip.transition()
.duration(100)
.style('opacity', .9);
tooltip.text((d3.select(this)))
.style('left', `${d3.event.pageX + 2}px`)
.style('top', `${d3.event.pageY - 18}px`);
})
.on('mouseout', function (d, i, elements) {
d3.selectAll(`.bar-${i}`)
.style('stroke', 'none')
.style('stroke-width', '0')
tooltip.transition()
.duration(400)
.style('opacity', 0);
//
})
.on("click",function (d, i, elements) {
if(toggleSelected == true) {
d3.selectAll(`.bar:not(.bar-${i})`)
.style("opacity",0.2)
toggleSelected = false;
} else {
d3.selectAll(`.bar`)
.style("opacity",1)
toggleSelected = true;
}
})
var bars = context.append("g");
bars.attr("clip-path", "url(#clip)");
bars.selectAll("bar")
.data(series)
.enter().append("g")
.attr("fill", function(d) { return z(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr('class', 'barContext')
.attr("width",5)
.attr("x", function(d) { return Ctx(d.data["Date"])})
.attr("y", function(d) { return Cty(d[1])})
.attr("height", function(d) { return Cty(d[0]) - Cty(d[1])})
context.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection
x.domain(s.map(Ctx.invert, Ctx));
let max = 0, min = 0, valueX =0,valueY=0;
series.map((stack, i) => {
stack.filter(el => el.data.Date >= x.domain()[0] && el.data.Date <= x.domain()[1])
.forEach(el => {
valueX = Math.max(valueX,y(el.data["Buy"]))
valueY = Math.min(valueY,y(el.data["Buy"]))
max = Math.max(max, el.data["Buy"])
min = Math.min(min, el.data["Sell"])
});
});
valueY= Math.abs(valueY)
value = valueX > valueY ? valueX : valueY;
value = Math.floor(value)
y.domain([min,max])
var tran = svg.transition().duration(350);
if ((s[1]-s[0])>10)
{
focus.selectAll(".bar")
.transition().duration(200)
.attr("x", function(d) { return x(d.data["Date"])})
.attr("y", function(d) { return y(d[1])})
.attr("width",20)
.attr("height", function(d) {
return (y(d[0]) - y(d[1]))});
focus.select(".x-axis").transition(tran).call(xAxis);
focus.select(".axis--y").transition(tran).call(yAxis);
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(Ctx).domain());
var previousy = y.domain()
let max = 0, min = 0,valueX=0,valueY=0;
series.map((stack, i) => {
stack.filter(el => el.data.Date >= x.domain()[0] && el.data.Date <= x.domain()[1])
.forEach(el => {
max = Math.max(max, el.data["Buy"])
min = Math.min(min, el.data["Sell"])
});
});
var tran = svg.transition().duration(350);
y.domain([min,max])
focus.selectAll(".bar")
.transition().duration(200)
.attr("x", function(d) { return x(d.data["Date"])})
.attr("y", function(d) { return y(d[1])})
.attr("width",20)
.attr("height", function(d) { return (y(d[0]) - y(d[1]))});
focus.select(".x-axis").transition(tran).call(xAxis)
focus.select(".axis--y").transition(tran).call(yAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t))
};
</script>
Your problem is in this code:
...
.on("click",function (d, i, elements) {
if(toggleSelected == true) {
d3.selectAll(`.bar:not(.bar-${i})`)
.style("opacity",0.2)
toggleSelected = false;
} else {
d3.selectAll(`.bar`)
.style("opacity",1)
toggleSelected = true;
}
})
...
When you click on orange rect, argument i it is the length of all blue rects + index of clicked orange rect.
You can get appropriate index, for example, from class attribute this way:
.on("click",function (d, i, elements) {
var index = parseInt(this.getAttribute('class').replace('bar bar-', ''), 10);
if(toggleSelected == true) {
d3.selectAll(`.bar:not(.bar-${index})`)
.style("opacity",0.2)
toggleSelected = false;
} else {
d3.selectAll(`.bar`)
.style("opacity",1)
toggleSelected = true;
}
})
Absolutely the same problem for mouseover and mouseout event-handlers. Look at my fork of your fiddle - https://jsfiddle.net/tcg3r04f/3/
I have a scatterplot that has zoom functioning perfectly. I am trying to add a tooltip such that on 'mouseenter' on a <circle> element, the tooltip fires. I have this working, i.e the 'mouseenter' event is called but I cannot zoom while the mouse stays on this <circle>. Is there a way to get them to both occur at the same time?
Here is a minimal version of the code that reproduces the issue.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<button id="resetBtn">Reset</button>
<div id="chart"></div>
<script>
var data = [
{
x: 0.5, y: 0.5
},
{
x: 0.6, y: 0.6
},
{
x: 0.45, y: 0.65
},
{
x: 0.76, y: 0.61
},
{
x: 0.51, y: 0.05
},
{
x: 0.16, y: 6.8
}
];
var plot = volcanoPlot()
.xColumn("x")
.yColumn("y");
d3.select('#chart')
.data([data])
.call(plot);
function volcanoPlot() {
var width = 960,
height = 500,
margin = {top: 20, right: 20, bottom: 40, left: 50},
xColumn,
dotRadius = 10,
yColumn,
xScale = d3.scaleLinear(),
yScale = d3.scaleLog();
function chart(selection){
var innerWidth = width - margin.left - margin.right, // set the size of the chart within its container
innerHeight = height - margin.top - margin.bottom;
selection.each(function(data) {
xScale.range([0, innerWidth])
.domain(d3.extent(data, function(d) { return d[xColumn]; }))
.nice();
yScale.range([0, innerHeight])
.domain(d3.extent(data, function(d) { return d[yColumn]; }))
.nice();
var zoom = d3.zoom()
.scaleExtent([1, 20])
.translateExtent([[0, 0], [width, height]])
.on('zoom', zoomFunction);
var svg = d3.select(this).append('svg')
.attr('height', height)
.attr('width', width)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
d3.select('#resetBtn')
.style('top', margin.top * 1.5 + 'px')
.style('left', margin.left * 1.25 + 'px')
.on('click', reset);
svg.append('defs').append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('height', innerHeight)
.attr('width', innerWidth);
// add the axes
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale);
var gX = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + innerHeight + ')')
.call(xAxis);
gX.append('text')
.attr('class', 'label')
.attr('transform', 'translate(' + width / 2 + ',' + (margin.bottom - 6) + ')')
.attr('text-anchor', 'middle')
.text(xColumn);
var gY = svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
gY.append('text')
.attr('class', 'label')
.attr('transform', 'translate(' + (0 - margin.left / 1.5) + ',' + (height / 2) + ') rotate(-90)')
.style('text-anchor', 'middle')
.text(yColumn);
var zoomBox = svg.append('rect')
.attr('class', 'zoom')
.attr('height', innerHeight)
.attr('width', innerWidth)
.attr('fill', 'none')
.attr('pointer-events', 'all')
.call(zoom);
var circles = svg.append('g')
.attr('class', 'circlesContainer')
.attr('clip-path', 'url(#clip)');
circles.selectAll(".dot")
.data(data)
.enter().append('circle')
.attr('r', dotRadius)
.attr('cx', function(d) { return xScale(d[xColumn]); })
.attr('cy', function(d) { return yScale(d[yColumn]); })
.attr('class', 'dot')
.attr('stroke', 'none')
.on('mouseenter', function(){
console.log("hi");
});
function zoomFunction() {
var transform = d3.zoomTransform(this);
d3.selectAll('.dot')
.attr('transform', transform)
.attr('r', dotRadius / Math.sqrt(transform.k));
gX.call(xAxis.scale(d3.event.transform.rescaleX(xScale)));
gY.call(yAxis.scale(d3.event.transform.rescaleY(yScale)));
}
function reset() {
var ease = d3.easePolyIn.exponent(4.0);
d3.select('.zoom')
.transition().duration(750)
.ease(ease)
.call(zoom.transform, d3.zoomIdentity);
}
});
}
chart.width = function(value) {
if (!arguments.length) return width;
width = value;
return chart;
};
chart.height = function(value) {
if (!arguments.length) return height;
height = value;
return chart;
};
chart.margin = function(value) {
if (!arguments.length) return margin;
margin = value;
return chart;
};
chart.xColumn = function(value) {
if (!arguments.length) return xColumn;
xColumn = value;
return chart;
};
chart.yColumn = function(value) {
if (!arguments.length) return yColumn;
yColumn = value;
return chart;
};
return chart;
}
</script>
</body>
</html>
You're calling your zoom function on the rectangle and, because of that, the zoom will not work when you hover over the circles. It has nothing to do with the mouseenter function: you can remove the mouseenter and the zoom still won't work when you hover over the circles.
A simple solution is calling the zoom on your SVG group, not on the rectangle:
var svg = d3.select(this).append('svg')
.attr('height', height)
.attr('width', width)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(zoom);
Here is your code with that change only:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<button id="resetBtn">Reset</button>
<div id="chart"></div>
<script>
var data = [
{
x: 0.5, y: 0.5
},
{
x: 0.6, y: 0.6
},
{
x: 0.45, y: 0.65
},
{
x: 0.76, y: 0.61
},
{
x: 0.51, y: 0.05
},
{
x: 0.16, y: 6.8
}
];
var plot = volcanoPlot()
.xColumn("x")
.yColumn("y");
d3.select('#chart')
.data([data])
.call(plot);
function volcanoPlot() {
var width = 960,
height = 500,
margin = {top: 20, right: 20, bottom: 40, left: 50},
xColumn,
dotRadius = 10,
yColumn,
xScale = d3.scaleLinear(),
yScale = d3.scaleLog();
function chart(selection){
var innerWidth = width - margin.left - margin.right, // set the size of the chart within its container
innerHeight = height - margin.top - margin.bottom;
selection.each(function(data) {
xScale.range([0, innerWidth])
.domain(d3.extent(data, function(d) { return d[xColumn]; }))
.nice();
yScale.range([0, innerHeight])
.domain(d3.extent(data, function(d) { return d[yColumn]; }))
.nice();
var zoom = d3.zoom()
.scaleExtent([1, 20])
.translateExtent([[0, 0], [width, height]])
.on('zoom', zoomFunction);
var svg = d3.select(this).append('svg')
.attr('height', height)
.attr('width', width)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(zoom);
d3.select('#resetBtn')
.style('top', margin.top * 1.5 + 'px')
.style('left', margin.left * 1.25 + 'px')
.on('click', reset);
svg.append('defs').append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('height', innerHeight)
.attr('width', innerWidth);
// add the axes
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale);
var gX = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + innerHeight + ')')
.call(xAxis);
gX.append('text')
.attr('class', 'label')
.attr('transform', 'translate(' + width / 2 + ',' + (margin.bottom - 6) + ')')
.attr('text-anchor', 'middle')
.text(xColumn);
var gY = svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
gY.append('text')
.attr('class', 'label')
.attr('transform', 'translate(' + (0 - margin.left / 1.5) + ',' + (height / 2) + ') rotate(-90)')
.style('text-anchor', 'middle')
.text(yColumn);
var zoomBox = svg.append('rect')
.attr('class', 'zoom')
.attr('height', innerHeight)
.attr('width', innerWidth)
.attr('fill', 'none')
.attr('pointer-events', 'all');
var circles = svg.append('g')
.attr('class', 'circlesContainer')
.attr('clip-path', 'url(#clip)');
circles.selectAll(".dot")
.data(data)
.enter().append('circle')
.attr('r', dotRadius)
.attr('cx', function(d) { return xScale(d[xColumn]); })
.attr('cy', function(d) { return yScale(d[yColumn]); })
.attr('class', 'dot')
.attr('stroke', 'none')
.on('mouseenter', function(){
console.log("hi");
});
function zoomFunction() {
var transform = d3.zoomTransform(this);
d3.selectAll('.dot')
.attr('transform', transform)
.attr('r', dotRadius / Math.sqrt(transform.k));
gX.call(xAxis.scale(d3.event.transform.rescaleX(xScale)));
gY.call(yAxis.scale(d3.event.transform.rescaleY(yScale)));
}
function reset() {
var ease = d3.easePolyIn.exponent(4.0);
d3.select('.zoom')
.transition().duration(750)
.ease(ease)
.call(zoom.transform, d3.zoomIdentity);
}
});
}
chart.width = function(value) {
if (!arguments.length) return width;
width = value;
return chart;
};
chart.height = function(value) {
if (!arguments.length) return height;
height = value;
return chart;
};
chart.margin = function(value) {
if (!arguments.length) return margin;
margin = value;
return chart;
};
chart.xColumn = function(value) {
if (!arguments.length) return xColumn;
xColumn = value;
return chart;
};
chart.yColumn = function(value) {
if (!arguments.length) return yColumn;
yColumn = value;
return chart;
};
return chart;
}
</script>
</body>
</html>
What is causing me this error.. am i parsing the date wrongly or something else?? Actually i am trying to update the data periodically and pass it to javascript function that produces a d3 graph.... is my update method wrongly written??? please help me out with this....
<script type="text/javascript">
//<![CDATA[
$.noConflict();
jQuery(document)
.ready(
function($) {
var x=(${rawEventsMB.jsonString});
var data1=JSON.stringify(x);
var data = JSON.parse(data1);
function InitChart(data) {
try {
//alert("data is:: "+data);
var parseDate = d3.time
.format("%Y-%m-%d %H:%M:%S").parse;
bisectDate = d3.bisector(function(d) {
return d.time;
}).left;
data.forEach(function(d) {
d.time = parseDate(d.time);
d.count = +d.count;
});
var vis = d3.select("#visualisation"), WIDTH = 1000, HEIGHT = 300, MARGINS = {
top : 20,
right : 20,
bottom : 20,
left : 50
},
xScale = d3.time.scale().domain(
[ d3.min(data, function(d) {
return d.time;
}), d3.max(data, function(d) {
return d.time;
}) ]).range(
[ MARGINS.left,
WIDTH - MARGINS.right ]),
yScale = d3.scale.linear()
.range([ HEIGHT - MARGINS.top,MARGINS.bottom ])
.domain(d3.extent(data, function(d) {return d.count;})),
xAxis = d3.svg.axis().scale(xScale)
.orient("bottom")
.ticks(10)
.tickFormat(d3.time.format("%d %b %X")),
yAxis = d3.svg.axis().scale(yScale)
.orient("left");
vis.append("svg:g")
.attr("class", "x axis")
.attr("transform","translate(0,"+ (HEIGHT - MARGINS.bottom)+ ")")
.call(xAxis)
.selectAll("text").style("text-anchor","end")
.attr("dx","-.6em")
.attr("dy","-.55em")
.attr("transform","rotate(-90)"),
vis.append("text")
.attr("transform","translate("+ (WIDTH / 2)+ " ,"+ (HEIGHT + MARGINS.bottom*4)+ ")")
.style("text-anchor","middle")
.style("fill", "red")
.style("font-weight", "bold")
.text("Time");
vis.append("svg:g")
.attr("class", "y axis")
.attr("transform","translate("+ (MARGINS.left)+ ",0)")
.call(yAxis),
vis.append("text")
.attr("transform","rotate(-90 "+ MARGINS.left* 1.4 + " "+ HEIGHT / 5 + ")")
.style("text-anchor", "middle")
.style("fill", "red")
.text("Number of events ")
.style("font-weight", "bold");
var focus = vis.append("g").style(
"display", "none");
focus.append("circle").attr("class", "y")
.style("fill", "blue").style(
"stroke", "blue").attr("r",
4);
/* focus.append("text").attr("class","y").style("text-anchor","middle")
.style("fill", "red").style("font-weight", "bold"); */
focus.append("foreignObject")
.attr("width", 180)
.attr("height", 500).style("font", "bold 11px 'Helvetica Neue'").style("fill", "red").attr("class", "tooltip");
// append the rectangle to capture mouse
vis.append("rect").attr("width", WIDTH)
.attr("height", HEIGHT).style(
"fill", "none").style(
"pointer-events", "all")
.on("mouseover", function() {
focus.style("display", null);
}).on("mouseout", function() {
focus.style("display", "none");
}).on("mousemove", mousemove);
var lineGen = d3.svg.line().x(function(d) {
return xScale(d.time);
}).y(function(d) {
return yScale(d.count);
}).interpolate("interpolation");
vis.append('svg:path').attr('d',
lineGen(data)).attr('stroke',
'green').attr('stroke-width', 2)
.attr('fill', 'none');
} catch (ex) {
alert(ex);
}
function mousemove() {
var formatTime = d3.time.format("%e %b %X");
var x0 = xScale.invert(d3.mouse(this)[0]), i = bisectDate(
data, x0, 1), d0 = data[i - 1], d1 = data[i], d2 = x0
- d0.time > d1.time - x0 ? d1 : d0;
focus.select("circle.y").attr(
"transform",
"translate(" + xScale(d2.time)
+ "," + yScale(d2.count)
+ ")"),
/* focus.select("text")
.attr("transform","translate(" + xScale(d2.time)+ "," + yScale(d2.count)+ ")"); */
focus.select("foreignObject").attr("transform","translate(" + xScale(d2.time)+ "," + yScale(d2.count)+ ")")
.html("time:"+formatTime(d2.time)+", <br/> " + "count:"+d2.count);
}
InitChart(data);
var myVar=setTimeout(function() { InitChart(data); }, 1000);
}
});
//]]>
</script>`
I found a reference to the issue I am facing in this link
'nemesv' has provided with a solution to the actual issue.
var myApp = angular.module('app', []);
myApp.directive("lineChart", function() {
return {
restrict: 'E',
scope: {
data: '=',
id: '#'
},
link: function (scope, element, attrs) {
scope.$watch( 'data', function ( data ) {
d3.select("#"+attrs.id).select("svg").remove();
if (data) {
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = element[ 0 ].parentElement.offsetWidth - margin.left - margin.right,
height = element[ 0 ].parentElement.offsetHeight - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var bisectDate = d3.bisector(function(d) { return d[0]; }).left;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.innerTickSize(-height)
.ticks(4)
.outerTickSize(0)
.tickPadding(5)
.tickFormat(function(d) { return d3.time.format('%d/%m %H:%M')(new Date(d)); });
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
var line = d3.svg.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); });
var svg = d3.select(element[0]).append("svg")
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox','0 0 '+ element[ 0 ].parentElement.offsetWidth +' '+ element[ 0 ].parentElement.offsetHeight )
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var minX = d3.min(data, function (item) { return d3.min(item.values, function (d) { return d[0]; }); });
var maxX = d3.max(data, function (item) { return d3.max(item.values, function (d) { return d[0]; }); });
var minY = d3.min(data, function (item) { return d3.min(item.values, function (d) { return d[1]; }); });
var maxY = d3.max(data, function (item) { return d3.max(item.values, function (d) { return d[1]; }); });
x.domain([minX, maxX]);
y.domain([0, maxY]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var domaine = svg.selectAll(".domaine")
.data(data)
.enter().append("g")
.attr("class", "domaine");
domaine.append("path")
.attr("class", "line")
.attr("d", function (d) {
return line(d.values);
})
.style("stroke", function (d) {
return d.color;
});
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
for(var i=0;i<data.length;i++){
focus.append("g")
.attr("class", "focus"+i)
.append("circle")
.attr("r", 4.5);
svg.select(".focus"+i)
.append("text")
.attr("x", 9)
.attr("dy", ".35em");
}
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]);
var series = data.map(function(e) {
var i = bisectDate(e.values, x0, 1),
d0 = e.values[i - 1],
d1 = e.values[i];
return x0 - d0[0] > d1[0] - x0 ? d1 : d0;
});
for(var i=0; i<series.length;i++){
var selectedFocus = svg.selectAll(".focus"+i);
selectedFocus.attr("transform", "translate(" + x(series[i][0]) + "," + y(series[i][1]) + ")");
selectedFocus.select("text").text(series[i][1]);
}
}
}
});
}
};
});
function MainCtrl($scope) {
$scope.lineData = [{"key": "users","color": "#16a085","values": [[1413814800000,4034.418],[1413815400000,5604.155000000001],[1413816000000,6343.079],[1413816600000,7308.226],[1413817200000,9841.185],[1413817800000,6571.891],[1413818400000,4660.6005000000005],[1413819000000,4555.4795],[1413819600000,5963.723],[1413820200000,9179.9595]]},{"key": "users 2","color": "#d95600","values": [[1413814800000,3168.183],[1413815400000,1530.8435],[1413816000000,2416.071],[1413816600000,1274.309],[1413817200000,1105.0445],[1413817800000,2086.0299999999997],[1413818400000,712.642],[1413819000000,1676.725],[1413819600000,3721.46],[1413820200000,2887.7975]]}];
}
Presently, if I hover over one line, I can see another dot hovering over the other line as well - i.e. mouseover is working simultaneously on both the lines. Could this mouseover/tooltip be done separately on each line i.e. when I hover over one line, focus circle will appear for only that line?
jsfiddle example can be seen here
I'm expecting the chart to update correctly with the new dataset contained in the function "redraw", however this isn't happening.
Although the current rects are updated with the new dataset, the issue seems to be that the enter().append() part of the code isn't working:
var groups = svg.selectAll('g')
.data(dataset, retd);
groups.style('fill', function (d, i) {
return colours(i);
});
groups.enter()
.append('g')
.style('fill', function (d, i) {
return colours(i);
});
groups.exit().remove();
//update the Rects
var rects = groups.selectAll('rect')
.data(function (d) {
return d;
}, thisy);
rects.transition()
.attr('x', function (d) {
return xScale(d.x0);
})
.attr('y', function (d, i) {
return yScale(d.y);
})
.attr('height', function (d) {
return yScale.rangeBand();
})
.attr('width', function (d) {
return xScale(d.x);
});
rects.enter()
.append('rect')
.attr('x', function (d) {
return xScale(d.x0);
})
.attr('y', function (d, i) {
return yScale(d.y);
})
.attr('height', function (d) {
return yScale.rangeBand();
})
.attr('width', function (d) {
return xScale(d.x);
});
Any help/insight would be appreciated.
Since the first draw worked fine, I turned it into a function. Only had to remove the already written SVG to make it reusable:
JSFiddle
<button id="reset">Redraw</button>
<script>
document.getElementById('reset').onclick = function() {
var dataset = [
{"data":[
{"IssueMonth":"Apr","IS":"350","month":"Apr"},
{"IssueMonth":null,"IS":"100","month":"Aug"},
{"IssueMonth":null,"IS":"0","month":"Dec"}],
"name":"CHF"},
{"data":[
{"IssueMonth":null,"IS":"100","month":"Apr"},
{"IssueMonth":null,"IS":"200","month":"Aug"},
{"IssueMonth":null,"IS":"40","month":"Dec"}],
"name":"GBP"}
];
drawit(dataset);
}
var dataset = [
{"data":[
{"IssueMonth":"Apr","IS":"350","month":"Apr"},
{"IssueMonth":null,"IS":"0","month":"Aug"},
{"IssueMonth":null,"IS":"0","month":"Dec"}],
"name":"CHF"},
{"data":[
{"IssueMonth":null,"IS":"100","month":"Apr"},
{"IssueMonth":null,"IS":"0","month":"Aug"},
{"IssueMonth":null,"IS":"50","month":"Dec"}],
"name":"GBP"}
];
drawit(dataset);
function drawit(dataset) {
var margins = {
top: 40,
left: 40,
right: 40,
bottom: 40
};
var width = 400;
var height = 500 - margins.top - margins.bottom;
var series = dataset.map(function (d) {
return d.name;
});
var dataset = dataset.map(function (d) {
return d.data.map(function (o, i) {
// Structure it so that your numeric
// axis (the stacked amount) is y
return {
y: +o.IS,
x: o.month
};
});
});
stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function (group) {
return group.map(function (d) {
// Invert the x and y values, and y0 becomes x0
return {
x: d.y,
y: d.x,
x0: d.y0
};
});
});
d3.select("svg").remove();
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.top + margins.bottom)
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')')
var xMax = d3.max(dataset, function (group) {
return d3.max(group, function (d) {
return d.x + d.x0;
});
})
var xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, width]);
var months = dataset[0].map(function (d) {
return d.y;
});
var yScale = d3.scale.ordinal()
.domain(months)
.rangeRoundBands([0, height], .1);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
var colours = d3.scale.category20();
var groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.style('fill', function (d, i) {
return colours(i);
});
var rects = groups.selectAll('rect')
.data(function (d) {
return d;
})
.enter()
.append('rect')
.attr('x', function (d) {
return xScale(d.x0);
})
.attr('y', function (d, i) {
return yScale(d.y);
})
.attr('height', function (d) {
return yScale.rangeBand();
})
.attr('width', function (d) {
return xScale(d.x);
});
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
}
</script>