Home > OS >  Updating D3 interactive barchart by removing bars that are already there
Updating D3 interactive barchart by removing bars that are already there

Time:01-31

Issue: I want to update the bars in my graph so that when the "Dreamworks" button is clicked, it appends new bars and gets rid of the old ones. I know it is an enter(), exit() issue, but I do not know exactly how to implement it.

Context: When my button is clicked, it activates a function that extracts the inner HTML of my button and uses it to filter my data so only observations from a company remain. The code works, but instead of getting rid of old bars, it appends the new bars on top of the old ones. When you look in the console, a new "g" element appears (which contains the new "rects") every time the button is clicked. I lowered the opacity of the "rects" to show what is going on. I removed all exit() and remove() attempts from my code because nothing was working.

HTML Code:

            <div class= "button-holder">
                <button class= "button button-dreamworks">DreamWorks</button>
                <button class= "button button-disney">Disney</button>
                <button class= "button button-pixar">Pixar</button>
            </div>
                <div class = "chart chart1"></div>

JS Code:

async function drawBar() {
    // 2. Create Chart Dimensions 
    const width = 600
    let dimensions = {
        width,
        height: width*0.6, 
        margin: {
            top: 30,
            right: 10,
            bottom: 50,
            left: 50
        }
    }
    
    dimensions.boundedWidth = dimensions.width
        -dimensions.margin.right -dimensions.margin.left 
    dimensions.boundedHeight = dimensions.height 
        -dimensions.margin.top -dimensions.margin.left 

// 3. Draw Canvas 
const wrapper = d3.select(".chart1")
    .append("svg")
    .attr("width", dimensions.width)
    .attr("height", dimensions.height)



// 4. Load Data  
    const raw_data = await d3.csv("./data/all_movie_data.csv")


    
    const drawBarChart = function(company_name) {
    const dataset = raw_data.filter(function(d){ return  d["company"] == company_name })
    const xAccessor = d => d["name"]
    const yAccessor = d => parseFloat(d.budget) 


let bounds = wrapper
    .append("g")
    .attr("class", "bounds")
    .style(
      "transform",
      `translate(${dimensions.margin.left}px,${dimensions.margin.top}px)`
    );


// 5. Create scales 
const xScale = d3.scaleBand()
    .domain(dataset.map(xAccessor))
    .range([0,dimensions.boundedWidth])
    .padding(0.4);
    
const yScale = d3.scaleLinear()
    .domain(d3.extent(dataset,yAccessor))
    .range([dimensions.boundedHeight, 0])


// 6. Draw Data

bounds.selectAll("rect")
    .data(dataset)
    .join("rect")
    .attr("x", (d) =>  xScale(xAccessor(d))) 
    .attr("y", (d) => yScale(yAccessor(d)))
    .attr("width",  xScale.bandwidth()) 
    .attr("height", (d) => dimensions.boundedHeight - yScale(yAccessor(d))) 
    .attr("fill", "blue");
    

}

//6. Interactions 
drawBarChart("Pixar");

const button1 = d3.select(".button-dreamworks")
    .node()
    .addEventListener("click", onClick1) 

function onClick1() {
    const company = document.querySelector(".button-dreamworks").innerHTML;
    drawBarChart(company);
    }

}

drawBar(); 

You can find a version of my code in this code pen: https://codepen.io/larylc/pen/XWzbQGy

Everything is the same except for the data, which I just made up to show the issue.

CodePudding user response:

Answer: I understand the enter(), exit() structure now.

  1. By setting the "rects" as a variable (which I called bars) I can now manipulate them ( this gave me a selection object that was different from before).

  2. I can now add exit() and remove() to the bars variable. This was not possible before.

  3. I moved the bounds variable to the section where my canvas was, which completed the exit() and enter() pattern. Every time a button was clicked, the SVG elements in the DOM (the bars) would match the number of data elements that were supposed to be added. So if I had 22 bars in the DOM, it would update to 34(or whatever the new dataset was). I tried this before and it worked but my bars were not updating correctly, which brings me to my last point.

  4. The last problem was the bars would be added or removed to match the number of new data points without changing existing ones. This meant that the DOM would not update the bars to match the actual data. So if I had 13 existing bars and the new data was 22, it would just keep the 13 and add the last 9. This did not necessarily match the data. So I needed to add the merge(bars) statement to ensure that all my bars (including the existing ones) would update.

My New JS Code (with all of the buttons working)

async function drawBar() {
    //  Create Chart Dimensions 
    const width = 600
    let dimensions = {
        width,
        height: width*0.6, 
        margin: {
            top: 30,
            right: 10,
            bottom: 50,
            left: 50
        }
    }
    
    dimensions.boundedWidth = dimensions.width
        -dimensions.margin.right -dimensions.margin.left 
    dimensions.boundedHeight = dimensions.height 
        -dimensions.margin.top -dimensions.margin.left 

//  Draw Canvas 
const wrapper = d3.select(".chart1")
    .append("svg")
    .attr("width", dimensions.width)
    .attr("height", dimensions.height)

let bounds = wrapper
    .append("g")
    .attr("class", "bounds")
    .style(
      "transform",
      `translate(${dimensions.margin.left}px,${dimensions.margin.top}px)`
    );
    


// Load Data  
const raw_data = await d3.csv("./data/all_movie_data.csv")



//  Function that draws data 
const drawBarChart = function(company_name) {
    const dataset = raw_data.filter(function(d){ return  d["company"] == company_name })
    const xAccessor = d => d["name"]
    const yAccessor = d => parseFloat(d.budget) 


//  Create scales 
const xScale = d3.scaleBand()
    .domain(dataset.map(xAccessor))
    .range([0,dimensions.boundedWidth])
    .padding(0.4);
    
const yScale = d3.scaleLinear()
    .domain(d3.extent(dataset,yAccessor))
    .range([dimensions.boundedHeight, 0])


//  Draw Data

const bars = bounds.selectAll("rect")
    .data(dataset)

bars.join("rect").merge(bars)
    .attr("x", (d) =>  xScale(xAccessor(d))) 
    .attr("y", (d) => yScale(yAccessor(d)))
    .attr("width",  xScale.bandwidth()) 
    .attr("height", (d) => dimensions.boundedHeight - yScale(yAccessor(d))) 
    .attr("fill", "blue")
    .attr("opacity", 0.4)

    
bars.exit().remove();



}

// Interactions 
drawBarChart("Pixar");

// All Buttons and functions that triggers data change 
const button1 = d3.select(".button-dreamworks")
    .node()
    .addEventListener("click", onClick1) 

function onClick1() {
    const company = document.querySelector(".button-dreamworks").innerHTML;
    drawBarChart(company);
}

const button2 = d3.select(".button-disney")
    .node()
    .addEventListener("click", onClick2) 

function onClick2() {
    const company2 = document.querySelector(".button-disney").innerHTML;
    drawBarChart(company2);
    }

const button3 = d3.select(".button-pixar")
    .node()
    .addEventListener("click", onClick3) 

function onClick3() {
    const company3 = document.querySelector(".button-pixar").innerHTML;
    drawBarChart(company3);
    }


}

drawBar(); 


  •  Tags:  
  • Related