D3 v4图表不更新栏 - 但附加新轴数据

时间:2017-07-23 21:36:44

标签: javascript d3.js

我已经看到这类问题问了很多,但没有找到答案我找到了解决它。

我在这里创建了一个关于我的代码的简化版本的小提琴

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

这里是一个ganttish对象的片段 - 最好看看小提琴虽然上下文

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()
    */
  }

旁注: 我有一个Vue js应用程序,我正在实现一个Gantt(ish)风格的水平条形图。在小提琴中,我通过创建一个Object来模拟Vue部分,然后在其上调用重绘方法,此模式模拟组件中的观察者在父数据更改时更新数据集。我面临的问题是一样的。

问题: 当我将数据更改为图表时,它不会更新我的图表或文本。然而,它确实将新轴添加到旧轴之上。

我知道我应该在任何.enter()上调用.exit()。remove(),以便在下一次数据推送时清除它们,但是在我尝试它的任何地方它都会失败。我可以通过在每次抽奖中创建一个新的svg来实现它,但我知道我将无法进行任何过渡 - 这似乎是一种非常糟糕的方法:)

什么是扭曲我的面条是如果我将额外的数据推送到新的数据对象,它会将其附加好,一次 - 然后再不会这样做。似乎一旦添加了数据数组中的元素X,它就不会再次更新。

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

我知道this.dataSet正在更新,它似乎没有被D3接受

任何帮助都会非常感谢D3菜鸟:)

1 个答案:

答案 0 :(得分:0)

您的问题是您没有正确处理数据连接。我强烈建议阅读Mike Bostock的一些例子,可能从Three Little Circles开始。总结一下:

通过调用.data()创建的D3连接包含3个选项:

  • 输入(需要创建的DOM元素,因为它们存在于数据中但在DOM中尚不存在)
  • 退出(需要删除的DOM元素,因为它们不再在数据中表示)
  • 更新(已存在但仍存在于数据中的DOM元素)

在您的代码中,您使用以下代码处理enter()选项:

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

问题是你没有处理任何其他选择。我这样做的方式是:

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)