I have had mixed results when combining for loops with d3 visuals; in this case it seems to be the most straight-forward solution to have a matrix of circle packs. However, one problem is that if I create the visual this way, the output can be slightly misleading. In the snippet below you will notice that the biggest circle in the third circle pack (152) looks just as big as the biggest circle in the first circle pack (200). So in its current form, the circle packs just reflect the proportions, and the changes in absolute size are not portrayed.
var margins = {top:20, bottom:300, left:30, right:100};
var height = 600;
var width = 1080;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");
var data = [
[
{'id':'1Q19'},
{'id':'pooled','parentId':'1Q19','size':29.5},
{'id':'spv','parentId':'1Q19', 'size':11},
{'id':'single','parentId':'1Q19', 'size':200}
],
[
{'id':'2Q19'},
{'id':'pooled','parentId':'2Q19','size':31},
{'id':'spv','parentId':'2Q19', 'size':15},
{'id':'single','parentId':'2Q19', 'size':171}
],
[
{'id':'3Q19'},
{'id':'pooled','parentId':'3Q19','size':28},
{'id':'spv','parentId':'3Q19', 'size':12},
{'id':'single','parentId':'3Q19', 'size':152}
],
[
{'id':'4Q19'},
{'id':'pooled','parentId':'4Q19','size':25},
{'id':'spv','parentId':'4Q19', 'size':214},
{'id':'single','parentId':'4Q19', 'size':101}
],
];
var colorMap = {
'1Q19':"#e7eef8",
'2Q19':"#e7eef8",
'3Q19':"#e7eef8",
'4Q19':"#e7eef8",
'pooled':"#f6d18b",
'spv':"#366092",
'single':"#95b3d7"
};
var strokeMap = {
"pooled":"#000",
"single":"#000",
"spv":"#fff"
};
for (var j=0; j <(data.length); j++) {
var vData = d3.stratify()(data[j]);
var vLayout = d3.pack().size([250, 250]);
var vRoot = d3.hierarchy(vData).sum(function (d) { return d.data.size; });
var vNodes = vRoot.descendants();
vLayout(vRoot);
var thisClass = "circ"+String(j);
var vSlices = graphGroup.selectAll('.'+thisClass).data(vNodes).attr('class',thisClass).enter().append('g');
//console.log(vNodes)
vSlices.append('circle')
.attr('cx', function(d, i) {
return d.x+(j*300)
})
.attr('cy', function (d) { return d.y; })
.attr('r', function (d) { return d.r; })
.style('fill', function(d) { return colorMap[d.data.id]});
vSlices.append('text')
.attr('x', function(d,i) {return d.x+(j*300)})
.attr('y', function(d) {return d.y+5})
.attr('text-anchor','middle')
.style('fill', function(d) {return strokeMap[d.data.id]})
.text(function(d) {return d.data.data.size ? d.data.data.size : null});
}
<script src="https://d3js.org/d3.v5.min.js"></script>
Question
How can I establish a baseline/uniform scale for each of my circle packs in the circle pack matrix? I want the background/overall parent circle to be the same size, but the child circles to factor in absolute values in the packing process.
Note: I'm content with there being more empty space in the circle pack; perhaps in some instances the diameters may not fully span the parent circle. As long as the circles are tangent, the overall aesthetic theme should carry through.
The fact that you're using a loop to create elements in a D3 code is quite problematic, that's true... however, that's not the problem here. Let's see what you said:
I want the background/overall parent circle to be the same size, but the child circles to factor in absolute values in the packing process [...] I'm content with there being more empty space in the circle pack.
Well, unfortunately, that's not how a circle packing works. What you have right now is the correct data visualisation: the leaves would have different sizes, even if they have the same value, depending on the values of the other leaves. A circle packing is a dynamic process/algorithm.
That being said, my suggestion is: leave it as it is (but fix that cumbersome loop).
However, even if I disagree (from a dataviz point) with your request, here is a solution. Set a square root scale:
var radiusScale = d3.scaleSqrt()
.domain([0,250])
.range([0,125]);
And pass the size values to pack.radius:
var vLayout = d3.pack().size([250, 250])
.radius(function(d){
return radiusScale(d.data.data.size)
});
And here is the result:
var margins = {top:20, bottom:300, left:30, right:100};
var height = 600;
var width = 1200;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");
const radiusScale = d3.scaleSqrt()
.domain([0,250])
.range([0,125]);
var data = [
[
{'id':'1Q19'},
{'id':'pooled','parentId':'1Q19','size':29.5},
{'id':'spv','parentId':'1Q19', 'size':11},
{'id':'single','parentId':'1Q19', 'size':200}
],
[
{'id':'2Q19'},
{'id':'pooled','parentId':'2Q19','size':31},
{'id':'spv','parentId':'2Q19', 'size':15},
{'id':'single','parentId':'2Q19', 'size':171}
],
[
{'id':'3Q19'},
{'id':'pooled','parentId':'3Q19','size':28},
{'id':'spv','parentId':'3Q19', 'size':12},
{'id':'single','parentId':'3Q19', 'size':152}
],
[
{'id':'4Q19'},
{'id':'pooled','parentId':'4Q19','size':25},
{'id':'spv','parentId':'4Q19', 'size':214},
{'id':'single','parentId':'4Q19', 'size':101}
],
];
var colorMap = {
'1Q19':"#e7eef8",
'2Q19':"#e7eef8",
'3Q19':"#e7eef8",
'4Q19':"#e7eef8",
'pooled':"#f6d18b",
'spv':"#366092",
'single':"#95b3d7"
};
var strokeMap = {
"pooled":"#000",
"single":"#000",
"spv":"#fff"
};
for (var j=0; j <(data.length); j++) {
var vData = d3.stratify()(data[j]);
var vLayout = d3.pack().size([250, 250])
.radius(function(d){
return radiusScale(d.data.data.size)
});
var vRoot = d3.hierarchy(vData).sum(function (d) { return d.data.size; });
var vNodes = vRoot.descendants();
vLayout(vRoot);
var thisClass = "circ"+String(j);
var vSlices = graphGroup.selectAll('.'+thisClass).data(vNodes).attr('class',thisClass).enter().append('g');
//console.log(vNodes)
vSlices.append('circle')
.attr('cx', function(d, i) {
return d.x+(j*(j === 3 ? 320 : 310))
})
.attr('cy', function (d) { return d.y; })
.attr('r', function (d) { return d.r; })
.style('fill', function(d) { return colorMap[d.data.id]});
vSlices.append('text')
.attr('x', function(d,i) {return d.x+(j*(j === 3 ? 320 : 310))})
.attr('y', function(d) {return d.y+5})
.attr('text-anchor','middle')
.style('fill', function(d) {return strokeMap[d.data.id]})
.text(function(d) {return d.data.data.size ? d.data.data.size : null});
}
<script src="https://d3js.org/d3.v5.min.js"></script>
Pay attention to the fact that, in the last pack, the overall circle is not the same size (it's bigger). Being the same size is simple impossible, given the packing logic.
This question already has answers here:
d3 time scale - last bar on graph is appearing outside of the graph
(2 answers)
How to use x and width in a bar chart with scaleTime?
(1 answer)
Closed 4 years ago.
I am trying to graph a bar chart using a simple dataset by using D3.js. I managed to get most done, but I have one problem left. The bars on the very right GO OVER the y-axis and will not show correctly.
This is the code:
codepen
$(document).ready(function(){
var dataset;
var data;
var date;
var temp1 = [];
var temp2 = [];
var url = "https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json";
d3.json(url, function(json) {
dataset = json.data;
for (var j = 0; j < dataset.length; j++) {
temp1[j] = dataset[j][1]
}
data = temp1;
for (var j = 0; j < dataset.length; j++) {
temp2[j] = dataset[j][0]
}
date = temp2;
minDate = new Date(date[0]);
maxDate = new Date(date[date.length - 1]);
var svgWidth = 1000;
var svgHeight = 450;
var barWidth = (svgWidth/data.length);
var svg = d3.select("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
var xScale = d3.scaleTime()
.domain([minDate, maxDate])
.range([0, svgWidth]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([svgHeight, 0]);
var x_axis = d3.axisBottom()
.scale(xScale);
var y_axis = d3.axisRight()
.scale(yScale);
var xAxisTranslate = svgHeight - 20;
svg.append("g")
.attr("transform", "translate(0 " + xAxisTranslate +")")
.call(x_axis);
svg.append("g")
.attr("transform", "translate(0, -20)")
.call(y_axis);
var barChart = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("y", function(d) {
return yScale(d);
})
.attr("height", function(d) {
return svgHeight - yScale(d);
})
.attr("width", barWidth)
.attr("transform", function (d, i) {
var translate = [barWidth * i, - 20];
return "translate("+ translate +")";
});
});
});
How do I get the bar chart to draw values in my dataset correctly/fully?
Is there a way to tell d3 to use a opacity in the colorScale?
Assuming I have something like
const colorScale = scaleLinear()
.range(colorRamp)
.clamp(true);
where colorRamp is
const colorRamp = ["#ff70....", ] <- 30 colors in the array
Is there a way to tell d3 colorScale to use an opacity?
You can specify rgba colors to include opacity in your fill using a d3 scale:
var color = d3.scaleLinear()
.domain([1,10])
.range(["rgba(0,0,0,0)","rgba(0,0,0,1)"]);
var color = d3.scaleLinear()
.domain([1,10])
.range(["rgba(0,0,0,0)","rgba(0,0,0,1)"]);
var svg = d3.select("body")
.append("svg");
var background = svg.append("rect")
.attr("width",500)
.attr("height",200)
.attr("fill","orange");
var rects = svg.selectAll(null)
.data(d3.range(10))
.enter()
.append("circle")
.attr("cy",50)
.attr("cx",function(d) { return d*15+15; })
.attr("fill", function(d) { return color(d); })
.attr("r",6);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>
If using a color ramp which uses hex values, you could convert it so that it uses rgb values and add an opacity/alpha value(s) after, I have simply applied the same opacity to each color here, but it would be easy to modify it:
var ramp = ["#ff0000","#0000ff"];
ramp = ramp.map(function(color) {
color = color.substring(1);
color.split("");
var i = 0;
var r = parseInt(color[i++],16)*16+parseInt(color[i++],16);
var g = parseInt(color[i++],16)*16+parseInt(color[i++],16);
var b = parseInt(color[i++],16)*16+parseInt(color[i++],16);
r = Math.round(r/2.56);
g = Math.round(g/2.56);
b = Math.round(b/2.56);
opacity = 0.5;
return "rgba("+r+","+g+","+b+","+opacity+")";
})
console.log(ramp);
var color = d3.scaleLinear()
.domain([1,10])
.range(ramp);
var svg = d3.select("body")
.append("svg");
var rects = svg.selectAll(null)
.data(d3.range(10))
.enter()
.append("circle")
.attr("cy",50)
.attr("cx",function(d) { return d*15+15; })
.attr("fill", function(d) { return color(d); })
.attr("r",6);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>
I read "Interactive Data Visualization for the web" by Scott Murray, but this book use d3 version3. I've tried to fix it, but some problems happen, and my code is below. I got errors about "y: Expected length, "NaN".", and maybe my stack function doesn't work. However, I don't know how to solve it. I need someone to help me.
// declare variable
var svgWidth = 500,
svgHeight = 300,
svgData = [],
maxValue = 16;
svgData = getData(svgData);
// set stack color
var color = d3.scaleOrdinal(d3.schemeCategory10);
// create stack layout
var stack = d3.stack();
stack(svgData);
// define x,y scale
var xScale = d3.scaleBand()
.domain(d3.range(svgData[0].length))
.rangeRound([0, svgWidth])
.paddingInner(0.05),
yScale = d3.scaleLinear()
.domain([0, d3.max(svgData, function(d){
return d3.max(d, function(d){
d.y0 + d.y;
});
})])
.range([0, svgHeight])
.clamp(true);
// create svg
var svg = d3.select("body")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
// add group and fill color for each row of data
var group = svg.selectAll("g")
.data(svgData)
.enter()
.append("g")
.style("fill", function(d, i){
return color(i);
});
// add a rect for each data value
var rect = group.selectAll("rect")
.data(function(d){
return d;
})
.enter()
.append("rect")
.attr("x", function(d, i){
return xScale(i);
})
.attr("y", function(d){
return yScale(d.y0);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d){
return yScale(d.y);
});
// get data
function getData(data){
var temp =0,
tempArr = [];
data = [];
for(var i=0; i<3; i++){
tempArr = [];
for(var j=0; j<5; j++){
temp = Math.round(Math.random() *maxValue);
tempArr.push( { x: j, y: temp });
}
data.push(tempArr);
}
return data;
}
I am new to svg and d3. I am using topojson to draw states and country and I want to reuse them and position them . I am using "def" and "use" tags but when I give x and y attributes to "use", instead of local coordinate system, it just transforms the paths relatively.
JSFiddle - http://jsfiddle.net/kaushal793/byaka4yL/
Is there any other way to position a path on a particular x,y position on local coordinates?
var states={};
var final_input=[];
var width = 960,
height = 500;
var main_margin = {top: 20, right: 190, bottom: 100, left: 60};
var path = d3.geo.path();
var projection = path.projection();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var main = svg.append("def").append("g")
.attr("id","expanding g");
d3.json("topo/india-states.json", function(error, us) {
us.objects.asasas.geometries.forEach(function(d){
states[d.properties.HASC_1.split("IN.")[1]]=true;
})
var tf = us.transform;
console.log(tf);
/*var labScale = d3.scale.linear()
.range([65,150]).domain([minRank,maxRank]);
*/
var scale=d3.scale.linear().range([800,3000]);
var featureCollection = topojson.feature(us, us.objects.asasas);
var bounds = d3.geo.bounds(featureCollection);
var labScale = d3.scale.linear().range([100,0]).domain([min,max]);
featureCollection.features.forEach(function(d){
if(states[d.properties.HASC_1.split("IN.")[1]] == true){
final_input.push(d);
}
});
var centerX = d3.sum(bounds, function(d) {return d[0];}) / 2,
centerY = d3.sum(bounds, function(d) {return d[1];}) / 2;
var projection = d3.geo.mercator()
.scale(800)
.center([centerX, centerY]);
var kx = tf.scale[0],
ky = tf.scale[1],
dx = tf.translate[0],
dy = tf.translate[1];
console.log(kx+" "+ky+" "+dx+" "+dy);
path.projection(projection);
console.log(featureCollection.features);
var zoom = d3.behavior.zoom()
.translate(projection.translate())
.scaleExtent([height, Infinity])
.scale(projection.scale())
.on("zoom", function() {
projection.translate(d3.event.translate).scale(d3.event.scale)
main.selectAll("path.zoomable").attr("d", path);
projection.translate(d3.event.translate).scale(d3.event.scale)
main.selectAll("path").attr("d", path);
});
main.selectAll("path")
.data(final_input)
.enter()
.append("g")
.attr("id",function(d){
return "g_" +d.properties.HASC_1.split("IN.")[1];
})
.append("path")
.attr("d", path)
.attr("id", function(d){
return d.properties.HASC_1.split("IN.")[1];
})
.style("fill",function(d){
//return d3.lab(labScale(d.properties.ve)*1.5,0,-100)
return "lightblue";
})
.on("mouseover",function(){
d3.select(this).style("stroke-width",2);
d3.select(this).append("title").text(function(d){
return "State:"+d.properties.HASC_1});
})
.on("mouseout",function(){
d3.select(this).style("stroke-width",0.25+"px");
});
var use = svg.append("use")
.attr("x",0)
.attr("y",0)
.attr("xlink:href","#g_GJ");
});