Adding an auto brush to the d3 timeline chart - javascript

I am working on a d3 timeline chart -- but on load - I want the brush to be automatically deployed -- with the option of fine-tuning on a particular set of in/out dates
https://jsfiddle.net/nu1z4d3r/
https://jsfiddle.net/2y8gkas3/8/ -- latest example --
I've tried adding the -- draw brush logic to the bottom of the code base
https://bl.ocks.org/micahstubbs/3cda05ca68cba260cb81
what would be the correct values to make this work -- should xTop be x2?
function drawBrush(a, b) {
// define our brush extent
// define our brush extent
// note that x0 and x1 refer to the lower and upper bound of the brush extent
// while x2 refers to the scale for the second x-axis, for the context or brush area.
// unfortunate variable naming :-/
var x0 = xTop.invert(a*w)
var x1 = xTop.invert(b*w)
console.log("x0", x0)
console.log("x1", x1)
brush.extent([x0, x1])
// now draw the brush to match our extent
// use transition to slow it down so we can see what is happening
// set transition duration to 0 to draw right away
//brush(d3.select(".brush").transition().duration(500));
// now fire the brushstart, brushmove, and brushend events
// set transition the delay and duration to 0 to draw right away
//brush.event(d3.select(".brush").transition().delay(1000).duration(500));
}
// call drawBrush once on load with the default value
//var zoomA = d3.select("input#a")[0][0].value;
//var zoomB = d3.select("input#b")[0][0].value;
var zoomA = 0;
var zoomB = -1;
drawBrush(zoomA, zoomB);
/*
// update the extent and call drawBrush again
window.setTimeout(function() {
d3.select("input#a")[0][0].value = .2;
d3.select("input#b")[0][0].value = .7;
var zoomA = d3.select("input#a")[0][0].value;
var zoomB = d3.select("input#b")[0][0].value;
drawBrush(zoomA, zoomB)
}, 2500);
*/

With the brush -- there were some modifications I had to make
https://jsfiddle.net/m6ueL79o/3/
where the brush is called -- we append a variable to the artefact. We make a 2nd call with "brush.move, x1.range()" -- this loads the scrubber
var brush = d3.brushX()
.extent([[0, 0], [w, miniHeight]])
.on("brush", brushed);
var gBrush = mini.append("g")
.attr("class", "x brush")
.call(brush)
.call(brush.move, x1.range());
otherwise -- to load just the chart first -- do not have the .call(brush.move... and at the base add "drawBrush(timeBegin, timeEnd);"

Related

How to add a tooltip to area chart after zoom interaction

I'm creating an area chart in D3 that includes two main features:
1) a time slider to change the date range on the x axis and update the chart (similar to a brush-to-zoom)
2) a tooltip that appears on hover to show the exact value at a given date
These two features are working when the chart loads, but when I change the x axis range by dragging the slide I encounter a problem: the area chart transitions as it should by zooming in with a new axis and area, but when I hover to reveal the tooltip, the tooltip location and values do not match the new path of the line/area.
I have tried wrapping all the code that creates the tooltip objects (appended to focus var) in a function, and then recalling that function when the slider is moved. I have also tried storing the x scale in a variable that is updated when the time slider is moved, and passing that to the drawFocus() function. The tooltip still appears in the original location.
Screenshot of where tooltip appears after zooming using time slider
Pasting link to bl.ock for full code below, but I think the problem is coming from something in the tipMove() function which is called whenever the user hovers over the chart. (current_x is storing the x scale defined by the time slider):
// function that adds tooltip on hover
function tipMove() {
// below code finds the date by bisecting and
// stores the x and y coordinate as variables
let x0 = current_x.invert(d3.mouse(this)[0]);
let i = bisectDate(data, x0, 1);
let d0 = data[i - 1];
let d1 = data[i];
let d = x0 - d0.date > d1.date - x0 ? d1 : d0;
// place the focus objects on the same path as the line
focus.attr('transform', `translate(${current_x(d.date)}, ${y(d.value)})`);
// position the x line
focus.select('line.x')
.attr('x1', 0)
.attr('x2', x(d.date))
.attr('y1', 0)
.attr('y2', 0);
// console.log(x0)
// position the y line
focus.select('line.y')
.attr('x1', 0)
.attr('x2', 0)
.attr('y1', 0)
.attr('y2', height - y(d.value));
// position the text
focus.select('text').text(d.value).transition() // slowly fade in the tooltip
.duration(100)
.style("opacity", 1);
// show the circle on the path
focus.selectAll('.focus circle')
.style("opacity", 1)
};
Full code here: https://blockbuilder.org/bendoesdata/99c2ca152f760b6d93c923ee9462f4fa
I expect the tooltip to follow the line after the chart is zoomed on, but instead the tooltip follows the original path of the line (which isn't visible within the clip path anymore).

Remove d3 axis transition on initialization

I am using this example to create my own real-time graph using d3. In my version the graph is initialized with existing data. Problem is, the x-axis initialization causes a very small portion of the graph to show while it is transitioning or collapsing on the right before finally showing the normal scale and resultantly the normal graph. I am pretty sure the axis is causing it because the moment the axis returns to normal so does the graph. Is there a way to remove this transition at the begging or otherwise have it not skew the graph or not show until it is ready? Here is the problem in action, better than me trying to explain it: http://codepen.io/Dordan/pen/NbBjPB/
Here is the code snippet for creating the x-axis:
var limit = 60 * 1;
var duration = 750;
var now = new Date(Date.now() - duration);
var x = d3.time.scale().domain([now - (limit - 2), now - duration]).range([0, width]);
var axis = svg.append('g')
.attr('class', 'x-axis')
.attr('transform', 'translate(0,' + height + ')')
.call(x.axis = d3.svg.axis().scale(x).orient('bottom'));
The instantiation of your x scale is missing the '* duration' when you're calculating the domain. Use this instead and it works well:
var x = d3.time.scale().domain([now - (limit - 2) * duration, now - duration]).range([0, width]);

Placing D3 tooltip in cursor location

I'm using d3-tip in my visualisation. I now want to add tooltips to elements that are very wide and may extend out of the visible canvas. By default, the tooltip is shown in the horizontal center of an object, which means in my case that the tooltip might not be in the visible area. What I need is the tooltip showing up in the horizontal position of the cursor but I don't know how to change the tooltip position correctly. I can set an offset and I can get the coordinates of the cursor, but what I can't get is the initial position of the tooltip so that I can compute the right offset. Nor can I set an absolute position:
.on("mouseover",function(d){
var coordinates = [0, 0];
coordinates = d3.mouse(this);
var x = coordinates[0];
var y = coordinates[1];
tip.offset([-20,20]); // this works
tip.attr("x",40); // this doesn't
tip.show(d);
})
If you want to use offset, you can get the initial position of the tooltip after tip.show(d):
tip.style('top');
tip.style('left');
Similarly, to set the absolute position:
.on('mouseover', function(d){
var x = d3.event.x,
y = d3.event.y;
tip.show(d);
tip.style('top', y);
tip.style('left', x);
})
The previously stated answer did not work for me (and cannot be modified as "suggested edit queue is full.."), but with some minor adjustments, it is working fine:
.on('mouseover', function(d){
var x = d3.event.x,
y = d3.event.y;
tip.show(d);
tip.style('top', y-10 + 'px'); // edited
tip.style('left', x+'px'); // edited
})

Unable to get zoom by dragging a rectangle over a d3 series chart to work properly

I am trying to get zoom to work by dragging a rectangle over my series plot to identify the interval of zooming. Here is my plunkr
http://plnkr.co/edit/isaHzvCO6fTNlXpE18Yt?p=preview
You can see the issue by drawing a rectangle with the mouse over the chart - The new chart overshoots the boundary of the X and Y axes. I thought my group under the svg would take care of the bounds of the series (path) but I am clearly mistaken. After staring at it for a long time, I could not figure it out. Please ignore the angular aspect of the plunkr. I think the issue is somewhere in the
//Build series group
var series = svgGroup.selectAll(".series")
.data(data)
.enter().append("g")
.attr("class", "series");
//Build each series using the line function
series.append("path")
.attr("class", "line")
.attr("d", function (d) {
return line(d.series);
})
.attr("id", function (d) {
//While generating the id for each series, map series name to the path element.
//This is useful later on for dealing with legend clicks to enable/disable plots
legendMap[d.name] = this;
//Build series id
return buildPathId(d.name);
})
.style("stroke", function (d) {
//Use series name to get the color for plotting
return colorFcn(d.name);
})
.style("stroke-width", "1px")
.style("fill", "none");
Any help with this is appreciated.
Thank you very much.
I think the method renderChartWithinSpecifiedInterval(minX, maxX, minY, maxY, pixelCoordinates) maybe has some problem there.
It seems the parameter like max_x passed in line 130 are a very big value like time seconds
var svg = renderChartWithinSpecifiedInterval(min_X, max_X, min_Y, max_Y, false);
max_X,min_X are value like 1415171404335
min_Y = 0, max_Y = 100
But in dragDrop call in line 192
function gEnd(d,i){
svg.selectAll(".zoom-rect").remove();
var svgGp = svg.select("g");
var groupTransform = d3.transform(svgGp.attr("transform"));
var xOffset = groupTransform.translate[0];
var yOffset = groupTransform.translate[1];
var xBegin = Math.min(xStart,xDyn) - xOffset;
var xEnd = Math.max(xStart,xDyn) - xOffset;
var yBegin = Math.min(yStart,yDyn) - yOffset;
var yEnd = Math.max(yStart,yDyn) - yOffset;
renderChartWithinSpecifiedInterval(xBegin, xEnd, yBegin, yEnd, true);
//It seems here the parameters values are all pixels
like xBegin = 100, xEnd = 200
}
hope it helps!

d3js: zooming when there are two y axes

reference: https://github.com/mbostock/d3/wiki/Zoom-Behavior
//make zoom
var zoomFirst = d3.behavior.zoom()
.x(x)
.y(y1)
.scaleExtent([0, 3])
.size([w, h])
//.center([w/2+200, h/2-200])
.on("zoom", zoomedFirst);
function zoomedFirst() {
svgContainer.select(".x.axis").call(xAxis);
svgContainer.selectAll(".y.axis.axisLeft").call(yAxisLeft);
//set y2's scale manually
svgContainer.select(".price")
.attr("d", line1(priceData))
.attr("class", "price");
svgContainer.select(".difficulty")
.attr("d", line2(difficultyData))
.attr("class", "difficulty");
}
d3.behavior.zoom() supports autoscaling of x and y axes. However, I have to scale two y axes at the same time. When zoom() is triggered, I can get the current scale and translate info from d3.event.scale and d3.event.translate, but I cant figure out how to make appropriate scale for the second y axis(y2) with them.
I am also looking at https://github.com/mbostock/d3/wiki/Quantitative-Scales.
Since y1's range is automatically adjusted by zoom, if there is a way to get y1's current range, I can get its min and max and set y2's range based on them. However, the document doesn't specify a way to get range given a scale object.
Calling y1.range() (without any arguments) will return you the [min, max] of the scale.
From the docs:
If values is not specified, returns the scale's current output range.
Most accessor functions in D3 work like this, they return you (get) the value if you call them without any arguments and set the value if you call them with arguments and return the this object for easy chaining:
d3Object.propertyName = function (_) {
if (!arguments.length) return propertyName;
propertyName = _;
return this;
}
However, the zoom behaviour alters the domain and not the range of the scales.
From the docs:
Specifies an x-scale whose domain should be automatically adjusted when zooming.
Hence, you do do not need to get/set the range, but instead the domain of the scales y1 and y2: y2.domain(y1.domain()).
Since the zoom function already manages all the ratios, a more abbreviated answer would be:
var zoomFirst = d3.behavior.zoom()
.x(x)
.y(y1)
.scaleExtent([0, 3])
.size([w, h])
.on("zoom", function() {
zoomSecond.scale(zoom.scale());
zoomSecond.translate(zoom.translate());
// Update visual. Both y domains will now be updated
});
// Create copy for y2 scale
var zoomSecond = d3.behavior.zoom()
.x(x)
.y(y2) // <-- second scale
.scaleExtent([0, 3]) // <-- extent
This assumes you have called only zoomFirst to your visual.

Categories

Resources