Disabling all D3 animations (for testing) - animation

I'm looking for a D3 equivalent to jQuery.fx.off = true.
Say you are writing tests (with Mocha, QUnit, etc.) for an app that uses D3. The app has some D3 animations (with .transition()).
Animations are really bad for tests:
First, they are slow.
Second, because they are asynchronous, they can easily cause flickering tests. Ideally, you'd want to avoid any calls to setTimeout / setInterval / requestAnimationFrame.
Is there a way to disable all D3 animations, so that they instantly (and ideally, synchronously) jump to the end state? (Perhaps if there's not an option, we can hook into timer.js?)

An alternative to mocking out transitions is executing them synchronously directly to their final state.
With D3.js v4, use:
function flushAllD3Transitions() {
var now = performance.now;
performance.now = function() { return Infinity; };
d3.timerFlush();
performance.now = now;
}
With D3.js v3 and previous, do:
function flushAllD3Transitions() {
var now = Date.now;
Date.now = function() { return Infinity; };
d3.timer.flush();
Date.now = now;
}
See also d3 issue 1789.

I do not know of a native way to do it in d3. But you can easily modify the d3 selector API to skip animations by augmenting the d3 prototypes:
HTML code to be animated:
<svg width="200" height="200">
<rect x="1" y="1" width="0" height="100" />
</svg>
Animation and D3-augmentation code:
function animate(color){
d3.selectAll("rect")
.attr("width", 0).attr("fill", "white")
.transition().duration(1000)
.attr("width", 100).attr("fill", color)
}
function augment(){
// add a duration function to the selection prototype
d3.selection.prototype.duration = function(){ return this }
// hack the transition function of d3's select API
d3.selection.prototype.transition = function(){ return this }
}
animate("red")
console.log("normal animation done")
setTimeout( function(){
augment()
console.log("d3 hacked!")
animate("green")
console.log("animation skipped")
}, 1200 )
Attention! This hack may not work as a complete solution for you. You may want to extend this solution with other transition().* functions that are not available on the d3.selection.prototype and that you use in your application.
You may also consider other forms of animation supported by d3. Maybe there is more than <selection>.transition() that I am not aware of.

Seems like you can mock d3.timer function:
var d3timer = d3.timer;
d3.timer = function(callback, delay, then) {
d3timer(callback, 0, 0);
};

One approach you could take is to use a fake timer in your testing suite, like Sinon, which works with Mocha or QUnit. Jasmine also has a mock timer built in. I'd think this a better approach because it means the code you're testing is closer to the running code (as opposed to sabotaging the transition functions).

Related

Vue 3 composable performance issues?

In the following example from the vue 3 documentation, we're creating a composable to track the mouse movement. https://vuejs.org/guide/reusability/composables.html#mouse-tracker-example
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => {
console.log('mouse event listener bound')
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
And then using it within our components to retreive the mouse position on the page.
<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
While this works, what I find strange is that using this method you end up with event listeners being attached for each component. So if there are 100 components using useMouse I'll have 100 event listeners attached to the body, each performing the update function which, when added up with other composables, can soon become a performance bottleneck.
Why is the official documentation recommending using this method? Wouldn't a singleton be better suited to this use case? If so how would I create a singleton composable, the vue3 documentation doesn't seem to mention singletons at all.
The document shows that example for the practical purpose only. So the way you are using the composable decides the impact of it on the performance.
If you want to use its function on 100 components at the same time, yes, a singleton would be a good option. But if you want to use the composable in many pages that are separated by the vue router, the composable is totally fine.

dc.js - dynamically change valueAccessor of a stacked layer in a lineChart and redraw it

I am trying to realize a dashboard to display basic data.
I am actually completely stuck on an issue. Strangely enough, I couldn't find anything even similar to it online, so I don't have many leads on how to move forward.
I have mainly two charts:
a lineChart called "stackChart" that
displays consumption as a base layer with its valueAccessor function
dispalys production as a stacked layer with its value Accessor function
a barChart called "volumeChart" that is simply the rangeChart for the lineChart
I use radio buttons to select whether to aggregate the grouped data by sum or by average (using the same approach as this example) and then I just use:
stackChart.valueAccessor(/*function with new value (avg or sum)*/);
dc.redrawAll();
to refresh the base layer (consumption).
What I don't manage to do is to refresh the "stacked layer" by updating its valueAccessor! I can't find any way to access its valueAccessor (or, worst case, just completely remove the stacked layer and then add a new refreshed stacked layer using just ".stack(...)").
Here is the respective part of my code where the chart is built:
// Charts customization #js
stackChart
.renderArea(true)
.height(350)
.transitionDuration(1500)
.dimension(dateDim)
.group(powByTime, "Consumption")
// BASE LAYER valueAccessor HERE
.valueAccessor(function(d) { return d.value.conSum; })
.x(d3.time.scale().domain([minDate, maxDate]))
.xUnits(d3.time.days)
.elasticY(true)
.renderHorizontalGridLines(true)
.legend(dc.legend().x(80).y(0).itemHeight(13).gap(5))
.brushOn(false)
// STACKED LAYER HERE
.stack(powByTime, "Production", function(d) { return d.value.prodSum; })
.rangeChart(volumeChart)
.controlsUseVisibility(true)
;
And here is where I look for changes in the radio buttons and re-draw the layers:
// Listen for changes
d3.selectAll('#select-operation input')
.on('click', function() {
var aggrMode = this.value; // fetch "avg" or "sum" from buttons
// UPDATE BASE LAYER HERE:
stackChart.valueAccessor(function(d) { var sel = accessors[aggrMode]['consPow']; return d.value[sel]; });
// ???HOW TO UPDATE STACKED LAYER valueAccessor function???
//stackChart.stack.valueAccessor(function(d) { var sel = accessors[aggrMode]['prodPow']; return d.value[sel]; });
dc.redrawAll();
});
If you need more details on what I am trying to do and full code you can check here.
As a reference, here is what it looks like:
I don't really know dc.js, but it may be possible that you can't change an accessor once it's been set. Try writing a single function for your accessor that will return either the sum or the average, depending on the state of some variable that you can set.
#Ryan's solution will probably work fine (and may be a better design), but here's the lowdown on the dc.js API with respect to stacking, in case you need it.
As described in this issue the group and stack API is pretty weird. It grew organically, in a backward-compatible way, so both the stacks and the value accessors on top of the stacks sort of branch out in a beautiful fractal of... well, no it's pretty messy.
But the issue also suggests the solution for your problem. Since chart.group() resets the set of stacks, just go ahead and build them all from scratch in your event handler:
stackChart.group(powByTime, "Consumption") // this resets the stacks
.valueAccessor(function(d) { var sel = accessors[aggrMode]['consPow']; return d.value[sel]; })
.stack(powByTime, "Production", function(d) { var sel = accessors[aggrMode]['prodPow']; return d.value[sel]; });
Internally it's just emptying an array of layers/stacks and then populating it with some references.
This is quite efficient since dc.js doesn't store your data except where it is bound to the DOM elements. So it is the same amount of work to redraw using the old group and value accessor as it is to redraw using new ones.

casper js screenshot not working with svg masks

I'm creating visualizations with d3 that use svg masks and I'm trying to get a screenshot using casper js. It usually works fine except it's not rendering the mask properly; it shows all of the layers rather than just the one being masked. This is the casper code:
var casper = require('casper').create();
casper.start('http://localhost:3000', function() {
this.captureSelector('bicycles.png', 'svg');
});
casper.run();
Anybody else having issues with this?
Thanks, Tom
I don't know if it will help, but in my tests I always set the viewport size and then add a delay before running any other code. One reason for the delay is because we are using knockout and DOM elements have not been fully created by the time the 'start' function is called.
So your test would look like this:
var casper = require('casper').create();
casper.start('http://localhost:3000', function () {
casper.viewport(1024, 768);
});
casper.wait(1000); // Wait for knockout bindings and animations...
casper.then(function () {
this.captureSelector('bicycles.png', 'svg');
});
casper.run();
Probably won't help you, but worth a try?

How to animate a dojo gauge?

I'm trying to animate the duration of the value change in a dojo gauge, but I think I'm missing out something, and I can't figure out what it is.
So far, I've got this code working, but the indicator just moves from one point to another, with no animation whatsoever.
require(["dojo/ready", "dojo/dom", "dojox/dgauges/components/black/CircularLinearGauge", "dojox/dgauges/GaugeBase"],
function(ready, dom, CircularLinearGauge, GaugeBase) {
var gauge = new CircularLinearGauge({value:10, animationDuration:5000}, dom.byId("circularGauge"));
setInterval(function() {
var randomValue = Math.floor((Math.random() * 100) + 1);
gauge.set("value", randomValue);
gauge.refreshRendering();
}, 10000);
});
Any help would be highly appreciated, thanks in advance
Looks like its an issue with dojox.dgauges.components.DefaultPropertiesMixin. If you replace the _setValueAttr function to
_setValueAttr: function(v) {
this.getElement("scale").getIndicator("indicator").set("value", v);
}
it should animate for you.
As a side note, all the other functions in DefaultPropertiesMixin sets each property directly instead of using the set function. It might be advisable to change them to use the set function instead.

Prevent Templates From Discarding DOM Elements

I am attempting to integrate a D3.js visualization into a Meteor app. After the page has loaded, a D3 function injects DOM elements into a <div> in my template, based on available data.
Whenever there is a reactive update anywhere on the page, however, Meteor dumps the contents of the template that were injected by my D3.js function. I can re-insert the elements, but this leads to an undesirable flicker and decrease in performance.
Any idea on how to suppress this dropping of externally injected elements? I gather that since these elements were not part of the template originally, they are discarded as part of Meteor's 'cleanup' process.
With the introduction of the Spark templating engine in version 0.4.0, they've introduced the {{#constant}} block helpers to address this issue.
http://meteor.com/blog/2012/08/31/introducing-spark-a-new-live-page-update-engine
Your html template should look something like this....
<template name="samplePageTemplate">
<div id="samplePage" class="page">
{{#constant}}
<div id="sampleGraph"></div>
{{/constant}}
</div>
</template>
And the javascript should look something like this...
Template.samplePageTemplate.destroyed = function () {
this.handle && this.handle.stop();
};
Template.samplePageTemplate.rendered = function () {
self.node = self.find("svg");
if (!self.handle) {
self.handle = Meteor.autorun(function(){
$('#sampleGraph').html('');
renderChart();
});
};
};
function renderChart(){
// lots of d3 chart specific stuff
var vis = d3.select("#sampleGraph").append("svg:svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight)
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
// more d3 chart specific stuff
});
};
I've sometimes had to use self.node instead of self.handle, but otherwise it should be fairly straight forward.
Have you tried giving unique ids to the D3 injected elements (or at least a parent element)? From the doc (http://docs.meteor.com/#livehtml):
Just make sure that each of your focusable elements either has a unique id, or has a name that is unique within the closest parent that has an id. Meteor will preserve these elements even when their enclosing template is rerendered, but will still update their children and copy over any attribute changes.

Resources