I'm newbie in "d3.js", and I'm trying to implement a "doughnut chart" with some description info in the right side, I have grouped each of them in groups like<g class="device-info">, and I want all the created groups <g class="device-info"> to be in one group <g class="device-desc">, but I'm facing trouble to do it, any help will be appreciated.
here is my code:
var colors = ["#249AFF", "#CFDCE5", '#76D3C1']
var data1 = {online: 1430,offline:342, test: 200};
var pie = d3.pie()
.value(function(data1) {return data1.value })
var data_ready = pie(d3.entries(data1))
var arc = d3.arc()
.innerRadius(60)
.outerRadius(70)
.startAngle(data_ready => data_ready.startAngle)
.endAngle(data_ready => data_ready.endAngle)
var svg = d3.select(".svg")
.attr('width', 350)
.attr('height', 220)
svg.selectAll('path')
.data(data_ready)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', (d, i) => colors[i])
.attr('stroke', 'none')
.attr('transform', "translate(100,130)")
var groups = svg.selectAll(".groups")
.data(data_ready)
.enter()
.append("g")
.attr("class", "device-info")
.attr('transform', ( d, i ) => {
i *= 30
return `translate(90, ${i})`
})
groups.append('circle')
.attr('fill', (d,i) => colors[i])
.attr('transform', `translate(147, 0)`)
.attr('r','5')
.attr('cx', -20)
.attr("cy", ( d, i ) => {
return i * 19
})
groups.append('text')
.text(data1 => data1.data.key)
.attr('fill', function(d,i){ return(colors[i]) })
.attr('class', 'devices-status')
.attr('transform', `translate(150, 5)`)
.style('text-transform', 'capitalize')
.style('font-size', 14)
.attr("dx", -15 )
.attr("dy", ( d, i ) => i * 20)
groups.append('text')
.text(data1 => data1.data.value + " devices")
.attr('fill', 'white')
.style('font-size', 15)
.attr('class', 'devices')
.attr('transform', `translate(140, 5)`)
.attr("dx", -15 )
.attr("dy", ( d, i ) => {
i += 2;
return i * 12
})
svg.append("text")
.text('Devices by status')
.attr('transform' , "translate(10, 25)")
.style('text-transform', 'uppercase')
.style('fill', 'rgb(255,255,255)')
.style('font-size', 15)
svg.append("text")
.text(`Total devices ${1772}`)
.attr('transform' , "translate(10, 45)")
.style('text-transform', 'capitalize')
.style('fill', 'rgba(255,255,255, .6)')
.style('font-size', 13)
and here is a demo:
Click here
Just append the container group to the SVG, with the respective class...
var containerGroup = svg.append("g")
.attr("class", "device-desk");
And then append groups to that container group:
var groups = containerGroup.selectAll(".groups")
.data(data_ready)
.enter()
//etc...
The text for the cells in my d3 treemap don't wrap and overflow the other cells. This my project
I want the text to look like this project. I've looked at their code (and many others) but I can't get it to work in my project.
The problem area is:
svg.append('text')
.selectAll('tspan')
.data(root.leaves())
.enter()
.append('tspan')
.attr("x", (d) => d.x0 + 5)
.attr("y", (d) => d.y0 + 20)
.text( (d) => d.data.name) //.html( (d) => d.data.name.replace(/\s/g, "<br>"))
.attr("font-size", "0.6em")
.attr("fill", "white");
I tried using .html rather than .text as in the comment. In Safari and Chrome the text still overflowed the cells. In Firefox only the first word of the movie name was displayed.
We have two options to display the text ina similar way to the example you provide.
The first and easisest approach is to keep your code structure and make a similar procedure to split the text as the example provided:
d.data.name.split(/(?=[A-Z][^A-Z])/g)
So lets change your code a little bit:
svg.selectAll('text')
.data(root.leaves())
.enter()
.append('text')
.selectAll('tspan')
.data(d => {
return d.data.name.split(/(?=[A-Z][^A-Z])/g) // split the name of movie
.map(v => {
return {
text: v,
x0: d.x0, // keep x0 reference
y0: d.y0 // keep y0 reference
}
});
})
.enter()
.append('tspan')
.attr("x", (d) => d.x0 + 5)
.attr("y", (d, i) => d.y0 + 15 + (i * 10)) // offset by index
.text((d) => d.text)
.attr("font-size", "0.6em")
.attr("fill", "white");
This should accomplish the desired display. We have to take into account that labels are a very difficult to position and display in way which avoids overlapping since it would require a little more computation at build time.
The second approach is to change a little the code structure and create cells, pretty much like the example provided:
const cell = svg.selectAll('g')
.data(root.leaves())
.enter()
.append('g') // create a group for each cell / movie
.attr('transform', d => `translate(${d.x0},${d.y0})`) // let the group element handle the general positioning
.on('mousemove', d => {
//...
})
.on('mouseout', d => {
//...
});
cell.append('rect') // append rect for each cell / movie
.attr('id', d => d.data.id)
.attr('class', 'tile')
.attr('data-name', d => d.data.name)
.attr('data-value', d => d.data.value)
.attr('data-category', d => d.data.category)
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)
.attr('fill', d => color(d.data.category));
cell.append('text') // append text node for each cell / movie
.selectAll('tspan')
.data(d => d.data.name.split(/(?=[A-Z][^A-Z])/g)) // split the name and use that as data to create indiviual tspan elements
.enter()
.append('tspan') // append tspan node for each element of the string which got split
.attr('font-size', '8px')
.attr('x', 4)
.attr('y', (d, i) => 13 + 10 * i) // offset the y positioning with the index of the data
.text(d => d);
CodePen for approach 1
CodePen for approach 2
Full code for approach 1:
// !! IMPORTANT README:
// You may add additional external JS and CSS as needed to complete the project, however the current external resource MUST remain in place for the tests to work. BABEL must also be left in place.
const w = 960;
const h = 600;
const padding = 60;
const svg = d3.select("#container").append("svg")
.attr("width", w).attr("height", h);
const legendsvg = d3.select("#legend").append("svg")
.attr("width", 960).attr("height", 50);
const legendPadding = 10;
d3.json("https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/movie-data.json")
.then(function(data) {
var root = d3.hierarchy(data).sum(function(d){ return d.value});
var treeMap = d3.treemap()
.size([w, h])
.paddingInner(1);
treeMap(root);
const toolTip = d3
.select("#container")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var color = d3.scaleOrdinal()
.domain(["Action", "Drama", "Adventure", "Family", "Animation", "Comedy", "Biography"])
.range(["#db8a00", "#75b0ff", "#13ad37", "#5d6d00", "#757582", "#d37cff", "#f96868"])
svg.selectAll("rect")
.data(root.leaves())
.enter().append("rect")
.attr("class", "tile")
.attr("data-name", (d) => d.data.name)
.attr("data-category", (d) => d.data.category)
.attr("data-value", (d) => d.data.value)
.attr('x', (d) => d.x0)
.attr('y', (d) => d.y0)
.attr('width', (d) => d.x1 - d.x0)
.attr('height', (d) => d.y1 - d.y0)
.style("stroke", "black")
.style("fill", (d) => color(d.parent.data.name))
.on("mouseover", (d, i) => {
toolTip
.transition()
.duration(0)
.style("opacity", 0.8);
toolTip
.attr("id", "tooltip")
.html(function() {
return "<span>" + "Name: " + d.data.name + "<br />" + "Category: " + d.data.category + "<br />" + "Value: " + d.data.value + "</span>";
})
.style("left", d3.event.pageX - 87.5 + "px") // -87.5 is half width of tooltip in css
.style("top", d3.event.pageY - 75 + "px")
.attr("data-value", d.data.value);
})
.on("mouseout", function(d) {
toolTip
.transition()
.duration(0)
.style("opacity", 0);
});
svg.selectAll('text')
.data(root.leaves())
.enter()
.append('text')
.selectAll('tspan')
.data(d => {
return d.data.name.split(/(?=[A-Z][^A-Z])/g) // split the name of movie
.map(v => {
return {
text: v,
x0: d.x0, // keep x0 reference
y0: d.y0 // keep y0 reference
}
});
})
.enter()
.append('tspan')
.attr("x", (d) => d.x0 + 5)
.attr("y", (d, i) => d.y0 + 15 + (i * 10)) // offset by index
.text((d) => d.text)
.attr("font-size", "0.6em")
.attr("fill", "white");
console.log(root.leaves());
/*svg.selectAll("text")
.data(root.leaves())
.enter()
.append("text")
.attr("x", function(d){ return d.x0+5})
.attr("y", function(d){ return d.y0+20})
.text(function(d){ return d.data.name })
.attr("font-size", "0.6em")
.attr("fill", "white")*/
legendsvg.selectAll('rect')
.data(root.children)
.enter()
.append('rect')
.attr('class', 'legend-item')
.style('stroke', 'white')
.attr('x', (d,i) => i*140 )
.attr('width', 130)
.attr('height', 20)
.style('fill', d => color(d.data.name))
legendsvg.selectAll('text')
.data(root.children)
.enter()
.append('text')
.attr('x', (d,i) => i*140)
.attr('y', 40)
.text(d => d.data.name);
//had to change the legend below because it wouldn't pass fcc test
/*legendsvg.append("g").classed("legend", true).classed("legend-item", true);
const legend = d3.legendColor().shape("rect")
.shapeWidth(90).cells(7).orient("horizontal").scale(color);
legendsvg.select(".legend").call(legend);*/
});
Full code for approach 2:
// !! IMPORTANT README:
// You may add additional external JS and CSS as needed to complete the project, however the current external resource MUST remain in place for the tests to work. BABEL must also be left in place.
const w = 960;
const h = 600;
const padding = 60;
const svg = d3.select("#container").append("svg")
.attr("width", w).attr("height", h);
const legendsvg = d3.select("#legend").append("svg")
.attr("width", 960).attr("height", 50);
const legendPadding = 10;
d3.json("https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/movie-data.json")
.then(function(data) {
var root = d3.hierarchy(data).sum(function(d){ return d.value});
var treeMap = d3.treemap()
.size([w, h])
.paddingInner(1);
treeMap(root);
const toolTip = d3
.select("#container")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var color = d3.scaleOrdinal()
.domain(["Action", "Drama", "Adventure", "Family", "Animation", "Comedy", "Biography"])
.range(["#db8a00", "#75b0ff", "#13ad37", "#5d6d00", "#757582", "#d37cff", "#f96868"])
const cell = svg.selectAll('g')
.data(root.leaves())
.enter()
.append('g')
.attr('transform', d => `translate(${d.x0},${d.y0})`)
.on('mousemove', d => {
toolTip.transition()
.duration(200)
.style('opacity', 0.75);
toolTip.attr('data-value', d.data.value);
toolTip.html(
'Name: ' + d.data.name + '<br>' +
'Category: ' + d.data.category + '<br>' +
'Value: ' + d.data.value
)
.style('top', `${d3.event.pageY + 10}px`)
.style('left', `${d3.event.pageX + 8}px`);
})
.on('mouseout', d => {
toolTip.transition()
.duration(200)
.style('opacity', 0);
});
cell.append('rect')
.attr('id', d => d.data.id)
.attr('class', 'tile')
.attr('data-name', d => d.data.name)
.attr('data-value', d => d.data.value)
.attr('data-category', d => d.data.category)
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)
.attr('fill', d => color(d.data.category));
cell.append('text')
.selectAll('tspan')
.data(d => d.data.name.split(/(?=[A-Z][^A-Z])/g))
.enter()
.append('tspan')
.attr('font-size', '8px')
.attr('x', 4)
.attr('y', (d, i) => 13 + 10*i)
.text(d => d);
legendsvg.selectAll('rect')
.data(root.children)
.enter()
.append('rect')
.attr('class', 'legend-item')
.style('stroke', 'white')
.attr('x', (d,i) => i*140 )
.attr('width', 130)
.attr('height', 20)
.style('fill', d => color(d.data.name))
legendsvg.selectAll('text')
.data(root.children)
.enter()
.append('text')
.attr('x', (d,i) => i*140)
.attr('y', 40)
.text(d => d.data.name);
//had to change the legend below because it wouldn't pass fcc test
/*legendsvg.append("g").classed("legend", true).classed("legend-item", true);
const legend = d3.legendColor().shape("rect")
.shapeWidth(90).cells(7).orient("horizontal").scale(color);
legendsvg.select(".legend").call(legend);*/
});
I am trying to add some text at the end of the bars of a d3js bar chart.
The bar chart has transition with a delay. The source code can be found here https://bl.ocks.org/deciob/ffd5c65629e43449246cb80a0af280c7.
Unfortunately, with my code below the text does not follow the bars and I am not sure what I am doing wrong.
I thought the append text should be placed in the drawBars function no?
function drawBars(el, data, t) {
let barsG = el.select('.bars-g')
if (barsG.empty()) {
barsG = el.append('g')
.attr('class', 'bars-g');
}
const bars = barsG
.selectAll('.bar')
.data(data, yAccessor);
bars.exit()
.remove();
bars.enter()
.append('rect')
.attr('class', d => d.geoCode === 'WLD' ? 'bar wld' : 'bar')
.attr('x', leftPadding)
.attr('fill', function (d) {return d.geoColor;})
bars.enter()
.append('text')
.attr('x', d => xScale(xAccessor(d)))
.attr('y', d => yScale(yAccessor(d)))
.text('Hello')
.merge(bars).transition(t)
.attr('y', d => yScale(yAccessor(d)))
.attr('width', d => xScale(xAccessor(d)))
.attr('height', yScale.bandwidth())
.delay(delay)
}
What I am trying to achieve is for the text to follow the bars (and also later for the text to be updated to another value).
Thanks for any kind of help.
Found the answer, for anyone wondering you need to create a new function (eg: drawText()) and call it in later just below where the drawBars() function is called:
function drawText(el, data, t) {
var labels = svg.selectAll('.label')
.data(data, yAccessor);
var new_labels = labels
.enter()
.append('text')
.attr('class', 'label')
.attr('opacity', 0)
.attr('y', d => yScale(yAccessor(d)))
.attr('fill', 'blue')
.attr('text-anchor', 'middle')
new_labels.merge(labels)
.transition(t)
.attr('opacity', 1)
.attr('x', d => xScale(xAccessor(d))+50)
.attr('y', d => yScale(yAccessor(d)))
.text(function(d) {
return d.value;
});
labels
.exit()
.transition(t)
.attr('y', height)
.attr('opacity', 0)
.remove();
}
I have a stacked bar chart made in D3 v5.
https://codepen.io/bental/pen/oMbrjL
I haven't quite got my head around the update pattern, the bars overwrite.
I think I have to grab the inner rects on the update, but I cant quite make this work. I'm sure my misunderstanding and error lies in the plotArea() function
function plotArea() {
const stackedData = d3.stack().keys(keys)(data);
const layer = svg.append('g')
.selectAll('g')
.data(stackedData);
layer
.transition()
.attr('x', (d) => graphAxes.x(d.data.interval))
.attr('y', (d) => graphAxes.y(d[1]))
.attr('height', (d) => graphAxes.y(d[0]) - graphAxes.y(d[1]))
.attr('width', graphAxes.x.bandwidth());
layer
.enter().append('g')
.attr('class', d => 'data-path type-' + d.key)
.selectAll('rect')
.data(d => d)
.enter().append('rect')
.attr('x', (d) => {
return graphAxes.x(d.data.interval)
})
.attr('y', (d) => graphAxes.y(d[1]))
.attr('width', graphAxes.x.bandwidth())
.attr('height', (d) => graphAxes.y(d[0]) - graphAxes.y(d[1]));
layer.exit()
.transition()
.delay(function(d, i) {
return 30 * i;
})
.duration(1500)
.attr('y', graphAxes.y(0))
.attr('height', graphDimensions.height - graphAxes.y(0))
.remove();
}
Any help appreciated
hello world> I have been battling this problem for a while now. Im trying to create a bar graph that will take an array of objects as data with time being a date object and value being a number.
My Scale looks like this
d3.time.scale.utc()
.domain(d3.extent(data, function (d) { return d.time; }))
.rangeRound([20, this.state.width - 60]).nice(data.length);
My rectangles are being drawn like this, using the same scale
const self = this,
xScale = this.state.xScale,
yScale = this.state.yScale,
barWidth = this.getBarWidth(data.length),
bars = chart.selectAll('rect')
.data(data);
// UPDATE
bars
.transition()
.duration(500)
.style('fill', color)
.attr('x', function(d) {
console.log(xScale(d.time)- (barWidth / 2));
return xScale(d.time) - (barWidth / 2);
})
.attr('width', barWidth)
.attr('y', function(d) { return yScale(d.value); })
.attr('height', function(d) { return self.state.height - yScale(d.value); });
// ENTER
bars
.enter()
.append('rect')
.style('fill', color)
.attr('class', 'bar')
.attr('x', function(d) {
console.log(xScale(d.time) - barWidth);
return xScale(d.time) - barWidth;
})
.attr('width', barWidth + (barWidth / data.length))
.attr('y', function(d) { return yScale(d.value); })
.attr('height', function(d) { return self.state.height - yScale(d.value); });
// EXIT
bars
.exit()
.transition()
.duration(500)
.style('fill', 'red')
.style('opacity', 0)
.remove();
I get the problem below where the ticks have some length and the axis another and the tick marks don't match the bars.
Please friends, help me find a solution to the below problem.
My bar problem