Making more space for a graph legend in d3js - javascript

The names of the legend don't fully show.They are cut off, if I increase the width, the bars just grow bigger. How can I accommodate more space for my legend?
I tried appending the legend to the 'svg' tag instead of the 'g' tag but still not the desired results. I even plotted the axis, bars and legend on the 'svg' tag but its still not working.
javascript
const g= svg.append('g')
.attr("transform", `translate(${margin.left},${margin.top})`)
const xAxis= g.append('g')
.call(d3.axisBottom(x).tickSizeOuter(0))
.attr("transform", "translate(0," + innerHeight + ")")
const yAxis= g.append('g')
.call(d3.axisLeft(y).tickSizeOuter(0))
//stack the data? --> stack per subgroup
var stackedData = d3.stack()
.keys(subgroups)
(data)
var legend = g.append('g')
.attr('class', 'legend')
.attr('transform', `translate(${210},${20})`);
legend.selectAll('rect')
.data(subgroups)
.enter()
.append('rect')
.attr('x', 0)
.attr('y', function(d, i){
return i * 20;})
.attr('width', 14)
.attr('height', 14)
.attr('fill', function(d, i){
return color(i);
});
legend.selectAll('text')
.data(subgroups)
.enter()
.append('text')
.text(function(d){
return d;
})
.attr('x',18)
.attr('y', function(d, i){
return i * 18;
})
.attr('text-anchor', 'start')
.attr('alignment-baseline', 'hanging');
//below is my plotted data
telescope,allocated,unallocated
IRSF,61,28
1.9-m,89,0
1.0-m,64,23
// width=300 and heigh=300 for svg
I want to show the full names of the legends just next to the right of the bars for the graph.
Link to the graph is here.
How do I solve the problem?

Your issue is that the SVG's width is too narrow to accomodate showing all of the legend texts.
If your graph follows D3 conventions, space for elements that are auxilliary to graph itself (axis names, ticks, legends, etc.) is made using an margin object. Although I can't see how your graph is made, it looks like yours is setup to use a margin object as well. Following D3 conventions the margin values are fixed, which would explain why changing the width value just makes the bars wider yet still doesn't make space for the legend texts.
Therefore, locate the margin object and change its right value to something higher. Inspecting your example, it looks like doubling it should do it.
Hope this helps!

Related

D3 Bar Graph: flipping the Y-axis flips my data

I ran into a common problem while making a bar graph in d3: my y-axis was upside down. After some googling, I found out that the best way to fix this is by reversing the y-domain. The only problem is, when I did that, the bars on my graph switched positions, so the largest was at the beginning instead of the end. I need the y-axis to be correct, without changing my bars.
Bars in correct positions, but the y-axis is upside-down
Bars are incorrect, but the y-axis is right-side-up
Here is the code: https://codepen.io/lucassorenson/pen/rPRadR?editors=0010
const yScale = d3.scaleLinear()
.domain([0, d3.max(json, (d) => d[1])])
.range([h - padding, padding]);
const yAxis = d3.axisLeft(yScale);
This is the code that I changed. If you reverse the range ([padding, h - padding]), the bars are correct but the axis is not.
The fix is simply to exchange the callback functions of the attributes "height" and "y".
Your code was:
...
svg.selectAll('rect')
.data(json)
.enter()
.append('rect')
.attr('width', 3)
.attr('height', (d) => yScale(d[1]))
.attr('x', function(d, i){
return i*4 + padding
})
.attr('y', function(d){
return h - yScale(d[1]) - padding
})
change it to:
...
svg.selectAll('rect')
.data(json)
.enter()
.append('rect')
.attr('width', 3)
.attr('y', (d) => yScale(d[1]))
.attr('x', function(d, i){
return i*4 + padding
})
.attr('height', function(d){
return h - yScale(d[1]) - padding
})
With the attribute "y" you typically set the upper edge of the rectangle and with "height" you denote how far it goes down.

Redrawing a key on top of a D3 projection after transition

I'm working with a D3 map projection similar to Mike Bostock's Choropleth seen here.
The issue I'm having is that I've added a transition; and when I transition the projection, the map key (seen in the top right corner) is being covered by the background color of the map.
I know I probably just need to redraw the g layer after the transition, but I'm not able to get that working as expected.
I'm originally drawing the key on the map with the following code:
var g = svg.append("g")
.attr("class", "key")
.attr("transform", "translate(0,40)");
g.selectAll("rect")
.data(color.range().map(function(d) {
d = color.invertExtent(d);
if (d[0] == null) d[0] = x.domain()[0];
if (d[1] == null) d[1] = x.domain()[1];
return d;
}))
.enter().append("rect")
.attr("height", 8)
.attr("x", function(d, i) { return 350 + (i * 30)})
.attr("width", 30)
.attr("fill", function(d) { console.log(d[1]); return color(d[1]); });
g.append("text")
.attr("class", "caption")
.attr("x", x.range()[0])
.attr("y", -6)
.attr("fill", "#000")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Number of Licensed Establishments");
g.call(d3.axisBottom(x)
.tickSize(13)
.tickValues(color.domain()))
.select(".domain")
.remove();
Then I'm transitioning the projection with this code (which also works fine).
path = d3.geoPath(projection);
svg.selectAll("path").transition().duration(2000).attr("d", path);
But the key gets covered. I've tried redrawing it like this:
g.selectAll("g").attr("transform", "translate(0,40)");
It doesn't do anything though. What step am I missing to correctly redraw that g layer on top?
Transitioning a path shouldn't change where it appears in the DOM. Transitioning element attributes with d3 modifies that element in place in the DOM. The following example should demonstrate this (path is appended first and should be behind the text, the path then transitions its d attribute through two d3 symbol paths remaining behind the text):
var svg = d3.select('body').append('svg').attr('width',400).attr('height',200);
var cross = "M-21.213203435596427,-7.0710678118654755L-7.0710678118654755,-7.0710678118654755L-7.0710678118654755,-21.213203435596427L7.0710678118654755,-21.213203435596427L7.0710678118654755,-7.0710678118654755L21.213203435596427,-7.0710678118654755L21.213203435596427,7.0710678118654755L7.0710678118654755,7.0710678118654755L7.0710678118654755,21.213203435596427L-7.0710678118654755,21.213203435596427L-7.0710678118654755,7.0710678118654755L-21.213203435596427,7.0710678118654755Z";
var star = "M0,-29.846492114305246L6.700954981042517,-9.223073285798176L28.38570081386192,-9.223073285798177L10.8423729164097,3.5229005144437298L17.543327897452222,24.146319342950797L1.7763568394002505e-15,11.400345542708891L-17.543327897452215,24.1463193429508L-10.842372916409698,3.522900514443731L-28.38570081386192,-9.22307328579817L-6.7009549810425195,-9.223073285798176Z";
var wye = "M8.533600336205877,4.926876451265144L8.533600336205877,21.9940771236769L-8.533600336205877,21.9940771236769L-8.533600336205877,4.9268764512651435L-23.31422969000131,-3.6067238849407337L-14.78062935379543,-18.387353238736164L0,-9.853752902530289L14.78062935379543,-18.387353238736164L23.31422969000131,-3.6067238849407337Z"
var symbol = svg.append('path')
.attr('transform','translate(100,100)')
.attr('d', cross )
.attr("fill","orange");
var text = svg.append('text')
.attr('x', 100)
.attr('y', 105)
.style('text-anchor','middle')
.text('THIS IS SOME TEXT')
symbol.transition()
.delay(2000)
.attr('d', star )
.duration(2000)
.transition()
.attr('d', wye )
.duration(2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Given your example, it is likely that the key is initially rendered behind the features of the map - only there is no overlap between the two. Each appears as intended. When transitioning, with say a zoom, the features overlap and the key is hidden. As noted in the comments, try g.raise() or d3.select(".key").raise() to move the key to the bottom of the parent container, effectively lifting it above other svg elements (as elements are rendered in the order they appear in the DOM, as close as we get to a z-index in svg). You should only need to apply .raise() once - as the transition won't change the ordering, or alternatively, ensure that the key is appended to the svg last.

How to change the position of legend in Scatter Chart D3.js

I want to shift the legends on top of the chart
I tried using CSS position relative but seems that it does not work on SVG
please have a look at the codepen
http://codepen.io/7deepakpatil/pen/LkaKoy
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
above is the code where the legend are being generated.
is it possible using css or please provide any d3 or JS way to fix this.
Use SVG translate. Similar to what you have but you need to apply it to the whole legend. So I appended a 'g' element, gave it an id for easy selection later and applied a translate before you appended anything to it, like so :
var legend = svg.append('g').attr('id','legendContainer')
.attr("transform", function(d) { return "translate(-200,100)"; }) //this is the translate for the legend
.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
Updated codepen : http://codepen.io/anon/pen/GqLXRx

How can I get my D3 heatmap to resize properly when using div widths instead of the viewBox and preserveAspectRatio attributes?

(EDIT: viewBox and preserveAspectRatio are great, but aren't rendering properly in IE9, so I can't use that.)
I've been working on resizing this heatmap (here it is on a block). By selecting the width of a div, appending an SVG to it, and setting my SVG's width to that of the div, I've been able to make the heatmap responsive.
Nevertheless, I've run issues when I've tried to update the heatmap on resize. Here's the resize function:
d3.select(window).on("resize", resize);
function resize(){
marginHM = {top:35, right:0, bottom:50, left:30};
divWidthHM = parseInt(d3.select("#chartHM").style("width"));
widthHM = divWidthHM-marginHM.left-marginHM.right;
gridSize = Math.floor(widthHM/24);
legendElementWidth = gridSize*2.665;
heightHM = (9*gridSize)-marginHM.top-marginHM.bottom;
svgWidthHM = widthHM + marginHM.left+marginHM.right;
svgHeightHM = heightHM+marginHM.top+marginHM.bottom;
svgHM.select("svg").style("width", (svgWidthHM+20) + "px")
.style("height", (svgHeightHM+30) + "px");
dayLabels.attr("y", function (d, i){ return i*gridSize;})
.attr("transform", "translate(-6," + gridSize/1.5+")");
timeLabels.attr("x", function(d,i) {return i * gridSize;})
.attr("transform", "translate(" + gridSize/2+", -6)");
cards.attr("x", function(d) {return d.hour *gridSize; })
.attr("y", function(d) {return d.day * gridSize; })
.attr("width", gridSize)
.attr("height", gridSize);
d3.selectAll("rect.HMLegend")
.attr("x", function(d, i){ return legendElementWidth * i;})
.attr("y", 7.2*gridSize)
.attr("width", legendElementWidth)
.attr("height", gridSize/2);
d3.selectAll("text.HMLegendText")
.attr("x", function(d, i){ return legendElementWidth *i;})
.attr("y", 8*gridSize);
}
If you open the heatmap and drag the window to a wider size than it initially loaded in, you'll find that the heatmap grows, but the SVG's dimensions are cut off at the initial width; I find this really strange, considering I update the width in the resize function. You can decrease the width from its original value, and the map will size down, but you can't increase it above the original value of the width.
Additionally, I run into issues with the legend bar and legend labels. The the legend labels don't shift on window resize, and when you click back and forth between USA and New York, the legend colours often move off screen or show duplicates. Have been staring at this all evening, and am getting to the point where everything's blurring into one nonsensical line of code. Would really appreciate someone weighing in on exactly what it is I'm messing up!

d3.js Bubble Force Chart application

http://jsfiddle.net/pPMqQ/146/
I'm developing a bubble chart application that is a derivative of a force chart to provide a little bit of movement.
Here is some of the code. As I create the svg - I also set up the viewBox - which I think could be used to adjust the size of the chart - but I've not been able to adjust the ratios properly to get the correct scaling.
I've also added here the code that adds the nodes - as the node size is calculated - its using a scale variable to affect the size of the orbs.
var svg = d3.select(selector)
.append("svg")
.attr("class", "bubblechart")
.attr("width", parseInt(w + margin.left + margin.right,10))
.attr("height", parseInt(h + margin.top + margin.bottom,10))
.attr('viewBox', "0 0 "+parseInt(w + margin.left + margin.right,10)+" "+parseInt(h + margin.top + margin.bottom,10))
.attr('perserveAspectRatio', "xMinYMid")
.append("g")
.attr("transform", "translate(0,0)");
methods.force = d3.layout.force()
.charge(1000)
.gravity(100)
.size([methods.width, methods.height])
var bubbleholder = svg.append("g")
.attr("class", "bubbleholder")
var bubbles = bubbleholder.append("g")
.attr("class", "bubbles")
var labelbubble = bubbleholder.append("g")
.attr("class", "labelbubble")
// Enter
nodes.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", 1)
.style("fill", function (d) { return methods.fill(d.label); })
.call(methods.force.drag);
// Update
nodes
.transition()
.delay(300)
.duration(1000)
.attr("r", function (d) { return d.radius*scale; })
// Exit
nodes.exit()
.transition()
.duration(250)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", 1)
.remove();
I have the remaining issues
scaling is an issue
I've set the width/heights via data attributes - currently I have a scaling variable set to adjust the size of the orbs depending on the width of the chart. I would like to find a more scientific way of ensuring the chart is resized accordingly and also that the elements always remain central (don't become obscured).
ensuring the smaller elements are on top of the big elements
I've also noticed that small objects may randomly fall underneath larger orbs, is there a way to organize the rendering of the orbs dependant on size, so bigger elements always sit at the bottom layer.
I've solved the second problem by sorting the data via value.
http://jsfiddle.net/pPMqQ/149/
data = data.sort(function(a, b) {return b.value - a.value})
Currently I have a scale variable depending on the width of the chart - var scale = methods.width*.005;
but its not very scientific per say
If the chart is 150 width
http://jsfiddle.net/pPMqQ/150/
the chart renders - but the bubbles no longer fit in the space.
the chart at 250 px
http://jsfiddle.net/pPMqQ/151/

Categories

Resources