`chartCartesian` "draw" handler created in `decorate` is called with stale data
const legend = /* some component */
const chart = fc
.chartCartesian(d3.scaleLinear(), d3.scaleLinear())
.decorate((selection) => {
selection
.enter()
.append("d3fc-svg")
.style("grid-column", 3)
.style("grid-row", 5)
.style("height", "2em")
.on("measure.legend", (event) => {
legend.width(event.detail.width);
})
.on("draw.legend", (event, data) => {
// `data` is always equal to `firstData`
d3.select(event.currentTarget).select("svg").datum(data.criteria).call(legend);
});
});
const render = (el, data) => {
d3.select("#chart").datum(data).transition().call(chart);
}
const firstData = /* some data */
render("#chart", firstData)
const nextData = /* some different data */
render("#chart", nextData)
Hacky workaround:
let localData = null;
const legend = /* some component */
const chart = fc
.chartCartesian(d3.scaleLinear(), d3.scaleLinear())
.decorate((selection) => {
selection
.enter()
.append("d3fc-svg")
.style("grid-column", 3)
.style("grid-row", 5)
.style("height", "2em")
.on("measure.legend", (event) => {
legend.width(event.detail.width);
})
.on("draw.legend", (event) => {
const data = localData;
d3.select(event.currentTarget).select("svg").datum(data.criteria).call(legend);
});
});
const render = (el, data) => {
localData = data
d3.select("#chart").datum(data).transition().call(chart);
}
const firstData = /* some data */
render("#chart", firstData)
const nextData = /* some different data */
render("#chart", nextData)
The problem here is that D3 doesn't automatically propagate the joined data from the parent element. I you look at the sourcecode for d3fc chart, you can see that we propagate data for each of the child element - notice that the plot area elements receive the data joined with the chart, whereas the axes are joined to their orientation:
https://github.com/d3fc/d3fc/blob/master/packages/d3fc-chart/src/cartesian.js#L55
You can used exactly this pattern within your decorate function to provide the data you need:
const legend = /* some component */
const legendDataJoin = fc.dataJoin("d3fc-svg", "legend");
const chart = fc
.chartCartesian(d3.scaleLinear(), d3.scaleLinear())
.decorate((selection, data) => {
legendDataJoin(selection, [data])
.style("grid-column", 3)
.style("grid-row", 5)
.style("height", "2em")
.on("measure.legend", (event) => {
legend.width(event.detail.width);
})
.on("draw.legend", (event, data) => {
d3.select(event.currentTarget).select("svg").datum(data.criteria).call(legend);
});
});
This will ensure that the correct data is propagated to the draw event
Ah, ok. I think I understand. I suppose I misunderstood how the events are dispatched.
Related: The container passed to decorate is not "transition propagated"
https://github.com/d3fc/d3fc/blob/b294f0a6f326b9eb001498e6760c0f0c8772620f/packages/d3fc-chart/src/cartesian.js#L168
vs
https://github.com/d3fc/d3fc/blob/b294f0a6f326b9eb001498e6760c0f0c8772620f/packages/d3fc-chart/src/cartesian.js#L118-L126
There seems to be no way to access the original selection to propagateTransition after the fact inside decorate, so I'm using a similar localSelection trick...
const propagateTransition = (maybeTransition) => (selection) => {
if (isTransition(maybeTransition)) {
try {
selection = selection.transition(maybeTransition);
} finally {
}
}
return selection;
};
const legend = annotationLegend().key((d) => d.name);
const legendDataJoin = fc.dataJoin("d3fc-svg", "legend");
const chart = fc
.chartCartesian(d3.scaleLinear(), d3.scaleLinear())
.svgPlotArea(createArea())
.decorate((selection, data) => {
legendDataJoin(selection, [data])
.style("grid-column", 3)
.style("grid-row", 5)
.on("measure.legend", (event) => {
legend.width(event.detail.width);
})
.on("draw.legend", (event, data) => {
propagateTransition(localSelection)(
d3.select(event.currentTarget).select("svg").datum(data.criteria)
).call(legend);
});
});
let localSelection = null;
export const render = (el, data) => {
//...
localSelection = d3.select(el)
.datum(data)
.transition()
.duration(500)
.on("end", () => {
chart.svgPlotArea(createArea());
// TODO: I have no idea why `propagateTransition` is not catching the "can't find transition"
// error, forcing me to re-assign `localSelection`...
localSelection = d3.select(el).datum(data).call(chart);
})
.call(chart);
};