d3.js - Issue appending rect to existing rect - d3.js

I'm building a schedule in d3.js. I've managed to create bars to represent the timeline,
but now I want to append additional rects to the existing rect to highlight events that happen within that time. The data is attached to the the bar data, so I thought it would be as simple as creating an each function and appending another rect using that data, however I cannot get the additional rects to show. They show in the html when I inspect it, but not visible in the browser. Here is the code i am using, can anyone see where I am going wrong?
var innerRects = this.graph.append("rect")
.attr("class", "innerRects")
.attr("rx", 3)
.attr("ry", 3)
.attr("x", (d: any) => this.timeScale(dateFormat(d.startTime)) + this.sidePadding)
.attr("y",(d, i) => i * this.gap + this.topPadding )
.attr("width", (d: any) => (this.timeScale(dateFormat(d.endTime))-this.timeScale(dateFormat(d.startTime))))
.attr("height", this.barHeight)
.attr("stroke", "none")
.style("cursor", "pointer")
.style("fill",(d: any) => d.color)
.each(function(d) {
const parent = d3.select(this)
parent.append("rect")
.data(d)
.attr("class", "innerRects2")
.attr("width", (d: any) => (this.timeScale(dateFormat(d.endTime))-this.timeScale(dateFormat(d.startTime))))
.attr("height", 20)
.attr("x", (d: any) => this.timeScale(dateFormat(d.startTime)) + this.sidePadding)
.attr("y", (d, i) => i * this.gap + this.topPadding)
.style("fill", "red")
})

Related

D3 in react is creating multiple graphs of value update

I am trying to update the color of the graph, on updating, all the previous graphs are also visible
Here is my code:-
class BarChart extends Component {
state = {
color: "green",
};
componentDidUpdate = (prevProps, prevState) => {
if (prevState.color != this.props.color) {
this.drawChart();
}
};
drawChart() {
const data = [12, 5, 6, 6];
const svg = d3
.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 400)
svg
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", (d, i) => i * 70)
.attr("y", (d, i) => 400 - 10 * d)
.attr("width", 65)
.attr("height", (d, i) => d * 10)
.attr("fill", this.props.color);
svg
.selectAll("text")
.data(data)
.enter()
.append("text")
.text((d) => d)
.attr("x", (d, i) => i * 70)
.attr("y", (d, i) => 400 - 10 * d - 3);
svg.data(data).exit().remove();
}
render() {
return <div>{this.drawChart}</div>
);
}
}
I've figured that I need to change the selectAll part, but don't know exactly how to change it?
You need to include an .exit declaration
svg
.selectAll("text")
.data(data)
.exit()
.remove()
svg
.selectAll("rect")
.data(data)
.exit()
.remove()
http://bl.ocks.org/alansmithy/e984477a741bc56db5a5
You may want to instead use a .selectAll("g") element/container to avoid having to maintain 'text' and 'rect' selections separately.

How to display an image inside a circle inside an arc in d3

I've got a sunburst chart drawn and this works fine. I've tried to add a circle that displays an image within each segment (path) and I can't get them to show up. Weirdly if I inspect the DOM the circles are exactly where I want them to be in the tree but they simply don't display. Is anybody able to help?
.create("svg")
.attr("viewBox", [0, 0, width, width])
.style("font-size", ".6rem");
const g = svg
.append("g")
.attr("transform", `translate(${width / 2},${width / 2})`);
const path = g
.append("g")
.selectAll("path")
.data(root.descendants().slice(1))
.join("path")
.attr("fill", (d) => {
while (d.depth > 1) d = d.parent;
return color(d.data.name);
})
.attr("fill-opacity", (d) =>
arcVisible(d.current) ? (d.children ? 0.7 : 0.5) : 0
)
.attr("d", (d) => arc(d.current));
path
.filter((d) => d.children)
.style("cursor", "pointer")
.on("click", clicked);
const defs = path.append("defs").attr("id", "imgdefs");
const iconPattern = defs
.append("pattern")
.attr("id", "iconPattern")
.attr("height", 1)
.attr("width", 1)
.attr("x", "0")
.attr("y", "0");
iconPattern
.append("image")
.attr("xlink:href", function (d) {
return d.data.img;
})
.attr("height", 15)
.attr("width", 15);
path
.append("circle")
.attr("r", 5)
.attr("cy", 0)
.attr("cx", 0)
.attr("fill", "url(#iconPattern)");
path
.append("title")
.attr("pointer-events", "none")
.attr("color", "white")
.on("click", clicked)
.text((d) => d.data.name);```
Here is your code sample refactored to add g instead of path and add path, circle, and text under g:
const segments = g
.selectAll("g.segment")
.data(root.descendants().slice(1))
.join("g")
.classed("segment", true)
segments.append("path)
.attr("fill", ...)
.attr("fill-opacity", ...);
segments.filter((d) => d.children)
.style("cursor", "pointer")
.on("click", clicked);
const defs = segments.append("defs").attr("id", "imgdefs");
const iconPattern = defs
.append("pattern")
...
iconPattern
.append("image")
...
segments.append("circle")
.attr("r", 5)
.attr("cy", 0)
.attr("cx", 0)
.attr("fill", "url(#iconPattern)");
segments.append("title")
.attr("pointer-events", "none")
.attr("color", "white")
.on("click", clicked)
.text((d) => d.data.name);

How do I make d3 bars grow in height at same pace during an animation

My bar charts start growing in height slowly and then rapidly towards the end.
I want them to grow at the same pace
I have set ease to easeLinear but it doesn't appear to have any impact.
As an experiment, I used .ease(d3.easeBounce) but nothing changed. The bars started growing slowly and then rapidly towards the end.
Where am I going wrong?
function renderVerticalBars(data, measurements, metric, countryID) {
let selectDataForBarCharts = d3.select("svg")
.selectAll("rect")
.data(data, d => d[countryID])
selectDataForBarCharts
.enter()
.append("rect")
.attr('width', measurements.xScale.bandwidth())
.attr("height", 0)
.attr('y', d => measurements.yScale(0))
.merge(selectDataForBarCharts)
.attr("fill", d => setBarColor(d))
.attr("transform", `translate(0, ${measurements.margin.top})`)
.attr('width', measurements.xScale.bandwidth())
.attr('x', (d) => measurements.xScale(d[countryID]))
.on('mouseover', (event, barData) => { displayComparisons(event, barData, data, metric, countryID, measurements) })
.on('mouseout', (event) => { renderValuesInVerticalBars(data, metric, countryID, measurements) })
.transition()
.ease(d3.easeLinear)
.duration(4000)
.attr("height", d => measurements.innerHeight - measurements.yScale(d[metric]))
.attr("y", (d) => measurements.yScale(d[metric]))
}

How do you update an existing svg element with the d3v5 join syntax

I'm trying to make a heatmap where the x and y cell values can remain constant however the "fill" attribute can change based on the data. I tried to do something like this:
var tiles = svg.selectAll('rect')
.data(data.values, function(d) {return d.mt_aa+':'+d.position_aa;})
// set up the enter/update/exit block
tiles.join(
function(enter) {
return enter
.append("rect")
.attr("x", function(d) {return x(d.mt_aa) }) // set x coordinate
.attr("y", function(d) {return y(d.position_aa) }) // set y coordinate
.attr("width", x.bandwidth() ) // set tile width
.attr("height", y.bandwidth() ) // set tile height
.style("fill", function(d) {return myColor(d.score) }) // set tile fill based on viridis pallete
.style("opacity", 0); // set opacity as 0 so elements are added but not visible
},
function(update) {
return update
.transition()
.duration(1000)
.attr("x", function(d) {return x(d.mt_aa) })
.attr("y", function(d) {return y(d.position_aa) })
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.style("fill", function(d) {return myColor(d.score) })
.style("opacity", 0);
},
function(exit){
return exit
.transition()
.duration(1000)
.style('opacity', 0)
.on('end', function() {
d3.select(this).remove();
});
}
)
.transition()
.duration(1000)
.style("opacity", 1);
This mostly works, transitions appropriately, etc., but only if one or more of the heatmap cells are new. If I pass through data where nothing changes but the fill (d.score) nothing transitions/updates. I suspect this is occurring because nothing is being added or removed and so as far as d3 is concerned nothing needs to be updated and the update block never executes. However I'm unsure how to go about solving this using the join syntax in d3 v5.
Updated with the working answer suggested by #anbnyc
// create tiles with 'rect' elements
var tiles = svg.selectAll('rect') // from our svg select the rectangle elements, important for the enter/update/exit block below
.data(data.values, function(d) {return d.mt_aa+':'+d.position_aa;}) // bind the data to the rect selection
const t = svg.transition()
.duration(1000);
// set up the enter/update/exit block
tiles.join(
enter => enter
.append("rect")
.attr("x", function(d) {return x(d.mt_aa) })
.attr("y", function(d) {return y(d.position_aa) })
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.style("fill", function(d) {return myColor(d.score) })
.style("opacity", 0)
.call(enter => enter.transition(t)
.style("opacity", 1)),
update => update
.attr("x", function(d) {return x(d.mt_aa) })
.attr("y", function(d) {return y(d.position_aa) })
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.style("opacity", 0)
.call(update => update.transition(t)
.style("fill", function(d) {return myColor(d.score) })
.style("opacity", 1)),
exit => exit
.style('opacity', 0)
.call( exit => exit.transition(t)
.remove())
)
Your update function needs to return a selection, but it's currently returning a transition. Use .call to apply a transition to a selection inside join. See examples at https://observablehq.com/#d3/selection-join

Transition height stacked bar chart

I have a codepen here - https://codepen.io/anon/pen/GybENz
I've created a simple stacked bar chart with a legend to filter the chart.
I'd like to animated the height of the bar from the bottom axis up.
Currently its animating from the left and down
let layersBar = layersBarArea.selectAll('.layer').data(stackedSeries)
.enter()
.append('g')
.attr('class', 'layer')
.style('fill', (d, i) => {
return colors[i];
});
layersBar.selectAll('rect')
.data((d) => {
return d
})
.enter()
.append('rect')
.attr('height', 100)
.transition()
.duration(400)
.attr('height', (d, i) => {
return y(d[0]) - y(d[1]);
})
.attr('y', 0)
.attr('y', (d) => {
return y(d[1]);
})
.attr('x', (d, i) => {
return x(d.data.date)
})
.attr('width', x.bandwidth());
}
Set the x position, the width, the y position (as the baseline) and the height (as zero) before the transition:
.attr('height', 0)
.attr("y", h - margin.bottom - margin.top)
.attr('x', (d, i) => {
return x(d.data.date)
})
.attr('width', x.bandwidth())
Here is the updated CodePen: https://codepen.io/anon/pen/ypdoMK?editors=0010
PS: It would be a good idea transitioning each rectangle individually. For instance, if the user clicked usedInf, you should transition only those rectangles... however, since you did this...
layersBarArea.selectAll('g.layer').remove();
... at the beginning of your drawChart function, which is a wrong approach, such suggestion will need a big refactor, out of the scope of this question/answer.

Resources