Home > database >  How to get the bars on a bar chart to float directly downward, but only when scrolling to that eleme
How to get the bars on a bar chart to float directly downward, but only when scrolling to that eleme

Time:12-01

I'm using D3 to create a bar chart that looks like this:

enter image description here

Currently, the bars are floating in on an angle from the top left corner (because of the D3 coordinate system). I would like the bars to float directly downward, but only when the user scrolls to that part of the page (note: the page is quite lenghty and it would take a few minutes for the user to scroll to that element).

Here is my code:

    let data = [
{"color": "Blue", "plotValue": 11},
{"color": "Red", "plotValue": 20},
{"color": "Orange", "plotValue": 21},
{"color": "Purple", "plotValue": 30},
{"color": "Green", "plotValue": 35},
{"color": "Violet", "plotValue": 40},
               ]


        vis.margin = {top: 70, right: 90, bottom: 90, left: 80};
        vis.width = 800 - vis.margin.left - vis.margin.right;

        // initialize the drawing area
        vis.svg = d3.select('#'   vis.parentElement).append('svg')
            .attr('width', vis.width   vis.margin.left   vis.margin.right)
            .attr('height', vis.height   vis.margin.top   vis.margin.bottom)
            .append('g')
            .attr('transform', `translate (${vis.margin.left}, ${vis.margin.top})`);


        // scales and axes
        vis.xScale = d3.scaleBand()
            .range( [ 0, vis.width ] )
            .padding(0.4);

        vis.yScale = d3.scaleLinear()
            .range( [ vis.height, 0 ] );

        vis.xAxis = d3.axisBottom()
            .scale(vis.xScale);

        vis.yAxis = d3.axisLeft()
            .scale(vis.yScale);

        // add chart title
        vis.svg.append('g')
            .attr('class', 'title bar-chart-title')
            .append('text')
            .text(vis.chartTitle)
            .attr('transform', `translate(${vis.width / 2   45}, -50)`)
            .attr('text-anchor', 'middle');

        // tooltip
        vis.tooltip = d3.select('body').append('div')
            .attr('class', 'tooltip')
            .attr('id', 'barChartTooltip');


        // create the axis groups
        vis.xAxisGroup = vis.svg.append('g')
            .attr('class', 'x-axis axis')
            .attr('transform', 'translate(0, '   vis.height   ')');

        vis.yAxisGroup = vis.svg.append('g')
            .attr('class', 'y-axis axis');



        let vis = this;

        // update the domains
        vis.xScale.domain(vis.data.map(function (d) { return d.color; }));
        vis.yScale.domain( [ 0, 100 ] );

        // draw the bars
        vis.bars = vis.svg.selectAll('.bar')
            .data(vis.data)

        vis.bars.exit().remove();

        vis.bars
            .enter()
            .append('rect')
            .attr('class', 'bar')
            .on('mouseover', function(event, d) {
                d3.select(this)
                    .attr('stroke-width', '2px')
                    .attr('stroke', 'grey')
                    .attr('fill', 'blue')

                vis.tooltip
                    .style('opacity', 1)
                    .style('left', event.pageX   20   'px')
                    .style('top', event.pageY   'px')
                    .html(`
                    <div style='border: thin solid grey; border-radius: 5px; background: lightgrey; padding: 20px'>
                    <h3>${d.color}</h3>
                    <h4> ${100- d.plotValue.toLocaleString()}% decline </h4>
                    </div>
                                        
                    `)

            })
            .on('mouseout', function(event, d) {
                d3.select(this)
                    .attr('stroke-width', 1)
                    .attr('stroke', '#456983')
                    .attr('fill', function(d) {
                        return 'red'
                    })

                vis.tooltip
                    .style('opacity', 0)
                    .style('left', 0)
                    .style('top', 0)
                    .html(``);

            })
            .merge(vis.bars)
            .transition()
            .duration(1200)
            .attr('x', d => vis.xScale(d.color) )
            .attr('y', d => vis.yScale(d.plotValue) )
            .attr('width', vis.xScale.bandwidth() )
            .attr('height', function(d) { return vis.height - vis.yScale(d.plotValue); })
            .attr('fill', 'red')
//            .attr('stroke', 'grey');


        // add the axes
        vis.xAxisGroup
            .transition()
            .duration(500)
            .style('font-size', '15px')
            .style('color', 'blue')
            .call(d3.axisBottom((vis.xScale)))
            .selectAll('text')
            .attr('y', 30)
            .attr('x', -35)
            .attr('dy', '.35em')
            .attr('transform', 'rotate(-30)')
        ;

        vis.yAxisGroup
            .transition()
            .duration(500)
            .style('font-size', '15px')
            .style('color', 'blue')
            .call(d3.axisLeft(vis.yScale));

        vis.text = vis.svg.selectAll('.text')
            .data(vis.data)

        vis.text
            .enter()
            .append('text')
            .attr('class', 'text')
            .attr('text-anchor', 'middle')
            .attr('x', d => vis.xScale(d.color)   27)
            .attr('y', d => vis.yScale(d.plotValue) - 10)
            .text( function (d) {
                return '-'   (100 - d.plotValue)   '%';
            })

How would I amend my code to get the bars to float directly downward, and only triggered when the user scrolls to that part of the page?

Thanks in advance for any advice you can give!

CodePudding user response:

There's no such a thing like D3 coordinate system, that's just the SVG coordinate system: 0,0 is the top left corner.

For fixing your issue you just need to set the x, y and width attributes when the bars enter, and then change the y and height attributes on the transition. I believe showing the bars going up is way better for the user, but showing them coming from the top is trivial (just change the original y position):

const vis = {};

vis.data = [{
    "color": "Blue",
    "plotValue": 11
  },
  {
    "color": "Red",
    "plotValue": 20
  },
  {
    "color": "Orange",
    "plotValue": 21
  },
  {
    "color": "Purple",
    "plotValue": 30
  },
  {
    "color": "Green",
    "plotValue": 35
  },
  {
    "color": "Violet",
    "plotValue": 40
  },
];


vis.margin = {
  top: 70,
  right: 90,
  bottom: 90,
  left: 80
};
vis.width = 800 - vis.margin.left - vis.margin.right;
vis.height = 400 - vis.margin.left - vis.margin.right;

// initialize the drawing area
vis.svg = d3.select("body").append('svg')
  .attr('width', vis.width   vis.margin.left   vis.margin.right)
  .attr('height', vis.height   vis.margin.top   vis.margin.bottom)
  .append('g')
  .attr('transform', `translate (${vis.margin.left}, ${vis.margin.top})`);


// scales and axes
vis.xScale = d3.scaleBand()
  .range([0, vis.width])
  .padding(0.4);

vis.yScale = d3.scaleLinear()
  .range([vis.height, 0]);

vis.xAxis = d3.axisBottom()
  .scale(vis.xScale);

vis.yAxis = d3.axisLeft()
  .scale(vis.yScale);

// add chart title
vis.svg.append('g')
  .attr('class', 'title bar-chart-title')
  .append('text')
  .text(vis.chartTitle)
  .attr('transform', `translate(${vis.width / 2   45}, -50)`)
  .attr('text-anchor', 'middle');

// tooltip
vis.tooltip = d3.select('body').append('div')
  .attr('class', 'tooltip')
  .attr('id', 'barChartTooltip');


// create the axis groups
vis.xAxisGroup = vis.svg.append('g')
  .attr('class', 'x-axis axis')
  .attr('transform', 'translate(0, '   vis.height   ')');

vis.yAxisGroup = vis.svg.append('g')
  .attr('class', 'y-axis axis');

// update the domains
vis.xScale.domain(vis.data.map(function(d) {
  return d.color;
}));
vis.yScale.domain([0, 100]);

// draw the bars
vis.bars = vis.svg.selectAll('.bar')
  .data(vis.data)

vis.bars.exit().remove();

vis.bars
  .enter()
  .append('rect')
  .attr('class', 'bar')
  .attr('x', d => vis.xScale(d.color))
  .attr('y', d => vis.yScale(0))
  .attr('width', vis.xScale.bandwidth())
  .attr("height", 0)
  .on('mouseover', function(event, d) {
    d3.select(this)
      .attr('stroke-width', '2px')
      .attr('stroke', 'grey')
      .attr('fill', 'blue')

    vis.tooltip
      .style('opacity', 1)
      .style('left', event.pageX   20   'px')
      .style('top', event.pageY   'px')
      .html(`
                    <div style='border: thin solid grey; border-radius: 5px; background: lightgrey; padding: 20px'>
                    <h3>${d.color}</h3>
                    <h4> ${100- d.plotValue.toLocaleString()}% decline </h4>
                    </div>
                                        
                    `)

  })
  .on('mouseout', function(event, d) {
    d3.select(this)
      .attr('stroke-width', 1)
      .attr('stroke', '#456983')
      .attr('fill', function(d) {
        return 'red'
      })

    vis.tooltip
      .style('opacity', 0)
      .style('left', 0)
      .style('top', 0)
      .html(``);

  })
  .merge(vis.bars)
  .transition()
  .duration(1200)
  .attr('y', d => vis.yScale(d.plotValue))
  .attr('height', function(d) {
    return vis.height - vis.yScale(d.plotValue);
  })
  .attr('fill', 'red')
//            .attr('stroke', 'grey');


// add the axes
vis.xAxisGroup
  .transition()
  .duration(500)
  .style('font-size', '15px')
  .style('color', 'blue')
  .call(d3.axisBottom((vis.xScale)))
  .selectAll('text')
  .attr('y', 30)
  .attr('x', -35)
  .attr('dy', '.35em')
  .attr('transform', 'rotate(-30)');

vis.yAxisGroup
  .transition()
  .duration(500)
  .style('font-size', '15px')
  .style('color', 'blue')
  .call(d3.axisLeft(vis.yScale));

vis.text = vis.svg.selectAll('.text')
  .data(vis.data)

vis.text
  .enter()
  .append('text')
  .attr('class', 'text')
  .attr('text-anchor', 'middle')
  .attr('x', d => vis.xScale(d.color)   27)
  .attr('y', d => vis.yScale(d.plotValue) - 10)
  .text(function(d) {
    return '-'   (100 - d.plotValue)   '%';
  })
<script src="https://d3js.org/d3.v7.min.js"></script>

Regarding doing the animation only when the user gets to the SVG, thats a completely different question, please keep just 1 issue per question here at SO. Thus, feel free to post another question for that separate issue (you don't need to use the tag in that new question).

  • Related