I'm pretty new to D3 and am working on a reusable bar chart which can be sent new data and it will update the existing graph based on the new data that it receives.
Currently, it is correctly drawing the chart, but when the data changes it is overlaying a graph of the new data over the old graph instead of updating it.
I believe the issue must be with my bars variable and how I'm binding my data but I can't be sure. The data is simply the score of a reddit post from the reddit JSON API.
Example:
My code for the chart is:
d3.namespace.barChart = function() {
var data, group, height, margin = {top: 0, right: 0, bottom: 0, left: 0}, width;
function chart(container) {
group = container;
// Determine the max score and timestamps from the Reddit data for use in our scales
var maxScore = d3.max(data, function(d) { return d.data.score; });
data.forEach(function(d) {
d.data.created *= 1000;
});
// Create the scales to ensure data fits in the graphs
var xScale = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeBands([0, width], 0.25);
var yScale = d3.scale.linear()
.domain([0, maxScore])
.range([0, height]); // .range(starting, )
var yScaleLine = d3.scale.linear()
.domain([0, maxScore])
.range([height, 0]);
var colorScale = d3.scale.linear()
.domain(d3.extent(data, function(d) { return d.data.score; }))
.range(['#DA4453', '#A0D468'])
.interpolate(d3.interpolateRgb);
// Create the Y-Axis function. yScaleLine is used as the scale to ensure
// that the data goes from 0 at the bottom left to max score in the top left
var yAxis = d3.svg.axis()
.scale(yScaleLine)
.orient('left');
// Setup our chart using our width, height and margins.
group.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
// Add a group element for the primary graphic that is moved
// using the margins. Left and top are used since 0,0 is top left corner
var graph = group.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Append our yAxis to the SVG canvas and move it according to the margins
group.append('g')
.attr('class', 'y axis')
.call(yAxis)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Inside of our group, we need to make a 'rect' for each item in the dataset
// We link the data, and append one rect for each piece of data.
var bars = graph.selectAll('rect')
.data(data, function(d) { return d.data.id; });
bars.enter()
.append('rect');
// We begin with the height of each bar as 0 allowing us to transition to their full height
bars.attr({
x: function(d, i) { return xScale(i); },
y: function() { return height; },
height: 0,
width: xScale.rangeBand(),
fill: function(d) { return colorScale(d.data.score); }
});
// Events must be linked before a transition to function
bars.on('mouseover', function() {
d3.select(this).attr('fill', '#000');
})
.on('mouseleave', function() {
d3.select(this).attr('fill', function(d) { return colorScale(d.data.score); });
});
bars.transition().duration(150)
.attr({
x: function(d, i) { return xScale(i); },
y: function(d) { return height - yScale(d.data.score); },
height: function(d) { return yScale(d.data.score); },
width: xScale.rangeBand()
});
bars.exit().transition().duration(150).attr({ height: 0.0001 }).remove();
}
EDIT:
After digging into the created source it seems it's not updating but rather just continuing to create a new svg g element for all the rects. Removing the group.append('g') chain fixes the graph not updating but the graph is no longer in a group and makes the SVG very messy.
Related
I have two line charts. One I coded here and the other here that I copy pasted to compare why mine did not align correctly. Neither chart is using a transform to translate the line. What am I missing in the chart that does align correctly. Thank you.
Not working correctly chart:
var svg = d3.select("body") //create Svg element
.append("svg")
.attr("height",500)
.attr("width", 700)
.style("border", "solid 1px red")
.attr("transform","translate(100,0)"); // To align svg at the center in the output tab.
var data = [
{ day:0, stock_value: 0 },
{ day:5, stock_value: 100 },
{ day:10, stock_value: 200 },
{ day:15, stock_value: 400 },
{ day:20, stock_value:150 }];
var xScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.day))
.range([0,500]);
var yScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.stock_value))
.range([400,0]);
svg.append('g')
.call(d3.axisBottom(xScale))
.attr('transform','translate(70,450)');
svg.append('g')
.call(d3.axisLeft(yScale))
.attr('transform','translate(70,50)');
svg.append('text')
.text('days')
.attr('transform','translate(270,490)');
svg.append('text')
.text('value')
.attr('transform','translate(30,300) rotate(-90)')
var generator = d3.line()
.x(function(d) { return xScale(d.day); })
.y(function(d) { return yScale(d.stock_value); });
svg.append('path')
.datum(data)
.attr('d', generator)
.attr('fill','none')
.attr('stroke','blue')
.attr('stroke-width','2px');
You're on the right track!
Looks like we need to apply the same transform to our path as we're applying to our axes here.
You can see in the screenshots that both the x-axis and y-axis are being offset in the svg using a transform -- that's so that the y-axis can be seen inside of the space created for the svg, inside the red line. If we don't apply that transform, then we won't be able see our y-axis.
So in order for our path to line up with our axes, we need to apply that same transformation to our path. :)
Updated pen.
By the way, in D3, one thing you will encounter a lot is the so-called "margin convention." It's completely up to you of course and there are definitely arguments to be made for not using it, but if it's helpful, the creator of D3 wrote up a demo of the margin convention that I think illustrates it pretty well which is linked here.
I hope this helps! ✌️
var margin = {top: 20, right: 20, bottom: 60, left: 80},
width = 700 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("body") //create Svg element
.append("svg")
.attr("height",500)
.attr("width", 700)
.style("border", "solid 1px red")
.attr("transform","translate(100,0)"); // To align svg at the center in the output tab.
var data = [
{ day:0, stock_value: 0 },
{ day:5, stock_value: 100 },
{ day:10, stock_value: 200 },
{ day:15, stock_value: 400 },
{ day:20, stock_value:150 }];
var xScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.day))
.range([0,500]);
var yScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.stock_value))
.range([400,0]);
svg.append('g')
.call(d3.axisBottom(xScale))
.attr('transform',`translate(${70}, ${450})`);
svg.append('g')
.call(d3.axisLeft(yScale))
.attr('transform',`translate(${70},${50})`);
svg.append('text')
.text('days')
.attr('transform','translate(270,490)');
svg.append('text')
.text('value')
.attr('transform','translate(30,300) rotate(-90)')
var generator = d3.line()
.x(function(d) { return xScale(d.day); })
.y(function(d) { return yScale(d.stock_value); });
svg.append('path')
.datum(data)
.attr('d', generator)
.attr('fill','none')
.attr('stroke','blue')
.attr('stroke-width','2px')
// both of your axes are having a transform applied to account for your svg's margins, so apply the same values to your path here:
.attr('transform',`translate(${70},${50})`);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
I just tried out d3js for some days and I want to beautify the x and y scales of my graph to be something like this
But this is what I got so far.
I have tried changing from scaleBand() to scaleLinear() and fix the normally bandwidth() method to a constant value, the graph just would not show.
This is the code
mounted () {
this.generateChart()
},
methods: {
generateChart () {
// set the dimensions and margins of the graph
const margin = { top: 20, right: 20, bottom: 30, left: 30 }
const width = 1850 - margin.left - margin.right
const height = 200 - margin.top - margin.bottom
// make the area for the graph to stay
const svg = d3.select('#heatmap')
.append('svg') // svg area can include headers and color scales
.attr('width', width + margin.left + margin.right) // set width
.attr('height', height + margin.top + margin.bottom) // set height
.append('g') // new g tag area for graph only
.attr('transform', `translate(${margin.left}, ${margin.bottom})`)
// stick g tag to the bottom
// range function generate graph scales
// TODO: make a range using date and time
const xLabel = d3.range(259)
const yLabel = d3.range(23, -1, -1)
// create x, y scales and axes
const x = d3.scaleBand()
.domain(xLabel)
.range([0, width])
.padding(0.05)
svg.append('g')
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom(x))
const y = d3.scaleBand()
.domain(yLabel)
.range([height, 0])
.padding(0.05)
svg.append('g').call(d3.axisLeft(y))
d3.json('../predictions.json').then(function (data) {
svg.selectAll()
.data(data.heatmaps.kw.Sand_Heads)
.enter()
.append('rect')
.attr('x', function (d) {
return x(d[1]) // return cell's position
})
.attr('y', function (d) {
return y(d[0])
})
.attr('cx', 1)
.attr('cy', 1)
.attr('width', x.bandwidth()) // return cell's width
.attr('height', y.bandwidth()) // return cell's height
.style('fill', function (d) {
return rgbaToHex(0, 128, 255, 100 * d[2])
})
.on('mouseover', function () { // box stroke when hover
d3.select(this)
.style('stroke', 'black')
.style('opacity', 1)
})
.on('mouseout', function () { // fade block stroke when mouse leave the cell
d3.select(this)
.style('stroke', 'none')
.style('opacity', 0.8)
})
})
}
Note: I have to make it work with date selection in the future too.
This is the structure of the data I'm working on.
{
"days": ["2019-04-11", "2019-04-12", ..., "2019-12-25"],
"heatmaps": {
"kw": {
"Tilly_Point": [[5, 112, 0.0012], [6, 112, 0.0016], ...],
"Mouat_Point": [...]
},
"hw": {
...
}
}
}
Explanation:
the first element of subarray in Tilly_Point is the time of the whale found. ranging from 0 to 23 (midnight to next midnight) and 5 means 05:00 A.M. to 06:00 A.M.
the second element is the nth day of the operation. It's 112 meaning it's the 112th day of the operation. which is 1 August 2019
the last element is the real data being plotted on the graph. the higher -> darker colour towards the real color with 1 opacity
By looking at the desired design we can understand what you mean by "beautify" is reducing the number of ticks. And you are absolutely correct: in very few and specific situations we need to show all of them; most of the times, the design is cleaner and the user benefits from a more tidy dataviz if we choose what ticks to display.
That's clear if we look at this basic example I wrote, simulating your axes:
const svg = d3.select("svg");
const yScale = d3.scaleBand()
.domain(d3.range(25))
.range([10, 80])
.paddingInner(1);
const xScale = d3.scaleBand()
.domain(d3.range(261))
.range([25, 490])
.paddingInner(1);
d3.axisLeft(yScale)(svg.append("g").attr("transform", "translate(25,0)"));
d3.axisBottom(xScale)(svg.append("g").attr("transform", "translate(0,80)"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="100"></svg>
There are different approaches for reducing the number of ticks here: you can explicitly chose the ticks to show by value or, as I'll do in this answer, you can simply choose how many of them to show. Here, I'll do this using the remainder operator (%) filtering the scale's domain and passing it to tickValues (since you have a band scale we cannot use ticks), for instance showing every 6th value for the y axis:
.tickValues(yScale.domain().filter((_, i) => !(i % 6)))
Here is the result:
const svg = d3.select("svg");
const yScale = d3.scaleBand()
.domain(d3.range(25))
.range([10, 80])
.paddingInner(1);
const xScale = d3.scaleBand()
.domain(d3.range(261))
.range([25, 490])
.paddingInner(1);
d3.axisLeft(yScale).tickValues(yScale.domain().filter((_, i) => !(i % 6)))(svg.append("g").attr("transform", "translate(25,0)"));
d3.axisBottom(xScale).tickValues(xScale.domain().filter((_, i) => !(i % 20)))(svg.append("g").attr("transform", "translate(0,80)"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
Normally chart data starts at the bottom of the Y-axis and left of the X-axis. However I have this bar chart that I'm trying to create in D3 that starts 30px above the bottom of the Y-axis and 30px to the right of the left of the X-axis (see mock-up design below). The 30px padding should be maintained on top and to the right as well.
I can't wrap my head around how this should be implemented because the axes lines should still be drawn all the way across but the ticks and the bar chart data should be padded 30px all the way around and the scale should be maintained.
Note: I've removed the rest of the ticks and tick values for clarity. X-axis ticks should be placed in the middle of each bar.
For achieving what you want you'll have to change the settings of the scales. Since you have a bar chart, I'm assuming you have:
A band scale for the x position;
A linear scale for the y position;
Also, because you didn't share any running code, I'll base my answer on this basic bar chart from d3noob.
The first step is setting your paddings:
const horPadding = 30;
const vertPadding = 30;
Now let's change the scales:
Band scale
For setting the padding in the band scale, we'll use scale.paddingOuter.
Because the value passed to that method is a multiple of scale.step() (that is, if you pass 1 it equals to passing scale.step()), we'll use that to calculate how much is 30px in padding. The math is simple:
scale.paddingOuter(horPadding / x.step());
Linear scale
Here the math is a bit more complicated. Basically, we'll calculate how much below zero we have to go to get exactly 30px (assuming that your lower domain is zero, which is a very basic rule in bar charts!).
That can be done with this as the first value of the domain, replacing 0:
-(d3.max(data, function(d) {
return d.sales;
}) * vertPadding / height)
Here, sales is the property used for the bars' height and height is obviously the height used for the scale and the axis. Change them according to your needs.
Then, don't forget to use scale(0) to set the base of the rectangles. In that d3noob code I'm sharing that would be:
return y(0) - y(d.sales);
And this is the result:
var csv = `salesperson,sales
Bob,33
Robin,12
Anne,41
Mark,16
Joe,59
Eve,38`;
const horPadding = 30;
const vertPadding = 30;
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 600 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var x = d3.scaleBand()
.range([0, width])
.padding(0.1);
var y = d3.scaleLinear()
.range([height, 0]);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
const data = d3.csvParse(csv, d3.autoType);
x.domain(data.map(function(d) {
return d.salesperson;
}))
.paddingOuter(horPadding / x.step());
y.domain([-(d3.max(data, function(d) {
return d.sales;
}) * vertPadding / height), d3.max(data, function(d) {
return d.sales;
})])
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x(d.salesperson);
})
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(d.sales);
})
.attr("height", function(d) {
return y(0) - y(d.sales);
});
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
.bar {
fill: steelblue;
}
<script src="//d3js.org/d3.v4.min.js"></script>
Example Code Fiddle
I am working on a heatmap. In which I make the rectangles of heat map (width,height) programmatically depending upon data.
I want to add sliders for zooming on X-axis(time range) , Y-axis(distance range).
I tried d3 scaling options which works fine. But the scales(x-axis, y-axis) don't scale in proportion to rectangles of graph. Like if a rectangle was between 10,20 miles y-axis scale. it goes further than 20 miles on scaling.
Then I tried viewbox on svg. It works . Scales are kept in proportion to graph exactly.
I want to keep the proportion of scales and graph on scaling but not want to increase the size of scales labels as it makes the graph ugly.
Here is code snippet for how I am making my graph initially
d3.json('dateWiseNewDataRight.json',function(err,right_dat){
// console.log(right_dat);
var dateGroups=_.groupBy(right_dat, "DATE");
var data = [];
var x= 0,y=0;
var tlength=0;
var totalDates=Object.keys(dateGroups);
var graphWidth=(total_width/totalDates.length)-6;
for(var key in dateGroups){
tlength=0;
data = [];
y=0;
var segmentMiles=0;
var currentGraphData=dateGroups[key];
var road=currentGraphData[0]['ROAD'];
for(var i = 0; i < currentGraphData.length-1; i++) {
tlength+=currentGraphData[i].MILES;
}
for (var i = 0; i < currentGraphData.length-1; i++) {
var height=0;
segmentMiles=segmentMiles+currentGraphData[i].MILES;
for(var j in times){
if(road!=currentGraphData[i]['ROAD']){
road=currentGraphData[i]['ROAD'];
height=1;
for(var k=0;k<times.length;k++){
data.push({value:20000,x:x,y:y, height:height ,width:col_width,name:"",tmc:"", length:"",road:""});
x=x+col_width;
}
break;
}
else{
col_width=graphWidth/24;
var Congestion= currentGraphData[i][times[j]];
height=(currentGraphData[i].MILES/tlength)*total_height;
//road=leftDat[i]['ROAD'];
data.push({value:Congestion,x:x,y:y, height:height ,width:col_width,name:currentGraphData[i]['NAME'],tmc:currentGraphData[i]['TMC CODE'], length:currentGraphData[i]['MILES'],road:currentGraphData[i]['ROAD'],miles:segmentMiles});
// x=x+col_width;
}
x=x+col_width;
}
y=y+height;
x=0;
}
plotSegmentNames(panelData);
var margin = { top: 50, right: 0, bottom: 10, left: 10 };
$('.heat-map-2').append('<div class="chart-right-'+key+' " style="width: '+graphWidth+'px;float:left;margin:3px;;overflow:hidden"></div>');
var graphDiv='.chart-right-'+key;
var right_Svg = d3.select(graphDiv)
.append("svg")
.attr("class", "chart")
.attr("width",graphWidth)
.attr("height", total_height )
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var right_color_chart = right_Svg.append("g")
.attr("class", "rightHeatMap");
right_color_chart.call(tip);
var color = d3.scale.linear()
.domain([d3.min(data), 1])
.range(["blue", "green"]);
right_color_chart.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d,i) {return d.x; })
.attr("y", function(d,i) { return d.y; })
.attr("width", col_width)
.attr("height", function(d) { return d.height; })
.attr("road",function(d){
return d.road;
})
.attr("miles", function(d) { return d.miles; })
.style("fill", function(d) {return chooseColor(d.value);})
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
var right_xAxisScale = d3.time.scale(),
right_xAxis = d3.svg.axis()
.orient('bottom')
.ticks(d3.time.hour,1)
.tickFormat(d3.time.format('%I %p'))
.tickSubdivide(6);
right_xAxis.scale(right_xAxisScale.range([0,graphWidth]).domain([timeFormat.parse(times[0]),timeFormat.parse(times[times.length-1])]));
right_Svg.append('g')
.attr('class','x axis')
.call(right_xAxis)
.append('text')
.attr('transform','translate('+total_width+',0)');
var yAxisScale = d3.scale.linear()
.range([0,xAxisHeight])
.domain([0,tlength]),
yAxis = d3.svg.axis()
.orient('right')
.ticks(5)
.scale(yAxisScale);
right_Svg.append('g')
.attr('transform','translate('+1+','+0+')')
.attr('class','y axis')
.call(yAxis)
// .append('text')
// .text('Length')
// .attr('transform','translate(100,'+total_height+') rotate(-90)');
}
var testTimes =times;
var distanceRange=[0,60];
$("#scale-slider")
.slider({
animate:true,
range: true,
min: 0,
max: 1440,
step: 24,
values: [0, 1440],
slide: function (e, ui) {
var sliderTime= calculateSiderTime(e,ui);
testTimes=[sliderTime.nob1Time,sliderTime.nob2Time];
$('.x.axis').remove();
$('.y.axis').remove();
/* redrawHeatMaps('left',left_color_chart,'leftHeatMap',leftDat,testTimes,tlength);
redrawHeatMaps('right',right_color_chart,'rightHeatMap',right_dat,testTimes,tlength);*/
redrawYHeatMaps('left',left_color_chart,'leftHeatMap',leftDat,testTimes,tlength,distanceRange);
redrawYHeatMaps('right',right_color_chart,'rightHeatMap',right_dat,testTimes,tlength,distanceRange);
}
})
.on("slidechange", function( e, ui ) {
});
$("#distance-slider")
.slider({
animate:true,
range: true,
min: 0,
max: 60,
step: 5,
values: [0, 60],
slide: function (e, ui) {
distanceRange=ui.values;
$('.x.axis').remove();
$('.y.axis').remove();
// left_color_chart.attr("transform", "translate("+ d3.event.translate + ")scale(" + d3.event.scale + ")");
redrawYHeatMaps('left',left_color_chart,'leftHeatMap',leftDat,testTimes,tlength,distanceRange);
redrawYHeatMaps('right',right_color_chart,'rightHeatMap',right_dat,testTimes,tlength,distanceRange);
$('.slider-distance1').html(ui.values[0]);
$('.slider-distance2').html( ui.values[1]);
}
})
.on("slidechange", function( e, ui ) {
});
});
Just only edit yAxisScale's domain when scale event is occurred.
Here is updated fiddle.
First, remove y method in zoom. It helps auto-scaling for an axis but, it is not your case. I'll give an explanation at last.
zoom = d3.behavior.zoom()
.scaleExtent([0, 5])
.scale(1)
.on("zoom", zoomed);
After, adjust yAxisScale domain when scale value is changed.
function zoomed() {
yAxisScale.domain([0, tlength / d3.event.scale]); // added
leftSvg.select(".y.axis").call(yAxis);
zoomIn();
}
Why use division not multiplication? Because if you scale twice, axis values only shown up a half in comparison with its original values.
If you use zoom's y method, it is going to auto-scale yAxisScale using multiplication. So, I said it is not your case above.
I assume you may want to zoom, via mousewheel, into the heatmap cells with the axis not scaling.
Here are a few suggestions. Give them a try.
1) Change zoom behavior to:
zoom = d3.behavior.zoom().scaleExtent([0,5]).scale(1).on("zoom", zoomIn);
2) Remove the zoomed function and change the zoomIn:
function zoomIn(){
var t = d3.event.translate,
s = d3.event.scale;
left_color_chart.attr("transform","translate("+t+")scale("+s+")")
}
I'm trying to make a singular column for a bar graph in d3.js, the purpose of which is to bar-graph the coefficients of the other line graph in my program. I'm familiar with how they are made when the data is in .csv format, but in this case right now I'm trying to make it from three variables. The three variables are:
var xtwo;
var xone;
var xzero;
which have values put into them in a later part. I've built a skeleton based on what I know and have seen, which is right here:
//Bar Graph
var barmargin = {
top: 20,
right: 20,
bottom: 30,
left: 60
},
barwidth = 500 - barmargin.left - barmargin.right,
barheight = 350 - barmargin.top - barmargin.bottom;
//X scale
var barx = d3.scale.ordinal()
.rangeRoundBands([0, barwidth], .1);
//Y scale
var bary = d3.scale.linear()
.rangeRound([barheight, 0]);
//bar graph colors
var color = d3.scale.ordinal()
.range(["#FF5C33", "#F48C00", "#FFFF5C"]);
// Use X scale to set a bottom axis
var barxAxis = d3.svg.axis()
.scale(barx)
.orient("bottom");
// Same for y
var baryAxis = d3.svg.axis()
.scale(bary)
.orient("left")
.tickFormat(d3.format(".2s"));
// Addchart to the #chart div
var svg = d3.select("#chart").append("svg")
.attr("width", barwidth + barmargin.left + barmargin.right)
.attr("height", barheight + barmargin.top + barmargin.bottom)
.append("g")
.attr("transform", "translate(" + barmargin.left + "," + barmargin.top + ")");
//Where data sorting happens normally
var bardata.data([xzero, xone, xtwo]);
//Y domain is from zero to 5
y.domain([0, 5]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + barheight + ")")
.call(barxAxis);
svg.append("g")
.attr("class", "y axis")
.call(baryAxis);
bardata.selectAll("rect")
.data(function(d) {
return d.types;
})
.enter().append("rect")
.attr("width", barx.rangeBand())
.attr("y", function(d) {
return bary(d.y1);
})
.attr("height", function(d) {
return bary(d.y0) - bary(d.y1);
})
.style("fill", function(d) {
return color(d.name);
});
but I can't really figure out how to make it work correctly. I thought that I could manually make the .data but it seems to not be working that way.
Full code if necessary: https://jsfiddle.net/tqj5maza/1/
Broadly speaking: you want to create three bars, sat on top of each other, from three different values. The three values will be enough to scale the bars, but they in themselves won't be enough to position the bars- each bar needs to be offset by the size of the bars that have gone before.
d3 can only read the values that are already in the data you send it- you can't really access the previous values as you go, as each datum is bound to a separate element. Thus, what you need to do is to create some new data, which has all the numbers required to display it.
Here's one way that you might do that:
var canvas = d3.select("#canvas").append("svg").attr({width: 400, height: 400})
var values = [50, 90, 30]
var colours = ['#FA0', '#0AF', '#AF0']
var data = []
var yOffset = 0
//Process the data
for(var i = 0; i < values.length; i++) {
var datum = {
value : values[i],
colour : colours[i],
x: 0,
y: yOffset
}
yOffset += values[i]
data.push(datum)
}
var bars = canvas.selectAll('rect').data(data)
bars
.enter()
.append('rect')
.attr({
width : 30,
height : function(d) {
return d.value
},
y : function(d) {
return d.y //
}
})
.style({
fill : function(d) {
return d.colour
}
})
http://jsfiddle.net/r3sazt7m/
d3's layout functions all do more or less this- you pass them a set of data, and they return new data containing the values that the SVG drawing instructions require.