When hovering over certain transitioning elements in a D3JS chart, the animation execution is paused if other elements are also in the process of transitioning

Currently, I have been designing a horizontal bar chart and experimenting with transitions on various elements like rect and circle. The transitions are applied to attributes like width and r to achieve the desired effect. Everything seems to be working fine until I encounter an issue when hovering over one of the elements before all transitions have completed.

Each element, including rect and circle, undergoes its transition with different delays, causing some to become visible before others. However, the main problem arises when hovering over any rect while other elements are still transitioning. It causes all transitions to halt, resulting in a messy final state for the chart.

The root cause: I am puzzled as to why hovering over a seemingly independent element can disrupt the expected behavior of other elements. This interference prevents the chart from reaching its intended final state.

function draw(){

  var width =  $( window ).width() ;
  var height =  document.body.clientHeight ;

  var data = [
    {country:"Pichonita", growth: 15},
    {country:"Andromeda", growth: 12},
    {country:"India", growth: 33},
    {country:"Indonesia", growth: 22},
    {country:"Russia", growth: 6},
    {country:"Mars", growth: 41},
    {country:"Pluton", growth: 16},
    {country:"Earth", growth: 24},
    {country:"Neptune", growth: 8}
  ]; 

    //set margins
    var margin = {top:30, right:30, bottom:30, left:40};
    var width = width - margin.left - margin.right*2.5;
    var height = height - margin.top - margin.bottom;

    //set scales & ranges

    var xScale = d3.scaleLinear()
      .range([0,  width - 100])

    var yScale = d3.scaleBand()
      .range([0, height]).padding(.2)

    //draw the svg

    var svg = d3.select("body")
      .append("svg")
      .attr("width", width + margin.left + margin.right * 3)
      .attr("height",height + margin.top + margin.bottom)
      .append("g")
      .attr("transform","translate(" + margin.left*2 + "," + margin.top  + ")")

      //force data

       data.forEach(function(d){
      return d.growth = +d.growth;
       });

      //set domains

      yScale.domain(data.map(d => d.country))

      xScale.domain([0, d3.max(data, d=> d.growth)])

      //add X & Y axes and append the bars to Y axis

      var xAxis = svg.append("g")
          .attr("class",xAxis)
          .attr("transform","translate(" + 0 + "," + height + ")")
.call(d3.axisBottom(xScale))

     var yAxis =  svg.append("g")
           .attr("class",yAxis)
           .call(d3.axisLeft(yScale))
           .selectAll("rect")
           .data(data)
           .enter()
           .append("rect")
           .attr("stroke","transparent")
           .attr("stroke-width",4)
           .on("mouseover", function(){d3.select(this).transition().duration(600).attr("stroke","#6D2077").attr("stroke-width",3).style("fill","#6D2077")
            d3.selectAll(".textCircle").transition().duration(600)
           .attr("r",yScale.bandwidth() / 1.9)
           .attr("stroke","#6D2077")
           .attr("stroke-width",1)
    }) 
          
  .on("mouseout", function(){d3.select(this)
      .transition()
      .duration(600)
  .attr("stroke","transparent")
          .attr("stroke-width",0)
          .style("fill","#00338D")
 d3.selectAll(".textCircle")
          .transition().duration(600)
.attr("r", yScale.bandwidth() / 2)
          .attr("stroke","transparent")
}) 
          .attr("class","bar")
          .attr("height",yScale.bandwidth())
          .attr("x",0.5)
          .attr("y",function(d){
           return  yScale(d.country)
         })
         .attr("width",0)
         .transition()
         .duration(3800)
         .delay( (d,i)=> (i+1) *200)
         .ease(d3.easeElastic)
         .attr("width", function(d){
           return xScale(d.growth)
         })
        .style("fill","#00338D")

        var newG = svg.append("g")
        
         newG.selectAll("circle")
        .data(data)
        .enter()
        .append("circle")
        .attr("class","textCircle")
        .attr("cx",d=> xScale(d.growth) )
        .attr("cy",d=> yScale(d.country) + yScale.bandwidth() / 2)
        .attr("r",0)
        .transition()
          .duration(1200)
        .delay( (d,i)=> (i+1) *450)
        .attr("r",yScale.bandwidth() / 2)
        .attr("opacity",1)
        .style("fill","#0091DA")
        .attr("stroke","transparent")
        }

  draw();

  $( window ).resize(function() {
    $("body").empty();
    draw();
  });
html{ 
  height: 98%;
  margin: 0;
  padding: 0;
}

body{
  min-height: 98%;
  margin: 0;
  padding: 0;
}

svg{
  text-rendering: geometricPrecision;
  shape-rendering:geometricPrecision;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

Answer №1

It seems like there is some confusion happening due to the inclusion of `mouseover` and `mouseout` event listeners that are conflicting with ongoing transition events. To resolve this issue, refrain from adding the `mouseover`/`mouseout` listeners until after the chart bars have completed their initial transition. You can achieve this by listening for the end of the transition using `transition.on('end', function(){...})`, and then attaching the mouse event listeners to the DOM elements once the transition has finished.

d3.select('#whateverItIs')
// code to execute prior to transition
.transition()
// define transition actions here
.on('end', function() {
  d3.select(this)
    .on("mouseover", function() {
      // mouseover handler code goes here
    })
    .on("mouseout", function() {
      // mouseout handler code goes here
    })
})

In your existing code:

function draw() {

  var width = $(window).width();
  var height = document.body.clientHeight;

  var data = [{
      country: "Pichonita",
      growth: 15
    },
    {
      country: "Andromeda",
      growth: 12
    },
    {
      country: "India",
      growth: 33
    },
    {
      country: "Indonesia",
      growth: 22
    },
    {
      country: "Russia",
      growth: 6
    },
    {
      country: "Mars",
      growth: 41
    },
    {
      country: "Pluton",
      growth: 16
    },
    {
      country: "Earth",
      growth: 24
    },
    {
      country: "Neptune",
      growth: 8
    }
  ];

  // set margins
  var margin = {
    top: 30,
    right: 30,
    bottom: 30,
    left: 40
  };
  var width = width - margin.left - margin.right * 2.5;
  var height = height - margin.top - margin.bottom;

  //set scales & ranges

  var xScale = d3.scaleLinear()
    .range([0, width - 100])

  var yScale = d3.scaleBand()
    .range([0, height]).padding(.2)

  // draw the svg

  var svg = d3.select("body")
    .append("svg")
    .attr("width", width + margin.left + margin.right * 3)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left * 2 + "," + margin.top + ")")

  // force data

  data.forEach(function(d) {
    return d.growth = +d.growth;
  });

  // set domains

  yScale.domain(data.map(d => d.country))

  xScale.domain([0, d3.max(data, d => d.growth)])

  // add X & Y axes and append the bars to Y axis

  var xAxis = svg.append("g")
    .attr("class", xAxis)
    .attr("transform", "translate(" + 0 + "," + height + ")")
    .call(d3.axisBottom(xScale))

  var yAxis = svg.append("g")
    .attr("class", yAxis)
    .call(d3.axisLeft(yScale))
    .selectAll("rect")
    .data(data)
    .enter()
    .append("rect")
    .attr("stroke", "transparent")
    .attr("stroke-width", 4)
    .attr("class", "bar")
    .attr("height", yScale.bandwidth())
    .attr("x", 0.5)
    .attr("y", function(d) {
      return yScale(d.country)
    })
    .attr("width", 0)
    .transition()
    .duration(3800)
    .delay((d, i) => (i + 1) * 200)
    .ease(d3.easeElastic)
    .attr("width", function(d) {
      return xScale(d.growth)
    })
    .style("fill", "#00338D")
    .on('end', function() {
      d3.select(this)
        .on("mouseover", function() {
          d3.select(this)
            .transition().duration(600)
            .attr("stroke", "#6D2077")
            .attr("stroke-width", 3)
            .style("fill", "#6D2077")
          d3.selectAll(".textCircle")
            .transition().duration(600)
            .attr("r", yScale.bandwidth() / 1.9)
            .attr("stroke", "#6D2077")
            .attr("stroke-width", 1)
        })
        .on("mouseout", function() {
          d3.select(this)
            .transition()
            .duration(600)
            .attr("stroke", "transparent")
            .attr("stroke-width", 0)
            .style("fill", "#00338D")
          d3.selectAll(".textCircle")
            .transition().duration(600)
            .attr("r", yScale.bandwidth() / 2)
            .attr("stroke", "transparent")
        })

    })

  var newG = svg.append("g")

  newG.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("class", "textCircle")
    .attr("cx", d => xScale(d.growth))
    .attr("cy", d => yScale(d.country) + yScale.bandwidth() / 2)
    .attr("r", 0)
    .transition()
    .duration(1200)
    .delay((d, i) => (i + 1) * 450)
    .attr("r", yScale.bandwidth() / 2)
    .attr("opacity", 1)
    .style("fill", "#0091DA")
    .attr("stroke", "transparent")
}

draw();

$(window).resize(function() {
  $("body").empty();
  draw();
});
html{ 
  height: 98%;
  margin: 0;
  padding: 0;
}

body{
  min-height: 98%;
  margin: 0;
  padding: 0;
}

svg{
  text-rendering: geometricPrecision;
  shape-rendering:geometricPrecision;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

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

What's causing the member to be undefined?

client.on('raw', (e) => { if (e.t === 'MESSAGE_REACTION_ADD' && e.d.message_id === '813150433651851265' && e.d.emoji.name === "✅" ) { const guild = client.guilds.cache.get(e.d.gui ...

Switching the markLine in vega lite to a markBar causes it to lose its sorting arrangement

I have created the following data visualization: data = [{"student_name": "student 0", "e": "100.15", "d": "127.81"}, {"student_name": "student 1", "e": "100.30", "d": "189.94"}, {"student_name": "student 2", "e": "100.15", "d": "105.33"}, {"student_nam ...

Who needs a proper naming convention when things are working just fine? What's the point of conventions if they don't improve functionality?

I am a newcomer to the world of JavaScript programming and stumbled upon this example while practicing. <html> <head> <script type="text/javascript"> function changeTabIndex() { document.getElementById('1').tabIndex="3" d ...

After pressing the button to access the sidebar, all I see is a plain white screen

I've been diligently working on a school project, but I'm encountering some issues with the Sidebar button in the top left corner. Whenever I click on the button, it opens up to display a blank white page. Can anyone provide me with some assistan ...

Verify the occurrence of an element within an array inside of another array

Here is the scenario: const arr1 = [{id: 1},{id: 2}] const arr2 = [{id: 1},{id: 4},{id: 3}] I need to determine if elements in arr2 are present in arr1 or vice versa. This comparison needs to be done for each element in the array. The expected output sho ...

Issues encountered with Three.js MeshBasicMaterial functionality

I am currently working on generating a texture using Three.js. The texture_f1 source I am using is a .png file, which allows the background to show through. The issue arises when attempting to set the background color using color: 0xffffff in conjunction ...

Is there a way to adjust the transparency of individual words in text as you scroll down a page, similar to the effect on https://joincly

Is there a way to achieve a text filling effect on page scroll similar to the one found here: . The specific section reads: "Deepen customer relationships. Own the brand experience. Add high margin revenue. Manage it all in one place. Get back your pr ...

How do you obtain the string name of an unknown object type?

In my backend controllers, I have a common provider that I use extensively. It's structured like this: @Injectable() export class CommonMasterdataProvider<T> { private readonly route:string = '/api/'; constructor(private http ...

Using a string to access a property within a ReactJS object

I am looking to simplify my React Component by referencing a JS object property using a string. This will allow me to remove repetitive conditional statements from my current render function: render() { const { USD, GBP, EUR } = this.props.bpi; ...

- Determine if a div element is already using the Tooltipster plugin

I have been using the Tooltipster plugin from . Is there a way to check if a HTML element already has Tooltipster initialized? I ask because sometimes I need to update the text of the tooltip. To do this, I have to destroy the Tooltipster, change the tit ...

Newly inserted JSON component remains invisible

I am currently using express.js to create an API. My mongoose is returning a JSON object and I need to append an element to each item in the result.docs. This is how I am attempting to achieve that: for(let a in result.docs) { result.docs[a].link ...

"Track the upload progress of your file with a visual progress

I have written this code for uploading files using Ajax and PHP, and I am looking to incorporate a progress bar to indicate the percentage of the upload. Here is the code snippet: <script> $("form#data").submit(function(){ var formData = new ...

Issue with Vue.js: Difficulty sending an array of values to an endpoint

I am currently in the process of learning Vue in order to complete my project, which has a Java Spring backend. The endpoint I am working with expects an object with the following values: LocalDate date; Long buyerId; Long supplierId; List<OrderDetails ...

Directing traffic from one webpage to another without revealing the file path in the Routes.js configuration

Recently starting out in Reactjs and utilizing Material-UI. My inquiry is regarding transitioning between pages using a sidebar, where in Material-UI it's required to display the page in the sidebar which isn't what I desire. var dashRoutes = [ ...

Bringing a React Context Back to its Original State

The default state of a React Context is defined as: export const defaultState: UsersState = { isModalOpen: false, isCancelRequest: false, companyId: 0, users: [] }; After cancelling the modal, the goal is to reset the state back to its default va ...

The total height of an HTML element, which takes into account the margin of the element itself

Is there a way to accurately calculate the height of an element including margins, even when dealing with child elements that have larger margins than their parents? HTMLElement.offsetHeight provides the height excluding margin, while this function from ...

What is the method for including a dynamic image within the 'startAdornment' of MUI's Autocomplete component?

I'm currently utilizing MUI's autocomplete component to showcase some of my objects as recommendations. Everything is functioning correctly, however, I am attempting to include an avatar as a start adornment within the textfield (inside renderInp ...

Can other JavaScript event listeners be synchronized with an asynchronous event?

My goal is to manipulate a web page using Tampermonkey where there is a button with certain event listeners followed by a redirect. Is it possible for me to insert my own click event handler into the button, which triggers an AJAX request and waits for it ...

What is the best way to determine if a value from my array is present within a different object?

I have an array with oid and name data that I need to compare against an object to see if the oid value exists within it. Here is the array: const values = [ { "oid": "nbfwm6zz3d3s00", "name": "" ...

Is it possible to create cloud functions for Firebase using both JavaScript and TypeScript?

For my Firebase project, I have successfully deployed around 4 or 5 functions using JavaScript. However, I now wish to incorporate async-await into 2 of these functions. As such, I am considering converting these specific functions to TypeScript. My conc ...