score:3

Accepted answer

Your immediate problem is an easy miss:

Every update you create new parent g elements and conduct a enter/update/exit cycle on those new g elements:

 var area = focus.append("g").selectAll('path')

The newly appended g element above will be empty as it has no children yet; therefore, the enter selection will contain one element for every item in the data array. As you never remove or re-select the old g elements, they just sit there untouched by the new selections and any subsequent enter/update/exit cycles. The result is we just layer on new data every timne we run the update function.

The parent g elements should be appended outside of the update function: they don't need updating: where there was one there will still be one and the parent's properties remain unchanged. The parent g elements are independent of the data, they only hold children that represent data. We should set up the parent g elements once outside the update function, alongside all the other things that need to be set up once. Then we can use those parents to set up our enter/update/exit cycle since we'll be selecting elements in the same parents each update (as opposed to using new empty parents each update).

Below I've taken a subset of your chart (for ease of demonstration) and appended all your parent g elements that only need be appended once and appended them prior to the update function being called. I also took your area and line generators and removed them from the update function: they don't change either here. This applies to tooltip div as well, and the axes themselves: the parent g is appended once outside the update function but the update function calls the axis generators on those g elements.

As an aside, there's a lot of overlapping variable names in your code, I may have altered names to avoid that. I've used the merge method when entering and updating the line.

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<style>
    .brush rect.selection {
        fill: none;
        opacity: 1;
    }
    rect.handle{
        fill: #666;
    }

</style>
</head>
<body>

<div id="buttons">
    <button id="All" class="button selected">All</button>
    <button id="Men" class="button">Men</button>
    <button id="Women" class="button">Women</button>
</div>
<div id='ptchart'></div>

<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

var json = [
    {
      "date": "2020-01-02",
      "gender": "Men",
      "value": 4320
    },
    {
      "date": "2020-01-02",
      "gender": "Women",
      "value": 984
    },
    {
      "date": "2020-01-15",
      "gender": "Men",
      "value": 4624
    },
    {
      "date": "2020-01-15",
      "gender": "Women",
      "value": 1005
    },
    {
      "date": "2020-02-03",
      "gender": "Men",
      "value": 5488
    },
    {
      "date": "2020-02-03",
      "gender": "Women",
      "value": 978
    },
    {
      "date": "2020-02-18",
      "gender": "Men",
      "value": 5842
    },
    {
      "date": "2020-02-18",
      "gender": "Women",
      "value": 1006
    },
    {
      "date": "2020-03-02",
      "gender": "Men",
      "value": 6925
    },
    {
      "date": "2020-03-02",
      "gender": "Women",
      "value": 1004
    },
    {
      "date": "2020-03-16",
      "gender": "Men",
      "value": 6132
    },
    {
      "date": "2020-03-16",
      "gender": "Women",
      "value": 948
    },
    {
      "date": "2020-04-01",
      "gender": "Men",
      "value": 5852
    },
    {
      "date": "2020-04-01",
      "gender": "Women",
      "value": 685
    },
    {
      "date": "2020-04-15",
      "gender": "Men",
      "value": 8697
    },
    {
      "date": "2020-04-15",
      "gender": "Women",
      "value": 497
    },
    {
      "date": "2020-05-01",
      "gender": "Men",
      "value": 4547
    },
    {
      "date": "2020-05-01",
      "gender": "Women",
      "value": 468
    }
]

var margin = { top: 30, right: 210, bottom: 110, left: 50 },
    width = 1000 - margin.left - margin.right,
    height = 440 - margin.top - margin.bottom;

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

var xAxis = d3.axisBottom(x).tickFormat(d3.timeFormat("%b %Y")).ticks(5),
    yAxis = d3.axisLeft(y);

var svg = d3.select("#ptchart").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom);

svg.append("defs").append("clipPath")
    .attr("id", "clip")
    .append("rect")
    .attr("width", width)
    .attr("height", height);

// Add all the `g` parent elements now:
var focus = svg.append("g")
    .attr("class", "focus")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
var areaG = focus.append("g");
var lineG = focus.append("g")
                 .attr("clip-path", "url(#clip)");

var stooltip = d3.select("body").append("div")
    .attr("class", "tooltip");
            
var xAxisG = focus.append("g")
            .attr("class", "axis axis--x")
            .attr("transform", "translate(0," + height + ")")           

var yAxisG = focus.append("g")
             .attr("class", "axis axis--y")         
            
    
// area and line generators:
var area = d3.area()
    .x(function (d) { return x(d.date); })
    .y0(height)
    .y1(function (d) { return y(d.value); })

var line = d3.line()
    .x(function (d) { return x(d.date); })
    .y(function (d) { return y(d.value); }) 


const parseDate = d3.timeParse("%Y-%m-%d");

let data = json
updateChart(data)

    d3.selectAll(".button").on("click", function () {
        var section = d3.select(this).attr("id");
        
        if (section == 'All') { data = json } else {
            data = json.filter(d => d.gender == section)
        }

        updateChart(data);
    })

    updateChart(json);


function updateChart(selectedData) {
    
    // Manage data
    const data = d3.nest()
        .key(d => d.date)
        .rollup(v => d3.sum(v, d => d.value))
        .entries(selectedData);

    data.forEach((d) => {
        d.date = parseDate(d.key);
        d.value = +d.value;
    })

    // Calculate new scale values:
    x.domain(d3.extent(data, d => d.date));
    y.domain([0, d3.max(data, d => d.value) + 500]);

    // Main chart:
    // Do the areas:
    var areaPaths = areaG.selectAll('path')
        .data([data])

        areaPaths.enter().append("path")
            .attr("class", "area")
            .attr('fill', '#eeeeee')
        
        areaPaths.transition()
            .attr("d", area)
            .attr("clip-path", "url(#clip)");
        
        areaPaths.exit().remove()

    // Do the lines:
    var linePaths = lineG.selectAll('path')
            .data([data]);
         
            // Update/exit
            linePaths.enter().append("path")
            .merge(linePaths)       
            .attr("class", "line")
            .attr("d", line)
            .attr('fill', 'none')
            .attr('stroke', '#333')
            
   linePaths.exit().remove();

    // Update the axes:
    xAxisG.call(xAxis);
    yAxisG.call(yAxis);

}

</script>
</body>
</html>

But a more fundamental issue is that you don't need an enter/update/exit cycle for most of what you're doing. This is most pronounced for the capacity line: it is independent of the data (its properties are hardcoded). But also, the number of lines is fixed at one - there is no need to exit a line or area, and once appended there is no need to enter a line.

The purpose of the enter/update/exit cycle is to ensure that the number of elements in the selection match the number of items in the data array. If we always have the same number of elements, then enter/update/exit is overkill. We can simplify the code so that your update function merely updates existing elements. Instead of appending just a parent g we can append a child line initially as well. This way our update function is cleaner and the code overall simpler (only appends, no explicit enter/update/exit here), while still retaining the functionality and spirit of D3:

I've modified the properties of the capacity line so that it appears in visual range

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<style>
    .brush rect.selection {
        fill: none;
        opacity: 1;
    }
    rect.handle{
        fill: #666;
    }

</style>
</head>
<body>

<div id="buttons">
    <button id="All" class="button selected">All</button>
    <button id="Men" class="button">Men</button>
    <button id="Women" class="button">Women</button>
</div>
<div id='ptchart'></div>

<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

var json = [
    {
      "date": "2020-01-02",
      "gender": "Men",
      "value": 4320
    },
    {
      "date": "2020-01-02",
      "gender": "Women",
      "value": 984
    },
    {
      "date": "2020-01-15",
      "gender": "Men",
      "value": 4624
    },
    {
      "date": "2020-01-15",
      "gender": "Women",
      "value": 1005
    },
    {
      "date": "2020-02-03",
      "gender": "Men",
      "value": 5488
    },
    {
      "date": "2020-02-03",
      "gender": "Women",
      "value": 978
    },
    {
      "date": "2020-02-18",
      "gender": "Men",
      "value": 5842
    },
    {
      "date": "2020-02-18",
      "gender": "Women",
      "value": 1006
    },
    {
      "date": "2020-03-02",
      "gender": "Men",
      "value": 6925
    },
    {
      "date": "2020-03-02",
      "gender": "Women",
      "value": 1004
    },
    {
      "date": "2020-03-16",
      "gender": "Men",
      "value": 6132
    },
    {
      "date": "2020-03-16",
      "gender": "Women",
      "value": 948
    },
    {
      "date": "2020-04-01",
      "gender": "Men",
      "value": 5852
    },
    {
      "date": "2020-04-01",
      "gender": "Women",
      "value": 685
    },
    {
      "date": "2020-04-15",
      "gender": "Men",
      "value": 8697
    },
    {
      "date": "2020-04-15",
      "gender": "Women",
      "value": 497
    },
    {
      "date": "2020-05-01",
      "gender": "Men",
      "value": 4547
    },
    {
      "date": "2020-05-01",
      "gender": "Women",
      "value": 468
    }
]

var margin = { top: 30, right: 210, bottom: 110, left: 50 },
    margin2 = { top: 380, right: 200, bottom: 70, left: 50 },
    width = 1000 - margin.left - margin.right,
    height = 440 - margin.top - margin.bottom,
    height2 = 490 - margin2.top - margin2.bottom;

var x = d3.scaleTime().range([0, width]),
    x2 = d3.scaleTime().range([0, width]),
    y = d3.scaleLinear().range([height, 0]),
    y2 = d3.scaleLinear().range([height2, 0]);

var xAxis = d3.axisBottom(x).tickFormat(d3.timeFormat("%b %Y")).ticks(5),
    xAxis2 = d3.axisBottom(x2).ticks(12).tickSize(0),
    yAxis = d3.axisLeft(y);

var brush = d3.brushX()
    .extent([[0, 0], [width, height2]])
    .on("brush", brushed);

var svg = d3.select("#ptchart").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom);

svg.append("defs").append("clipPath")
    .attr("id", "clip")
    .append("rect")
    .attr("width", width)
    .attr("height", height);

// Set up chart elements:
var focus = svg.append("g")
    .attr("class", "focus")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
// Set up main chart elements:  
var mainArea =  focus.append("g")
    .attr("clip-path", "url(#clip)")
    .append("path")
    .attr("class", "area")
    .attr('fill', '#eeeeee')
    
var mainLine = focus.append("g")  
    .attr("clip-path", "url(#clip)")
    .append("path")
    .attr("class", "line")
    .attr('fill', 'none')
    .attr('stroke', '#333')    
    
var capacityLine = focus.append("g")
    .append("line")
    .attr('class', 'capacityline')
    .attr('stroke-width', '1px')
    .attr('stroke', '#4D4E56')
    .attr('stroke-dasharray', 4)   

var xAxisG = focus.append("g") 
    .attr("class", "axis axis--x")
    .attr("transform", "translate(0," + height + ")")
    
var yAxisG = focus.append("g")
    .attr("class", "axis axis--y")
        
// Set up brushable chart elements:
var context = svg.append("g")
    .attr("class", "context")
    .attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
    
var brushableArea = context.append("g")
    .attr("clip-path", "url(#clip)")
    .append("path")
    .attr("class", "area")
    .attr('fill', '#eeeeee');
    
var brushableLine = context.append("g")
    .attr("clip-path", "url(#clip)")
    .append("path")
    .attr("class", "line")
    .attr('fill', 'none')
    .attr('stroke', '#333')  
    
var brushAxisG = context.append("g")
   .attr("class", "axis axis--x")
   .attr("transform", "translate(0," + height2 + ")")

var brushG = context.append("g")
   .attr("class", "brush")
   
// Set up tooltip div:
var stooltip = d3.select("body").append("div")
    .attr("class", "tooltip");

const parseDate = d3.timeParse("%Y-%m-%d");

    
// Area and line generators:
var area = d3.area()
  .x(function (d) { return x(d.date); })
  .y0(height)
  .y1(function (d) { return y(d.value); })

var line = d3.line()
  .x(d => x(d.date))
  .y(d => y(d.value))   
  
var area2 = d3.area()
  .x(d => x2(d.date))
  .y0(height2)
  .y1(d => y2(d.value))

var line2 = d3.line()
  .x(d => x2(d.date))
  .y(d => y2(d.value))

updateChart(json)

d3.selectAll(".button").on("click", function () {
    var section = d3.select(this).attr("id");
        
    if (section == 'All') { data = json } else {
       data = json.filter(d => d.gender == section)
     }

    updateChart(data);
})
    
function updateChart(selectedData) {
    const data = d3.nest()
     .key(d => d.date)
     .rollup(v => d3.sum(v, d => d.value))
     .entries(selectedData);

    data.forEach((d) => {
      d.date = parseDate(d.key);
      d.value = +d.value;
    })

    // Update scales:
    x.domain(d3.extent(data, d => d.date));
    y.domain([0, d3.max(data, d => d.value) + 500]);
    x2.domain(x.domain());
    y2.domain(y.domain());
    
    // Update axes:
    xAxisG.call(xAxis);
    yAxisG.call(yAxis);
    
    // Update area and lines of main chart:
    mainArea.datum(data)
      .attr("d", area);
        
    mainLine.datum(data)
      .attr("d",line);

    capacityLine.transition()
      .attr('x1', 0)
      .attr('y1', d => y(1000))
      .attr('x2', width)
      .attr('y2', d => y(1000))

    // Update brushable area:
    brushableArea.datum(data)
      .attr("d", area2);
    
    brushableLine.datum(data)
      .attr("d", line2);

  // Update brush axis:
  brushAxisG.call(xAxis2);

  // Update brush:
  brushG.call(brush)
     .call(brush.move, x.range());
}

function brushed() {

    var selection = d3.event.selection;
    x.domain(selection.map(x2.invert, x2));
    focus.selectAll(".area")
        .attr("d", area)
    focus.selectAll(".line")
        .attr("d", line)

    focus.select(".axis--x").call(xAxis);
}

</script>
</body>
</html>

The above uses .datum() which passes the binds the provided value to the selected element: it is equivalent to your use of .data([data]) in you enter/update/exit selections in your original code.


Related Query