Making Chart render for negative value efficiently - javascript

This thread is a continuation of below:
Trying to make dynamic D3 Chart with Bar and Difference line/value
I was trying to test the chart using negative values (i.e -ve) but it is not working properly. I made some changes in the yAxis scale Domain by passing dynamic min value instead of static "0" value.
At "0" there should be a dotted line and the -ve value should display down of that and +ve (positive values) values start from "0" and height till its value.
Please help me to solve the issue.
Below is the Code:
var barData = [{
"Time": "2019",
"Value": 5388
},
{
"Time": "2020",
"Value": 6453
},
{
"Time": "2021",
"Value": -4345
},
{
"Time": "2022",
"Value": 7345
},
{
"Time": "2023",
"Value": 8345
},
{
"Time": "2024",
"Value": 5345
},
{
"Time": "2025",
"Value": 6345
},
{
"Time": "2026",
"Value": 3345
}
];
const container = d3.select('#graph');
const divWidth = parseInt(container.style('width'));
const divHeight = parseInt(container.style('height'));
// Consider this width and Height are dynamic for div "graphID" because I am trying to responsive design
const margin = {
top: 30,
right: 50,
bottom: 50,
left: 50
};
const width = divWidth - margin.left - margin.right;
const height = divHeight - margin.top - margin.bottom;
//To add svg in the visualization node i.e Dome node
const svg = container.append("svg")
.attr("width", divWidth)
.attr("height", divHeight)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
//To add tooltip for bar
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
const defs = svg.append("defs");
const marker = defs.append("marker")
.attr("id", "arrowhead")
.attr("markerWidth", "10")
.attr("markerHeight", "7")
.attr("refX", "0")
.attr("refY", "3.5")
.attr("orient", "auto")
const polygon = marker.append("polygon")
.attr("fill", "gray")
.attr("points", "0 0, 10 3.5, 0 7")
const xScale = d3.scaleBand()
.domain(barData.map(d => d.Time))
.range([0, width + margin.right]);
const xAxis = d3.axisBottom(xScale);
//Adding g attribute to svg for x axis
svg.append('g')
.attr("transform", `translate(0,${height})`)
.call(xAxis);
const yAxisMax = barData.reduce((max, item) => Math.max(max, item.Value), 0) * 1.5;
const yAxisMin = barData.reduce((min, item) => Math.min(min, item.Value), 0) * 1.5;
const yScale = d3.scaleLinear()
.domain([yAxisMin, yAxisMax])
.range([height, 0]);
const yAxis = d3.axisLeft(yScale).ticks(4);
svg.append('g')
.call(yAxis);
const bars = svg.selectAll('g.bar')
.data(barData)
.enter()
.append('g')
.classed('bar', true)
.attr('transform', d => `translate(${xScale(d.Time) + xScale.bandwidth() / 2}, 0)`);
/*
const staticColor = "steelblue",
highlightColor = "orange";
var sheet = document.createElement('style')
sheet.innerHTML = ".bar {fill: "+staticColor+"} .highlight {fill:"+highlightColor+"}";
document.body.appendChild(sheet);
*/
bars.append('rect')
.attr('x', -20)
.attr('width', 40)
.attr('y', d => yScale(d.Value))
.attr('height', d => height - yScale(d.Value))
.attr('fill', 'blue')
//.attr("class", "bar")
.on("mousemove", onMouseOver)
.on("mouseout", onMouseOut);
function onMouseOver(d, i) {
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html("Year: " + (d.Time) + "<br>" + "Value: " + (d.Value));
d3.select(this).attr('fill', "#eec42d");
//d3.select(this).attr('class', 'highlight');
//this.setState({ fillColour: 'green' });
}
function onMouseOut(d, i) {
tooltip.style("display", "none");
d3.select(this).attr('fill', "blue");
//d3.select(this).attr('class', 'bar');
//this.setState({ fillColour: 'blue' });
}
bars.append('text')
.text(d => d.Value)
.attr('text-anchor', 'middle')
.attr('y', d => yScale(d.Value))
.attr('dy', -5);
bars.filter((d, i) => i < barData.length - 1)
.append('path')
.attr('d', (d, i) => `M 5,${yScale(d.Value) - 20} V ${Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 60} H ${xScale.bandwidth() - 5} V ${yScale(barData[i + 1].Value) - 25}`)
.style('stroke', 'gray')
.style('fill', 'none')
.attr('marker-end', 'url(#arrowhead)')
bars.filter((d, i) => i < barData.length - 1)
.append('rect')
.attr('x', 15)
.attr('y', (d, i) => Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 70)
.attr('width', xScale.bandwidth() - 30)
.attr('height', 20)
.attr('rx', 10)
.style('fill', 'white')
.style('stroke', 'gray');
bars.filter((d, i) => i < barData.length - 1)
.append('text')
.text((d, i) => `${barData[i + 1].Value > d.Value ? '+' : ''}${Math.round((barData[i + 1].Value / d.Value * 100) - 100)}%`)
.attr('x', xScale.bandwidth() / 2)
.attr('y', (d, i) => Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 56)
.attr('text-anchor', 'middle')
.style('fill', 'black');
#graph {
width: 600px;
height: 500px;
}
text {
font-size: 12px;
font-family: "Ubuntu";
}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 5px;
text-align: left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="graph">
</div>

Try this:
const barData = [{
"Time": "2019",
"Value": 5388
},
{
"Time": "2020",
"Value": 6453
},
{
"Time": "2021",
"Value": -4345
},
{
"Time": "2022",
"Value": 7345
},
{
"Time": "2023",
"Value": 8345
},
{
"Time": "2024",
"Value": 5345
},
{
"Time": "2025",
"Value": 6345
},
{
"Time": "2026",
"Value": 3345
}
];
const container = d3.select('#graph');
const divWidth = parseInt(container.style('width'));
const divHeight = parseInt(container.style('height'));
// Consider this width and Height are dynamic for div "graphID" because I am trying to responsive design
const margin = {
top: 50,
right: 50,
bottom: 50,
left: 50
};
const width = divWidth - margin.left - margin.right;
const height = divHeight - margin.top - margin.bottom;
//To add svg in the visualization node i.e Dome node
const svg = container.append("svg")
.attr("width", divWidth)
.attr("height", divHeight)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
//To add tooltip for bar
const tooltip = d3.select("body").append("div").attr("class", "toolTip");
const defs = svg.append("defs");
const marker = defs.append("marker")
.attr("id", "arrowhead")
.attr("markerWidth", "10")
.attr("markerHeight", "7")
.attr("refX", "0")
.attr("refY", "3.5")
.attr("orient", "auto")
const polygon = marker.append("polygon")
.attr("fill", "gray")
.attr("points", "0 0, 10 3.5, 0 7")
const xScale = d3.scaleBand()
.domain(barData.map(d => d.Time))
.range([0, width + margin.right]);
const xAxis = d3.axisBottom(xScale)
//Adding g attribute to svg for x axis
const yAxisMax = barData.reduce((max, item) => Math.max(max, item.Value), 0) * 1.5;
const yAxisMin = barData.reduce((min, item) => Math.min(min, item.Value), 0) * 1.5;
const yAxisRange = Math.max(yAxisMax, Math.abs(yAxisMin));
const yScale = d3.scaleLinear()
.domain([-yAxisRange, yAxisRange])
.range([height, 0]);
const yAxis = d3.axisLeft(yScale).ticks(4);
svg.append('g')
.call(yAxis);
const bars = svg.selectAll('g.bar')
.data(barData)
.enter()
.append('g')
.classed('bar', true)
.attr('transform', d => `translate(${xScale(d.Time) + xScale.bandwidth() / 2}, 0)`);
bars.append('rect')
.attr('x', -20)
.attr('width', 40)
.attr('y', d => Math.min(yScale(d.Value), height/2))
.attr('height', d => d.Value > 0 ?
(height/2 - yScale(d.Value)) :
(yScale(d.Value) - height/2)
)
.attr('fill', 'blue')
.on("mousemove", onMouseOver)
.on("mouseout", onMouseOut);
function onMouseOver(d, i) {
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html("Year: " + (d.Time) + "<br>" + "Value: " + (d.Value));
d3.select(this).attr('fill', "#eec42d");
}
function onMouseOut(d, i) {
tooltip.style("display", "none");
d3.select(this).attr('fill', "blue");
}
bars.append('text')
.text(d => d.Value)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', d => d.Value > 0 ? 'baseline' : 'hanging')
.attr('y', d => yScale(d.Value))
.attr('dy', d => d.Value > 0 ? -5 : 5);
bars.filter((d, i) => i < barData.length - 1)
.append('path')
.attr('d', (d, i) => `M 5,${Math.min(yScale(d.Value) - 20, height/2)} V ${Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 60} H ${xScale.bandwidth() - 5} V ${Math.min(yScale(barData[i + 1].Value) - 25, height/2 - 10)}`)
.style('stroke', 'gray')
.style('fill', 'none')
.attr('marker-end', 'url(#arrowhead)')
bars.filter((d, i) => i < barData.length - 1)
.append('rect')
.attr('x', 15)
.attr('y', (d, i) => Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 70)
.attr('width', xScale.bandwidth() - 30)
.attr('height', 20)
.attr('rx', 10)
.style('fill', 'white')
.style('stroke', 'gray');
bars.filter((d, i) => i < barData.length - 1)
.append('text')
.text((d, i) => `${barData[i + 1].Value > d.Value ? '+' : ''}${Math.round((barData[i + 1].Value / d.Value * 100) - 100)}%`)
.attr('x', xScale.bandwidth() / 2)
.attr('y', (d, i) => Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 56)
.attr('text-anchor', 'middle')
.style('fill', 'black');
const xAxisG = svg.append('g')
.attr("transform", `translate(0,${height/2})`)
.call(xAxis);
xAxisG.selectAll('.tick').each(function() {
const tick = d3.select(this);
const text = tick.select('text').text();
const data = barData.find(d => d.Time === text);
if (data.Value < 0) {
tick.select('text').style('fill', 'white');
tick.select('line').style('stroke', 'white');
}
})
#graph {
width: 700px;
height: 500px;
}
text {
font-size: 12px;
font-family: "Ubuntu";
}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 5px;
text-align: left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="graph">
</div>

Related

How to add filter to create single path from one bar to another bar (not in a loop)

I am have a requirement where the bar chart will containing max 4 bars and I am trying to add a filter on a bar chart where I can pick a particular bar from 4 bars and add a path to display the data.
Example
Different Paths:
Bar1 --> Bar3
Bar2 --> Bar4
Bar3 --> Bar2
Bar4 --> Bar1
Please help me to understand how can I pick any specific bar and set a path to another bar like above example?
I have created different path code for the same but not able to get how I can get the target bar to create the proper path.
Below is my code:
var barData = [{
"Time": "Bar1",
"Value": 5388
},
{
"Time": "Bar2",
"Value": 6453
},
{
"Time": "Bar3",
"Value": 3345
},
{
"Time": "Bar4",
"Value": 5345
}];
const container = d3.select('#graph');
const divWidth = parseInt(container.style('width'));
const divHeight = parseInt(container.style('height'));
// Consider this width and Height are dynamic for div "graphID" because I am trying to responsive design
const margin = {top: 30, right: 50, bottom: 50, left: 50};
const width = divWidth - margin.left - margin.right;
const height = divHeight - margin.top - margin.bottom;
//To add svg in the visualization node i.e Dome node
const svg = container.append("svg")
.attr("width", divWidth)
.attr("height", divHeight);
const svgG = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
//To add tooltip for bar
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
const defs = svg.append("defs");
const marker = defs.append("marker")
.attr("id","arrowhead")
.attr("markerWidth","10")
.attr("markerHeight","7")
.attr("refX","0")
.attr("refY","3.5")
.attr("orient","auto")
const polygon = marker.append("polygon")
.attr("fill","gray")
.attr("points","0 0, 10 3.5, 0 7")
const xScale = d3.scaleBand()
.domain(barData.map(d => d.Time))
.range([0, width+margin.right]);
const xAxis = d3.axisBottom(xScale);
//Adding g attribute to svg for x axis
svgG.append('g')
.attr("transform", `translate(0,${height})`)
.call(xAxis);
const yAxisMax = barData.reduce((max, item) => Math.max(max, item.Value), 0) * 1.5;
const yScale = d3.scaleLinear()
.domain([0, yAxisMax])
.range([height, 0]);
const yAxis = d3.axisLeft(yScale).ticks(4);
svgG.append('g')
.call(yAxis);
const bars = svgG.selectAll('g.bar')
.data(barData)
.enter()
.append('g')
.classed('bar', true)
.attr('transform', d => `translate(${xScale(d.Time) + xScale.bandwidth() / 2}, 0)`);
/*
const staticColor = "steelblue",
highlightColor = "orange";
var sheet = document.createElement('style')
sheet.innerHTML = ".bar {fill: "+staticColor+"} .highlight {fill:"+highlightColor+"}";
document.body.appendChild(sheet);
*/
bars.append('rect')
.attr('x', -20)
.attr('width', 40)
.attr('y', d => yScale(d.Value))
.attr('height', d => height - yScale(d.Value) )
.attr('fill', 'blue')
//.attr("class", "bar")
.on("mousemove", onMouseOver)
.on("mouseout", onMouseOut);
function onMouseOver(d,i)
{
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html("Year: " + (d.Time) + "<br>" + "Value: " + (d.Value));
d3.select(this).attr('fill', "#eec42d");
//d3.select(this).attr('class', 'highlight');
//this.setState({ fillColour: 'green' });
}
function onMouseOut(d,i)
{
tooltip.style("display", "none");
d3.select(this).attr('fill', "blue");
//d3.select(this).attr('class', 'bar');
//this.setState({ fillColour: 'blue' });
}
bars.append('text')
.text(d => d.Value)
.attr('text-anchor', 'middle')
.attr('y', d => yScale(d.Value))
.attr('dy', -5)
;
//path from Bar1 to Bar3
bars.filter((d, i) => i == 0 )
.append('path')
.attr('d', (d, i) => `M 5,${yScale(d.Value) - 20} V ${Math.min(yScale(d.Value), yScale(barData[i+2].Value)) - 60} H ${xScale.bandwidth() - 5} V ${yScale(barData[i+2].Value) - 25}`)
.style('stroke', 'gray')
.style('fill', 'none')
.attr('marker-end', 'url(#arrowhead)')
//path from Bar2 to Bar4
bars.filter((d, i) => i == 1 )
.append('path')
.attr('d', (d, i) => `M 5,${yScale(d.Value) - 20} V ${Math.min(yScale(d.Value), yScale(barData[i+2].Value)) - 60} H ${xScale.bandwidth() - 5} V ${yScale(barData[i+2].Value) - 25}`)
.style('stroke', 'gray')
.style('fill', 'none')
.attr('marker-end', 'url(#arrowhead)')
//path from Bar3 to Bar2
bars.filter((d, i) => i == 2 )
.append('path')
.attr('d', (d, i) => `M 5,${yScale(d.Value) - 20} V ${Math.min(yScale(d.Value), yScale(barData[i-1].Value)) - 60} H ${xScale.bandwidth() - 5} V ${yScale(barData[i-1].Value) - 25}`)
.style('stroke', 'gray')
.style('fill', 'none')
.attr('marker-end', 'url(#arrowhead)')
//path from Bar3 to Bar2
bars.filter((d, i) => i == 3 )
.append('path')
.attr('d', (d, i) => `M 5,${yScale(d.Value) - 20} V ${Math.min(yScale(d.Value), yScale(barData[0].Value)) - 60} H ${xScale.bandwidth() - 5} V ${yScale(barData[0].Value) - 25}`)
.style('stroke', 'gray')
.style('fill', 'none')
.attr('marker-end', 'url(#arrowhead)')
/*
bars.filter((d, i) => i < barData.length - 1)
.append('rect')
.attr('x', 15)
.attr('y', (d, i) => Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 70)
.attr('width', xScale.bandwidth() - 30)
.attr('height', 20)
.attr('rx', 10)
.style('fill', 'white')
.style('stroke', 'gray');
bars.filter((d, i) => i < barData.length - 1)
.append('text')
.text((d, i) => `${barData[i + 1].Value > d.Value ? '+' : ''}${Math.round((barData[i + 1].Value / d.Value * 100) - 100)}%`)
.attr('x', xScale.bandwidth() / 2)
.attr('y', (d, i) => Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 56)
.attr('text-anchor', 'middle')
.style('fill', 'black');
*/
#graph {
width: 600px;
height: 400px;
}
text {
font-size: 12px;
font-family: "Ubuntu";
}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 5px;
text-align: left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="graph">
</div>
Here is an example with targets:
var barData = [{
"Time": "Bar1",
"Value": 5388,
"Target": 2
},
{
"Time": "Bar2",
"Value": 6453,
"Target": 0,
},
{
"Time": "Bar3",
"Value": 3345,
"Target": 3,
},
{
"Time": "Bar4",
"Value": 5345,
"Target": 1,
}];
const container = d3.select('#graph');
const divWidth = parseInt(container.style('width'));
const divHeight = parseInt(container.style('height'));
const margin = {top: 30, right: 50, bottom: 50, left: 50};
const width = divWidth - margin.left - margin.right;
const height = divHeight - margin.top - margin.bottom;
//To add svg in the visualization node i.e Dome node
const svg = container.append("svg")
.attr("width", divWidth)
.attr("height", divHeight);
const svgG = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
//To add tooltip for bar
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
const defs = svg.append("defs");
const marker = defs.append("marker")
.attr("id","arrowhead")
.attr("markerWidth","10")
.attr("markerHeight","7")
.attr("refX","0")
.attr("refY","3.5")
.attr("orient","auto")
const polygon = marker.append("polygon")
.attr("fill","gray")
.attr("points","0 0, 10 3.5, 0 7")
const xScale = d3.scaleBand()
.domain(barData.map(d => d.Time))
.range([0, width+margin.right]);
const xAxis = d3.axisBottom(xScale);
//Adding g attribute to svg for x axis
svgG.append('g')
.attr("transform", `translate(0,${height})`)
.call(xAxis);
const yAxisMax = barData.reduce((max, item) => Math.max(max, item.Value), 0) * 1.5;
const yScale = d3.scaleLinear()
.domain([0, yAxisMax])
.range([height, 0]);
const yAxis = d3.axisLeft(yScale).ticks(4);
svgG.append('g')
.call(yAxis);
const bars = svgG.selectAll('g.bar')
.data(barData)
.enter()
.append('g')
.classed('bar', true)
.attr('transform', d => `translate(${xScale(d.Time) + xScale.bandwidth() / 2}, 0)`);
/*
const staticColor = "steelblue",
highlightColor = "orange";
var sheet = document.createElement('style')
sheet.innerHTML = ".bar {fill: "+staticColor+"} .highlight {fill:"+highlightColor+"}";
document.body.appendChild(sheet);
*/
bars.append('rect')
.attr('x', -20)
.attr('width', 40)
.attr('y', d => yScale(d.Value))
.attr('height', d => height - yScale(d.Value) )
.attr('fill', 'blue')
.on("mousemove", onMouseOver)
.on("mouseout", onMouseOut);
function onMouseOver(d,i)
{
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html("Year: " + (d.Time) + "<br>" + "Value: " + (d.Value));
d3.select(this).attr('fill', "#eec42d");
//d3.select(this).attr('class', 'highlight');
//this.setState({ fillColour: 'green' });
}
function onMouseOut(d,i)
{
tooltip.style("display", "none");
d3.select(this).attr('fill', "blue");
}
bars.append('text')
.text(d => d.Value)
.attr('text-anchor', 'middle')
.attr('y', d => yScale(d.Value))
.attr('dy', -5)
;
const topPosition = i => yScale(9000) + i * 15;
const pathBetweenBars = (d, i) => {
const delta = d.Target - i;
const targetValue = barData[d.Target].Value;
const targetX = delta * xScale.bandwidth() - 5;
const sourceY = yScale(d.Value);
const targetY = yScale(targetValue);
const topY = topPosition(i);
return `M 5,${sourceY - 20} V ${topY} H ${targetX} V ${targetY - 25}`;
};
const LABEL_WIDTH = 50;
const midPosition = (d, i) => {
const delta = d.Target - i;
return delta * xScale.bandwidth() / 2;
}
bars.filter(d => d.Target != null)
.append('path')
.attr('d', (d, i) => pathBetweenBars(d, i))
.style('stroke', 'gray')
.style('fill', 'none')
.attr('marker-end', 'url(#arrowhead)')
bars.filter((d) => d.Target != null)
.append('rect')
.attr('x', (d, i) => midPosition(d, i) - LABEL_WIDTH / 2)
.attr('y', (d, i) => topPosition(i) - 10)
.attr('width', LABEL_WIDTH)
.attr('height', 20)
.attr('rx', 10)
.style('fill', 'white')
.style('stroke', 'gray');
bars.filter((d, i) => d.Target != null)
.append('text')
.text((d, i) => `${barData[d.Target].Value > d.Value ? '+' : ''}${Math.round((barData[d.Target].Value / d.Value * 100) - 100)}%`)
.attr('x', (d, i) => midPosition(d, i))
.attr('y', (d, i) => topPosition(i) + 3)
.attr('text-anchor', 'middle')
.style('fill', 'black');
#graph {
width: 600px;
height: 400px;
}
text {
font-size: 12px;
font-family: "Ubuntu";
}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 5px;
text-align: left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="graph">
</div>

Trying to make dynamic D3 Chart with Bar and Difference line/value

I have asked the below help with respect to creation of Bar chart with showing difference value percentage and line, below is the link:
How to create a % difference arrow line with value in a bar chart using D3.js
I have created this thread to seek help on making the same chart more dynamic and below are my try:
Making it dynamic in a way to fit in any resolution i.e making responsive (any width and height of Div can render the chart)
Tryin to do a reverse difference calculation.(currently it is calculating forward difference calculation and showing forward arrow, but with backward calculation need to show arrow in backward direction)
(Edited:
For example: the calculation is basically will start from last bar and then it will continue till the first bar. for example: (6453-7345)/7345 = -12% and (6453-5388)/6453 = -17%.)
Trying to get the difference by first bar and last bar value i.e:
Also below is the code:
var barData = [{
"Time": "2019",
"Value": 5388
},
{
"Time": "2020",
"Value": 6453
},
{
"Time": "2021",
"Value": 4345
},
{
"Time": "2022",
"Value": 7345
},
{
"Time": "2023",
"Value": 5345
}];
// Consider this width and Height are dynamic for div "graphID" because I am trying to responsive design
const divWidth = 700,
divHeight = 700;
//Adding Margin to Viz Area
var margin = {top: 30, right: 50, bottom: 0, left: 50},
width = parseInt(divWidth,10) - margin.left - margin.right,
height = parseInt(divHeight,10) - margin.top - margin.bottom;
//To add svg in the visualization node i.e Dome node
const svg = d3.select("#graphID").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 + ")");
//Adding x axis width i.e based on Viz Width
const xScale = d3.scaleBand()
.domain(barData.map(d => d.Time))
.range([0, width]);
const xAxis = d3.axisBottom(xScale);
//Adding g attribute to svg for x axis
svg.append('g')
//.attr('transform', 'translate(20,170)')
.attr("transform", "translate(10," + (height - 50) + ")")
.call(xAxis);
/*
//To get the Max value from an json object
const maxVal = barData.reduce((acc, shot) => acc = acc > shot.Value ? acc : shot.Value, 0);
const valMax = Math.max.apply(barData, barData.map(function(o) { return o.Value; }));
alert(valMax);
const yScale = d3.scaleLinear()
.domain([0, maxVal+(maxVal/2)])
.range([150, 0]);
*/
const yAxisMax = Math.max.apply(barData, barData.map(function(o) { return o.Value; }));
const yScale = d3.scaleLinear()
.domain([0, yAxisMax+(yAxisMax/2)])
.range([height, 0]);
const yAxis = d3.axisLeft(yScale).ticks(4);
svg.append('g')
//.attr('transform', 'translate(50,170)')
.attr("transform", "translate(10,-50)")
//.attr("transform", "translate(20," + (height - 50) + ")")
.call(yAxis);
const bars = svg.selectAll('g.bar')
.data(barData)
.enter()
.append('g')
.classed('bar', true)
.attr('transform', d => `translate(${xScale(d.Time) + 50 + xScale.bandwidth() / 20}, 170)`)
bars.append('rect')
.attr('x', -10)
.attr('width', 40)
.attr('y', d => -height + yScale(d.Value))
.attr('height', d => yScale(d.Value) )
.style('fill', 'blue')
bars.append('text')
.text(d => d.Value)
.attr('text-anchor', 'middle')
.attr('y', d => -(height - 55) + yScale(d.Value))
bars.filter((d, i) => i < barData.length - 1)
.append('path')
.attr('d', (d, i) => `M 5,${-170 + yScale(d.Value)} V ${-210 + yScale(d.Value)} H ${xScale.bandwidth() - 5} V ${-180 + yScale(barData[i + 1].Value)}`)
.style('stroke', 'gray')
.style('fill', 'none')
.attr('marker-end', 'url(#arrowhead)')
bars.filter((d, i) => i < barData.length - 1)
.append('rect')
.attr('x', 15)
.attr('y', d => -220 + yScale(d.Value))
.attr('width', xScale.bandwidth() - 30)
.attr('height', 20)
.attr('rx', 10)
.style('fill', 'white')
.style('stroke', 'gray');
bars.filter((d, i) => i < barData.length - 1)
.append('text')
.text((d, i) => `${barData[i + 1].Value > d.Value ? '+' : '-'}${Math.round((barData[i + 1].Value / d.Value * 100) - 100)}%`)
.attr('x', xScale.bandwidth() / 2)
.attr('y', d => -207 + yScale(d.Value))
.attr('text-anchor', 'middle')
.style('fill', 'black');
#graphID
{
width:500px;
height:700px
}
text {
font-size: 12px;
font-family: "Ubuntu";
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="graphID" width=500 height=700>
</div>
Here is a solution for responsive sizing:
Get dimentions of your parent container:
const container = d3.select('#graph');
const divWidth = parseInt(container.style('width'));
const divHeight = parseInt(container.style('height'));
Create SVG with the DIV dimentions:
const svg = container.append("svg")
.attr("width", divWidth)
.attr("height", divHeight);
Calculate the chart size using the margins:
const margin = {top: 30, right: 50, bottom: 50, left: 50};
const width = divWidth - margin.left - margin.right;
const height = divHeight - margin.top - margin.bottom;
Add a parent <g> and position it using the margins. All other elements should be placed under:
const svgG = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
var barData = [{
"Time": "2019",
"Value": 5388
},
{
"Time": "2020",
"Value": 6453
},
{
"Time": "2021",
"Value": 4345
},
{
"Time": "2022",
"Value": 7345
},
{
"Time": "2023",
"Value": 5345
}];
const container = d3.select('#graph');
const divWidth = parseInt(container.style('width'));
const divHeight = parseInt(container.style('height'));
// Consider this width and Height are dynamic for div "graphID" because I am trying to responsive design
const margin = {top: 30, right: 50, bottom: 50, left: 50};
const width = divWidth - margin.left - margin.right;
const height = divHeight - margin.top - margin.bottom;
//To add svg in the visualization node i.e Dome node
const svg = container.append("svg")
.attr("width", divWidth)
.attr("height", divHeight);
const svgG = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const xScale = d3.scaleBand()
.domain(barData.map(d => d.Time))
.range([0, width]);
const xAxis = d3.axisBottom(xScale);
//Adding g attribute to svg for x axis
svgG.append('g')
.attr("transform", `translate(0,${height})`)
.call(xAxis);
const yAxisMax = barData.reduce((max, item) => Math.max(max, item.Value), 0) * 1.5;
const yScale = d3.scaleLinear()
.domain([0, yAxisMax])
.range([height, 0]);
const yAxis = d3.axisLeft(yScale).ticks(4);
svgG.append('g')
.call(yAxis);
const bars = svgG.selectAll('g.bar')
.data(barData)
.enter()
.append('g')
.classed('bar', true)
.attr('transform', d => `translate(${xScale(d.Time) + xScale.bandwidth() / 2}, 0)`)
bars.append('rect')
.attr('x', -20)
.attr('width', 40)
.attr('y', d => yScale(d.Value))
.attr('height', d => height - yScale(d.Value) )
.style('fill', 'blue')
bars.append('text')
.text(d => d.Value)
.attr('text-anchor', 'middle')
.attr('y', d => yScale(d.Value))
.attr('dy', -5)
bars.filter((d, i) => i < barData.length - 1)
.append('path')
.attr('d', (d, i) => `M 5,${yScale(d.Value) - 20} V ${Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 60} H ${xScale.bandwidth() - 5} V ${yScale(barData[i + 1].Value) - 20}`)
.style('stroke', 'gray')
.style('fill', 'none')
.attr('marker-end', 'url(#arrowhead)')
bars.filter((d, i) => i < barData.length - 1)
.append('rect')
.attr('x', 15)
.attr('y', (d, i) => Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 70)
.attr('width', xScale.bandwidth() - 30)
.attr('height', 20)
.attr('rx', 10)
.style('fill', 'white')
.style('stroke', 'gray');
bars.filter((d, i) => i < barData.length - 1)
.append('text')
.text((d, i) => `${barData[i + 1].Value > d.Value ? '+' : '-'}${Math.round((barData[i + 1].Value / d.Value * 100) - 100)}%`)
.attr('x', xScale.bandwidth() / 2)
.attr('y', (d, i) => Math.min(yScale(d.Value), yScale(barData[i + 1].Value)) - 56)
.attr('text-anchor', 'middle')
.style('fill', 'black');
#graph
{
width:500px;
height:700px
}
text {
font-size: 12px;
font-family: "Ubuntu";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="graph">
</div>

how to access specific data in an array using D3.js v5 for a stacked bar graph tooltip

I have a JSON file below. I'm adding all the values together in field 1 and all the values in field 2 etc... then trying to get the total amount shown on a tooltip. So when I hover over each 'rect', I have a total field amount as well as group amounts.
[
{
"group": "Field 1",
"0-45": 12345,
"46-65": 91568,
"66-85": 13087,
"85+": 7045
},
{
"group": "Field 2",
"0-45": 62345,
"46-65": 15347,
"66-85": 37688,
"85+": 7007
}
]
Here is the sample code...
let totalColVal = d3.nest()
.key(function(d){return (d.group)})
.rollup(function(totals){
return d3.sum(totals, function(d) {return d3.sum(d3.values(d))})
}).entries(data)
console.log(totalColVal)
This returns
(4) [{…}, {…}, {…}, {…}]
0: {key: "Field 1", value: 124045}
1: {key: "Field 2", value: 122387}
2: {key: "Field 3", value: 172041}
3: {key: "Field 4", value: 67594}
Above gets the total amount for each field, but how do I get the tooltip to show each value when I hover over each rect.
//tooltip
let mouseover = function(d) {
let subgroupName = d3.select(this.parentNode).datum().key;
// console.log(subgroupName)
let subgroupValue = d.data[subgroupName];
// console.log(subgroupValue)
tooltip.html('<b>Field:</b> <span style="color:yellow">' + d.data.group + '</span>' + '<br>\
' + '<b>Count:</b> <span style="color:yellow">' + formater(subgroupValue) + '</span>' + '<br>\
' + ' <b>Size Band:</b> <span style="color:yellow">' + subgroupName + '</span>' + '<br>\
' + '<b>Field Total:</b> <span style="color:yellow">' + totalColVal + '</span>')
.style("opacity", 1)
};
let mousemove = function(d) {
tooltip.style('left', (d3.event.pageX - 70) + 'px')
.style('top', (d3.event.pageY - 85) + 'px')
};
let mouseleave = function(d) {
tooltip.style("opacity", 0)
};
totalColVal returns [object Object],[object Object],[object Object],[object Object] on my tooltip.
Assistance will be very much appreciated.
JS fiddle here
To get the value from the ith bar, there is actually a second argument, next to d, that you can access in all d3 functions. i denotes the index of that element in the array you passed it.
I changed the mouseover function to just use i here, and it resolved your issue:
const canvas = d3.select('#stacked');
const stackedSvg = canvas.append('svg')
.attr('preserveAspectRatio', 'xMinYMin meet')
.attr('viewBox', '0 0 900 500')
.classed('svg-content', true);
let margin = {top: 40, right: 30, bottom: 20, left: 70},
width = 260 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
// append the svg object to the body of the page
let svg = stackedSvg.append('g')
.attr('width', width)
.attr('height', height)
.attr('transform', `translate(${margin.left}, ${margin.top})`);
//number formater
const formater = d3.format(',d');
let myData = [
{
"group": "Field 1",
"0-45": 12345,
"46-65": 91568,
"66-85": 13087,
"85+": 7045
},
{
"group": "Field 2",
"0-45": 62345,
"46-65": 15347,
"66-85": 37688,
"85+": 7007
},
{
"group": "Field 3",
"0-45": 11457,
"46-65": 28456,
"66-85": 124564,
"85+": 7564
},
{
"group": "Field 4",
"0-45": 19234,
"46-65": 26754,
"66-85": 14153,
"85+": 7453
}
]
//tooltip
let tooltip = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
// Parse the Data
Promise.resolve(myData)
.then(data => {
// console.log(data);
//select the size bands
let keys = d3.keys(data[0]).slice(1);
// console.log(keys);
// List of groups in JSON. value of the first column called group
let groups = d3.map(data, function(d){return(d.group)}).keys();
// console.log(groups);
// X axis
let x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x)
.tickSizeOuter(0));
svg.append('text')
.attr('class', 'xLabel')
.attr('text-anchor', 'end')
.attr('x', width / 2 + 20)
.attr('y', height + 30)
.text('Size Band')
.style('font-size', 10);
// Y axis
let y = d3.scaleLinear()
.domain([0, d3.max(data, d => d3.sum(keys, k => +d[k]))])
.range([ height, 0 ]);
svg.append('g')
.call(d3.axisLeft(y)
.tickSizeOuter(0)
.tickSizeInner(- width)
.tickPadding(5))
.selectAll(".tick")
.style("stroke-dasharray", ("1, 1")) //dash line across graph.
.each(function (d, i) {
if ( d == 0 ) {
this.remove();
}
});
svg.append('text')
.attr('class', 'yLabel')
.attr('transform', 'rotate(-90)')
.attr('y', - 40)
.attr('x', - height / 2 + 20)
.attr('text-anchor', 'end')
.text('Units')
.style('font-size', 10);
// color
let color = d3.scaleOrdinal()
.domain(keys)
.range(['brown', 'steelblue', 'olivedrab', 'darkorange']);
//stack the data --> stack per subgroup
let stackedData = d3.stack()
.keys(keys)
(data);
console.log(stackedData)
let totalColVal = d3.nest()
.key(function(d){return (d.group)})
.rollup(function(totals){
return d3.sum(totals, function(d) {return d3.sum(d3.values(d))})
}).entries(data)
console.log(totalColVal)
//tooltip
let mouseover = function(d, i) {
let subgroupName = d3.select(this.parentNode).datum().key;
// console.log(subgroupName)
let subgroupValue = d.data[subgroupName];
// console.log(subgroupValue)
tooltip.html('<b>Field:</b> <span style="color:yellow">' + d.data.group + '</span>' + '<br>\
' + '<b>Count:</b> <span style="color:yellow">' + formater(subgroupValue) + '</span>' + '<br>\
' + ' <b>Size Band:</b> <span style="color:yellow">' + subgroupName + '</span>' + '<br>\
' + '<b>Field Total:</b> <span style="color:yellow">' + totalColVal[i].value + '</span>')
.style("opacity", 1)
};
let mousemove = function(d) {
tooltip.style('left', (d3.event.pageX - 70) + 'px')
.style('top', (d3.event.pageY - 85) + 'px')
};
let mouseleave = function(d) {
tooltip.style("opacity", 0)
};
// Show the bars
svg.append('g')
.selectAll('g')
// Enter in the stack data = loop key per key = group per group
.data(stackedData)
.enter().append('g')
.attr('fill', function(d) { return color(d.key) })
.selectAll('rect')
// enter a second time = loop subgroup per subgroup to add all rectangles
.data(function(d) { return d; })
.enter().append('rect')
.on('mouseover', mouseover)
.on('mousemove', mousemove)
.on('mouseleave', mouseleave)
.transition()
.attr('y', d => y(d.height))
.delay(function(d, i) {
return i * 100
})
.ease(d3.easeLinear)
.attr('x', function(d) { return x(d.data.group); })
.attr('y', function(d) { return y(d[1]); })
.attr('height', function(d) { return y(d[0]) - y(d[1]); })
.attr('width',x.bandwidth())
.style('stroke', 'black')
.style('opacity', 0.9);
});
body {
font-family: halyard-display, sans-serif;
/* background-color: black; */
}
div.tooltip {
position: absolute;
text-align: left;
width: fit-content;
height: fit-content;
padding: 5px 5px;
font-size: 16px;
background:rgb(24, 23, 23);
color: white;
border-radius: 5px;
pointer-events: none;
}
.yLabel {
fill: #DEDC00;
font-style: italic;
font-weight: 600;
}
.xLabel {
fill: #DEDC00;
font-style: italic;
font-weight: 600;
}
g .tick text {
font-size: 8px;
color: grey;
}
g .tick text:hover {
font-size: 12px;
}
g .tick {
color: #DEDC00;
}
<script src="https://d3js.org/d3.v5.js"></script>
<div id="stacked"></div>

Creating horizontal legend with d3.js (v4)

Currently I am working on one of FCC's project and essentially I am done with the overall project.
However, I am hard time making a simple legend, especially when I am using scaleQuantile as my colorScale
Here's my codepen link : http://codepen.io/neotriz/pen/PGOLBb
Essentially I want to create a horizontal legend that outputs all of the colors (inside the array color) and append its text, based on the data.
I could hard-code the text of the legend, but I rather do it in abstract (text is based on the data itself)
If anyone can point me the right direction, I will appreciate it.
let toolTip = d3.select("#canvas")
.append("div")
.classed("toolTip",true)
.style("opacity",0)
const colors = ["#5e4fa2", "#3288bd", "#66c2a5", "#abdda4",
"#e6f598", "#ffffbf", "#fee08b", "#fdae61",
"#f46d43", "#d53e4f", "#9e0142"];
const width = w - (margin.left + margin.right);
const height = h - (margin.top + margin.bottom);
const yOffset = 40;
//lets create new object to add degree key and its value
data = rawData.map( oneData => {
let degree = base + oneData.variance
return Object.assign({}, oneData, {degree: degree})
})
const svg = d3.select("#canvas")
.append("svg")
.attr("id","chart")
.attr("width", w)
.attr("height", h)
const chart = svg.append("g")
.classed("display", true)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const yearParser = d3.timeParse("%Y")
const monthParser = d3.timeParse("%m")
const x = d3.scaleTime()
.domain(d3.extent(data,function(d){
let year = yearParser(d.year)
return year
}))
.range([0,width]);
const y = d3.scaleTime()
.domain([monthParser(data[0].month),monthParser(data[11].month)])
.range([0,height-yOffset])
const xAxis = d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y")).tickSize(9)
const yAxis = d3.axisLeft(y)
.tickFormat(d3.timeFormat("%B")).tickSize(0).tickPadding(6);
const colorScale = d3.scaleQuantile()
.domain(d3.extent(data,function(d){
return d.degree
}))
.range(colors)
function toolTipText(d){
const rawMonth = monthParser(d.month);
const monthFormat = d3.timeFormat("%B");
const month = monthFormat(rawMonth)
let text = '<strong>' + month + " " + d.year + '</strong>'+ '<br>';
text += d.degree.toFixed(3) +' °C'+ '<br>';
text += 'Variance: '+d.variance +' °C'+ '<br>';
return text
}
function drawAxis(params){
//draw xAxis
this.append("g")
.call(params.axis.x)
.classed("x axis", true)
.attr("transform", "translate(0,"+ height +")")
.selectAll("text")
.style("font-size",16)
//draw yAxis
this.append("g")
.call(params.axis.y)
.classed("y axis",true)
.attr("transform","translate(0,0)")
.selectAll("text")
.attr("dy",25)
.style("font-size",14)
//label x axis
this.select(".x.axis")
.append("text")
.classed("x axis-label",true)
.attr("transform","translate(-60,"+ -height/2 +") rotate(-90)")
.style("fill","black")
.text("Months")
this.select(".y.axis")
.append("text")
.classed("y axis-label",true)
.attr("transform","translate("+ width/2 +","+ (height+60) +")")
.style("fill","black")
.text("Years")
}
function plot(params){
if (params.initialize){
drawAxis.call(this,params)
}
//enter()
this.selectAll(".degree")
.data(params.data)
.enter()
.append("rect")
.classed("degree", true)
//update
this.selectAll(".degree")
.transition()
.delay(100)
.attr("x",function(d,i){
let year = yearParser(d.year)
return x(year)
})
this.selectAll(".degree")
.attr("y",function(d,i){
let month = monthParser(d.month)
return y(month)
})
this.selectAll(".degree")
.attr("width", 4)
this.selectAll(".degree")
.attr("height", yOffset)
this.selectAll(".degree")
.style("fill", function(d,i){
return colorScale(d.degree)
})
.on("mouseover",function(d,i){
let text = toolTipText(d)
toolTip.transition()
.style("opacity",.9)
toolTip.html(text)
.style("left", (d3.event.pageX + 15) + "px")
.style("top", (d3.event.pageY - 28) + "px")
d3.select(this)
.style("stroke","gray")
.style("stroke-width", 3)
})
.on("mouseout",function(d,i){
toolTip.transition()
.style("opacity",0)
d3.select(this)
.style("stroke","none")
})
//exit()
this.selectAll(".degree")
.data(params.data)
.exit()
.remove()
}
plot.call(chart,{
base: base,
data: data,
axis: {
x: xAxis,
y: yAxis
},
initialize: true
})
Here's a quick implementation:
function drawLegend(){
var legend = this.select(".x.axis").append("g"),
legW = 40;
legend.selectAll('rect')
.data(colorScale.range())
.enter()
.append('rect')
.attr('width', legW)
.attr('x', function(d,i){
return i * legW;
})
.attr('y', 50)
.attr('height', 20)
.style('fill', function(d){
return d;
});
legend.selectAll('text')
.data(colorScale.quantiles())
.enter()
.append('text')
.attr('x', function(d,i){
return (i + 1) * legW;
})
.attr('y', 80)
.text(function(d,i){
var rv = Math.round(d*10)/10;
if (i === 0) rv = '<' + rv;
else if (i === (colorScale.quantiles().length - 1)) rv = '>' + rv;
return rv;
})
.style('fill', 'black')
.style('stroke', 'none');
}
Full running code:
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="UTF-8" />
<title>FCC Heat-Map</title>
<style>
body,
html {
text-align: center;
background: #e2e2e2;
}
#canvas {
margin: 65px auto;
width: 1300px;
height: 600;
background: white;
-webkit-box-shadow: 0px 0px 31px -5px rgba(0, 0, 0, 0.5);
-moz-box-shadow: 0px 0px 31px -5px rgba(0, 0, 0, 0.5);
box-shadow: 0px 0px 31px -5px rgba(0, 0, 0, 0.5);
}
#chart {}
.title {
padding-top: 11px;
font-size: 2em;
font-weight: bold;
}
.year {
font-size: 1.7em;
}
.description {
font-size: 0.9em;
margin-bottom: -35px;
}
.axis-label {
font-size: 25px;
}
.toolTip {
color: white;
position: absolute;
text-align: center;
max-width: 250px;
max-height: 98px;
padding: 7px;
font: 15px sans-serif;
background: black;
border: 25px;
border-radius: 12px;
line-height: 18px;
pointer-events: none;
box-shadow: 0px 0px 12px -10px;
}
</style>
</head>
<body>
<div id="canvas"></div>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script>
var colorScale = null;
$(document).ready(function() {
const w = 1300;
const h = 600;
const margin = {
top: 50,
bottom: 80,
left: 100,
right: 20
}
function title() {
const mainTitle = document.createElement("div");
mainTitle.innerHTML = "Monthly Global Land-Surface Temperature"
mainTitle.className = "title";
const year = document.createElement("div");
year.innerHTML = "1753 - 2015";
year.className = "year";
const description = document.createElement("div")
description.innerHTML = 'Temperatures are in Celsius and reported as anomalies relative to the Jan 1951-Dec 1980 average.<br>' +
'Estimated Jan 1951-Dec 1980 absolute temperature ℃: 8.66 +/- 0.07';
description.className = "description"
let chart = document.getElementById("canvas")
chart.appendChild(mainTitle)
chart.appendChild(year)
chart.appendChild(description)
}
function render(base, rawData) {
let toolTip = d3.select("#canvas")
.append("div")
.classed("toolTip", true)
.style("opacity", 0)
const colors = ["#5e4fa2", "#3288bd", "#66c2a5", "#abdda4",
"#e6f598", "#ffffbf", "#fee08b", "#fdae61",
"#f46d43", "#d53e4f", "#9e0142"
];
const width = w - (margin.left + margin.right);
const height = h - (margin.top + margin.bottom);
const yOffset = 40;
//lets create new object to add degree key and its value
data = rawData.map(oneData => {
let degree = base + oneData.variance
return Object.assign({}, oneData, {
degree: degree
})
})
const svg = d3.select("#canvas")
.append("svg")
.attr("id", "chart")
.attr("width", w)
.attr("height", h)
const chart = svg.append("g")
.classed("display", true)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const yearParser = d3.timeParse("%Y")
const monthParser = d3.timeParse("%m")
const x = d3.scaleTime()
.domain(d3.extent(data, function(d) {
let year = yearParser(d.year)
return year
}))
.range([0, width]);
const y = d3.scaleTime()
.domain([monthParser(data[0].month), monthParser(data[11].month)])
.range([0, height - yOffset])
const xAxis = d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y")).tickSize(9)
const yAxis = d3.axisLeft(y)
.tickFormat(d3.timeFormat("%B")).tickSize(0).tickPadding(6);
colorScale = d3.scaleQuantile()
.domain(d3.extent(data, function(d) {
return d.degree
}))
.range(colors)
function toolTipText(d) {
const rawMonth = monthParser(d.month);
const monthFormat = d3.timeFormat("%B");
const month = monthFormat(rawMonth)
let text = '<strong>' + month + " " + d.year + '</strong>' + '<br>';
text += d.degree.toFixed(3) + ' °C' + '<br>';
text += 'Variance: ' + d.variance + ' °C' + '<br>';
return text
}
function drawAxis(params) {
//draw xAxis
this.append("g")
.call(params.axis.x)
.classed("x axis", true)
.attr("transform", "translate(0," + height + ")")
.selectAll("text")
.style("font-size", 16)
//draw yAxis
this.append("g")
.call(params.axis.y)
.classed("y axis", true)
.attr("transform", "translate(0,0)")
.selectAll("text")
.attr("dy", 25)
.style("font-size", 14)
//label x axis
this.select(".x.axis")
.append("text")
.classed("x axis-label", true)
.attr("transform", "translate(-60," + -height / 2 + ") rotate(-90)")
.style("fill", "black")
.text("Months")
this.select(".y.axis")
.append("text")
.classed("y axis-label", true)
.attr("transform", "translate(" + width / 2 + "," + (height + 60) + ")")
.style("fill", "black")
.text("Years")
}
function drawLegend(){
var legend = this.select(".x.axis").append("g"),
legW = 40;
legend.selectAll('rect')
.data(colorScale.range())
.enter()
.append('rect')
.attr('width', legW)
.attr('x', function(d,i){
return i * legW;
})
.attr('y', 40)
.attr('height', 20)
.style('fill', function(d){
return d;
});
legend.selectAll('text')
.data(colorScale.quantiles())
.enter()
.append('text')
.attr('x', function(d,i){
return (i + 1) * legW;
})
.attr('y', 70)
.text(function(d,i){
var rv = Math.round(d*10)/10;
if (i === 0) rv = '<' + rv;
else if (i === (colorScale.quantiles().length - 1)) rv = '>' + rv;
return rv;
})
.style('fill', 'black')
.style('stroke', 'none');
}
function plot(params) {
if (params.initialize) {
drawAxis.call(this, params)
}
//enter()
this.selectAll(".degree")
.data(params.data)
.enter()
.append("rect")
.classed("degree", true)
//update
this.selectAll(".degree")
.transition()
.delay(100)
.attr("x", function(d, i) {
let year = yearParser(d.year)
return x(year)
})
this.selectAll(".degree")
.attr("y", function(d, i) {
let month = monthParser(d.month)
return y(month)
})
this.selectAll(".degree")
.attr("width", 4)
this.selectAll(".degree")
.attr("height", yOffset)
this.selectAll(".degree")
.style("fill", function(d, i) {
return colorScale(d.degree)
})
.on("mouseover", function(d, i) {
let text = toolTipText(d)
toolTip.transition()
.style("opacity", .9)
toolTip.html(text)
.style("left", (d3.event.pageX + 15) + "px")
.style("top", (d3.event.pageY - 28) + "px")
d3.select(this)
.style("stroke", "gray")
.style("stroke-width", 3)
})
.on("mouseout", function(d, i) {
toolTip.transition()
.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
})
//exit()
this.selectAll(".degree")
.data(params.data)
.exit()
.remove()
drawLegend.call(this);
}
plot.call(chart, {
base: base,
data: data,
axis: {
x: xAxis,
y: yAxis
},
initialize: true
})
}
const url = 'https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/global-temperature.json';
$.ajax({
type: "GET",
dataType: "json",
url: url,
beforeSend: () => {},
complete: () => {},
success: data => {
title()
const baseTemperature = data.baseTemperature;
const dataAPI = data.monthlyVariance;
render(baseTemperature, dataAPI);
},
fail: () => {
console.log('failure!')
},
error: () => {
let chart = document.getElementById('card');
chart.style.display = "table"
let errorMessage = document.createElement("h1");
errorMessage.innerHTML = "ERROR 404: File Not Found!"
errorMessage.className = "errorMessage";
chart.appendChild(errorMessage)
}
});
});
</script>
</body>
</html>

Need help using d3.js to create Stacked Bar Chart

I am currently trying to convert this (mbostock.github.com/d3/ex/stack.html) vertical stacked bar chart using d3.js to a horizontal stacked bar chart, but I have had no luck. if anyone has an example of a horizontally stacked bar chart from d3.js or knows how to modify the following code correctly or point me in the correct direction that would be a great help.
var margin = 20,
width = 960,
height = 500 - .5 - margin,
mx = m,
my = d3.max(data, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
}),
mz = d3.max(data, function(d) {
return d3.max(d, function(d) {
return d.y;
});
}),
x = function(d) { return d.x * width / mx; },
y0 = function(d) { return height - d.y0 * height / my; },
y1 = function(d) { return height - (d.y + d.y0) * height / my; },
y2 = function(d) { return d.y * height / mz; }; // or `my` to not rescale
var vis = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height + margin);
var layers = vis.selectAll("g.layer")
.data(data)
.enter().append("g")
.style("fill", function(d, i) { return color(i / (n - 1)); })
.attr("class", "layer");
var bars = layers.selectAll("g.bar")
.data(function(d) { return d; })
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d) + ",0)"; });
bars.append("rect")
.attr("width", x({x: .9}))
.attr("x", 0)
.attr("y", height)
.attr("height", 0)
.transition()
.delay(function(d, i) { return i * 10; })
.attr("y", y1)
.attr("height", function(d) { return y0(d) - y1(d); });
The trick is: treat it almost the same as a vertical stacked bar chart, but invert the x and y values before stacking, and then back again once stacked. Note the comments in the code below.
My blog post about this: http://datashaman.github.io/2014/01/26/horizontal-stacked-bar-chart-d3/
Demo of the code below: http://bl.ocks.org/datashaman/8621955
jsFiddle: http://jsfiddle.net/datashaman/rBfy5/2/
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
.bar {
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
#tooltip {
position: absolute;
text-align: center;
width: 40px;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<div id="tooltip" class="hidden">
<p><span id="value">100</span></p>
</div>
<script>
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
},
legendPanel = {
width: 240
},
width = 700 - margins.left - margins.right - legendPanel.width,
height = 100 - margins.top - margins.bottom,
dataset = [
{
data: [
{ month: 'Aug', count: 123 },
{ month: 'Sep', count: 234 },
{ month: 'Oct', count: 345 }
],
name: 'Series #1'
},
{
data: [
{ month: 'Aug', count: 235 },
{ month: 'Sep', count: 267 },
{ month: 'Oct', count: 573 }
],
name: 'Series #2'
}
],
series = dataset.map(function(d) { return d.name; }),
dataset = dataset.map(function(d) {
return d.data.map(function(o, i) {
// Structure it so that your numeric
// axis (the stacked amount) is y
return {
y: o.count,
x: o.month
};
});
}),
stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function(group) {
return group.map(function(d) {
// Invert the x and y values, and y0 becomes x0
return {
x: d.y,
y: d.x,
x0: d.y0
};
});
}),
svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right + legendPanel.width)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')'),
xMax = d3.max(dataset, function(group) {
return d3.max(group, function(d) {
return d.x + d.x0;
});
}),
xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, width]),
months = dataset[0].map(function(d) { return d.y; }),
_ = console.log(months),
yScale = d3.scale.ordinal()
.domain(months)
.rangeRoundBands([0, height], .1),
xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom'),
yAxis = d3.svg.axis()
.scale(yScale)
.orient('left'),
colours = d3.scale.category10(),
groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.style('fill', function(d, i) {
return colours(i);
}),
rects = groups.selectAll('rect')
.data(function(d) { return d; })
.enter()
.append('rect')
.attr('x', function(d) { return xScale(d.x0); })
.attr('y', function(d, i) { return yScale(d.y); })
.attr('height', function(d) { return yScale.rangeBand(); })
.attr('width', function(d) { return xScale(d.x); })
.on('mouseover', function(d) {
var xPos = parseFloat(d3.select(this).attr('x')) / 2 + width / 2;
var yPos = parseFloat(d3.select(this).attr('y')) + yScale.rangeBand() / 2;
d3.select('#tooltip')
.style('left', xPos + 'px')
.style('top', yPos + 'px')
.select('#value')
.text(d.x);
d3.select('#tooltip').classed('hidden', false);
})
.on('mouseout', function() {
d3.select('#tooltip').classed('hidden', true);
})
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'axis')
.call(yAxis);
svg.append('rect')
.attr('fill', 'yellow')
.attr('width', 160)
.attr('height', 30 * dataset.length)
.attr('x', width + margins.left)
.attr('y', 0);
series.forEach(function(s, i) {
svg.append('text')
.attr('fill', 'black')
.attr('x', width + margins.left + 8)
.attr('y', i * 24 + 24)
.text(s);
svg.append('rect')
.attr('fill', colours(i))
.attr('width', 60)
.attr('height', 20)
.attr('x', width + margins.left + 90)
.attr('y', i * 24 + 6);
});
</script>
</body>
</html>
Try Frank Guerino's examples:
http://bl.ocks.org/2141479 - horizontal bar chart
and
http://bl.ocks.org/2354192 - Multiple D3 Top Down and Bottom Up Stacked Bar Charts (without d3.layout.stack)
Combine the horizontal code with the Crimea example or Multiple D3 Top Down example and you should be on your track. Mainly what you have to look for is how to calculate the coordinates right in the horizontal setup. The rest is the same like any other stack example.
Best regards!

Categories