How to adjust svg height within table cell? - d3.js

First time using jsfiddle not too successful - jsfiddle.net/mic1/o9f18aLz/1/ (appreciate pointers here)
I need to place up to 5 charts vertically within a container where how many charts is not known until runtime. I decided a table/row/cell/div structure would be flexible ( is there a better design?)
<style type="text/css">
.element {
width: 120px;
height: 200px;
box-shadow: 0px 0px 12px rgba(0,255,255,0.5);
border: 1px solid rgba(127,255,255,0.25);
text-align: center;
cursor: default;
}
}
.tile-table {
/*table-layout:fixed;*/
width: 100%;
height: 100%;
border: 3px solid orange;
}
</style>
var tableSet1 = [ 33.3, 33.3, 33.3 ];
var div1 = d3.select("body").append("div")
.classed("element", true)
.append("table")
.classed("tile-table", true)
.selectAll("tr")
.data(tableSet1)
.enter()
.append("tr")
.style("height", function(d) {return d + '%'})
.style("background-color", "yellow")
.append("td")
.style("height", function(d) {return d + '%'})
.append(function(d) {return createChart1(d)});
function createChart1(d) {
var topChart = document.createElement( 'div' );
var svg = d3.select(topChart)
.append("svg")
.style("background-color", "lime")
.attr("height", function() {
return 20}) // using d or 100 + '% no good
.attr('width', function() {
return 100 +'%'});
return topChart;
}
Once I have the dataset - 3 charts at 33.3% height each, the svg for each chart should fill the height of each table row cell. I have it set at a bogus 20px here. 100 + % seems to set it to 100% of the top parent div, so would like to know what I should be manipulating here. I do have the percentage as d (e.g. 33.3) available in the called function.

Oh, this is a mess!
The first thing to know is that the SVG's aren't being set to the height of the table, they are being set to 150px, which is the "default" height for SVG if you don't set a valid height. All the values you are setting as percents are being treated as invalid.
Normally, setting an element's height using a percentage value would be valid iff (if and only if) the parent element has a defined height. Since you're setting the height of the <td> and <tr> elements, that should be straightforward -- but it's not.
CSS height and width properties work differently inside tables. The heights you set with the height property on the row and cell are actually treated as minimum heights for the row, and the cell's height is always dependent on the height required for its children. In other words, the cell never has a fixed height, and therefore percentage heights for its children never have meaning.
In the snippet, I've re-arranged your code to create multiple versions of the table, each with different settings for the SVG height. As you can see, 100%, 33% and null values all result in the SVGs being set to 150px tall, and the tables stretching to fit:
var tableSet1 = [ 33.3, 33.3, 33.3 ];
var div1 = d3.select("body").selectAll("div")
.data(["100%", "33%", "30px", null])
.enter().append("div")
.classed("element", true)
.each(function(option){
d3.select(this)
.append("table")
.classed("tile-table", true)
.selectAll("tr")
.data(tableSet1)
.enter()
.append("tr")
.style("height", function(d) {return d + '%'})
.style("background-color", "gray")
.append("td")
//.style("height", function(d) {return 100 + '%'})
.append(function(d) {return createChart1(d, option)});
});
function createChart1(d, option) {
var topChart = document.createElement( 'div' );
var svg = d3.select(topChart)
.append("svg")
.style("background-color", "seagreen")
.attr("height", function() {
return option})
.attr('width', function() {
return 100 +'%'});
return topChart;
}
.element {
width: 60px;
height: 200px;
box-shadow: 0px 0px 20px rgba(0,155,255,0.75);
border: 1px solid rgba(127,255,255,0.25);
text-align: center;
cursor: default;
display:inline-block;
margin:0 10px;
}
.tile-table {
/*table-layout:fixed;*/
width: 100%;
height: 100%;
border: 3px solid orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
So, what can you do?
Don't use tables. Tables are really only useful when you have multiple columns, and you want elements in each column to line up nicely. If you're just going to have one column, you can use <div> elements.
If you do really need a table structure, calculate the height of the SVG elements yourself by accessing the height of the parent div and then applying your percentage, and subtracting for borders and padding.

Related

D3 - Legend in seperate div element

I have a plunker here - https://plnkr.co/edit/inW1NfaUwechJt8C1hC9?p=preview
I'm trying to create a legend for this graph
I can do it by adding the legend to the svg but Id like more control over the styling and position
Is it possible to create the legend in a separate div element and use something like a ul list.
var legend = d3.select(".legend")
.data(colors)
.enter()
.append("g")
.attr("class", "legend")
.attr("transform", (d, i) => {
return "translate(20," + i * 35 + ")";
});
Yes, Based on your comment it appears as though a html list approach is what you want, I'll answer for that approach.
Rather than appending a g for each legend item, append li to a list. You can also append the div and ul with d3, just as any svg component. The only major change is appending the right type of element and setting its relevant properties - as this will be different with html than svg.
As with svg components, you can style with css or .attr/.style methods (I use both below).
Here's an example:
var data = [{"name":"Category 1", "value":1},{"name":"Category 2", "value":2},{"name":"Category 3", "value":3},{"name":"Category 4", "value":4},{"name":"Category 5", "value":5}];
var scale = d3.scaleLinear()
.range(["red","orange"])
.domain([1,5]);
var divLegend = d3.select("#divLegend");
divLegend.append("p")
.html("Legend Title")
.style("text-align","center")
.style("font-size","20px")
var list = divLegend.append("ul");
var entries = list.selectAll("li")
.data(data)
.enter()
.append("li");
// append rectangle and text:
entries.append("span")
.attr("class","rect")
.style("background-color", function(d) { return scale(d.value); })
entries.append("span")
.attr("class","label")
.html(function(d) { return d.name; })
ul {
list-style-type: none;
}
.rect {
width: 10px;
height: 10px;
display: inline-block;
}
.label {
margin-left: 10px;
}
#divLegend {
width: 150px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<div id="divLegend"></div>

d3.js Color range for map not displaying as per domain range

This how I am setting color range for my map
var color = d3.scaleThreshold()
.domain([1,1000])
.range(d3.schemeCategory20b);
My values in data which I have set in domain does not exceed 1000, so i have given domain range from 1 to 1000 on setting this range it is not displaying color as expected, like I wanna show my map with colors like domain
range [1,50] -> green & [51,250] -> yellow,[251,700] -> dark green &
[701,1000] -> yellow
How can I do that? Any ideas?
In a threshold scale, if the range has N values the domain has to have N - 1 values.
Therefore, just tell the scale exactly what you've told us in the question:
var color = d3.scaleThreshold()
.domain([50, 250, 700])
.range(["green", "yellow", "darkgreen", "yellow"]);
Here is a basic demo, hover over each rectangle to see its value:
var body = d3.select("body");
var color = d3.scaleThreshold()
.domain([50, 250, 700])
.range(["green", "yellow", "darkgreen", "yellow"]);
body.selectAll(null)
.data(d3.range(51).map(d => d * 20))
.enter()
.append("div")
.style("background-color", d => color(d))
.on("mouseover", function(d) {
console.log("value: " + d + ", color: " + color(d))
})
div {
min-width: 5px;
min-height: 20px;
display: inline-block;
margin: 1px;
border: 1px solid gray;
}
.as-console-wrapper { max-height: 30% !important;}
<script src="https://d3js.org/d3.v4.min.js"></script>

d3.js title above responsive chart with fixed aspect ratio

I am very new to programming in d3.js, and I was wondering if someone could help me out with the following:
I have modified a donut chart that I found on the web. I modified it to become responsive while still maintaining an aspect ratio of 1 for the circular donut chart. I was wondering what the best way would be to place a title above or below it. I have tried various approaches but I am unable to create one that works consistently.
Simplified JSFiddle here:
JSFiddle
code also attached here:
var width = 400;
var height = 400;
var x = {
value: 80.43,
color1: "#007EA7",
color2: "#d9d9d9"
};
var margin = {
left: 10,
top: 10,
right: 10,
bottom: 10
};
width = Math.min(width - margin.left - margin.right,
height - margin.top - margin.bottom);
height = width * 1; // Should be a perfect circle.
svg = d3.select("body")
.append("div")
.classed("svg-container", true)
.append("svg")
.attr("preserveAspectRatio", "xMidYMin meet")
.attr("viewBox", "0 0 " + (width) + " " + (height))
.classed("svg-content-responsive", true);
// Below is code to create the actual donut chart. It used the width and height attributes calculated above.
var outerRadius = Math.min(width, height) / 2,
innerRadius = (outerRadius / 5) * 4;
τ = 2 * Math.PI;
fontSize = (Math.min(width, height) / 5);
arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(0);
var background = svg.append("path")
.datum({
endAngle: τ
})
.style("fill", x.color2)
.attr("d", arc)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var text = svg.append("text")
.text(Math.round(x.value * 10) / 10 + '%')
.attr("text-anchor", "middle")
.style("font-size", fontSize + 'px')
.attr("dy", height / 2 + fontSize / 2.5)
.attr("dx", width / 2);
foreground = svg.append("path")
.datum({
endAngle: x.value / 100 * τ
})
.style("fill", x.color1)
.attr("d", arc)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
.svg-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.svg-content-responsive {
width: 100%;
height: 100%;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
Any comments, advise, references or code snippets that might help me are greatly appreciated.
Thanks,
Florian
Is there something wrong with adding something like this:
var title = svg.append("text")
.text('This is the title')
.attr("text-anchor", "middle")
.style("font-size", '12px')
.attr("dy", 10)
.attr("dx", width / 2);
exactly like the text?
PS: adding your approaches that failed would help people answer the question
If anyone stumbles on this question in the future: I decided to place my title below the chart and managed to solve it by slightly cheating.
I used the following CSS:
.svg-container {
height:100%;
width:100%;
min-height: 100%;
position:relative;
}
.svg-content-responsive {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
height:100%;
width: 100%;
position: relative;
padding-bottom: 50px;
}
.svg-gauge-title-container {
width: 100%;
height: 50px;
position:relative;
margin-top:-50px;
}
.svg-gauge-title {
position: relative;
width: 100%;
}
i.e. I added 50px of padding-bottom to the chart, and also added a title container with 50px of margin above. Might not be the cleanest way, but it works.

CSS class not working in tooltip on mouseover on text in D3.js

I am using CSS to show font size and background color on mouseover on text in D3.js
d3.select(this).append("text")
.classed("hover", true)
.attr('transform', function(d){
return 'translate(5, 50)';
})
.text(d.name);
"hover" class is not appling, its just displaying simple text
here is my CSS class
text.hover {
position: absolute;
text-align: left;
background-color: #FFFFEF;
width: 400px;
height: 135px;
padding: 10px;
border: 1px solid #D5D5D5;
font-family: arial,helvetica,sans-serif;
position: absolute;
font-size: 1.1em;
color: #333;
padding: 10px;
border-radius: 3px;
background-color: rgba(255,255,255,0.9);
color: #000;
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
-moz-box-shadow: 0 1px 5px rgba(0,0,0,0.4);
border:1px solid rgba(200,200,200,0.85);
}
What is the best way to apply CSS on text
The way you are handling the text is like you want it to be a div. You are using the wrong attributes, how can text have a background fill ?
I have edited the fiddle provided and shown that the class does work : https://jsfiddle.net/u1gpny6o/1/
All I have put in the hover class is a fill like so :
.hover {
fill:red;
}
What is it you're trying to do ? Is it create a div with text ? If so create a div, give it the class you have in the question, and append text to that div, does that make sense ?
EDIT:
From your comments I have come up with this : https://jsfiddle.net/u1gpny6o/3/
From this question (not the selected answer but the second one) : Show data on mouseover of circle
I have made a tooltip div like so :
var tooltip = d3.select("body")
.append("div")
.classed('hover', true)
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip");
So you can edit attributes in the css :
.hover {
background: #FFFFEF;
width: 400px;
height: 135px;
padding: 10px;
stroke: black;
stroke-width: 2px;
}
And it will appear on mouseover, move on mousemove(to mouse coordinates, this can be edited) and disappear on mouseout :
.on("mouseover", function(d) {
tooltip.text(d.name)
tooltip.style("visibility", "visible");
})
.on("mousemove", function() {
return tooltip.style("top",
(d3.event.pageY - 10) + "px").style("left", (d3.event.pageX + 10) + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});
What you were doing previously made no sense. You had the class hover and you were duplicating attributes in the CSS, for example, setting color twice and so on. Also giving the elements attributes you can't give. For example, SVG elements can't have a border but can have a stroke and so on.
As you mentioned, you want to load the visualization in a pop up window. I would do it like so :
function update(id, data){
var container = d3.select('#'+id) // then use this to append your vis to
. //the rest of the code the create the vis
.
.
.
}
And then when you hover over the node, just pass the id of the pop up to the update function along with the data (if need be) like so :
update(popupid, data);
It's not working likely because of the way you set up the hover class (doing text.hover) and how you are attaching it. Why not attach a mouseover listener to text and then assign the class:
Define your hover CSS class:
`.hover { ...styles ...}`
Use it on mouseover:
`select your text
.on("mouseover", function() {
select text
.classed("hover",true);
});`
Then you can reverse it similarly with mouseout.

Add links and hover effects to multi series donut chart in d3.js

I have a multi series donut chart created with the help of this question D3.js - Donut charts with multiple rings in d3.js, see fiddle below.
I'd like to be able to add hover effects, and also make each part clickable in the sense I'd like to assign a certin href to each slice of the chart. I have looked around quite a bit, but can't get my head around it - d3.js is quite complex for me I guess.
The code I have now: http://jsfiddle.net/mephisto73/o6shxw0d/
(function(){
var $container = $('.chart-container'),
τ = 2 * Math.PI,
width = $container.width(),
height = $container.height(),
outerRadius = Math.min(width,height)/2.5,
innerRadius = (outerRadius/5)*4,
fontSize = (Math.min(width,height)/4);
var dataset = {
weeks: [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
months: [1,1,1,1,1,1,1,1,1],
trimester: [1,1,1]
};
var color = d3.scale.ordinal() .range(['rgba(141,211,199,0.8)','rgb(255,255,179)','rgb(190,186,218)','rgb(251,128,114)','rgb(128,177,211)','rgb(253,180,98)','rgb(179,222,105)','rgb(252,205,229)','rgb(217,217,217)','rgb(188,128,189)','rgb(204,235,197)','rgb(255,237,111)']);
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc();
var svg = d3.select('.chart-container').append("svg")
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox','0 0 '+Math.min(width,height) +' '+Math.min(width,height) )
.attr('preserveAspectRatio','xMinYMin')
.append("g")
.attr("transform", "translate(" + Math.min(width,height) / 2 + "," + Math.min(width,height) / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g").attr("class", "arc");
var path = gs.selectAll("path")
.data(function(d) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", function(d, i, j) { return arc.innerRadius(innerRadius+(40*j)).outerRadius(innerRadius+(5*(j+5)))(d); });
});
$(function(){
var tooltip = d3.select(".tooltip");
var $container = $('.chart-container'),
τ = 2 * Math.PI,
width = $container.width(),
height = $container.height(),
outerRadius = Math.min(width,height)/2.5,
innerRadius = (outerRadius/5)*4,
fontSize = (Math.min(width,height)/4);
var dataset = {
weeks: [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
months: [1,1,1,1,1,1,1,1,1],
trimester: [1,1,1]
};
var color = d3.scale.ordinal() .range(['rgba(141,211,199,0.8)','rgb(255,255,179)','rgb(190,186,218)','rgb(251,128,114)','rgb(128,177,211)','rgb(253,180,98)','rgb(179,222,105)','rgb(252,205,229)','rgb(217,217,217)','rgb(188,128,189)','rgb(204,235,197)','rgb(255,237,111)']);
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc();
var svg = d3.select('.chart-container').append("svg")
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox','0 0 '+Math.min(width,height) +' '+Math.min(width,height) )
.attr('preserveAspectRatio','xMinYMin')
.append("g")
.attr("transform", "translate(" + Math.min(width,height) / 2 + "," + Math.min(width,height) / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g").attr("class", "arc");
var path = gs.selectAll("path")
.data(function(d) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", function(d, i, j) { return arc.innerRadius(innerRadius+(40*j)).outerRadius(innerRadius+(5*(j+5)))(d); })
.on("mousemove", function(d){
tooltip.style("left", d3.event.pageX+10+"px");
tooltip.style("top", d3.event.pageY-25+"px");
tooltip.style("display", "inline-block");
tooltip.select("span").text("Value: "+d.value);
}).on("mouseout",function(){
tooltip.style("display","none");
}).on("click",function(){
//write code to open
});
});
html,body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin:0;
padding:0;
width:100%;
height:100%;
}
text {
font: 10px sans-serif;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
.arc path {
stroke: #fff;
width:5px;
}
.arc path:hover {
background-color:#ccc;
}
.chart-container {
width:70%;
height:70%;
border: 1px dotted silver;
}
svg text{
font-size: 1em;
font-family: sans-serif;
}
.tooltip{
position: absolute;
display: none;
width: auto;
height: auto;
background: none repeat scroll 0 0 white;
border: 0 none;
border-radius: 8px 8px 8px 8px;
box-shadow: -3px 3px 15px #888888;
color: black;
font: 12px sans-serif;
padding: 5px;
text-align: center;
}
path:hover{
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>
<div class="chart-container"></div>
<div class='tooltip'>
<span></span>
</div>
I've added functionality for on mousemove, mouseout and click.
Try to read and do the modification in click function.
Hope you got it,If not ask me for more.

Resources