Scaling height of bars to container size - javascript

I'm wanting to scale the bar heights to the size of the SVG. http://jsfiddle.net/6d7984oa/
function d3_bar(s, dataset, barPadding) {
var self = s[0],
w = parseInt(d3.select(self).style("width")),
h = parseInt(d3.select(self).style("height")),
svg = d3.select(self)
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - d;
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d;
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
}
I've tried quite a few things but the math never works.
.attr("height", function(d) {
var min = 1,
max = h,
diff = max - min;
return h - ( diff ? ((d - min) / diff) * h : 1 );
})
Struggling to see how this may be done.

You should use D3 scales. A lot of info can be found here: Quantitative scales.
A basic linear scale goes like this:
var yScale = d3.scale.linear()
.domain([0, d3.max(yourdata)])
.range([0,h]);
Please look up the d3.max() function. Basically it looks for the largest value in the given array, but you can use a function to specify what value to look at, in case your data is an array of objects.
The linear scale can be explained as following:
The domain stands for the values that go in, the values that need to be scaled into something else. You give a range of values, the min and the max values between '[]'. In your case, this is the data representing the height.
The range of a scale is what comes out. So you define the min and the maximum possible outcomes, in our case 0 and the width of the svg.
So what happens when you use the scale? well, say your data goes from 0 to 100 and the width of your svg is 50 units. When your data is 0, the scale will return 0, when your data is 100, your scale will return 50. So if your data would be 50, then the scale will return.... 25. I choose easy numbers here, it works well on more difficult cases too :-).
EDIT: if forgot to mention how to use the scale for lets say your height attribute:
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - d;
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d){return yScale(d);})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
presuming of course that you named the scale 'yScale'.
Edit 2: My scale code had an error in the range. When scaling the y axes, you need to set the height in the range, not the width. So i fixed that.
Further more, when setting the y attribute of your rect, you need to use the scale for that one as well. Otherwise the bar height is scaled, but the matching y position isn't, resulting in awkward positioning of your rect tags. I should have mentioned that. Here is the correct code:
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d){return yScale(d);})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
You can see that I have changed the y attribute function using the yScale as well.
I apologize for the faulty code, I should check it better in the future.

Related

Transitioning a bar chart with negative values for the width

I am creating a horizontal bar chart using d3. And I am using an animation to "grow" the chart at startup. Here is the code.
// Create the svg element
d3.select("#chart-area")
.append("svg")
.attr("height", 800)
.attr("width", 800);
.data(dataValues) // This data is previously prepared
.enter().append("rect")
.style("fill", "blue")
.attr("x", function () { return xScale(0); }) // xScale is defined earlier
.attr("y", function (d) { return yScale(d); }) // yScale is defined earlier
.attr("height", yScale.bandwidth()) // yScale is defined earlier
// Initial value of "width" (before animation)
.attr("width", 0)
// Start of animation transition
.transition()
.duration(5000) // 5 seconds
.ease (d3.easeLinear);
// Final value of "width" (after animation)
.attr("width", function(d) { return Math.abs(xScale(d) - xScale(0)); })
The above code would work without any problem, and the lines would grow as intended, from 0 to whichever width, within 5 seconds.
Now, if we change the easing line to the following
// This line changed
.ease (d3.easeElasticIn);
Then, the ease would try to take the width to a negative value before going to a final positive value. As you can see here, d3.easeElasticIn returns negative values as time goes by, then back to positive, resulting in width being negative at certain points in the animation. So the bars do not render properly (because SVG specs state that if width is negative, then use 0)
I tried every solution to allow the bars to grow negatively then back out. But could not find any. How can I fix this problem?
Thanks.
As you already know, the use of d3.easeElasticIn in your specific code will create negative values for the rectangles' width, which is not allowed.
This basic demo reproduces the issue, the console (your browser's console, not the snippet's console) is populated with error messages, like this:
Error: Invalid negative value for attribute width="-85.90933910798789"
Have a look:
const svg = d3.select("svg");
const margin = 50;
const line = svg.append("line")
.attr("x1", margin)
.attr("x2", margin)
.attr("y1", 0)
.attr("y2", 150)
.style("stroke", "black")
const data = d3.range(10).map(function(d) {
return {
y: "bar" + d,
x: Math.random()
}
});
const yScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.y
}))
.range([0, 150])
.padding(0.2);
const xScale = d3.scaleLinear()
.range([margin, 300]);
const bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("x", margin)
.attr("width", 0)
.style("fill", "steelblue")
.attr("y", function(d) {
return yScale(d.y)
})
.attr("height", yScale.bandwidth())
.transition()
.duration(2000)
.ease(d3.easeElasticIn)
.attr("width", function(d) {
return xScale(d.x) - margin
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
So, what's the solution?
One of them is catching those negative values as they are generated and, then, moving the rectangle to the left (using the x attribute) and converting those negative numbers to positive ones.
For that to work, we'll have to use attrTween instead of attr in the transition selection.
Like this:
.attrTween("width", function(d) {
return function(t){
return Math.abs(xScale(d.x) * t);
};
})
.attrTween("x", function(d) {
return function(t){
return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
};
})
In the snippet above, margin is just a margin that I created so you can see the bars going to the left of the axis.
And here is the demo:
const svg = d3.select("svg");
const margin = 100;
const line = svg.append("line")
.attr("x1", margin)
.attr("x2", margin)
.attr("y1", 0)
.attr("y2", 150)
.style("stroke", "black")
const data = d3.range(10).map(function(d) {
return {
y: "bar" + d,
x: Math.random()
}
});
const yScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.y
}))
.range([0, 150])
.padding(0.2);
const xScale = d3.scaleLinear()
.range([0, 300 - margin]);
const bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("x", margin)
.attr("width", 0)
.style("fill", "steelblue")
.attr("y", function(d) {
return yScale(d.y)
})
.attr("height", yScale.bandwidth())
.transition()
.duration(2000)
.ease(d3.easeElasticIn)
.attrTween("width", function(d) {
return function(t) {
return Math.abs(xScale(d.x) * t);
};
})
.attrTween("x", function(d) {
return function(t) {
return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
};
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>

How Do I relate and combine bar chart with bubble chart in D3?

I want to use D3 for generating charts from JSON files. How do I combine/relate bar chart and bubble chart so when you click on either it should provide you details of both charts in a legend.
The each bubble must be below and center of the each bar. It must share x-axis of bar charts.
There are two different data sources for them.
No. of bars = No. of bubbles
I created the xscale
var xScale = d3.scale.ordinal() .domain( d3.range(dataset.length))
.rangePoints([0, w-50]);
This same scale is shared while drawing bar as well as circles.
I have changed your codepen code
Below is the complete code after change
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d,i) {
//return i * (w / dataset.length);
return xScale(i) ;
})
.attr("y", function(d) {
return h - (d * 10);
})
.attr("width", barWidth)
.attr("height", function(d) {
return d * 10;
})
.attr("fill","#d38e71");
var circles = svg.selectAll("circle")
//.data(dataset)
.data(num)
.enter()
.append("a")
.attr("xlink:href", "http://google.com")
.attr("xlink:title",function(d){
return "No. of campagins sent - "+d;
})
.attr("target","_blank")
.append("circle");
circles.attr("cx", function(d,i) {
/* return i* (wB / dataset.length)+(wB / dataset.length)/2-2;*/
return (xScale(i) + barWidth/2);
})
.attr("cy", function(d) {
return hB-35;
})
.attr("r", function(d) {
return rScale(d);
})
Note the following code for bar
.attr("x", function(d,i) {
//return i * (w / dataset.length);
return xScale(i) ;
})
Note the following code for circles.
circles.attr("cx", function(d,i) {
/* return i* (wB / dataset.length)+(wB / dataset.length)/2-2;*/
return (xScale(i) + barWidth/2);
})
One suggestion
Width of the bar is set via this statement (w / dataset.length - barPadding) This can be stored in a variable. So it will not calculate for each member in the chart.

D3 Javascript / SVG Part III ISsue

I'm following the part III tutorial of "Let' Make Some Charts" as an introduction to D3. Part of the tutorial calls for data insertion via TSV. Given I don't see this being an eventual use case for me, I'm attempting to modify the tutorial with the code below using a simple javascript array. However, nothing shows up on the page when I render in the browser. Can anyone shed some light on this?
Here's the tutorial link for some reference to the original code: http://bost.ocks.org/mike/bar/3/
My JS code:
<script>
var data = [4,8,15,16,23,42,57,89,100,160];
var width = 960,
height = 500; // have to make sure variables are case sensitive
var y = d3.scale.linear()
.domain([0, d3.max(data)]) // scaling based on max value
.range([height, 0]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
var barWidth = width / data.length;
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d,i) { return "translate(" + i * barWidth + ",0)";});
bar.append("rect")
.attr("y", function(d) { return y(d.value); })
.attr("width", barWidth - 1)
.attr("height", function(d) { return height - y(d.value); });
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d.value) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.value; });
function type(d) {
d.value = +d.value;
return d;
}
</script>
The code you've copied references a named attribute value to determine what to draw. The data you've created doesn't have this but just the data. So everywhere you have d.value, you need to reference just d.
Complete demo here.
Your problem stems from the fact that you're using an Array of numbers for your data, while in Mike Bostock's example he was using an Array of Objects (for example, var data = [{value: 30}, ...]). Thus you need to change all cases of d.value to d in your code, since your data is not longer an Object but just a number.
bar.append("rect")
.attr("y", function(d) { return y(d); }) // <---- delete .value
.attr("width", barWidth - 1)
.attr("height", function(d) { return height - y(d); }); // <---- delete .value
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d) + 3; }) // <---- delete .value
.attr("dy", ".75em")
.text(function(d) { return d; }); // <---- delete .value
Making these changes produces the following bar chart:

d3.js - Bar chart won't display with scale

I updated my code to use the scale method in D3. However, since then my bar chart won't display. What is the cause of this issue?
var dataset = [ ];
for (var i = 0; i < 14; i++) {var newNumber = Math.round(Math.random() * 70);
dataset.push(newNumber);}
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var widthScale = d3.scale.linear()
.domain([0,d3.max(dataset)])
.range([0,w]);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 3)
.attr("y", function (d,i) {return i* 36;})
.attr("width", function(d) {return widthScale;})
.attr("height", h / dataset.length - barPadding)
.attr("fill", function(d) {return "rgb(0, 0, " + (d * 10) + ")";});
A D3 scale is just a function translates values from a given input domain (your data values, 0 to max) to a specified output range (the width of your chart). Thus you have to apply the scale to your data, e.g. widthScale( d ). Right now you are assigning the widthScale function to the width attribute of your rect instead of the output value.
See the working fiddle: http://jsfiddle.net/S92u4/

D3 barplot only using half my data

I have a d3.js barplot using some json data containing 12 elements. The data value I'm using for bar height is fpkm. I'm able to return that value as a callback to d3's data function- but only for half the elements.
My problem is that only the first half of the values are appearing in my barplot. I only get 6 rows corresponding to my first 6 values.
I made a fiddle here: http://jsfiddle.net/z9Mvt/
I can't seem to figure out why it's only using half the elements in my json.
Any help = appreciated.
html:
<div align='center' id="GECGplot" style='width:98%;text-align:center;'></plot>
and the js:
var gecgData= {"nodeName":"GECG","children":[{"nodeName":0,"nodeData":{"id":"643139","library_id":"SI_5589","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"1.1241","fpkm_conf_lo":"0.898502","fpkm_conf_hi":"1.34969","fpkm_status":"OK","fpkm_percentile_compendium":"8.33","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":1,"nodeData":{"id":"872561","library_id":"SI_5596","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"1.12666","fpkm_conf_lo":"0.871059","fpkm_conf_hi":"1.38226","fpkm_status":"OK","fpkm_percentile_compendium":"16.67","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":2,"nodeData":{"id":"1031623","library_id":"SI_5553","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"1.21305","fpkm_conf_lo":"0.949369","fpkm_conf_hi":"1.47674","fpkm_status":"OK","fpkm_percentile_compendium":"25.00","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":3,"nodeData":{"id":"248423","library_id":"SI_5486","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"1.98203","fpkm_conf_lo":"1.64888","fpkm_conf_hi":"2.31519","fpkm_status":"OK","fpkm_percentile_compendium":"33.33","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":4,"nodeData":{"id":"1039674","library_id":"SI_5554","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"2.24514","fpkm_conf_lo":"1.83333","fpkm_conf_hi":"2.65696","fpkm_status":"OK","fpkm_percentile_compendium":"41.67","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":5,"nodeData":{"id":"304849","library_id":"SI_5485","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"2.29868","fpkm_conf_lo":"2.02514","fpkm_conf_hi":"2.57221","fpkm_status":"OK","fpkm_percentile_compendium":"50.00","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":6,"nodeData":{"id":"417495","library_id":"SI_5484","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"2.61196","fpkm_conf_lo":"2.28949","fpkm_conf_hi":"2.93442","fpkm_status":"OK","fpkm_percentile_compendium":"58.33","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":7,"nodeData":{"id":"928522","library_id":"SI_5595","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"2.94397","fpkm_conf_lo":"2.61962","fpkm_conf_hi":"3.26832","fpkm_status":"OK","fpkm_percentile_compendium":"66.67","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":8,"nodeData":{"id":"622876","library_id":"SI_5552","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"3.27303","fpkm_conf_lo":"2.79509","fpkm_conf_hi":"3.75097","fpkm_status":"OK","fpkm_percentile_compendium":"75.00","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":9,"nodeData":{"id":"50230","library_id":"SI_5487","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"9.88611","fpkm_conf_lo":"8.6495","fpkm_conf_hi":"11.1227","fpkm_status":"OK","fpkm_percentile_compendium":"83.33","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":10,"nodeData":{"id":"816444","library_id":"SI_5594","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"15.1868","fpkm_conf_lo":"13.8218","fpkm_conf_hi":"16.5519","fpkm_status":"OK","fpkm_percentile_compendium":"91.67","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}},
{"nodeName":11,"nodeData":{"id":"496931","library_id":"SI_5551","gene_id":"ENSG00000157554","gene_short_name":"ERG","fpkm":"52.249","fpkm_conf_lo":"50.8217","fpkm_conf_hi":"53.6763","fpkm_status":"OK","fpkm_percentile_compendium":"100.00","chr_id":"21","start":"39751948","end":"40033704","locus":"21:39751948-40033704","report":"0","tracking_id":null,"class_code":null,"nearest_ref":null,"tss_id":null,"length":null,"coverage":null,"fpkm_percentile_origin_tissue":null,"fpkm_percentile_collection_tissue":null,"fpkm_percentile_sample_cancer":null,"fpkm_fold_change_benign":null}}]}
;
//Width and height
// var w = $('#GECGplot').width();
var w = 700;
var h = 300;
var barPadding = 1;
var margin = {top: 40, right: 10, bottom: 20, left: 10};
var xScale = d3.scale.linear().
domain([0, 20]). // your data minimum and maximum
range([0, h]); // the pixels to map to, e.g., the width of the diagram.
//Create SVG element
var svg = d3.select("#GECGplot")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
// .data(dataset)
.data(function(d, i) {
return plotData[i].nodeData.fpkm;
})
.enter()
.append("rect")
.attr("x", function(d, i) {
// alert(plotData.length);
return i * (w / plotData.length);
})
.attr("y", function(d, i) {
alert(plotData[i].nodeData.fpkm);
return h - (plotData[i].nodeData.fpkm * 50); //Height minus data value
})
.attr("width", w / plotData.length - barPadding)
.attr("height", function(d, i) {
return plotData[i].nodeData.fpkm * 50; //Just the data value
})
.attr("fill", function(d, i) {
return "rgb(0, 0, " + (plotData[i].nodeData.fpkm * 50) + ")";
})
svg.selectAll("text")
.data(function(d, i) {
return plotData[i].nodeData.fpkm;
})
.enter()
.append("text")
.text(function(d, i) {
return plotData[i].nodeData.fpkm;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white")
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return i * (w / plotData.length) + (w / plotData.length - barPadding) / 2;
})
.attr("y", function(d, i) {
return h - (plotData[i].nodeData.fpkm * 50) + 14;
})
// alert(tableSchema);
Here you go. You bind the array "children" to the rectangle elements so you dont need the argument 'i' to access the value you need.
Also, I would recommend using the d3.scale.ordinal() for your x axis as opposed to calculating it explicitly from the data. Litte more flexible.
http://jsfiddle.net/Cef4D/
svg.selectAll("rect")
.data(plotData)
.enter().append("rect")
.attr("x", function(d, i) {return i * (w / plotData.length);})
.attr("y", function(d) {
return h - (d.nodeData.fpkm * 50); //Height minus data value
})
.attr("width", w / plotData.length - barPadding)
.attr("height", function(d, i) {
return d.nodeData.fpkm * 50; //Just the data value
})
.attr("fill", function(d, i) {
return "rgb(0, 0, " + (d.nodeData.fpkm * 50) + ")";
})

Categories