Reputation: 20555
I am trying to make sure that i can update my charts data with D3 for this i have the following function:
fetchData(scope.filters.title_id, scope.filters.competence_id, scope.filters.from, scope.filters.to).then(function (data) {
color.domain(d3.keys(data[0]).filter(function (key) {
return key !== "timestamp";
}));
data.forEach(function (d) {
d.timestamp = parseDate(d.timestamp);
});
var cities = color.domain().map(function (name) {
return {
name: name,
values: data.map(function (d) {
return {timestamp: d.timestamp, temperature: +d[name]};
})
};
});
x.domain(d3.extent(data, function (d) {
return d.timestamp;
}));
y.domain([
d3.min(cities, function (c) {
return d3.min(c.values, function (v) {
return v.temperature;
});
}),
d3.max(cities, function (c) {
return d3.max(c.values, function (v) {
return v.temperature;
});
})
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Niveau");
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.transition()
.attr("class", "line")
.attr("d", function (d) {
return line(d.values);
})
.style("stroke", function (d) {
return color(d.name);
});
city.append("text")
.datum(function (d) {
return {name: d.name, value: d.values[d.values.length - 1]};
})
.attr("transform", function (d) {
return "translate(" + x(d.value.timestamp) + "," + y(d.value.temperature) + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function (d) {
return d.name;
});
city.exit().remove();
})
However whenever i run it i get city.exit is not a function
The chart does sort of re render just in an unexpected way:
Original chart
After re render
As you can see the chart data does not rerender but the old data is not removed.
What am i doing wrong?
Fiddle
**The fiddle is constantly being updated with my progress and the answers i get!
Upvotes: 1
Views: 1192
Reputation: 6476
You are adding new axes every time and appending new path and text elements every time.
Ok, here's a full solution...
var margin = {
top: 6,
right: 80,
bottom: 30,
left: 30
},
width = 600 - 20 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%m-%Y").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(-height)
.tickPadding(10)
.tickSubdivide(true)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.tickPadding(10)
.tickSize(-width)
.tickSubdivide(true)
.ticks(5)
.orient("left")
.tickFormat(d3.format(".0f"));
var line = d3.svg.line()
.interpolate("cardinal")
.x(function (d) {
return x(d.timestamp);
})
.y(function (d) {
return y(d.temperature);
});
var svg = d3.select("#progressChart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
svg.append("g")
.attr("class", "y axis")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Niveau");
var render = function (newData, t) {
var data = fetchData(newData);
color.domain(d3.keys(data[0]).filter(function (key) {
return key !== "timestamp";
}));
data.forEach(function (d) {
d.timestamp = parseDate(d.timestamp);
});
var cities = color.domain().map(function (name) {
return {
name: name,
values: data.map(function (d) {
return {
timestamp: d.timestamp,
temperature: +d[name]
};
})
};
});
x.domain(d3.extent(data, function (d) {
return d.timestamp;
}));
y.domain([
d3.min(cities, function (c) {
return d3.min(c.values, function (v) {
return v.temperature;
});
}),
d3.max(cities, function (c) {
return d3.max(c.values, function (v) {
return v.temperature;
});
})]);
svg.selectAll(".x.axis")
.call(xAxis);
svg.selectAll(".y.axis")
.transition().duration(t)
.call(yAxis)
var city = svg.selectAll(".city")
.data(cities),
cityEnter = city.enter().append("g")
.attr("class", "city");
cityEnter
.append("path")
.attr("class", "line");
city.select(".line")
.transition().duration(t)
.attr("d", function (d) {
return line(d.values);
})
.style("stroke", function (d) {
return color(d.name);
});
cityEnter.append("text")
.attr("x", 3)
.attr("dy", ".35em");
city.select("text")
.text(function (d) {
return d.name;
})
.transition().duration(t)
.attr("transform", function (d) {
var final = d.values[d.values.length - 1];
return "translate(" + x(final.timestamp) + "," + y(final.temperature) + ")";
});
city.exit().remove();
};
var fetchData = function (newData) {
if (!newData) {
return [{
Forventet: 8,
Nuværende: 1,
timestamp: "12-4-2015"
}, {
Forventet: 8,
Nuværende: 2,
timestamp: "12-5-2015"
}, {
Forventet: 8,
Nuværende: 7,
timestamp: "12-6-2015"
}]
} else {
return [{
Forventet: 2,
Nuværende: 3,
timestamp: "12-4-2015"
}, {
Forventet: 6,
Nuværende: 5,
timestamp: "12-5-2015"
}, {
Forventet: 4,
Nuværende: 7,
timestamp: "12-6-2015"
}]
}
};
render(false, 0);
setTimeout(function () {
render(true, 2000)
}, 2000)
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
.axis path {
fill: none;
stroke: #bbb;
shape-rendering: crispEdges;
}
.axis text {
fill: #555;
}
.axis line {
stroke: #e7e7e7;
shape-rendering: crispEdges;
}
.axis, .axis-label {
font-size: 12px;
}
.line {
fill: none;
stroke-width: 1.5px;
}
.dot {
/* consider the stroke-with the mouse detect radius? */
stroke: transparent;
stroke-width: 10px;
cursor: pointer;
}
.dot:hover {
stroke: rgba(68, 127, 255, 0.3);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="progressChart"></div>
Upvotes: 3
Reputation: 21578
Your variable city
contains the enter selection computed by the data join.
var city = svg.selectAll(".city")
.data(cities)
.enter()
This enter selection will, of course, have no function .exit()
to return the elements to remove. The documentation of selection.enter()
has a good example on how to combine the enter, update and exit selection:
var update_sel = svg.selectAll("circle").data(data)
update_sel.attr(/* operate on old elements only */)
update_sel.enter().append("circle").attr(/* operate on new elements only */)
update_sel.attr(/* operate on old and new elements */)
update_sel.exit().remove() /* complete the enter-update-exit pattern */
For your code the following should work:
var city = svg.selectAll(".city")
.data(cities);
city.enter().append("g")
.attr("class", "city");
Upvotes: 2