Performance problem with 'Point-along-path' d3 visualization

I recently explored a d3 visualization where a point moves along a path, following the code example provided at https://bl.ocks.org/mbostock/1705868. During this movement, I observed that the CPU usage ranges from 7 to 11%.

In my current project, there are approximately 100 paths, and on each path, points (represented by circles) need to move from source to destination. This high number of simultaneous movements results in over 90% CPU consumption.

I attempted the following solution:

function translateAlong(path) {
    var length = path.getTotalLength();
    return function(d, i, a) {
        return function(t) {
            var point = path.getPointAtLength(t * length);
            return "translate(" + point.x + "," + point.y + ")";
        };
     };
}

var movingCircle = mapNode.append('circle')
    .attr('r', 2)
    .attr('fill', 'white')

movingCircle.transition()
    .duration(10000)
    .ease("linear")
    .attrTween("transform", translateAlong(path.node()))
    .each("end", function() {
        this.remove();
    });

How can we optimize this process to reduce CPU usage? Your insights are appreciated. Thank you.

Answer №1

There are multiple strategies to consider, each varying in effectiveness.

Ultimately, expensive operations are being performed every animation frame to calculate the new location of each point and re-render it. Therefore, it is crucial to minimize the cost of these operations.

If the frame rate drops below 60, it indicates that the CPU capacity is reaching its limit. Using frame rate as an indicator of CPU capacity allows for easier measurement compared to CPU usage.

I had many charts and theories planned for this approach, but once written out, it felt like common sense and I chose not to elaborate on it further.

The main objective is to enhance the number of transitions displayed at 60 frames per second, enabling a reduction in transitions to increase CPU capacity.


Let's work on running transitions with over 100 nodes along more than 100 paths at 60 frames per second.

D3v4

Incorporating d3v4 can offer some advantages. Synchronized transitions in version 4 have slightly improved performance times. Although transitioning with d3.transition is already efficient and inexpensive, upgrading to version 4 is worth considering.

Optimizing for different browsers by utilizing shaped nodes, transforming positions, or using cx,cy properties might yield minor gains. However, these optimizations were not implemented due to their relatively inconsequential benefits.

Canvas

SVG struggles to keep up with high-speed movements. Manipulating the DOM requires time, and additional elements slow down operations and consume more memory. While canvas may be less convenient from a programming standpoint, it proves to be faster than SVG for this particular task. Utilize detached circle elements to represent each node and transition them accordingly.

Improve efficiency by drawing two canvases: one for initial drawing and path storage (if necessary), and another to redraw each frame displaying the points. Set the datum of each circle to the length of the path it belongs to avoid calling path.getTotalLength() repetitively.

You may find examples like this helpful.

Canvas Simplified Lines

Using a detached node with SVG paths enables the use of path.getPointAtLength(), which can be quite effective. However, curved lines significantly hinder performance. Whenever possible, opt for straight lines (even if composed of multiple segments) for substantial improvements.

As an added benefit, use context.fillRect() instead of context.arc().

Pure JS and Canvas

In scenarios where D3 and detached nodes become cumbersome, consider leaving them behind and using typed arrays, context.imageData, and custom positioning formulas for nodes along paths. For quick reference, here's a simple example (100,000 nodes, 500,000 nodes, 1,000,000 nodes). These examples showcase transitions of 700,000 nodes at 10 frames per second on a slower system. Comparing against my experience with d3v3 and SVG, the difference in transition calculations and renderings per second is remarkable:

canvas A employs cardinal curves and circular markers, while canvas B uses straight lines and square markers (as depicted above).

An apparatus capable of rendering 1,000 transitioning nodes at 60 frames per second will have surplus capacity when handling only 100 nodes.

By halving the number of nodes, you can free up roughly half of the CPU capacity if transition calculations dominate activity and CPU usage nears 100%. In a slow canvas scenario mentioned earlier, transitioning 200 nodes at 60 frames per second allowed for approximately 50% CPU usage; reducing nodes to 100 resulted in a pleasant ~50% CPU utilization:

A horizontal centerline signifies 50% CPU usage, with the transition repeated six times.

To achieve significant savings, prioritize replacing complex cardinal curves with straight lines whenever feasible. Additionally, tailor your scripts to cater specifically to your needs for optimal results.

Compare these adjustments with the inclusion of straight lines and square nodes:

Again, a horizontal centerline indicates 50% CPU usage, with the transition repeated six times.

Rendering 1,000 transitioning nodes on 1,000 paths consisting of three segments showcases over a tenfold improvement compared to employing curved lines and circular markers.

Other Options

These approaches can be combined with those discussed earlier.

Avoid animating every point during each tick

If positioning all nodes during each transition tick leads to excessive CPU usage, reconsider this strategy. Instead of updating every node each tick, position only a fraction of circles, allowing each circle to retain smooth motion at 20 frames per second while reducing total calculations per frame. For canvas applications, nodes must still be rendered, but calculating positions for two-thirds of the nodes could be skipped. With SVG, modifying d3-transition to incorporate an every() method specifying how often transition values update could simplify this process.

Caching

While caching can be beneficial under certain circumstances, front-loading calculations or data loading might cause delays in initiating animations or impact performance upon first execution. Though caching has yielded positive outcomes in specific cases, refer to another answer for further information.

Answer №2

Update:

  • Here is the original code. (peak CPU around %99 for 100 points at 2.7Ghz i7)
  • Here is my optimized version. (peak CPU around 20% for 100 points at 2.7Ghz i7)

I have achieved an average speed improvement of 5 times.

The main bottleneck seems to be the frequent call to the getPointAtLength method every 17ms. To enhance performance, I recommend caching points in advance and calculating them only once with a specified resolution (such as dividing into 1000 parts).

  • Avoid excessive DOM method calls within requestAnimationFrame.

In the default scenario, there are 2 function calls - one when invoking getPointAtLength and then another during setting the translate operation.

You can replace the existing translateAlong function with the modified version below:

 // Updated function definitions go here

Apply this slight tweak to the tweening line:

.tween("transform", translateAlong(path.node()))

Remember, setting attributes is unnecessary; calling is sufficient. Check out the outcome at: http://jsfiddle.net/ibowankenobi/8kx04y29/

Please provide feedback on whether these changes resulted in any noticeable improvements.

Answer №3

To achieve this effect, consider using svg:animateMotion to move an element along a specified path. Take a look at the example provided in the documentation. In simple terms, you would need:

<svg>
  <path id="path1" d="...">
    <circle cx="" cy="" r="10" fill="red">
      <animateMotion dur="10s" repeatCount="0">
        <mpath xlink:href="#path1" />
      </animateMotion>
    </circle>
  </path>
</svg>

Although I haven't done performance testing on this method, utilizing built-in SVG functionality should provide optimal results.

Browser Compatibility

Following a suggestion from @ibrahimtanyalcin, it's worth noting that this feature is not supported in Internet Explorer or Microsoft Edge browsers.

Answer №4

My observations on computer performance:

  • @mbostock is utilizing 8% of CPU.
  • @ibrahimtanyalcin is using 11% CPU.
  • @Ian's CPU usage stands at 10%.

For @mbostock and @ibrahimtanyalcin, the CPU is consumed by the transition and transform updates.

If I incorporate 100 of these animations into a single SVG file, the breakdown is as follows:

  • @mbostock takes up 50% of CPU (fully utilizing one core).
  • @Ian's CPU usage is at 40%.

All animations appear to run smoothly without issues.

To enhance performance, one strategy could be to introduce a pause in the transform update function, as suggested here.

Edit

In an insightful response by Andrew Reid (source), several optimizations were highlighted.

I conducted a modified version of the Canvas+JS test with 100,000 iterations focusing solely on the calculation aspect within a 4000ms timeframe. Flexibility was explored through toggling order=false, controlled by t.

To ensure consistency, a custom random number generator was implemented for each run of the adapted code.

The block-based variation yielded 200 iterations.

Adhering to the guidance regarding parseInt():

Avoid substituting Math.floor() with parseInt().

The switch from parseInt() to Math.floor() resulted in 218 iterations.

Upon identification of a seemingly redundant line within the inner loop:

let p = new Int16Array(2);

Replacing it with:

let p;

elevated the performance to 300 iterations.

Implementing these adjustments achieved a higher capability to handle additional points while maintaining a 60 Hz frame rate.

Further experimentation unveiled that certain strategies led to decreased efficiency.

An interesting finding was that pre-calculating segment lengths and simplifying the calculation of segment with basic array lookups proved slower than the combined approach involving array operations such as Math.pow, additions, and Math.sqrt.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Methods for showing Internet Explorer not supported in Angular without needing to import polyfills

Due to the deprecation of IE in Angular 12, I need to inform users to switch to a supported browser by displaying a static warning on my page. To achieve this, I have implemented a simple snippet in the index.html file that appends a CSS class to the body ...

Error: The session ID is not currently active

I encountered a problem with my tests failing when running them on TFS, showing the following error: WebDriverError: No active session with ID Failed: No active session with ID Interestingly, these same tests are passing locally. Everything was working ...

Tabulate the number of items in an array based on the month and

I have received JSON data with dates indicating the creation time of multiple parcels. I want to analyze this data and calculate the total number of parcels created in each month. I am new to this process and unsure about which thread on Stack Overflow can ...

With TypeScript, you have the flexibility to specify any data type in the generic types when using the axios.get method

axios.get('/api') When working with TypeScript as shown above, it is important to designate types for better clarity. This allows us to reference the type definition of axios, like so: (method) AxiosInstance.get<any, AxiosResponse<any> ...

Rewrite the nginx configuration to eliminate the "/index.html" part of the URL while preserving the query

My Nginx configuration specifies index index.html as the default index page. When accessing it as a progressive web app (using Angular), the URL loads as /index.html#/route instead of /#/route for some reason. I want to: Check if the URL contains ...

Put the code inside a function. I'm new to this

My goal is to encapsulate this code: if($(window).width() > 980) { $(window).on("scroll", function() { if($(window).scrollTop() > 20) { //add black background $(".x-navbar").addClass("active"); $(".x-navbar .desktop ...

Access an attribute using slashes in Jquery

I've been struggling to use jQuery to select an object based on a unique filename attribute. However, I'm facing issues with escaping slashes when the selector is created using a variable. Despite spending hours trying different combinations, I s ...

What is the correct way to show a JavaScript response on the screen?

Here is the JavaScript code I used to call the server API: <script type='text/javascript'> call_juvlon_api(apikey, 'getAvailableCredits', '', function(response) { document.getElementById('show').innerHT ...

Having difficulty positioning the dropdown above the other elements in the body

Below, you'll notice that the dropdown menu isn't positioned correctly over other body elements like the timer. I'm utilizing bootstrap for the dropdown and redcountdown js for the timer. Here is the HTML: <div class="col-md-6 m-t-1 ...

Converting a text area into a file and saving it as a draft in the cloud with the

Can content from a text area be converted into a file of any chosen format and saved in the cloud? Additionally, should every modification made in the text area automatically update the corresponding file stored in the cloud? ...

Incorporating the power of ES6 into a pre-existing website using React

I currently have an established website with a page accessible through the URL www.example.com/apps/myApp. The myApp functionality is embedded within an existing HTML page and serves as a utility app. I am interested in learning React, so I see this as a g ...

Flickering issue with Chart.js chart persists upon reopening the page

Utilizing mustache.js, I am injecting a view file into a designated <div>. Within this view, I have incorporated a canvas element situated inside a specific div, showcasing a chart created with Chart.js. <div> <canvas id="gesundheitsve ...

What is the best way to retrieve strings from an asynchronous POST request?

I am currently working on implementing a signup function in my Angular app using a controller and a factory. However, I am facing an issue where the strings (associated with success or failure) are not being returned from the factory to the controller as e ...

When the webpage first loads, the CSS appears to be broken, but it is quickly fixed when

Whenever I build my site for the first time, the CSS breaks initially, but strangely fixes itself when I press command + s on the code. It seems like hot reloading does the trick here. During development, I can temporarily workaround this issue by making ...

Is there a way to execute a Node 6 npm package within a Node 5.6.0 environment?

I am currently utilizing a tool called easy-sauce to conduct cross-browser JavaScript tests. Essentially, my package.json file references this tool for the test command: { "scripts": { "test": "easy-sauce" } } Everything runs smoothly when I exec ...

Showing a collection of objects in a React component

**Recently started learning React and Node, and decided to fetch data into a functional component by following various tutorials. I successfully set up the server, connected it to the database, and fetched the data in React as per the tutorial instruction ...

Show a table with rows that display an array from a JSON object using JavaScript

My current code includes JSON data that I need to display in table rows, but I'm struggling to understand how to do so effectively. The output I am currently seeing is all the rows from the array stacked in one row instead of five separate rows as in ...

How can I verify if a date is after the current date using Node.js?

I am struggling to set up date validation that ensures the date is after the current date. This is what I have attempted so far: Router.post('/home', [ check('due_date') .isDate() .isAfter(new Date.now()) .wi ...

Submitting a form using jquery

I am working on a project that involves using a jquery fancyzoom box. Within this box, there is a contact form that should send an email upon submission. However, I am encountering issues with calling the form submit function due to the fancyzoom feature. ...

Displaying PDF content in a new browser tab by utilizing JavaScript

Currently, I am utilizing the struts2 framework alongside dojo for the UI. My goal is to display a PDF in a new browser window. The PDF inputstream is obtained from the server through a standard AJAX call using the GET method (not utilizing a Dojo AJAX cal ...