Adding Object with Transition in D3.js after Zooming - javascript

In the d3.js example, the goal is to change the X & Y scales to reveal a newly added circle which is added offscreen.
Currently, if you were to change the zoom scale, then click on the button to add a new circle, the zoom level suddenly resets to 1. Now dragging the view will cause the zoom scale to become correct again, which is the same zoom scale right before adding the circle.
However this problem goes away if you change redrawWithTransition() on line 123 to redraw() which removes the transitions.
Everything works fine if you add the circle without first zooming.
Why do we have to drag the view again to get the correct zoom scale back? How can we avoid having to do this additional drag and still use transitions in redrawWithTransition()? Thank you!!
Jsfiddle: http://jsfiddle.net/NgWmU/
Because panning again after adding the circle solves the problem, I tried calling redraw() at the end but theres no difference! Is the panning/zooming triggering something else in addition to redraw()?
Jsfiddle: http://jsfiddle.net/NgWmU/2/
First the user zooms/pans around and ends up with a view like in figure A. Next the new circle is added and we should get figure C, but instead we get figure B where the user's current translate/zoom is changed significantly.
Another case
If the user pan/zoom to a view similar to the top figure, adding the new circle should reposition the existing circles slightly, but instead the entire view is reset.
Was thinking about something like:
// Copy existing domains
var xMinOld = xMin,
xMaxOld = xMax,
yMinOld = yMin,
yMaxOld = yMax;
// Update domains
var yMin = d3.min(data.map( function(d) { return d; } )),
yMax = d3.max(data.map( function(d) { return d; } )),
xMin = d3.min(data.map( function(d) { return d; } )),
xMax = d3.max(data.map( function(d) { return d; } ));
if(x.domain()[0] > newData[0] || x.domain()[1] < newData[0]) {
if(x.domain()[0] > newData[0]) {
x.domain([xMin, xMaxOld]);
} else {
x.domain([xMinOld, xMax]);
}
zoom.x(x);
}
if(y.domain()[0] > newData[0] || y.domain()[1] < newData[0]) {
if(y.domain()[0] > newData[0]) {
y.domain([yMin, yMaxOld]);
} else {
y.domain([yMinOld, yMax]);
}
zoom.y(y);
}
But eventually the view will still be reset because we pass the updated scales x and y into zoom...

Related

How to avoid overlapping and stack long text legends in D3 graph?

I have D3 graph base on Multi-line graph 3 with v7: Legend, this sample contains few and shorts legends for the graph. In my sample I want to increase the length for legends and stack the data if is necessary, I want to avoid overlapping in the legends,
https://bl.ocks.org/d3noob/d4a9e3e45094e89808095a47da19808d
dataNest.forEach(function(d,i) {
svg.append("path")
.attr("class", "line")
.style("stroke", function() { // Add the colours dynamically
return d.color = color(d.key); })
.attr("d", priceline(d.value));
// Add the Legend
svg.append("text")
.attr("x", (legendSpace/2)+i*legendSpace) // space legend
.attr("y", height + (margin.bottom/2)+ 5)
.attr("class", "legend") // style the legend
.style("fill", function() { // Add the colours dynamically
return d.color = color(d.key); })
.text(d.key);
});
There are two possible solutions I can think of, shown in this JSFiddle.
First, if it is acceptable that the legend is not part of the svg, then realize the legend with a simple unordered list in a container next to the svg. This is probably the best when there is a varying number of legend entries and there are no restrictions considering the styling via css. The browser takes care of varying lengths and does an automatic line break.
Second, if the legend has to be a part of the svg, one can use the getBBox()-method that determines the coordinates of the smallest rectangle around an object inside an svg.
In a first step select all the legend entries that have been rendered and get the bounding boxes:
const bbox = svg.selectAll(".legend")
.nodes()
.map(legend_entry => legend_entry.getBBox());
With this array and the width of the svg, we can calculate the positions for each legend entry:
bbox.reduce((pos, box) => {
let left, right, line;
if (pos.length === 0) {
left = 0;
line = 1;
} else {
/* The legend entry starts where the last one ended. */
left = pos[pos.length - 1].right;
line = pos[pos.length - 1].line;
}
/* Cumulative width of legend entries. */
right = left + box.width;
/* If right end of legend entry is outside of svg, make a line break. */
if (right > svg_width) {
line = line + 1;
left = 0;
right = box.width;
}
pos.push({
left: left,
right: right,
line: line,
});
return pos;
}, []);
Margins and paddings have to be included manually in the calculation of the positions. Of course, one could obtain the maximum width of all legend entries and make them all the same width with center alignment as in the d3noob example.
In the JSFiddle, I realized this repositioning by first rendering a hidden legend and then an additional one that is visible. It is of course possible to use only one legend, but I would not to take any chances that the process of repositioning is visible in the rendered document.

How to reset scroll behaviour on zoom out?

I am using the lightning charts(in this case the chartXY) by arction for real time data visualization in my react application.When I zoom in using the mouse wheel or the zoom rectangle the chart stops scrolling as expected.When I zoom out the chart starts scrolling but does not follow the interval I set it to.Is there any way to reset the zoom to go back to the current point in real time.
X Axis configuration:
progressive scroll
interval(0,3000) //in milliseconds I presume
Series configuration:
data pattern Datapatterns.horizontalProgressive
I also cannot find a method to control the zoom Out rectangle please let me know if this is present.
Thanks in advance.
Normally when the zooming rectangle is used to zoom out, LightningChart JS will fit the view to the current data. In your case you would like to go back to the original axis interval. You can attach a listener to Axis.onScaleChange event. In this listener you can check if the new scale range would be larger than the original range. And if it is, set the axis interval back to the original range.
series.axisX
.onScaleChange((start, end) => {
if (end - start > intervalSize) {
series.axisX.setInterval(end - intervalSize, end)
}
})
The "Zoom out rectangle" is called "Fitting rectangle" in LightningChart JS. You can change the visual style of it with ChartXY.setFittingRectangleFillStyle and ChartXY.setFittingRectangleStrokeStyle
chart.setFittingRectangleFillStyle(new SolidFill({color: ColorRGBA(255,0,0,125)}))
// Extract required parts from LightningChartJS.
const {
lightningChart,
DataPatterns,
AxisScrollStrategies,
SolidFill,
ColorRGBA
} = lcjs
const {
createProgressiveTraceGenerator
} = xydata
// Create a XY Chart.
const chart = lightningChart().ChartXY()
// Create progressive line series.
const series = chart.addLineSeries({
dataPattern: DataPatterns.horizontalProgressive
})
// configure axis
const intervalSize = 100
series.axisX
.setScrollStrategy(AxisScrollStrategies.progressive)
.setInterval(0, intervalSize)
// limit the axis interval to the original interval size
series.axisX
.onScaleChange((start, end) => {
if (end - start > intervalSize) {
series.axisX.setInterval(end - intervalSize, end)
}
})
// "Zoom out rectangle"
chart.setFittingRectangleFillStyle(new SolidFill({color: ColorRGBA(255,0,0,125)}))
// Generate traced points stream using 'xydata'-library.
createProgressiveTraceGenerator()
.setNumberOfPoints(1000)
.generate()
.setStreamRepeat(true)
.toStream()
.forEach(data => {
series.add(data)
})
<script src="https://unpkg.com/#arction/xydata#1.2.1/dist/xydata.iife.js"></script>
<script src="https://unpkg.com/#arction/lcjs#1.3.1/dist/lcjs.iife.js"></script>

How to override the dragging events in C3.js

I am currently trying to implement a zooming feature that is slightly different from the one that already exists.
Actually, I would like that if a user clicks and drags on the graph it zooms on the so defined domain. I would like to do it that way because with the mouse wheel it prevents the user from the page up/down.
As it doesn't seem to be possible with the C3.js API, I tried to implement the drag event by following this little walkthrough on D3.js Drag Behaviour.
However, I didn't understand it well as it is not working when I try it on the graph.
Here's a sample of my code :
function setHandlers() {
/**
* A custom drag event to zoom on the graph
*/
var drag = d3.behavior.drag();
d3.selectAll('.c3-event-rects').on(".drag", null);
drag
.on('dragstart', function (d) {
d3.event.sourceEvent.stopPropagation();
console.log("start");
console.log(d)
})
.on('drag', function (d) {
console.log("on bouge :)")
})
.on('dragend', function (d) {
console.log("end");
console.log(d)
})
}
I call this function whenever I refresh my graph and I have already coded a custom handler for a double click (in the same function but I have it off to be more clear). I would like to know how to successfully trigger a drag event in a C3.js graph, especially the dragstart and dragend events?
c3-event-rects marks the layer that drives the events (tooltips, clicks, etc.). You don't usually want to move this unless you want the event reactions to be offset (like you get the bar 1 tooltips when you hover over someplace else than over bar 1)
Also, the layer has it's opacity set to 0, so you can't actually see it move unless you override it's opacity. Something like
.c3-event-rects {
fill: red;
fill-opacity: 0.2 !important;
}
That said, here is how you do this
var drag = d3.behavior.drag()
.origin(function () {
var t = d3.select(this);
var translate = d3.transform(t.attr('transform')).translate;
return { x: translate[0], y: translate[1]};
})
.on("drag", function () {
d3.select(this)
.attr("transform", "translate(" + d3.event.x + " " + d3.event.y + ")")
})
d3.selectAll('.c3-event-rects').call(drag);
Fiddle - http://jsfiddle.net/d5bhwwLh/
Here's how it looks - the red spot is where I had my mouse over to make the tooltip appear for the first set of bars

Legend toggling for d3.js pie chart

I'm working on a d3.js application.
In this example I am trying to toggle the slices when the user clicks on the legend components. It will initially take the complete data as its source, but if there is a previous manipulated data source will use that as a base. I've tried to hook into the toggling functionality as the legend is manipulated. I would prefer to separate the functionality - but wasn't sure how else to know if the slice is to be active or not.
Its not working as expected though, especially when trying to handle multiple active/non active slices.
http://jsfiddle.net/Qh9X5/3282/
onLegendClick: function(dt, i){
//_toggle rectangle in legend
var completeData = jQuery.extend(true, [], methods.currentDataSet);
newDataSet = completeData;
if(methods.manipulatedData){
newDataSet = methods.manipulatedData;
}
d3.selectAll('rect')
.data([dt], function(d) {
return d.data.label;
})
.style("fill-opacity", function(d, j) {
var isActive = Math.abs(1-d3.select(this).style("fill-opacity"));
if(isActive){
newDataSet[j].total = completeData[j].total;
}else{
newDataSet[j].total = 0;
}
return isActive;
});
//animate slices
methods.animateSlices(newDataSet);
//stash manipulated data
methods.manipulatedData = newDataSet;
}
Here is the onLegendClick function.
I am toggling the opacity of the inner fill on the rectangle when the user clicks.
I've tried to modify the value of the data accordingly - although it is not handling multiple toggles.
http://jsfiddle.net/Qh9X5/3324/
Ideally if the user tries to deactivate all slices, I would want it to reset the chart and restore all the slices in the process. Would be keen to streamline the code and maybe separate the logic and presentation layer for the styling of the rectangles in the legend.
line 234
onLegendClick: function(dt, i){
//_toggle rectangle in legend
var completeData = jQuery.extend(true, [], methods.currentDataSet);
newDataSet = completeData;
if(methods.manipulatedData){
newDataSet = methods.manipulatedData;
}
d3.selectAll('rect')
.data([dt], function(d) {
return d.data.label;
})
.style("fill-opacity", function(d, j) {
var isActive = Math.abs(1-d3.select(this).style("fill-opacity"));
if(isActive){
newDataSet[j].total = completeData[j].total;
}else{
newDataSet[j].total = 0;
}
return isActive;
});
//animate slices
methods.animateSlices(newDataSet);
//stash manipulated data
methods.manipulatedData = newDataSet;
}

How to navigate between points on flot chart?

I would like to build some sort of forward/back pager that will navigate between the points on my chart, highlighting the current point.
The closest example I've seen is something similar to the news legend on google finance charts, https://www.google.ca/finance?q=goog&ei=AD6dUvjOLMi0iAL9Uw
(you can click each news article/event and it will jump in the graph to the relevant point in time on the graph)
Can this be done with Flot and any suggestions on how to do this? Thanks!
Here's a real quick example to demonstrate one approach to this problem. On previous/next button clicks it highlights the previous/next point in the series and adjusts the xaxis so that the hightlighted point stays centered on the screen.
var highlightPoint = 15; // our first highlight
var xmin = 10, xmax = 20; // some arbitrary slice of series
var plot = $.plot("#placeholder", [ d1 ], //initial plot call
{
xaxis:{min: xmin, max: xmax}
});
plot.highlight(0,highlightPoint); // initial highlight
$('#prevPoint').click(function(){
plot.unhighlight(0,highlightPoint); // unhighlight previous selection
highlightPoint -= 1; // move to left
xmin = highlightPoint - 5; // adjust xaxis
xmax = highlightPoint + 5;
plot.getOptions().xaxes[0].min = xmin; // set xaxis into options
plot.getOptions().xaxes[0].max = xmax;
plot.setupGrid(); // refresh chart with new xaxis
plot.draw(); // redraw
plot.highlight(0,highlightPoint); //highlight new point
});
Makes more sense to look at the Fiddle here.

Categories

Resources