dwang040
dwang040

Reputation: 50

Having both mouse events and bar transitions

My intention is to create a graph that has a bar height transition every time it is drawn/ redrawn, and then once the bar is displayed, there can be mouse events (mouseenter, mouseleave, and mousemove) that will display a tooltip with info of the bar you're hovering over.

I have made it so that the mouse events are working, but where do I add the transition? If I add the transition above the '.attr("height")' the transition works, but then the X and Y axis aren't drawn and I get the error "unknown type: mouseenter." If I place the transition outside of the bar selection, and I do 'bar.transition()...,' the transition doesn't display but the X and Y axis are drawn and mouse event works.

Am I missing something? My understanding of how D3/ javascript nested function work is shoddy since we never discussed them in class, so I'm most likely overlooking a basic principle of how D3 properties are applied, etc.

EDIT: Looking at it, should I be declaring the transition above the

.attr('height', function (d)...

And I should move the click events to the bottom so that it's

bar.on('mouseenter', function (d)...

Current code:

function drawHistogram(type = 3, title = 'Total Count by Name - Alphabetically') {
    var url = "histogramData.tsv";

    var margin = 100;
    var width = 1000 - (margin * 2);
    var height = 500 - (margin * 2);

    var x = d3.scaleBand()
        .range([0, width])
        .padding(0.1); // space between bars

    var y = d3.scaleLinear()
        .range([height, 0]);

    var svg = d3.select('#graphBox').append('svg')
        .attr('width', width + margin + margin)
        .attr('height', height + margin + margin)
        .append('g')
        .attr('transform',
            'translate(' + margin + ',' + margin + ')');

    // add graph title and x/y labels
    svg.append('text')
        ...

    var gradient = d3.scaleLinear()
        .domain([0, 50])
        .range(['rgb(253,203,90)', 'rgb(253,56,170)']);

    d3.tsv(url, function (error, data) {
        if (error) throw error;

        // sort data based on type
        if (type == 0) { // name Z to A
            data.sort(function (a, b) {
                return d3.descending(a.name, b.name)
            });
        } else if (type == 1) { // count lowest to highest
            data.sort(function (a, b) {
                return a.count - b.count;
            });
        } else if (type == 2) { // count highest to lowest
            data.sort(function (a, b) {
                return b.count - a.count;
            });
        } // name A to Z

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

        x.domain(data.map(function (d) {
            return d.name;
        }));

        y.domain([0, d3.max(data, function (d) {
            return d.count;
        })]);

        var bar = svg.selectAll('.bar')
            .data(data)
            .enter()
            .append('rect')
            .attr('class', 'bar')

            .attr('x', function (d) {
                return x(d.name);
            })

            .attr('y', function (d) {
                return y(d.count);
            })
            .attr('width', x.bandwidth())

            // This is the transition code, this transition will allow the bars to dropdown. 
            // If you uncomment this, the bar effect will work as intended, however, my axis
            // are no longer drawn and the mouse events do not work. But the transition works.

            /*
            .transition()
            .duration(5000)
            .delay(function (d, i) {
                return i * 20;
            })
            */

            .attr('height', function (d) {
                return height - y(d.count);
            })

            // give bars horizontal gradient fill
            .style('fill', function (d, i) {
                return gradient(i);
            })

            .on('mouseenter', function (d) {
                bar = d3.select(this)
                    .attr('opacity', 0.5)

                tooltip.style('display', null)
                    .raise()
            })

            .on('mouseleave', function (d) {
                d3.select(this).attr('opacity', 1)
                tooltip.style('display', 'none')
            })

            .on('mousemove', function (d) {
                var xPos = d3.mouse(this)[0] + 5;
                var yPos = d3.mouse(this)[1] - 40;
                tooltip.attr('transform', 'translate(' + xPos + ',' + yPos + ')');
                tooltip.select('#name').text('Name: ' + d.name);
                tooltip.select('#count').text('Count: ' + d.count);
            });

        // I tried to add the transition outside of the bar's creation.
        // and call it here while adding the transition.
        // The transition here does not do anything but the axis are drawn
        // and mouse events work properly. 

        /*
        bar.transition()
            .duration(5000)
            .delay(function (d, i) {
                return i * 20;
            })
        */

        // add x and y axis legends
        svg.append('g')
            ...

        // add a clickable line over the x axis to sort chart alphabetically 
        svg.append('line')
            ...

        // add a clickable line over the y axis to sort chart by value
        svg.append('line')
            ....

    });

    // tooltip item
    var tooltip = svg.append('g')
        .attr('class', 'tooltip')
        .style('display', 'none');

    tooltip.append('rect')
        .attr('width', 80)
        .attr('height', 35)
        .attr('fill', 'white')
        .style('opacity', 1);

    tooltip.append('text')
        .attr('id', 'name')
        .attr('x', '0.5em')
        .attr('dy', '1.3em')
        .style('text-anchor', 'left')

    tooltip.append('text')
        .attr('id', 'count')
        .attr('x', '0.5em')
        .attr('dy', '2.3em')
        .style('text-anchor', 'left')
}

Upvotes: 2

Views: 485

Answers (2)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

There is a small misunderstanding in the accepted answer: you can subscribe to mouse events in the transitions, that's not a problem.

The issue is just a matter of method names. For whatever reason, D3 creator decided to give two different methods the same name. Therefore, the on() here:

selection.on

Is not the same method of the on() here:

transition.on

They are different methods, with the same name. That being said, transition.on is not used for mouse events (that would be selection.on instead), and it accepts only 4 typenames:

  • "start"
  • "end"
  • "interrupt"
  • "cancel"

And here you have a demo showing that you can subscribe to mouse events during a transition, provided that you use selection.on (in this example, d3.select(this).on...). Hover over the black rectangle, there is no mouse event listener. After the transition starts, the mouse event listener works:

const rect = d3.select("rect");
rect.transition()
  .delay(1000)
  .duration(5000)
  .attr("width", 300)
  .on("start", function() {
    d3.select(this).on("mousemove", function() {
      console.log("The mouse is over!");
    })
  })
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg>
  <rect width="100" height="150"></rect>
</svg>

Or you can also use the less known transition.selection(), which returns the selection corresponding to the transition. The difference from the snippet above is that here the listener is immediately attached:

const rect = d3.select("rect");
rect.transition()
  .delay(1000)
  .duration(5000)
  .attr("width", 300)
  .selection()
  .on("mousemove", function() {
    console.log("The mouse is over!");
  })
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg>
  <rect width="100" height="150"></rect>
</svg>

Upvotes: 2

Guillermo
Guillermo

Reputation: 1054

You can't subscribe to mouse events in transitions, so you should have the .on before the .transition.

Try something like this:

    var bar = svg.selectAll('.bar')
        .data(data)
        .enter()
        .append('rect')
        .attr('class', 'bar')
        .attr('x', function (d) {
            return x(d.name);
        })
        .attr('y', function (d) {
            return y(d.count);
        })
        .attr('width', x.bandwidth())
        .on('mouseenter', function (d) {
            bar = d3.select(this)
                .attr('opacity', 0.5)

            tooltip.style('display', null)
                .raise()
        })
        .on('mouseleave', function (d) {
            d3.select(this).attr('opacity', 1)
            tooltip.style('display', 'none')
        })
        .on('mousemove', function (d) {
            var xPos = d3.mouse(this)[0] + 5;
            var yPos = d3.mouse(this)[1] - 40;
            tooltip.attr('transform', 'translate(' + xPos + ',' + yPos + ')');
            tooltip.select('#name').text('Name: ' + d.name);
            tooltip.select('#count').text('Count: ' + d.count);
        })
        .transition()
        .duration(5000)
        .delay(function (d, i) {
            return i * 20;
        })
        .attr('height', function (d) {
            return height - y(d.count);
        })
        // give bars horizontal gradient fill
        .style('fill', function (d, i) {
            return gradient(i);
        });

Upvotes: 0

Related Questions