I am using this script to create a d3 barplot using r2d3 in R.
Example:
I would like to rotate the x-axis text labels. Does anyone know where the rotation should be applied? I am not familiar with javascript, only R unfortunately.
I have tried to insert the following based on other stackoverflow posts:
var txt = svg.selectAll('text').data(data);
txt.enter().append('text')
.attr('x', function(d, i) {return (i * col_width()) + (svg_width()* layer_left) + (col_width() * 0.5); })
.attr('y', function(d) {return svg_height()* 0.95;})
.style('font-size', '10px')
** .attr('transform', 'rotate(90)')**
.text(function(d) {return d.label;})
.style('font-family', 'arial')
.attr('text-anchor', 'middle');
txt.exit().remove();
txt.transition()
.duration(500)
.attr('x', function(d, i) {return (i * col_width()) + (svg_width()* layer_left) + (col_width() * 0.5); })
.attr('y', function(d) {return svg_height()* 0.95;})
.style('font-size', '10px')
** .attr('transform', 'rotate(90)')**
.text(function(d) {return d.label;})
.style('font-family', 'arial')
.attr('text-anchor', 'middle');
This does not work; the labels are no longer visible.
Thank you!
Related
I'm working on a horizontal bar graph and would like to display the values on each bar with a specific format. I've tried to code it in many ways but have been unsuccessful in achieving the results I want. I'm using the following code:
data = d3.json('data.json')
.then(data => {data
.forEach(d => {
d.country = d.country;
d.population = +d.population * 1000;
});
console.log(data);
render(data);
});
const xValuesNumberFormat = number => d3.format(',.0f')(number);
const xValues = d => d.population;
svg.selectAll('text')
.data(data)
.enter()
.append('text')
.attr('class', 'bar-value')
.attr('x', xPos)
.attr('y', yPos)
.text(xValues)
.attr('transform',`translate(5,`+yScale.bandwidth()/1.5+`)`)
;
[
{"country":"China","population":1415046},
{"country":"India","population":1354052},
{"country":"United States","population":326767},
{"country":"Indonesia","population":266795},
{"country":"Brazil","population":210868},
{"country":"Pakistan","population":200814},
{"country":"Nigeria","population":195875},
{"country":"Bangladesh","population":166368},
{"country":"Russia","population":143965},
{"country":"Mexico","population":130759}
]
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('class', 'rect')
//.attr('x', xPos)
.attr('y', yPos)
.attr('width', xPos)
.attr('height', yScale.bandwidth())
.on('mouseover', function (d)
{tooltip.style('display', null);})
.on('mouseout', function (d)
{tooltip.style('display', 'none');})
.on('mousemove', function (d)
{
var xPos = d3.mouse(this)[0] ;
var yPos = d3.mouse(this)[1] - 20;
tooltip.attr("transform", "translate("+xPos + "," + yPos +")");
tooltip.select('text').text(d3.format(',.0f')(d.population));
})
;
As soon as I try to format it I get Nan, undefined and various other errors.
enter image description here
As you can see from the image, I want the number next to the bar to be formatted like the number on the bar. Basically, I just want comma separators. Thank you for your help.
I figured it out.
svg.selectAll('text')
.data(data)
.enter()
.append('text')
.attr('class', 'bar-value')
.attr('x', xPos)
.attr('y', yPos)
.text(d => d3.format(',.0f')(+d.population))
.attr('transform', `translate(5,`+yScale.bandwidth()/1.5+`)`);
Thank you for taking the time to contribute.
I'm working on a modified version of this D3 code for parallel coordinates. I would like to add a background color for the axis labels. I realize I cannot set a background color using CSS styles because this is SVG text, so I tried to append rectangles but to no avail. Here is what I wrote but it's not drawing anything:
d3.selectAll(".label")
.append("rect")
.attr("x", function(d){ return d3.select(this).node().getBBox().x - 5;})
.attr("y", function(d){ return d3.select(this).node().getBBox().y - 5;})
.attr("width", function(d){ return d3.select(this).node().getBBox().width;})
.attr("height", function(d) {return d3.select(this).node().getBBox().height;})
.style("stroke", "black")
.style("fill", "yellow");
What am I doing wrong?
EDIT:
based on comments, I appended to the parent g rather than the text label itself. Now the code looks like this:
// Add an axis and title.
var gsvg= g.append("svg:g")
.attr("class", "axis")
.attr("id", "gsvg")
.each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
.attr("transform", "translate(0,0)");
d3.selectAll("#gsvg")
.append("rect")
.attr("x", function(d){ return this.parentNode.getBBox().x - 5;})
.attr("y", function(d, i){ return i%2 === 0 ? this.parentNode.getBBox().y - 20: this.parentNode.getBBox().y - 35;})
.attr("width", function(d){ return this.parentNode.getBBox().width;})
.attr("height", function(d) {return 20;})
.style("stroke", "lightgrey")
.style("stroke-width", 2)
.style("fill", function(d){
return self.fcolorMap[d];
})
.style("opacity", 0.5);
gsvg.append("svg:text")
.style("text-anchor", "middle")
.attr("y", function(d,i) { return i%2 == 0 ? -14 : -30 } )
.attr("x",0)
.attr("class", "axis-label")
.text(function(d) {
var s = d.split("|");
return s[s.length-1]; });
The only problem is now I need to figure out how to get the bounding boxes of the labels rather than those of the axes.
Data
MP,Party,Constituency,Status
"Abbott, Diane",Labour,Hackney North and Stoke Newington,Remain
"Abrahams, Debbie",Labour,Oldham East and Saddleworth,Remain
"Adams, Nigel",Conservative,Selby and Ainsty,Leave
I have created a svg group for each 'Party' populated with many circles to represent each 'MP' belonging to that Party.
The problem I have is that some of the parties are so large that they run right off the screen. Ideally I would like to set the width at about 10 circles before they return to the next 'line'.
I have found examples for setting the width of SVG text but not SVG shapes. Is it possible to create multiple lines of SVG shapes using D3?
Plunker
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
var chart = svg
.append('g')
.attr('transform', 'translate(50,100)')
var party = chart.selectAll(".party")
.data(mps)
.enter()
.append('g')
.attr('transform', function(d, i) {
return "translate(0," + (i * (height / mps.length) + ")")
})
party
.append('text')
.text(function(d) {
return d.key
})
party.selectAll('.members')
.data(function(d) {
return d.values
})
.enter()
.append('circle')
.attr('class', 'chart')
.attr('r', 5)
.attr('cy', '10')
.style('fill', function(d) {
return '#' + d.Color
})
.attr("transform", function(d, i) {
return "translate(" + i * 13 + ",20)"
});
Something like this perhaps? You'll likely have to adjust the values to suit.
.attr('transform', function(d, i) {
return "translate(" + ((i / 10) * 20)) + "," + ((i % 10) * (height / mps.length)) + ")")
I've been drawing a planetarium with planets and labels on it :
svg.append("circle").attr("r", 20).attr("cx", w/2)
.attr("cy", h/2).attr("class", "sun");
svg.append("text").attr("x", w/2)
.attr("y", h/2).attr("text-anchor", "middle").attr("fill", "grey").text("VICTOR HUGO");
var container = svg.append("g")
.attr("transform", "translate(" + w/2 + "," + h/2 + ")")
var orbit = container.selectAll(".orbit")
.data(dataset, key)
.enter()
.append("circle")
.attr("class", "orbit")
.attr("r", 0);
//function(d) {return (d.key * ((w/2)/151) + 20); });
var planets = container.selectAll(".planet")
.data(dataset, key)
.enter()
.append("circle")
.attr("class", "planet")
.attr("r", function(d) {return 5;})
.attr("cx", function(d) {return d.x; })
.attr("cy", function(d) {return d.y; })
.on("mouseover", function(d, i) {
stopTooltip = false
showTooltip(d);
});
d3.select("svg").on("click", function(d) {stopTooltip = true;});
var texts = container.selectAll(".text")
.data(dataset, key)
.enter()
.append("text")
.attr("class", "text")
.attr("dx", function(d) {return d.x;})
.attr("dy", ".35em")
.text(function(d) {return d.titre});
Then, I've been adding a legend and a click function, so that when you click on one or another of the legend subtitle, the dataset is updated (if you look at the tooltips, it is indeed updated).
////////////////////////Legend//////////////////////////////////
update = function(newDataset, key){
var newplanets = container.selectAll(".planet").data(newDataset, key);
newplanets.enter().append("circle");
newplanets.transition()
.duration(0)
.attr("r", function(d) {return 5;})
.attr("cx", function(d) {return d.x; })
.attr("cy", function(d) {return d.y; })
.on("mouseover", function(d, i) {
stopTooltip = false
showTooltip(d);
});
newplanets.exit().remove();
var newtexts = container.selectAll(".text").data(newDataset, key);
newtexts.enter().append("text");
newtexts.transition()
.duration(0)
.attr("class", "text")
.attr("dx", function(d) {return d.x;})
.attr("dy", ".35em")
.text(function(d) {return d.titre});
newtexts.exit().remove();
}
var legendRectSize = 7;
var legendSpacing = 20;
var VictorHugoClass = [
{ "VHClassName": '...Auteur (oeuvre)', "VHClass": r990o, "color": "#0000FF" },
{ "VHClassName": '...Auteur (expression)', "VHClass": r990e, "color": "#FBA10D" },
];
//Initiate container around Legend
var legendContainer = svg.append("g").attr("class","legendContainer")
.attr("transform", "translate(" + 30 + "," + (h - 90) + ")");
//Create title of Legend
var legendTitle = legendContainer.append('text')
.attr('x', 0)
.attr('y', legendRectSize - legendSpacing)
.attr("dy", "1em")
.attr('class', 'legendTitle')
.attr('transform', function() {
var height = legendRectSize + legendSpacing;
var offset = height * VictorHugoClass.length / 2;
var horz = -2 * legendRectSize;
var vert = -2.3 * height - offset;
return 'translate(' + horz + ',' + vert + ')';
})
.attr("fill", "black")
.text("Victor Hugo en tant que...")
.call(wrap, 200);
//Create container per circle/text pair
var legend = legendContainer
.selectAll('.legend')
.data(VictorHugoClass)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * VictorHugoClass.length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
})
.on("click", function(d) {
update(d.VHClass, d.key);
});
//Append circles to Legend
legend.append('circle')
.attr('r', legendRectSize)
.attr('cx', 4)
.attr('cy', legendRectSize/2 - legendSpacing)
.attr("opacity", 1)
.style("fill", "white")
.style('stroke', function(d) {return d.color;});
//Append text to Legend
legend.append('text')
.attr('x', legendRectSize + legendSpacing/2)
.attr('y', legendRectSize - legendSpacing)
.attr("fill", "black")
.text(function(d) { return d.VHClassName; });
Nevertheless, the labels won't update with the planets, and some of the data of the former dataset are still remaining when the second dataset is loaded into the visualization. My Plunker is here for more details : http://plnkr.co/edit/UnhqCZhME7ymxdIbpcIj?p=preview
Thank you very much for your help in this...
Here is a plunker modified from mbostock
I want to make the text labels drag-able and attach a line to the circle when dragged.
.call(drag) works on the dots but not the labels
label = container.append("g")
.attr("class", "label")
.selectAll(".label")
.data(dots)
.enter().append("text")
.text(function(d) {return d.x + d.y; })
.attr("x", function(d) {return d.x; })
.attr("y", function(d) {return d.y; })
.attr("text-anchor", "middle")
.call(drag)
Here's a JSFiddle I made to demonstrate draggable text labels in D3.js
https://jsfiddle.net/h1n6fuwr/
Essentially you want to define the following variables/functions:
const drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended)
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
}
function dragged(d) {
const elem = d3.select(this)
elem.attr('x', +elem.attr('x') + d3.event.dx)
elem.attr('y', +elem.attr('y') + d3.event.dy)
}
function dragended(d) {}
And then call .call(drag) on your text labels.
const labels = ['Drag Me1', 'Drag Me2', 'Drag Me3']
d3.select('svg')
.selectAll('text')
.data(labels)
.enter()
.append('text')
.text(d => d)
.attr('fill', 'green')
.attr('x', (d, i) => 10 + i*30)
.attr('y', (d, i) => 15 + i*30)
.call(drag)
Append a rect behind the text, then .call(drag) on your rect. To get a suitable rect, you can use text.getBBox().