Arranging graphs on one screen - javascript

I wan to four graphs side-by-side linearly but they keep overlapping. I am using d3.js and I would like to draw these four graphs side by side.
I tried drawing each graph in its own svg tag and then combine them but it doesn't work.
<script>
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
var TelescopeData = [
{ Average: 2000, TelescopeName: "1 meter" },
{ Average: 3000, TelescopeName: "1.9 meter" },
{ Average: 4000, TelescopeName: "Lesedi" }
];
var padding = { top: 40, right: 20, bottom: 30, left: 75 };
const innerWidth = width - padding.left - padding.right;
const innerHeight = height - padding.top - padding.bottom;
var colors = ["red", "black", "green"];
var yScale = d3
.scaleLinear()
.domain([0, d3.max(TelescopeData, d => d.Average)])
.range([innerHeight, 0])
.nice();
var xScale = d3
.scaleBand()
.domain(
TelescopeData.map(d => {
return d.TelescopeName;
})
)
.range([0, innerWidth])
.padding(0.4);
//xAxis
const xAxis = svg
.append("g")
.classed("xAxis", true)
.attr(
"transform",
`translate(${padding.left},${innerHeight + padding.top})`
)
.call(d3.axisBottom(xScale));
//yAxis
const yAxis = svg
.append("g")
.classed("yAxis", true)
.attr("transform", `translate(${padding.left},${padding.top})`)
.call(d3.axisLeft(yScale));
// now adding the data
const rectGrp = svg
.append("g")
.attr("transform", `translate(${padding.left},${padding.top})`);
rectGrp
.selectAll("rect")
.data(TelescopeData)
.enter()
.append("rect")
.attr("width", xScale.bandwidth())
.attr("height", (d, i) => {
return innerHeight - yScale(d.Average);
})
.attr("x", d => {
return xScale(d.TelescopeName);
})
.attr("y", function(d) {
return yScale(d.Average);
})
.attr("fill", (d, i) => {
return colors[i];
});
rectGrp
.append("text")
.attr("y", -20)
.attr("x", 50)
.text("Quarterly Average");
</script>
I expect to see the code attached here to be used for drawing 3 other graphs side-side with the first one

Are you wanting to have four graphs shown side by side on the screen next to each other?
That is what your question seems to imply, without having an example of what you want the output to look like. The other option would be, how to merge four graphs into one graph, which is an entirely different question.
Here is a very simple HTML document that will put the four graphs side by side on the screen. I think your problem was probably that you were selecting the element to draw to using the SVG selector instead of assigning an id to each different element and then selecting each element id.
<html>
<head>
<title>Example</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
function drawGraph(elementId) {
const svg = d3.select(elementId);
const width = +svg.attr("width");
const height = +svg.attr("height");
let TelescopeData = [
{ Average: 2000, TelescopeName: "1 meter" },
{ Average: 3000, TelescopeName: "1.9 meter" },
{ Average: 4000, TelescopeName: "Lesedi" }
];
let padding = { top: 40, right: 20, bottom: 30, left: 75 };
const innerWidth = width - padding.left - padding.right;
const innerHeight = height - padding.top - padding.bottom;
let colors = ["red", "black", "green"];
let yScale = d3
.scaleLinear()
.domain([0, d3.max(TelescopeData, d => d.Average)])
.range([innerHeight, 0])
.nice();
let xScale = d3
.scaleBand()
.domain(
TelescopeData.map(d => {
return d.TelescopeName;
})
)
.range([0, innerWidth])
.padding(0.4);
//xAxis
const xAxis = svg
.append("g")
.classed("xAxis", true)
.attr("transform",`translate(${padding.left},${innerHeight + padding.top})`)
.call(d3.axisBottom(xScale));
//yAxis
const yAxis = svg
.append("g")
.classed("yAxis", true)
.attr("transform", `translate(${padding.left},${padding.top})`)
.call(d3.axisLeft(yScale));
// now adding the data
const rectGrp = svg
.append("g")
.attr("transform", `translate(${padding.left},${padding.top})`);
rectGrp
.selectAll("rect")
.data(TelescopeData)
.enter()
.append("rect")
.attr("width", xScale.bandwidth())
.attr("height", (d, i) => {
return innerHeight - yScale(d.Average);
})
.attr("x", d => {
return xScale(d.TelescopeName);
})
.attr("y", function(d) {
return yScale(d.Average);
})
.attr("fill", (d, i) => {
return colors[i];
});
rectGrp
.append("text")
.attr("y", -20)
.attr("x", 50)
.text("Quarterly Average");
}
drawGraph("#testsvg1");
drawGraph("#testsvg2");
drawGraph("#testsvg3");
drawGraph("#testsvg4");
</script>
</head>
<body>
<svg id="#testsvg1" width="200" height="200"></svg>
<svg id="#testsvg2" width="200" height="200"></svg>
<svg id="#testsvg3" width="200" height="200"></svg>
<svg id="#testsvg4" width="200" height="200"></svg>
</body>
</html>

Related

d3js beeswarm with force simulation

I try to do a beeswarm plot with different radius; inspired by this code
The issue I have, is that my point are offset regarding my x axis:
The point on the left should be at 31.7%. I don't understand why, so I would appreciate if you could guide me. This could be improved by changing the domain of x scale, but this can't match the exact value; same issue if I remove the d3.forceCollide()
Thank you,
Data are available here.
Here is my code:
$(document).ready(function () {
function tp(d) {
return d.properties.tp60;
}
function pop_mun(d) {
return d.properties.pop_mun;
}
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 1280 - margin.right - margin.left,
height = 300 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var z = d3.scaleThreshold()
.domain([.2, .3, .4, .5, .6, .7])
.range(["#35ff00", "#f1a340", "#fee0b6",
"#ff0000", "#998ec3", "#542788"]);
var loading = svg.append("text")
.attr("x", (width) / 2)
.attr("y", (height) / 2)
// .attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment please…");
var formatPercent = d3.format(".0%"),
formatNumber = d3.format(".0f");
d3.json('static/data/qp_full.json').then(function (data) {
features = data.features
//1 create scales
var x = d3.scaleLinear()
.domain([0, d3.max(features, tp)/100])
.range([0, width - margin.right])
var y = d3.scaleLinear().domain([0, 0.1]).range([margin.left, width - margin.right])
var r = d3.scaleSqrt().domain([0, d3.max(features, pop_mun)])
.range([0, 25]);
//2 create axis
var xAxis = d3.axisBottom(x).ticks(20)
.tickFormat(formatPercent);
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
var nodes = features.map(function (node, index) {
return {
radius: r(node.properties.pop_mun),
color: '#ff7f0e',
x: x(node.properties.tp60 / 100),
y: height + Math.random(),
pop_mun: node.properties.pop_mun,
tp60: node.properties.tp60
};
});
function tick() {
for (i = 0; i < nodes.length; i++) {
var node = nodes[i];
node.cx = node.x;
node.cy = node.y;
}
}
setTimeout(renderGraph, 10);
function renderGraph() {
// Run the layout a fixed number of times.
// The ideal number of times scales with graph complexity.
// Of course, don't run too long—you'll hang the page!
const NUM_ITERATIONS = 1000;
var force = d3.forceSimulation(nodes)
.force('charge', d3.forceManyBody().strength(-3))
.force('center', d3.forceCenter(width / 2, height/2))
.force('x', d3.forceX(d => d.x))
.force('y', d3.forceY(d => d.y))
.force('collide', d3.forceCollide().radius(d => d.radius))
.on("tick", tick)
.stop();
force.tick(NUM_ITERATIONS);
force.stop();
svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", d => d.radius)
.style("fill", d => z(d.tp60/100))
.on("mouseover", function (d, i) {
d3.select(this).style('fill', "orange")
console.log(i.tp60,i)
svg.append("text")
.attr("id", "t")
.attr("x", function () {
return d.x - 50;
})
.attr("y", function () {
return d.y - 50;
})
.text(function () {
return [x.invert(i.x), i.tp60]; // Value of the text
})
})
.on("mouseout", function (d, i) {
d3.select("#t").remove(); // Remove text location
console.log(i)
d3.select(this).style('fill', z(i.tp60/100));
});
loading.remove();
}
})
})

Image is not showing in appended d3 gantt chart

I am trying to append some image in Gantt chart horizontal bar but it's not showing. But tags getting appended inside rect tag.
here is the code
const start_date = "2020-01-15";
const end_date = "2020-05-05";
const MARGIN = {
left: 50,
right: 50,
top: 50,
bottom: 50
}
const svg = d3.select('.graph').append('svg')
// console.log(svg)
const width = 800
const height = 600
const ticks = ['task1', 'task2', 'task3', 'task4', 'task5', 'task6'];
const x = d3.scaleTime().domain([new Date(start_date), new Date(end_date)]).range([0, (width - (MARGIN.left + MARGIN.right))])
const y = d3.scaleBand()
.domain(ticks)
.range([0, 400])
// .range([0, 50,100, 150, 200, 250, 300, 350,height])
const X = v => {
const minDate = d3.min(v, d => new Date(d.initDate))
const maxDate = d3.max(v, d => new Date(d.endDate))
return d3.scaleUtc().domain([minDate, maxDate]).range([MARGIN.right, width - MARGIN.left]);
}
const x_axis = d3.axisTop(x).ticks(6);
const y_axis = d3
.axisLeft(y)
.ticks(6)
.tickFormat(function(d, i){return ticks[i]})
.tickSize(15);
svg.attr('width', width).attr('height', height).attr('viewBox', `0 0 700 800`)
let chart = svg.append("g").attr('class', 'chart-holder').attr('transform', `translate(50,50)`);
chart
.append("g")
.attr('class', 'x axis')
.attr("transform", "translate(0,50)")
.call(x_axis);
chart
.append("g")
.attr('class', 'y axis')
.attr("transform", "translate(0, 50)")
.call(y_axis);
chart
.selectAll('rect')
.data([...this.state.data])
.enter()
.append('rect')
.attr('height', 40)
.attr('x', function(d){
return x(new Date(d.initDate))
})
.attr('y', function(d, i){
return y(d.name);
})
.attr('width', function(d, i){
return x(new Date(d.endDate)) - x(new Date(d.initDate))
})
.attr('rx', 17)
.attr('ry', 17)
.attr('fill', "blue")
.attr('transform', `translate(50, 60)`)
.append('image')
.attr("src", "https://miro.medium.com/max/1000/1*tv9pIQPhwumDnYBfCoapYg.jpeg")
.attr("width", 30)
.attr("height", 30)
and things I have already tried
xlink:href instead of href
svg:image instead of image
You cannot append under . Replace with , then append and under
const items =
chart.selectAll('g.item').data(...).enter().append('g').classed('item', true);
items.append('rect')...
items.append('image')...

d3.js stacked bar chart - modify stack order logic

I would like to create a stacked bar chart whereby the order of the rects is determined by data values (i.e. largest to smallest, tallest to shortest, richest to poorest, ect). To the best of my knowledge, after stacking data, the initial order seems to be preserved. This can be seen in my snippet, hardcoded data lets us see what's happening before and after d3.stack(). Note that the third rect fmc3 goes from being the third largest in t1 to the largest of all rects in t3 despite its position in the stack remaining the same:
var margins = {top:100, bottom:300, left:100, right:100};
var height = 600;
var width = 900;
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 = [
{period:'t1', fmc1:2, fmc2:5, fmc3:6, fmc4:9, fmc5:10},
{period:'t2', fmc1:3, fmc2:4, fmc3:9, fmc4:8, fmc5:11},
{period:'t3', fmc1:3, fmc2:5, fmc3:15, fmc4:12, fmc5:10},
];
var groups = d3.map(data, function(d){return(d.period)}).keys();
var subgroups = Object.keys(data[0]).slice(1);
var stackedData = d3.stack()
.keys(subgroups)
(data);
//console.log(stackedData);
var yScale = d3.scaleLinear()
.domain([0,80])
.range([height,0]);
var xScale = d3.scaleBand()
.domain(['t1','t2','t3'])
.range([0,width])
.padding([.5]);
var colorScale = d3.scaleOrdinal()
.domain(subgroups)
.range(["#003366","#366092","#4f81b9","#95b3d7","#b8cce4","#e7eef8","#a6a6a6","#d9d9d9","#ffffcc","#f6d18b","#e4a733","#b29866","#a6a6a6","#d9d9d9","#e7eef8","#b8cce4","#95b3d7","#4f81b9","#366092","#003366"].reverse());
graphGroup.append("g")
.selectAll("g")
.data(stackedData)
.enter().append("g")
.attr("fill", function(d) { return colorScale(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return xScale(d.data.period); })
.attr("y", function(d) { return yScale(d[1]); })
.attr("height", function(d) { return yScale(d[0]) - yScale(d[1]); })
.attr("width",xScale.bandwidth());
<script src="https://d3js.org/d3.v5.min.js"></script>
I suspect preserving the initial order may be somewhat necessary to calculate adjacent rects in the stack. However, on the other hand, ordering data before visualizing it is a very common, even preferred practice in the field of visualization and I would be surprised if no one has found a solution to this issue yet.
Question
Given there are no built-in features to specify the ordering of the rects in a stack, how should I approach the sort logic to achieve largest to smallest ordering?
Well, there is a built-in feature to specify the order, which is stack.order(). However, it specifies the order computing the entire series, not every single value the stack (which I believe is what you want... in that case, you'll have to create your own function).
So, for instance, using stack.order(d3.stackOrderDescending):
var margins = {
top: 0,
bottom: 0,
left: 0,
right: 0
};
var height = 300;
var width = 500;
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 = [{
period: 't1',
fmc1: 2,
fmc2: 5,
fmc3: 6,
fmc4: 9,
fmc5: 10
},
{
period: 't2',
fmc1: 3,
fmc2: 4,
fmc3: 9,
fmc4: 8,
fmc5: 11
},
{
period: 't3',
fmc1: 3,
fmc2: 5,
fmc3: 15,
fmc4: 12,
fmc5: 10
},
];
var groups = d3.map(data, function(d) {
return (d.period)
}).keys();
var subgroups = Object.keys(data[0]).slice(1);
var stackedData = d3.stack()
.keys(subgroups)
.order(d3.stackOrderDescending)
(data);
//console.log(stackedData);
var yScale = d3.scaleLinear()
.domain([0, 60])
.range([height, 0]);
var xScale = d3.scaleBand()
.domain(['t1', 't2', 't3'])
.range([0, width])
.padding([.5]);
var colorScale = d3.scaleOrdinal()
.domain(subgroups)
.range(["#003366", "#366092", "#4f81b9", "#95b3d7", "#b8cce4", "#e7eef8", "#a6a6a6", "#d9d9d9", "#ffffcc", "#f6d18b", "#e4a733", "#b29866", "#a6a6a6", "#d9d9d9", "#e7eef8", "#b8cce4", "#95b3d7", "#4f81b9", "#366092", "#003366"].reverse());
graphGroup.append("g")
.selectAll("g")
.data(stackedData)
.enter().append("g")
.attr("fill", function(d) {
return colorScale(d.key);
})
.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return xScale(d.data.period);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("height", function(d) {
return yScale(d[0]) - yScale(d[1]);
})
.attr("width", xScale.bandwidth());
<script src="https://d3js.org/d3.v5.min.js"></script>

D3 combination of bar and area chart

I am wondering is it possible to achieve the combination of area and bar chart in the way shown in the screenshot below?
Along with making the area in between clickable for some other action.
It would be really helpful if you can guide me to some of the examples to get an idea how to achieve the same.
I posted a codepen here. That creates a bar chart, and then separate area charts between each bar chart.
const BarChart = () => {
// set data
const data = [
{
value: 48,
label: 'One Rect'
},
{
value: 32,
label: 'Two Rect'
},
{
value: 40,
label: 'Three Rect'
}
];
// set selector of container div
const selector = '#bar-chart';
// set margin
const margin = {top: 60, right: 0, bottom: 90, left: 30};
// width and height of chart
let width;
let height;
// skeleton of the chart
let svg;
// scales
let xScale;
let yScale;
// axes
let xAxis;
let yAxis;
// bars
let rect;
// area
let areas = [];
function init() {
// get size of container
width = parseInt(d3.select(selector).style('width')) - margin.left - margin.right;
height = parseInt(d3.select(selector).style('height')) - margin.top - margin.bottom;
// create the skeleton of the chart
svg = d3.select(selector)
.append('svg')
.attr('width', '100%')
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
xScale = d3.scaleBand().padding(0.15);
xAxis = d3.axisBottom(xScale);
yScale = d3.scaleLinear();
yAxis = d3.axisLeft(yScale);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(0, ${height})`);
svg.append('g')
.attr('class', 'y axis');
svg.append('g')
.attr('class', 'x label')
.attr('transform', `translate(10, 20)`)
.append('text')
.text('Value');
xScale
.domain(data.map(d => d.label))
.range([0, width])
.padding(0.3);
yScale
.domain([0, 75])
.range([height, 0]);
xAxis
.scale(xScale);
yAxis
.scale(yScale);
rect = svg.selectAll('rect')
.data(data);
rect
.enter()
.append('rect')
.style('fill', d => '#00BCD4')
.attr('y', d => yScale(d.value))
.attr('height', d => height - yScale(d.value))
.attr('x', d => xScale(d.label))
.attr('width', xScale.bandwidth());
// call the axes
svg.select('.x.axis')
.call(xAxis);
svg.select('.y.axis')
.call(yAxis);
// rotate axis text
svg.select('.x.axis')
.selectAll('text')
.attr('transform', 'rotate(45)')
.style('text-anchor', 'start');
if (parseInt(width) >= 600) {
// level axis text
svg.select('.x.axis')
.selectAll('text')
.attr('transform', 'rotate(0)')
.style('text-anchor', 'middle');
}
data.forEach(
(d, i) => {
if (data[i + 1]) {
areas.push([
{
x: d.label,
y: d.value
},
{
x: data[i + 1].label,
y: data[i + 1].value
}
]);
}
}
);
areas = areas.filter(
d => Object.keys(d).length !== 0
);
areas.forEach(
a => {
const area = d3.area()
.x((d, i) => {
return i === 0 ?
xScale(d.x) + xScale.bandwidth() :
xScale(d.x);
})
.y0(height)
.y1(d => yScale(d.y));
svg.append('path')
.datum(a)
.attr('class', 'area')
.style('fill', d => '#B2EBF2')
.attr('d', area)
.on('click', d => {
console.log('hello click!');
});
}
)
}
return { init };
};
const myChart = BarChart();
myChart.init();
#bar-chart {
height: 500px;
width: 100%;
}
<script src="https://unpkg.com/d3#5.2.0/dist/d3.min.js"></script>
<div id="bar-chart"></div>
After creating the bar chart, I repackage the data to make it conducive to creating an area chart. I created an areas array where each item is going to be a separate area chart. I'm basically taking the values for the first bar and the next bar, and packaging them together.
data.forEach(
(d, i) => {
if (data[i + 1]) {
areas.push([
{
x: d.label,
y: d.value
},
{
x: data[i + 1].label,
y: data[i + 1].value
}
]);
}
}
);
areas = areas.filter(
d => Object.keys(d).length !== 0
);
I then iterate through each element on areas and create the area charts.
The only tricky thing here, I think, is getting the area chart to span from the end of the first bar to the start of the second bar, as opposed to from the end of the first bar to the end of the second bar. To accomplish this, I added a rectangle width from my x-scale to the expected x value of the area chart when the first data point is being dealt with, but not the second.
I thought of this as making two points on a line: one for the first bar and one for the next bar. D3's area function can shade all the area under a line. So, the first point on my line should be the top-right corner of the first bar. The second point should be the top-left corner of the next bar.
Attaching a click event at the end is pretty straightforward.
areas.forEach(
a => {
const area = d3.area()
.x((d, i) => {
return i === 0 ?
xScale(d.x) + xScale.bandwidth() :
xScale(d.x);
})
.y0(height)
.y1(d => yScale(d.y));
svg.append('path')
.datum(a)
.attr('class', 'area')
.style('fill', d => '#B2EBF2')
.attr('d', area)
.on('click', d => {
console.log('hello click!');
});
}
)
In the example below, I have combined a simple bar chart (like in this famous bl.lock) with some polygons in between. I guess it could also be achieved with a path.
const data = [
{ letter: "a", value: 9 },
{ letter: "b", value: 6 },
{ letter: "c", value: 3 },
{ letter: "d", value: 8 }
];
const svg = d3.select("#chart");
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
const width = +svg.attr("width") - margin.left - margin.right;
const height = +svg.attr("height") - margin.top - margin.bottom;
const xScale = d3.scaleBand()
.rangeRound([0, width]).padding(0.5)
.domain(data.map(d => d.letter));
const yScale = d3.scaleLinear()
.rangeRound([height, 0])
.domain([0, 10]);
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(yScale));
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", d => xScale(d.letter))
.attr("y", d => yScale(d.value))
.attr("width", xScale.bandwidth())
.attr("height", d => height - yScale(d.value));
// Add polygons
g.selectAll(".area")
.data(data)
.enter().append("polygon")
.attr("class", "area")
.attr("points", (d,i,nodes) => {
if (i < nodes.length - 1) {
const dNext = d3.select(nodes[i + 1]).datum();
const x1 = xScale(d.letter) + xScale.bandwidth();
const y1 = height;
const x2 = x1;
const y2 = yScale(d.value);
const x3 = xScale(dNext.letter);
const y3 = yScale(dNext.value);
const x4 = x3;
const y4 = height;
return `${x1},${y1} ${x2},${y2} ${x3},${y3} ${x4},${y4} ${x1},${y1}`;
}
})
.on("click", (d,i,nodes) => {
const dNext = d3.select(nodes[i + 1]).datum();
const pc = Math.round((dNext.value - d.value) / d.value * 100.0);
alert(`${d.letter} to ${dNext.letter}: ${pc > 0 ? '+' : ''}${pc} %`);
});
.bar {
fill: steelblue;
}
.area {
fill: lightblue;
}
.area:hover {
fill: sandybrown;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="400" height="300" id="chart"></svg>

How to use rangeRound in scaleTime in d3 v4?

I am trying to create a divergent bar chart which uses time scale(date) as x-axis. I am having trouble using ScaleBands with date, the date labels are overlapping.
This is what I got so far. https://jsfiddle.net/14ch7yeo/ when I use scaleTime, Unfortunately, the graph does not load.
I need to use zoom and brush on this graph.
<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data = [{"Date":"2015-01-02T00:00:00.000Z","Buy":554646.5,"Sell":-406301.3547},{"Date":"2015-02-02T00:00:00.000Z","Buy":565499.5,"Sell":-673692.5697},{"Date":"2015-03-02T00:00:00.000Z","Buy":421954.5,"Sell":-571685.4629},{"Date":"2015-04-02T00:00:00.000Z","Buy":466242.0,"Sell":-457477.7121},{"Date":"2015-05-02T00:00:00.000Z","Buy":350199.7,"Sell":-579682.8772},{"Date":"2015-06-02T00:00:00.000Z","Buy":391035.1,"Sell":-338816.6205},{"Date":"2015-07-02T00:00:00.000Z","Buy":437644.6,"Sell":-502329.557},{"Date":"2015-08-02T00:00:00.000Z","Buy":291978.9,"Sell":-504067.0329},{"Date":"2015-09-02T00:00:00.000Z","Buy":360913.8,"Sell":-489519.6652},{"Date":"2015-10-02T00:00:00.000Z","Buy":505799.1,"Sell":-723353.7089},{"Date":"2015-11-02T00:00:00.000Z","Buy":510691.0,"Sell":-374061.8139},{"Date":"2015-12-02T00:00:00.000Z","Buy":527757.1,"Sell":-597800.0116},{"Date":"2016-01-02T00:00:00.000Z","Buy":564799.1,"Sell":-451779.1593},{"Date":"2016-02-02T00:00:00.000Z","Buy":336533.7,"Sell":-522601.1707},{"Date":"2016-03-02T00:00:00.000Z","Buy":460684.6,"Sell":-643556.0079999999},{"Date":"2016-04-02T00:00:00.000Z","Buy":428388.1,"Sell":-349216.2376},{"Date":"2016-05-02T00:00:00.000Z","Buy":525459.5,"Sell":-597258.4075},{"Date":"2016-06-02T00:00:00.000Z","Buy":677659.1,"Sell":-513192.107},{"Date":"2016-07-02T00:00:00.000Z","Buy":365612.8,"Sell":-287845.8089},{"Date":"2016-07-03T00:00:00.000Z","Buy":358775.2,"Sell":-414573.209}]
var parseTime = d3.utcParse("%Y-%m-%dT%H:%M:%S.%LZ");
data.forEach(d => {
d["Date"] = parseTime(d["Date"]);
})
var series = d3.stack()
.keys(["Buy", "Sell"])
.offset(d3.stackOffsetDiverging)
(data);
var svg = d3.select("svg"),
margin = {top: 20, right: 30, bottom: 30, left: 60},
width = +svg.attr("width"),
height = +svg.attr("height");
var x = d3.scaleBand()
.domain(data.map(function(d) { return d['Date']; }))
.rangeRound([margin.left, width - margin.right])
.padding(0.1);
var y = d3.scaleLinear()
.domain([d3.min(series, stackMin), d3.max(series, stackMax)])
.rangeRound([height - margin.bottom, margin.top]);
var z = d3.scaleOrdinal()
.range(["green","red"]);
svg.append("g")
.selectAll("g")
.data(series)
.enter().append("g")
.attr("fill", function(d) { return z(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("width", x.bandwidth)
.attr("x", function(d) { return x(d.data["Date"]); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
svg.append("g")
.attr("transform", "translate(0,"+ (height-margin.top) + ")")
.call(d3.axisBottom(x));
svg.append("g")
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y));
function stackMin(serie) {
return d3.min(serie, function(d) { return d[0]; });
}
function stackMax(serie) {
return d3.max(serie, function(d) { return d[1]; });
}
</script>
d3.scaleTime has to be treated differently on a number of fronts.
The scale doesn't take padding as an argument:
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.Date; }))
.rangeRound([margin.left, width - margin.right]);
Time is continuous rather than discrete, so the widths of the bars need to be calculated manually, as a ratio of rect and series.length. I got this to work, but maybe you want something more elegant:
.attr("width", width/series.length - 450)

Categories