Hello I am trying to add color fill to my bar chart which has a linear scale. The code which I am trying somehow wont work. could you please let me know what I am doing wrong. sorry, I am pretty new to D3jS and JavaScript.
Thanks!
<script>
var data = [{ "MonthYearShortName": "2014-09-13T00:00:00", "Product": "Deposits", "Actual": 330393232.5, "Forecast": 495589848.75, "Target": 495589848.75 }, { "MonthYearShortName": "2014-09-13T00:00:00", "Product": "Fee Based", "Actual": 111868709.42, "Forecast": 167803064.13, "Target": 167803064.13 }, { "MonthYearShortName": "2014-09-13T00:00:00", "Product": "Lending", "Actual": 18146873.33, "Forecast": 27220309.995, "Target": 27220309.995 }];
var color = d3.scale.linear()
.domain(0, function (d) { return max(d.Actual); })
.range(["#f3c40e", "#7d6507"]);
var width = 420,
barHeight = 20;
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, width]);
var chart = d3.select('#ReportContent_ReportContent svg')
.attr("width", width)
.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data, function (d) { return d.Actual; })
.enter().append("g")
.attr("transform", function (d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", function (d) { return d.Actual / 1000000; })
.attr("height", function (d) { return d.Actual / 10000000;})
.attr("fill", color);
bar.append("text")
.attr("x", function (d) { return x(d.Actual) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function (d) { return d.Product; });
</script>
There are more issues with this bar chart than the color code, and even that one fix needs a bit more adjustment. The easiest thing was to work on a FIDDLE rather than try to squeeze all changes in a comment.
bar.append("rect")
.attr("width", function (d) { return x(d.Actual); }) //change
.attr("height", barHeight) //change
.attr("fill", function(d) { return color(d.Actual);} ); //change
There are still other changes...the domain setting was not quite right, and I also added a margin so that the text displays right using a text-anchor.
In any case, overall, I think this will take you closer to what you need.
You'll need to pass a function to attr('fill, ), not just a scale.
bar.append("rect")
.attr("width", function (d) { return d.Actual / 1000000; })
.attr("height", function (d) { return d.Actual / 10000000;})
.attr("fill", function(d) { return color(d); );
It also seems like your scale is not set up properly:
var color = d3.scale.linear()
.domain(0, THIS NEEDS TO BE A VALUE)
.range(["#f3c40e", "#7d6507"]);
The second part of the domain needs to be a value (or a function that evaluates to one)
Related
I'm trying to build a combo chart, i.e. vertical stack bar and a line chart together. I have built the graph but i want the value of each bar on top of the bar. I found certain code for text on top of single bar but not a clear answer for stacked bar. I have written down some code which is available below and I have commented it as // code i tried for text on top of each stack//. But that doesnt seem to work.
d3GroupBarChart(datas){
this.showData = datas
let textArray = [];
datas.forEach(element => {
element.stack.forEach(stack => {
textArray.push(stack)
});
});
if (datas === null || datas.length == 0) {
$(".sieir-chart").empty()
$('.sieir-chart').append(`<div class="no-card-data" >
<h5>No Data Available </h5>
</div>`)
return
}
$('.sieir-chart').html('')
var margin = { top: 20, right: 80, bottom: 100, left: 80 },
width = $('.group-bar-chart').width() - margin.left - margin.right,
height = 410 - margin.top - margin.bottom;
var svg: any = d3.select(".sieir-chart")
.append("svg")
.attr("viewBox", `0 0 ${$('.group-bar-chart').width()} 410`)
.attr("preserveAspectRatio", "xMinYMin meet")
var g = svg.append("g")
.attr("height", height)
.attr("transform",
"translate(" + (margin.left) + "," + (20) + ")");
var x: any = d3.scaleBand()
.range([0, width])
.domain(datas.map(function (d) { return d.group; }))
.padding(0.2);
var yMax = Math.max.apply(Math, datas.map(function (o) { return o.maxBarValue; }))
// Add Y axis
var y = d3.scaleLinear()
.domain([0, yMax])
.range([height, 0])
.nice();
var self = this;
var formatyAxis = d3.format('.0f');
g.append("g")
.style('font-weight', 'bold')
.call(d3.axisLeft(y).tickFormat(function (d: any) {
if (d % 1 === 0) {
return d.toLocaleString()
}
else {
return ''
}
}).ticks(5));
var y1Max = Math.max.apply(Math, datas.map(function (o) { return o.percentage; }))
var y1: any = d3.scaleLinear().range([height, 0]).domain([0, y1Max]);
var yAxisRight: any = d3.axisRight(y1).ticks(5)
// //this will make the y axis to the right
g.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (width) + " ,0)")
.style('font-weight', 'bold')
.call(yAxisRight);
// // text label for the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - (margin.left - 100))
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-family", "poppins_regular")
.text("Logged User Count");
// text label for the y1 axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y1", 0 - (margin.right - 50))
.attr("x", 0 - (height / 2))
.attr("dy", width + 130)
.style("text-anchor", "middle")
.style("font-family", "poppins_regular")
.text("Duration in min");
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll(".tick text")
.attr("transform", "translate(-5,7)rotate(-15)")
.style("text-anchor", "middle")
.style("font-size", "11px")
.style('font-weight', 'bold')
.call(this.wrap, x.bandwidth())
var subgroups = ["Total Headcount","Onboarded resource count"];
var groups = d3.map(datas, function (d) { return (d['group']) }).keys();
// Another scale for subgroup position?
var xSubgroup = d3.scaleBand()
.domain(subgroups)
.range([0, x.bandwidth()])
.padding(0.05)
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#006287', '#F68721'])
var self = this;
datas.forEach(data => {
// console.log("data",data);
g.selectAll("mybar")
// Enter in data = loop group per group
.data(datas)
.enter()
.append("g")
.attr("class","bars")
.attr("transform", function (d) { return "translate(" + x(d.group) + ",0)"; })
.selectAll("rect")
.data(function (d) { return subgroups.map(function (key) { return { key: key,
value: d[key] }; }); })
.enter().append("rect")
.attr("x", function (d) { return xSubgroup(d.key); })
.attr("y", function (d) { return y(d.value); })
.attr("width", xSubgroup.bandwidth())
.attr("height", function (d) { return height - y(d.value); })
.attr("fill", function (d) { return color(d.key); })
.append("svg:title")
.text(function (d) {
return `${d['key']}:` + d.value;
})
//code i tried for text on top of each stack
g.selectAll(".text")
.data(data.stack)
.enter().append("text")
.attr("class", "barstext")
.attr("x", function (d) { console.log("d", d); return x(d.name); })
.attr("y", function (d) { return y(d.value); })
.text(function (d) { console.log("text", d); return (d.value); })
// // line chart
var averageline = d3.line()
.x(function (d, i) { return x(d['group']) + x.bandwidth() / 2; })
.y(function (d) { return y1(d['percentage']); })
.curve(d3.curveMonotoneX);
var path = g.append("path")
.attr("class", "line")
.style("fill", "none")
.style("stroke", "#58D68D")
.style("stroke-width", 2)
.attr("d", averageline(datas));
g.selectAll("myCircles")
.data(datas)
.enter()
.append("circle")
.attr("class", "dot")
.style("fill", "white")
.style("stroke", "#58D68D")
.style("stroke-width", 2)
.style('cursor', 'pointer')
.attr("cx", function (d, i) { return x(d['group']) + x.bandwidth() / 2; })
.attr("cy", function (d) { return y1(d['percentage']); })
.attr("r", 3)
.append("svg:title")
.text(function (d) {
return "Percentage: " + d.percentage;
})
})
}
dummy data
[
{
"group": "Digital Process Industries",
"Total Headcount": 12,
"Onboarded resource count": 1,
"percentage": 13,
"maxBarValue": 12,
"stack": [
{
"name": "Total Headcount",
"value": 12
},
{
"name": "Onboarded resource count",
"value": 1
}
]
},
{
"group": "Digital Discrete Industries",
"Total Headcount": 6,
"Onboarded resource count": 6,
"percentage": 33,
"maxBarValue": 6,
"stack": [
{
"name": "Total Headcount",
"value": 6
},
{
"name": "Onboarded resource count",
"value": 6
}
]
}]
You are pretty close with your current solution. There are two main things you need to do to get this working correctly:
If you are looping over your data already (datas.forEeach) there is no need to rebind to it in your databinding for the group offset. You should be binding to the individual data element instead (so bind to [data] instead).
Set the group you create off the data element to a variable and append both the rectangles for the bars and the text for the labels to that group rather than the svg. The reason for this is that it is already offset for the group (via the transform call) so you just have to worry about the subgroup x scale.
See this jsfiddle for a working version of your code. I added comments prepended with EDITED -- to all the lines I changed along with an explanation of what I did.
I need to make a D3 BoxPlot.
I have a dataset with several billions of rows, and it is unfeasible to send the raw data to the client. So, I created an API and I send only the summarized version containing the max/min/std_dev values of each column.
In all the examples ( one two ) I saw using D3 BoxPlot, the data summarization is done on the client side (the opposite of my case).
Is it possible to use the BoxPlot with already calculated data? Does anyone have an example?
Well, since you are getting the already calculated data, the task here is even easier!
First, let's set the scales. In the data you copy/pasted in your comment you have max and min, which I'll use for the third and first quartiles. Since you don't have the second quartile (median) in your data, I'll use mean. Also, as your data have 3 identical objects, I change it a little bit, to make the boxes different.
So, setting the y scale:
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.max
}) * 1.1])
.range([h - padding, padding]);
Which is a standard linear scale.
For the x scale, I'm using a band scale:
var xScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.label
}))
.range([padding, w - padding])
.padding(0.4);
Which is very good to give us the left and right limits of the rectangles.
Now, it's just a matter of printing the rectangles and the lines (the medians).
For the rectangles, notice the math to get the third quartile as the top of the rectangle, and the first quartile as its height (y and height attributes):
var boxes = svg.selectAll("foo")
.data(data)
.enter()
.append("rect")
.attr("fill", "none")
.attr("stroke", "black")
.attr("x", function(d) {
return xScale(d.label)
})
.attr("width", xScale.bandwidth())
.attr("y", function(d) {
return yScale(d.max)
})
.attr("height", function(d) {
return yScale(d.min) - yScale(d.max)
});
And, finally, for the lines, we just use mean for both y1 and y2 values:
var lines = svg.selectAll("foo")
.data(data)
.enter()
.append("line")
.attr("stroke", "black")
.attr("stroke-width", 4)
.attr("x1", function(d) {
return xScale(d.label)
})
.attr("x2", function(d) {
return xScale(d.label) + xScale.bandwidth()
})
.attr("y1", function(d) {
return yScale(d.mean)
})
.attr("y2", function(d) {
return yScale(d.mean)
})
Here is a demo with your data structure:
var data = [{
"count": "2",
"min": "1.6",
"max": "4.1",
"label": "labelA",
"stddev": "0.72",
"mean": "3.1"
}, {
"count": "2",
"min": "1.1",
"max": "2.9",
"label": "labelB",
"stddev": "0.72",
"mean": "2.2"
}, {
"count": "2",
"min": "2.4",
"max": "3.6",
"label": "labelC",
"stddev": "0.72",
"mean": "2.7"
}];
var w = 500,
h = 200,
padding = 30,
padding2 = 20;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.max
}) * 1.1])
.range([h - padding2, 10]);
var xScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.label
}))
.range([padding, w - padding])
.padding(0.4);
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale);
var gX = svg.append("g")
.attr("transform", "translate(0," + (h - padding2) + ")")
.call(xAxis);
var gY = svg.append("g")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
var boxes = svg.selectAll("foo")
.data(data)
.enter()
.append("rect")
.attr("fill", "none")
.attr("stroke", "black")
.attr("x", function(d) {
return xScale(d.label)
})
.attr("width", xScale.bandwidth())
.attr("y", function(d) {
return yScale(d.max)
})
.attr("height", function(d) {
return yScale(d.min) - yScale(d.max)
});
var lines = svg.selectAll("foo")
.data(data)
.enter()
.append("line")
.attr("stroke", "black")
.attr("stroke-width", 4)
.attr("x1", function(d) {
return xScale(d.label)
})
.attr("x2", function(d) {
return xScale(d.label) + xScale.bandwidth()
})
.attr("y1", function(d) {
return yScale(d.mean)
})
.attr("y2", function(d) {
return yScale(d.mean)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
PS: Once you have the data for the whiskers, you can simply add the code for creating the lines to this basic structure, following the same principle.
You can find a boxplot rendering component as part of d3fc:
https://d3fc.io/api/series-api.html#boxplot
You can use this to render data that has already been 'summarised'
(Full disclosure: I'm one of the authors of d3fc)
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:
Using D3 I display a bunch of circles in different sizes, each filled with text. I'm stuck with finding the correct font size so that the text fits correct in the circle, depending of it's size and the length of the text. Long text should possibly be broken up in more lines. Here is my code:
var data = {
"name": "",
"children": [
{ "name": "This is a tag", "value": 242 },
{ "name": "Circle", "value": 162 },
{ "name": "Tree", "value": 80 },
{ "name": "My sentence is very long and needs breaks", "value": 80 },
]
}
var diameter = 300,
format = d3.format(",d");
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
d3.json(data, function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(data)
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return '#f88' });
// text part
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.style("font-size", function(d) { return Math.round(d.r/3)+'px'; })
.text(function(d) { return d.name.substring(0, d.r / 3); });
});
d3.select(self.frameElement).style("height", diameter + "px");
I have created a fiddle as well on http://jsfiddle.net/L4nMx/
I think I should calculate the width of the text and modify the font size until it matches the circle's size or something like that. Or is there any "strech" function to do this the easy way?
This solution is fine for me for now. It's not accurate maths but fits anyway.
See it in action on http://jsfiddle.net/L4nMx/3/
.style("font-size", function(d) {
var len = d.name.substring(0, d.r / 3).length;
var size = d.r/3;
size *= 10 / len;
size += 1;
return Math.round(size)+'px';
})
.text(function(d) {
var text = d.name.substring(0, d.r / 3);
return text;
});
Next step would be to break long text into multiple lines so you could enlarge font sizes in such cases but I didn't manage to solve this. It's not easy in SVG because simple line breaks are not possible. Maybe the wrapping solutions from the comments in the question can be added here - somehow...
There is this bl.ocks https://bl.ocks.org/mbostock/1846692
which is basically
node.append("circle")
.attr("r", function(d) { return d.r; });
node.append("text")
.text(function(d) { return d.name; })
.style("font-size", function(d) { return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 24) + "px"; })
.attr("dy", ".35em");
Well this is a noob question. I am just about beginning to explore the D3jS library.
I am trying to create a simple bar chart right now. For some reasons, the chart is not getting created. Please let me know where I am going wrong.
<style>.chart div {
background-color:red;
border:1px solid blue;
}</style>
<div id="ReportContent_ReportContent" style="border: 1px solid #58595d; border: 1px solid rgba(88, 89, 93, .3);">
<svg></svg>
</div>
<script>
var data = [{ "MonthYearShortName": "2014-09-13T00:00:00", "Product": "Deposits", "Actual": 330393232.5, "Forecast": 495589848.75, "Target": 495589848.75 }, { "MonthYearShortName": "2014-09-13T00:00:00", "Product": "Fee Based", "Actual": 111868709.42, "Forecast": 167803064.13, "Target": 167803064.13 }, { "MonthYearShortName": "2014-09-13T00:00:00", "Product": "Lending", "Actual": 18146873.33, "Forecast": 27220309.995, "Target": 27220309.995 }];
var width = 420,
barHeight = 20;
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, width]);
var chart = d3.select('#ReportContent_ReportContent svg')
.attr("width", width)
.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data, function (d) { return d.Actual; })
.enter().append("g")
.attr("transform", function (d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", function(d) { return d.Actual; })
.attr("height", barHeight - 1)
.attr("fill", "#000000");
bar.append("text")
.attr("x", function (d) { return x(d.Actual) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function (d) { return d.Product; });
</script>
One of the problems is that you're trying to access properties of the objects that belong to the array.
Here:
var bar = chart.selectAll("g")
.data(data.Actual)
try to do this instead:
var bar = chart.selectAll("g")
.data(data, function(d) {return d.Actual;})
I haven't tested the solution myself, but this should get rid of one of the errors.