Home > Net >  Show/hide D3 data points dynamically
Show/hide D3 data points dynamically

Time:02-05

I'm using Svelte and D3 to create a scatter-point graph. I need it to dynamically show and hide data points depending on the length of an array of years. If the array is empty, I want to show all of the points. If the array is populated, I want to show only the data points which occur on years which are present in the array.

The array is coming from a parent component, I've logged it with a reactive statement and the array is correctly updated in the child component as it needs to be.

My component is currently set up as follows:

onMount(() => {
    drawGraph();
    plotPoints();
  });

  $: filterYears.length,
      destroyPoints();
      plotPoints();
function drawGraph() {
    logic to render the graph with axis and titles relevant to the data input.
    this works fine.

}
  function plotPoints() {
    svg.selectAll("dot")
      .data(dataArray2)
      .enter()
      .append("circle")
      .attr("r", 3)
      .attr("cx", function(d) { return x(d.year); })
      .attr("cy", function(d) { return yRight(d.value); })
      .style("stroke", "#f8ff2a")
      .style('stroke-width', '1px')
      .style("fill", "#f8ff2a55")
      .style("display", d => (filterYears.length > 0 && !filterYears.includes(d.year)) ? "none" : "inline")
      .on('mouseover', function (event, datum) {
        d3.select(this).transition()
          .duration('100')
          .attr("r", 7);
        div2.transition()
          .duration(100)
          .style("opacity", 1)
        div2.html((datum.value).toFixed(2))
          .style("left", (event.offsetX   25)   "px")
          .style("top", (event.offsetY - 10)   "px")
      })
      .on('mouseout', function (event, datum) {
        d3.select(this).transition()
          .duration('200')
          .attr("r", 3);
        div2.transition()
          .duration('200')
          .style("opacity", 0);
      });
      console.log('plot point');
  }

I have 2 sets of data being rendered, the above is one of them.

function destroyPoints() {
    console.log('destroying points');
    svg.selectAll("dot").remove()
  }

All variables are correctly scoped and all functions have access to what they need.

The reactive statement seems to be called initially on component render and was triggering destroyPoints() which was trying to access svg before initialisation, causing an error, I have gotten around this with a really hacky setTimeout. If somebody could shed some light on why this happens and a better workaround I would love to hear it.

The real issue is that on change of filterYears, the points are not updating in the graph. Could somebody give me a hand here?

Thanks!

CodePudding user response:

Regular component code, including reactive statements, is executed before anything it mounted at which point DOM elements will not exist yet. It is common to just add a condition to reactive statements that depend on DOM bindings:

$: if (svg) {
  filterYears;
  destroyPoints();
  plotPoints();
}

There seems to be another error here which is obfuscated by deceptive formatting, the correct format of the original code is:

$: filterYears.length,
      destroyPoints();
plotPoints();

plotPoints() is not part of the reactive statement due to the ;.

(Also, in case you modify filterYears using functions like push: An assignment has to be used to trigger reactivity.)

CodePudding user response:

I am new to d3.js, but wouldnt a pre-refinement of the inputdata be more straight forward?

svg.selectAll("dot")
  .remove()
  .data(filteredDataArray.any() ? filteredDataArray : dataArray2)
  .enter()
  .append("circle") 

or maybe even more outside. just filter the dataArray

  • Related