简体   繁体   中英

D3 v4 graph not updating bars - but appending new axis data

I have seen this type of question asked a lot but no answers i found solved it for me.

I created a fiddle here of a stripped down simplified version of my code

https://jsfiddle.net/00m2cv84/4/

here is a snippet of the ganttish object - best to check out the fiddle though for context

const ganttish = function (data) {
  this.dataSet = data
  this.fullWidth = +document.getElementById('chart').clientWidth
  this.fullHeight = 700
  this.pad = { top: 50, right: 50, bottom: 50, left: 50 }
  this.h = this.fullHeight - this.pad.top - this.pad.bottom
  this.w = this.fullWidth - this.pad.left - this.pad.right
  this.svg = d3.select('#chart')
    .append('svg')
    .attr('width', this.fullWidth)
    .attr('height', this.fullHeight)
}

ganttish.prototype = {
  redraw (newDataSet) { 
    this.dataSet = newDataSet // this should overwrite the old data
    this.draw()
  },
  draw () {
    let y = d3.scaleBand()
    .padding(0.1)
    .range([0, this.h])
    y.domain(this.dataSet.map(d => d.idea_id))

    let x = d3.scaleTime().range([this.pad.left, this.w + this.pad.left])
    x.domain([
      d3.min(this.dataSet, d => new Date(Date.parse(d.start_date))),
      d3.max(this.dataSet, d => new Date(Date.parse(d.end_date)))
    ])

    let xAxis = d3.axisBottom(x).tickSize(-(this.h), 0, 0)
    let yAxis = d3.axisLeft(y).tickSize(-(this.w), 0, 0)

    let xA = this.svg.append('g')
      .attr('transform', 'translate(0,' + this.h + ')')
      .attr('class', 'x axis')
      .call(xAxis)

    let yA = this.svg.append('g')
      .attr('transform', 'translate(' + this.pad.left + ', 0)')
      .attr('class', 'y axis')
      .call(yAxis)

    let timeBlocks = this.svg.selectAll('.timeBlock')
      .data(this.dataSet)

    let tbGroup = timeBlocks
      .enter()
      .append('g')

    tbGroup
      .append('rect')
      .attr('class', 'timeBlock')
      .attr('rx', 5)
      .attr('ry', 5)
      .attr('x', d => x(new Date(Date.parse(d.start_date))))
      .attr('y', d => y(d.idea_id))
      .attr('width', d => parseInt(x(new Date(Date.parse(d.end_date))) - x(new Date(Date.parse(d.start_date)))))
      .attr('height', d => y.bandwidth())
      .style('fill', (d) => {
        return d.color ? d.color : 'rgba(123, 173, 252, 0.7)'
      })
      .style('stroke', 'black')
      .style('stroke-width', 1)

    tbGroup
      .append('text')
      .attr('class', 'timeBlockText')
      .attr('x', d => x(new Date(Date.parse(d.start_date))) + 10)
      .attr('y', d => y(d.idea_id) + (y.bandwidth() / 2))
      .attr('alignment-baseline', 'middle')
      .attr('font-size', '1em')
      .attr('color', 'black')
      .text(d => d.name)

    /**
      I have literally tried exit().remove() on pretty much everything i could think of :(
      this.svg.exit().remove()
      timeBlocks.exit().remove()
      this.svg.selectAll('.timeBlock').exit().remove()
      this.svg.selectAll('.timeBlockText').exit().remove()
      this.svg.select('.x').exit().remove()
      this.svg.select('.y').exit().remove()
    */
  }

Sidenote: I have a Vue js application and i'm implementing a Gantt(ish) style horizontal bar chart. In the fiddle i have simulated the Vue part by creating an Object which then you call the redraw method on it, this pattern simulates a watcher in the component updating the dataset when the parent data changes. The issue i face is the same.

issue: When i change the data to the chart it does not update my chart rects or text. It does however append the new axis' on top of the old ones.

i understand that i should be calling .exit().remove() on anything .enter() 'd when finished in order to clear them for the next data push, but everywhere i try this it fails. I can get it to work by creating a fresh svg on every draw, but i understand i won't be able to do any transitions - and it seems a very bad approach :)

What is twisting my noodle is that if i push extra data to the new data object, it appends it fine, once - then does not do it again. It seems once element X in the data array has been added it will not update again.

https://jsfiddle.net/00m2cv84/5/

I know that the this.dataSet is updating, it just does not seem to be accepted by D3

Any help would be greatly appreciated for a D3 noob :)

You're problem is that you're not handling your data join properly. I'd highly recommend reading some of Mike Bostock's examples probably starting with Three Little Circles . To summarize though:

D3 joins created by calling .data() contain 3 selections:

  • Enter (DOM elements that need to be created as they exist in the data but don't yet exist in the DOM)
  • Exit (DOM elements that need to be removed, as they're not longer represented in the data)
  • Update (DOM elements that already exist and still exist in the data)

Within your code you're handling the enter() selection with this bit of code:

let tbGroup = timeBlocks
  .enter()
  .append('g')

The problem is you're not handling any of the other selections. The way I'd go about doing this is:

let join = this.svg.selectAll('.timeBlock').data(this.dataSet);

// Get rid of any old data
join.exit().remove();

// Create the container groups. Note that merge takes the "new" selection
// and merges it with the "update" selection. We'll see why in a minute
const newGroups = join.enter()
                      .append('g')
                       .merge(join);

// Create all the new DOM elements that we need
newGroups.append("rect").attr("class", "timeBlock");
newGroups.append('text').attr('class', 'timeBlockText');

// We need to set values for all the "new" items, but we also want to
// reflect any changes that have happened in the data. Because we used
// `merge()` previously, we can access all the "new" and "update" items
// and set their values together.
join.select(".timeBlock")
    .attr('rx', 5)
    .attr('ry', 5)
    .attr('x', d => x(new Date(Date.parse(d.start_date))))
    .attr('y', d => y(d.idea_id))
    ...

join.select(".timeBlockText")
    .attr('x', d => x(new Date(Date.parse(d.start_date))) + 10)
    .attr('y', d => y(d.idea_id) + (y.bandwidth() / 2))
    .attr('alignment-baseline', 'middle')
    .attr('font-size', '1em')
    .attr('color', 'black')
    .text(d => d.name)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM