richarddmorey
richarddmorey

Reputation: 1160

d3, updating multiple paths

The following code has multiple paths on a single d3.js plot. I have the initial plot working, displaying the data set d0. It looks like this on load:

Displayed when loaded

I would like to display the data in d1 when the button is pressed, so it should look like this:

What it should look like after pressing the button

What currently happens is...nothing. No errors in the console, no change to the display, and no change to the DOM (that I can see?).

I've been through a dozen code examples and tutorials and just don't see what's going wrong here. Any help would be greatly appreciated (particularly if with an explanation of what I'm conceptually missing). Thanks!

var d0 = [{
    id: "A",
    values: [{
      x: .25,
      y: .9
    }, {
      x: .75,
      y: 1
    }]
  },
  {
    id: "B",
    values: [{
      x: .25,
      y: .5
    }, {
      x: .8,
      y: .4
    }]
  },
  {
    id: "C",
    values: [{
      x: .1,
      y: .1
    }, {
      x: .9,
      y: .1
    }]
  }
]


var d1 = [{
    id: "A",
    values: [{
      x: 0,
      y: 1
    }, {
      x: 1,
      y: 1
    }]
  },
  {
    id: "B",
    values: [{
      x: 0,
      y: .5
    }, {
      x: 1,
      y: .5
    }]
  },
  {
    id: "C",
    values: [{
      x: 0,
      y: 0
    }, {
      x: 1,
      y: 0
    }]
  }
]

var svg = d3.select("svg")

var margin = {
  top: 20,
  right: 80,
  bottom: 30,
  left: 50
}
var width = svg.attr("width") - margin.left - margin.right
var height = svg.attr("height") - margin.top - margin.bottom

g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")")

var scaleX = d3.scaleLinear().range([0, width]).domain([0, 1])
var scaleY = d3.scaleLinear().range([height, 0]).domain([0, 1])

g.append("g")
  .attr("class", "axis axis--x")
  .attr("transform", "translate(0," + height + ")")
  .call(d3.axisBottom(scaleX));

g.append("g")
  .attr("class", "axis axis--y")
  .call(d3.axisLeft(scaleY))

var line = d3.line()
  .x(function(d) {
    return scaleX(d.x)
  })
  .y(function(d) {
    return scaleY(d.y)
  });

var lineg = g.selectAll(".lineg")
  .data(d0)
  .enter()
  .append("g")
  .attr("class", "lineg");

lineg.append("path")
  .attr("class", "line")
  .attr("d", function(d) {
    return line(d.values)
  })

function update() {

  var i = g.selectAll(".lineg")
    .data(d1)

  i.enter()
    .append("g")
    .attr("class", "lineg")

  i.exit()
    .remove()

  g.selectAll(".line")
    .attr("d", function(d) {
      return line(d.values)
    })
}
.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
<br/>
<button onclick="update()">Change</button>

Upvotes: 1

Views: 422

Answers (1)

Ruben Helsloot
Ruben Helsloot

Reputation: 13139

Every SVG element that you call .data or .datum on has an internal reference to a data object. In your update call, you updated the data of the g elements that held the lines, but never of the lines themselves.

I was able to do that with the following code:

i.select(".line")
  .datum(function(d) {
    return d;
  })
  .attr("d", function(d) {
    return line(d.values)
  })

Here, starting from the newly selected groups i, which you already gave the correct data, I select the line, and then pass it the datum object from the g directly, through the function. That way, the datum object gets updated, and calling line(d.values) now actually uses entries from d1 instead of d0.

var d0 = [{
    id: "A",
    values: [{
      x: .25,
      y: .9
    }, {
      x: .75,
      y: 1
    }]
  },
  {
    id: "B",
    values: [{
      x: .25,
      y: .5
    }, {
      x: .8,
      y: .4
    }]
  },
  {
    id: "C",
    values: [{
      x: .1,
      y: .1
    }, {
      x: .9,
      y: .1
    }]
  }
]


var d1 = [{
    id: "A",
    values: [{
      x: 0,
      y: 1
    }, {
      x: 1,
      y: 1
    }]
  },
  {
    id: "B",
    values: [{
      x: 0,
      y: .5
    }, {
      x: 1,
      y: .5
    }]
  },
  {
    id: "C",
    values: [{
      x: 0,
      y: 0
    }, {
      x: 1,
      y: 0
    }]
  }
]

var svg = d3.select("svg")

var margin = {
  top: 20,
  right: 80,
  bottom: 30,
  left: 50
}
var width = svg.attr("width") - margin.left - margin.right
var height = svg.attr("height") - margin.top - margin.bottom

g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")")

var scaleX = d3.scaleLinear().range([0, width]).domain([0, 1])
var scaleY = d3.scaleLinear().range([height, 0]).domain([0, 1])

g.append("g")
  .attr("class", "axis axis--x")
  .attr("transform", "translate(0," + height + ")")
  .call(d3.axisBottom(scaleX));

g.append("g")
  .attr("class", "axis axis--y")
  .call(d3.axisLeft(scaleY))

var line = d3.line()
  .x(function(d) {
    return scaleX(d.x)
  })
  .y(function(d) {
    return scaleY(d.y)
  });

var lineg = g.selectAll(".lineg")
  .data(d0)
  .enter()
  .append("g")
  .attr("class", "lineg");

lineg.append("path")
  .attr("class", "line")
  .attr("d", function(d) {
    return line(d.values)
  })

function update() {
  var i = g.selectAll(".lineg")
    .data(d1)

  i.enter()
    .append("g")
    .attr("class", "lineg")

  i.exit()
    .remove()

  i.select(".line")
    .datum(function(d) {
      return d;
    })
    .attr("d", function(d) {
      return line(d.values)
    })
}
.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
<br/>
<button onclick="update()">Change</button>

Upvotes: 1

Related Questions