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:
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.