How can I add in Data Labels to my D3 bar chart? - javascript

I am completely new at D3. I have managed to create a basic bar chart but I am having trouble adding the data labels.. I tried adding in some things that I researched but it seems to take away the bar chart when I do this.. I still see the bars but not the drawing.
Can some please assist on where I would start the svg.selectall(text)?
var data = [
{ ScrumTeamName: 'Name1', score: 4},
{ ScrumTeamName: 'Name2', score: 6},
{ ScrumTeamName: 'Name3', score: 0},
];
var width = 800;
var height = 400;
var margin = { top: 50, bottom: 50, left: 50, right: 50 };
var svg = d3.select("#d3-container")
.append('svg')
.attr('height', height - margin.top - margin.bottom)
.attr('width', width - margin.left - margin.right)
.attr('viewBox', [0, 0, width, height]);
var x = d3.scaleBand()
.domain(d3.range(data.length))
.range([margin.left, width - margin.right])
.padding(0.1);
var y = d3.scaleLinear()
.domain([0, 20])
.range([height - margin.bottom, margin.top]);
svg
.append('g')
.attr('fill', 'royalblue')
.selectAll('rect')
.data(data.sort((a, b) => d3.descending(a.score, b.score)))
.join('rect')
.attr('x', (d, i) => x(i))
.attr('y', (d) => y(d.score))
.attr('height', d => y(0) - y(d.score))
.attr('width', x.bandwidth())
.attr('class', 'rectangle')
function xAxis (g){
g.attr('transform', `translate(0, ${height - margin.bottom})`)
g.call(d3.axisBottom(x).tickFormat(i => data[i].ScrumTeamName))
.attr('font-size', '20px')
}
function yAxis (g){
g.attr('transform', `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y).ticks(null, data.format))
.attr('font-size', '20px')
}
svg.append('g').call(yAxis);
svg.append('g').call(xAxis);
svg.node();

Here's how I do a simple bar chart label. You can just add this section:
svg.append('g')
.attr('fill', 'royalblue')
.selectAll('text')
.data(data.sort((a, b) => d3.descending(a.score, b.score)))
.join('text')
.text((d) => d.score)
.attr('x', (d, i) => x(i) - x.bandwidth()/2) // center of the bar
.attr('y', (d) => y(d.score) - 2) //lift off the bar
.style('text-anchor','middle')

Related

How to place the bars of a bar chart in the right positions of the xAxis using d3.js?

I'm making a bar chart but I'm having problems to match the bar positions with the xAxis. They're not in the right place, for example, by hovering the bar above the 2010 mark, you can see it shows a 2007 value. How can I fix that?
let url = "https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json";
const padding = 50;
const height = 460;
const width = 940;
const barthickness = 2.909090909090909;
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
var arr = [];
var years = [];
d3.json(url, function(data) {
for (let i = 0; i < data.data.length; i++) {
arr[i] = data.data[i];
years[i] = parseInt(data.data[i][0].slice(0,4));
}
const yScale = d3.scaleLinear()
.domain([0, d3.max(arr, (d) => d[1])])
.range([height - padding, padding]);
const xScale = d3.scaleLinear()
.domain([d3.min(years, d => d), d3.max(years, (d) => d)])
.range([padding, width - padding]);
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
svg.append("g")
.attr("transform", "translate(0," + (height - padding) + ")")
.call(xAxis);
svg.append('g')
.attr('transform', 'translate(' + padding + ', 0)')
.call(yAxis)
svg.selectAll('rect')
.data(arr)
.enter()
.append('rect')
.attr('fill', 'blue')
.attr('height', d => height - padding - yScale(d[1]))
.attr('width', barthickness)
.attr('x', (d, i) => padding + (3.2* i))
.attr('y', d => yScale(d[1]))
.append('title')
.text((d, i) => years[i] + ': ' + d[1])
});
<script src="https://d3js.org/d3.v4.min.js"></script>
The problem is that you are not using your x-scale to position the bars. You are using padding + (3.2* i) to set the x coordinate of the bars, which does not line up with your scale. Your chart is 840 pixels wide and has 275 bars, which would be ~3.055 pixels per bar. Your code is placing bars every 3.2 pixels, which is too far.
Typically with bar charts, rather than hard-coding a bar thickness, you use a band scale. You'll want to use your scales both in your axes and to position the bars.
Alternatively, since you are working with temporal data, you could also consider using an area chart instead of a bar chart.
Below I've provided two similarly looking charts for your data. One is a bar chart and the other an area chart.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="bar-chart"></div>
<div id="area-chart"></div>
<script>
const url = 'https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json';
d3.json(url).then(json => {
// convert the string into Date objects
const parse = d3.timeParse('%Y-%m-%d');
const data = json.data.map(d => ({ date: parse(d[0]), value: d[1] }));
barchart(data);
areachart(data);
});
function barchart(data) {
// set up
const margin = { top: 20, right: 20, bottom: 20, left: 30 };
const width = 600 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
const svg = d3.select('#bar-chart')
.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})`);
// scales
const x = d3.scaleBand()
.domain(data.map(d => d.date))
.range([0, width]);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([height, 0]);
// axes
// by default, axes for band scales show tick marks for every bar
// that would be too cluttered for this data, so we override this
// by explicitly setting tickValues()
const [minDate, maxDate] = d3.extent(data, d => d.date);
const xAxis = d3.axisBottom(x)
.tickSizeOuter(0)
// only show the year in the tick labels
.tickFormat(d3.timeFormat('%Y'))
.tickValues(d3.timeTicks(minDate, maxDate, 10));
const yAxis = d3.axisLeft(y)
.tickSizeOuter(0)
.ticks(10, '~s');
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(xAxis);
svg.append('g')
.call(yAxis);
// bars
// function to convert Date into string showing the month and year
const format = d3.timeFormat('%b %Y');
svg.selectAll('rect')
.data(data)
.join('rect')
.attr('x', d => x(d.date))
.attr('width', d => x.bandwidth())
.attr('y', d => y(d.value))
.attr('height', d => height - y(d.value))
.attr('fill', 'steelblue')
.append('title')
.text(d => `${format(d.date)}: ${d.value}`)
}
function areachart(data) {
// set up
const margin = { top: 20, right: 20, bottom: 20, left: 30 };
const width = 600 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
const svg = d3.select('#area-chart')
.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})`);
// scales
const x = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([0, width]);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([height, 0]);
// area generator
const area = d3.area()
.x(d => x(d.date))
.y0(y(0))
.y1(d => y(d.value))
.curve(d3.curveStepAfter);
// axes
const xAxis = d3.axisBottom(x)
.tickSizeOuter(0)
// only show the year in the tick labels
.tickFormat(d3.timeFormat('%Y'));
const yAxis = d3.axisLeft(y)
.tickSizeOuter(0)
.ticks(10, '~s');
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(xAxis);
svg.append('g')
.call(yAxis);
// area
svg.append('path')
.attr('d', area(data))
.attr('fill', 'steelblue')
}
</script>
</body>
</html>

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>

Why do bars appear with "inverted values"? d3.js

I'm trying to learn how to use d3.js in react, but something is wrong in my code.
I'm doing a bar chart, but the value of bar are "inverted", for example, a bar has a value of 30% but in the chart, the bar appears with 70% (like, 100% - 30% = 70%).
How can I fix that?
Here is my code: codeSandBox.
Other question that I have is: how can I change the height of bars? I want to add some margin-top to show everything of the y Axis, but if I do that, the bars still with the same height and don't match with yAxis value
The issue here is that you're swapping the y and height logic, it should be:
.attr("y", d => yScale(d.percent))
.attr("height", d => height - margin.bottom - margin.top - yScale(d.percent))
Or, if you set the working height as...
height = totalHeight - margin.bottom - margin.top
... it can be just:
.attr("y", d => yScale(d.percent))
.attr("height", d => height - yScale(d.percent))
On top of that (and this addresses your second question), you are using Bostock's margin convention wrong. You should translate the g group according to the margins, and then appending all the bars to that translated group, without translating them again. Also, append the axes' groups to that g group.
All that being said, this is the code with those changes:
const data = [{
year: 2012,
percent: 50
},
{
year: 2013,
percent: 30
},
{
year: 2014,
percent: 90
},
{
year: 2015,
percent: 60
},
{
year: 2016,
percent: 75
},
{
year: 2017,
percent: 20
}
];
const height = 300;
const width = 370;
const margin = {
top: 20,
right: 10,
bottom: 20,
left: 25
};
const xScale = d3.scaleBand()
.domain(data.map(d => d.year))
.padding(0.2)
.range([0, width - margin.right - margin.left]);
const yScale = d3
.scaleLinear()
.domain([0, 100])
.range([height - margin.bottom - margin.top, 0]);
const svg = d3
.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("margin-left", 10);
const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
g
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", d => xScale(d.year))
.attr("y", d => yScale(d.percent))
.attr("width", xScale.bandwidth())
.attr("height", d => height - margin.bottom - margin.top - yScale(d.percent))
.attr("fill", "steelblue")
g.append("g")
.call(d3.axisLeft(yScale));
g.append("g")
.call(d3.axisBottom(xScale))
.attr(
"transform",
`translate(0, ${height - margin.bottom - margin.top})`
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
svg
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", d => xScale(d.year))
.attr("y", d => height - yScale(100-d.percent))
.attr("width", xScale.bandwidth())
.attr("height", d => yScale(100-d.percent))
.attr("fill", "steelblue")
.attr("transform", `translate(${margin.left}, -${margin.bottom})`);
You need to minus the percentage from 100
Working: https://codesandbox.io/s/crimson-microservice-8kjqd

Y Axis not displaying for scatter plot with d3 v4

I am trying to create a scatter plot with d3.js (v4). I am a newbie in d3, and thanks to the limited documentation of examples with v4, am struggling to create the plot (already have asked here once before). My code is given below:
const margin = { top: 100, right: 50, left: 50, bottom: 50};
const width = 1300 - margin.right - margin.left;
const height = 1250 - margin.top - margin.bottom;
d3.csv("http://localhost:9000/data.csv", (error, data) => {
if (error) throw error;
const x = (d) => d["Category"];
const xScale = d3.scalePoint()
.domain(data.map((d) => d["Category"]))
.range([0, width]);
const xMap = (d) => xScale(x(d));
const xAxis = d3.axisBottom().scale(xScale);
// Plotting Score 1 for now
const y = (d) => d["Score 1"];
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, y)])
.range([height, 0]);
const yMap = (d) => yScale(y(d))
const yAxis = d3.axisLeft().scale(yScale);
const svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'axis axis--x')
.call(xAxis)
.attr('transform', 'translate(0, 800)')
.append('text')
.attr('class', 'label')
.attr('x', width)
.attr('y', -6)
.style('text-anchor', 'middle')
.text('Category');
svg.append('g')
.attr('class', 'axis axis--y')
.call(yAxis)
.attr('transform', 'rotate(-90)')
.attr('y', 0 - margin.left)
.attr('x', 0 - (height/2))
.attr("dy", "1em")
.style('text-anchor', 'middle')
.text('Score');
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('class', 'dot')
.attr('cx', xMap)
.attr('cy', yMap)
.attr('r', 3.5)
.attr('fill', 'red');
});
A few lines of data.csv are given:
Name,Category,Score 1,Score 2,Score 3,Average score
A1_P1,A01,2.3,2.4,4.1,2.4
A2_P1,A02,1.4,1.5,2.4,1.5
A3_P1,A03,0.9,0.9,0.9,0.9
A4_P1,B01,1.5,1.5,1,1.5
A5_P1,B02,1.2,1.2,1.4,1.2
A6_P1,B03,1,1,1.1,1
A7_P1,C01,1.6,1.2,1,1.2
A8_P1,C02,1.2,1.2,0.9,1.2
A9_P1,C03,1.1,1.1,1,1.1
A10_P1,D01,1.5,1.6,1.1,1.5
The x-axis is showing (but not the 'Category' label), and even more importantly, the y-axis is not showing at all. The points themselves are being shown correctly, though. Does anyone know what is wrong with my y-axis setting and axis labels? Thanks in advance!
When I start a new chart with d3, I find it best to start with a known simple set-up example to place my drawing g, axis, etc... This is my usual starting point.
That said, here's list of issues I see in your chart:
Not placing x-axis dynamically.
svg container is sized wrong and ends up on top of axis.
Attempting to append a text axis label to y axis but you never actually append the text (and then apply attributes and styling to the axis itself instead of that missing text element).
Cleaning this all up looks like this:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
const margin = {
top: 100,
right: 50,
left: 50,
bottom: 50
};
const width = 500 - margin.right - margin.left;
const height = 500 - margin.top - margin.bottom;
//d3.csv("data.csv", (error, data) => {
// if (error) throw error;
var data = [{"Name":"A1_P1","Category":"A01","Score 1":"2.3","Score 2":"2.4","Score 3":"4.1","Average score":"2.4"},{"Name":"A2_P1","Category":"A02","Score 1":"1.4","Score 2":"1.5","Score 3":"2.4","Average score":"1.5"},{"Name":"A3_P1","Category":"A03","Score 1":"0.9","Score 2":"0.9","Score 3":"0.9","Average score":"0.9"},{"Name":"A4_P1","Category":"B01","Score 1":"1.5","Score 2":"1.5","Score 3":"1","Average score":"1.5"},{"Name":"A5_P1","Category":"B02","Score 1":"1.2","Score 2":"1.2","Score 3":"1.4","Average score":"1.2"},{"Name":"A6_P1","Category":"B03","Score 1":"1","Score 2":"1","Score 3":"1.1","Average score":"1"},{"Name":"A7_P1","Category":"C01","Score 1":"1.6","Score 2":"1.2","Score 3":"1","Average score":"1.2"},{"Name":"A8_P1","Category":"C02","Score 1":"1.2","Score 2":"1.2","Score 3":"0.9","Average score":"1.2"},{"Name":"A9_P1","Category":"C03","Score 1":"1.1","Score 2":"1.1","Score 3":"1","Average score":"1.1"},{"Name":"A10_P1","Category":"D01","Score 1":"1.5","Score 2":"1.6","Score 3":"1.1","Average score":"1.5"}];
const x = (d) => d["Category"];
const xScale = d3.scalePoint()
.domain(data.map((d) => d["Category"]))
.range([0, width]);
const xMap = (d) => xScale(x(d));
const xAxis = d3.axisBottom().scale(xScale);
// Plotting Score 1 for now
const y = (d) => d["Score 1"];
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, y)])
.range([height, 0]);
const yMap = (d) => yScale(y(d))
const yAxis = d3.axisLeft().scale(yScale);
const svg = d3.select('body')
.append('svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'axis axis--x')
.call(xAxis)
.attr('transform', 'translate(0,' + height + ')')
.append('text')
.attr('class', 'label')
.attr('x', width)
.attr('y', -6)
.style('text-anchor', 'middle')
.text('Category');
svg.append('g')
.attr('class', 'axis axis--y')
.call(yAxis)
.append("text")
.attr('transform', 'rotate(-90)')
.attr('y', 0 - margin.left)
.attr('x', 0 - (height / 2))
.attr("dy", "1em")
.style('text-anchor', 'middle')
.text('Score')
.style('fill', 'black')
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('class', 'dot')
.attr('cx', xMap)
.attr('cy', yMap)
.attr('r', 3.5)
.attr('fill', 'red');
//});
</script>
</body>
</html>

D3.js transition starts at height 0, needs to start at previous value

I have a bar chart displaying data on which you can filter through different years with the press of a button. I want the chart to transition from the current value to the new value, but now it starts at the bottom each time you press a button. How can I fix this?
Thanks!
var margin = {top: 20, right: 20, bottom: 30, left: 20},
height = 500 - margin.top - margin.bottom,
width = 600 - margin.left - margin.right
function bars(data) {
max = d3.max(data)
var yScale = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, height])
var xScale = d3.scale.ordinal()
.domain(d3.range(0, data.length))
.rangeBands([0, width], .2)
var myChart = d3.select("#chart")
var bars = myChart.selectAll("rect.bar")
.data(data)
//enter
bars.enter()
.append("svg:rect")
.attr("class", "bar")
.attr("fill", "#800")
//apply to everything (enter and update)
bars.style('fill', '#C64567')
.attr('width', xScale.rangeBand())
.attr('x', function(d,i){
return xScale(i);
})
.attr('height', 0)
.attr('y', height)
bars.transition()
.attr('height', function(d){
return yScale(d);
})
.attr('y', function(d){
return height - yScale(d);
})
.duration(1000)
.ease('elastic')
}
function init() {
//setup the svg
var svg = d3.select("#svg")
.style('background', '#000')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("svg:g")
.attr("id", "chart")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
//UI
d3.select("#button1")
.on("click", function (d, i) {
bars(j1996);
})
d3.select("#button2")
.on("click", function (d, i) {
bars(j1997);
})
d3.select("#button3")
.on("click", function (d, i) {
bars(j1998);
})
//draw the bars
bars(j1996);
}
A fresh new look at it and I managed to find the solution. I removed the tag bars in this line:
//apply to everything (enter and update)
bars.style('fill', '#C64567')
So all the attributes are now set on the bar.enter() command, this way d3.js makes the transitions automatically from the last value

Categories