I have a csv that has the following structure:
date
A
B
'2022-01-02'
120
150
'2022-01-03'
160
170
Now, I am trying to build a symbol chart that has the x value as the date and the y value as the value of either A or B.
I would like to have the symbols in A to have the same colors as each other, and group B as well.
The problem is whenever I use my code to do this it gives me only 2 symbols instead of 4. i.e. it gives me one symbol per column not per data point. Also I am not sure how to force all points in the same column to be the same color?
I am using d3 v5
This is my code
var margin = {top: 20, right: 10, bottom: 20, left: 10};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
const timeConv = d3.timeParse("%Y-%m-%d");
const dataset = d3.csv("xxx.csv");
dataset.then(function(data) {
var slices = data.columns.slice(1).map(function(id) {
return {
id: id,
values: data.map(function(d){
return {
date: timeConv(d.date),
measurement: +d[id]
};
})
};
});
var svg2 = d3.select("body").append("svg")
.attr("width", (width + margin.left + margin.right))
.attr("height", (height + margin.top + margin.bottom))
.attr("transform", "translate(" + margin.left/4 + "," + margin.top/4 + ")");
// scales
const xScale = d3.scaleTime().range([0,width]);
const yScale = d3.scaleLinear().rangeRound([height, 0]);
xScale.domain(d3.extent(data, function(d){
return timeConv(d.date)}));
yScale.domain([(0), d3.max(slices1, function(c) {
return d3.max(c.values, function(d) {
return d.measurement; });
})
]);
// axis
const yaxis = d3.axisLeft()
.ticks(9)
.scale(yScale);
const xaxis = d3.axisBottom()
.ticks(d3.timeMonth.every(3))
.tickFormat(d3.timeFormat('%b %y'))
.scale(xScale);
var slices2 = slices.filter(slice => RegExp('someRegex').test(slice.id))
//console.log(xScale(timeConv(new Date(d.value.date))))
const symbol = d3.symbol().size(500)
const symbols = svg2_2.append('g').attr('id','symbols').selectAll("symbols")
.data(slices2)
.enter()
symbols.append("path").attr("d", symbol)
Related
I'm trying to create a diverging stacked bar chart. I have two svg areas - one for yes votes and one for no votes. I need the no votes (the bars on the left of the image: https://i.stack.imgur.com/NJveR.png) to start on the right side of the scale and jut out towards the left side of the scale. The bars are created in the rectangles section, in the selNo selection. When creating these bars, how should I define the x attribute so that the bars start from the right?
I've tried all sorts of combinations of adding and subtracting width, changing the scales, transforming the rects. I just can't seem to make it move where I want it!
Full Code:
var margin = { top: 50, right: 5, bottom: 20, left: 5 },
width = 450 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
var svgNo = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "svgNo")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var svgYes = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "svgYes")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var votesNo = [];
var votesYes = [];
/////////GET DATA/////////////
//access votes route in Flask app
d3.json("/votes").then(function (data) {
//loop through objects in route
data.forEach(function (d) {
//convert data to numeric
demYes = +d.democratic.yes
demNo = +d.democratic.no
repYes = +d.republican.yes
repNo = +d.republican.no
indYes = +d.independent.yes
indNo = +d.independent.no
//push desired data to arrays
votesYes.push(
{
"id": d._id,
"name": d.bill.bill_id,
"question": d.question,
"description": d.description,
"demYes": demYes,
"repYes": repYes,
"indYes": indYes,
})
votesNo.push(
{
"id": d._id,
"name": d.bill.bill_id,
"question": d.question,
"description": d.description,
"demNo": demNo,
"repNo": repNo,
"indNo": indNo,
})
});
/////////////STACK GENERATORS//////////////
//create stack generator for YES votes
var stackGenYes = d3.stack()
.keys(["demYes", "repYes", "indYes"]) //keys from votesYes
.order(d3.stackorderDescending)
//use generator to create data array
var stackedSeriesYes = stackGenYes(votesYes);
console.log(stackedSeriesYes);
//create stack generator for NO votes
var stackGenNo = d3.stack()
.keys(["demNo", "repNo", "indNo"]) //keys from votesNo
//use generator to create data array
var stackedSeriesNo = stackGenNo(votesNo);
console.log(stackedSeriesNo);
/////////////SCALE FUNCTIONS////////////
//assign colors to parties
var colorScale = d3.scaleOrdinal()
.domain(["demYes", "repYes", "indYes"])
.range(["#086fad", "#c7001e", "#8A2BE2"]);
var yScale = d3.scaleBand()
.domain(votesYes.map(d => d.id)) //unique identifiers
.range([0, height])
.padding(0.1);
var xScaleYes = d3.scaleLinear()
.domain([0, 535]) //num members of congress
.range([0, width]);
var xScaleNo = d3.scaleLinear()
.domain([0, 535])
.range([width, 0]);
////////////////RECTANGLES////////////////
//create g tags for each YES key
var selYes = d3.select("#svgYes")
.select('g')
.selectAll('g.seriesYes')
.data(stackedSeriesYes)
.join('g')
.classed('series', true)
.style('fill', (d) => colorScale(d.key)); //assign color
//create YES bars
selYes.selectAll('rect')
.data(d => d)
.join('rect')
.attr('width', d => xScaleYes(d[1]) - xScaleYes(d[0])) //length of bars
.attr('x', d => xScaleYes(d[0])) //bar starting point (x)
.attr('y', d => yScale(d.data.id)) //bar starting point (y)
.attr('height', 32) //thickness of bar
//create g tags for each NO key
var selNo = d3.select("#svgNo")
.select('g')
.selectAll('g.seriesNo')
.data(stackedSeriesNo)
.join('g')
.classed('series', true)
.style('fill', (d) => colorScale(d.key));
//create NO bars
selNo.selectAll('rect')
.data(d => d)
.join('rect')
.attr('width', d => xScaleNo(d[0]) - xScaleNo(d[1])) //length of bars
.attr('x', d => width - xScaleNo(d[0])) //bar starting point (x)
.attr('y', d => yScale(d.data.id)) //bar starting point(y)
.attr('height', 32); //thickness of bar
});
I have a dataset that the age field is represent as range, such as 0--8,9--17,18--23,etc.
year,gender,age,population
2002,Female,0--8,0
2002,Female,9--17,25
2002,Female,18--20,291
2002,Female,21--23,375
2002,Female,24--26,212
2002,Female,27--29,108
2002,Female,30--38,74
2002,Female,39--47,0
I want to use this age in the X axis of my barplot. How can I calculate the domain in this range form? Seems like I can't just use
d3.max(dataset, function(d) { return +d.age})
Below is my code until creating yScale:
function dataProcessor(d) {
return {
year: +d.year,
gender: d.gender,
age: +d.age,
population: +d.population
};
}
width = 600
height = 400
var svg = d3.select("svg");
var margin = {top: 50,
right: 50,
bottom: 50,
left: 100
};
chartWidth = +svg.attr("width") - margin.left - margin.right;
chartHeight = +svg.attr("height") - margin.top - margin.bottom;
g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv('years.csv', dataProcessor).then(function(data) {
});
var xScale = d3.scaleBand().rangeRound([0, width])
.padding(0.1)
.domain([0, data.length-1])
var populationMax = d3.max(data, function(d) {
return +d.population
});
var populationMin = d3.min(data, function(d) {
return +d.population
});
var yScale = d3.scaleLinear()
.domain([populationMin, populationMax])
.range([height, 0]);
These age ranges aren’t continuous; they’re ordinal. Essentially, these are labels that happen to have a particular order, not numbers.
Rather than use d3.scaleLinear(), use d3.scaleBand() where the domain is ["0--8", "9--17", ... , "39--47"]. d3.scaleBand() maps from a discrete domain to continuous range. It’s designed to be used for things like bar charts, so it also includes a bandwidth() method to tell you how wide each band is based on the size of your domain, range, and any padding that you’ve configured the scale for.
let x = d3.scaleBand(["0--8", "9--17", "18--20", "21--23",
"24--26", "27--29", "30--38" , "39--47"]);
.range([[0, width]);
Then (assuming you already have a data-bound selection called bars) you can position each bar with something like
bars.
.attr("x", d => x(d.age))
.attr("width", x.bandwidth());
I'd like to space the first tick from the origin point, but would also like to have a line connecting them. I suppose I could append an svg to do this for me, but there has to be an easier way that I am missing in the documentation. An image example of what I'm aiming for can be found here
Here's an example of the issue I'm having:
var margin = {
top: 20,
right: 10,
bottom: 40,
left: 40
};
var padding = 20;
var height = 100;
var width = 400
var xAxisTimeScale = [];
for(var i = 8; i < 21; i++) {
xAxisTimeScale.push(i);
}
// scales
var xScale = d3.scaleLinear()
.domain([0, 12])
.range([padding, width - padding]);
var yScale = d3.scaleLinear()
.domain([0, 10])
.range([height, 0]);
function convertTimeToString(time) {
if(time > 12) {
return (time - 12) + "PM";
} else {
return time + "AM";
}
}
var xAxis = d3.axisBottom(xScale)
.ticks(13)
.tickFormat(function(d,i){ return convertTimeToString(xAxisTimeScale[i]);});
var yAxis = d3.axisLeft(yScale);
var svg = d3.select("body").append("svg")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// add axes
svg.append('g')
.call(yAxis);
svg.append('g')
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>
Here are the approaches I came up with:
Change x axis's path's d attribute to 'M0,0.5V0.5H'+(width-padding). Relevant code changes:
svg.select('.x.axis path.domain').attr('d', function() {
return 'M0,0.5V0.5H'+(width-padding);
});
How did I come up with 0.5 in there? I analyzed d3.js and came across the V0.5 (which was V0 in d3-version3 used for creating X-axis domain. For more details on how a path is formed, check out SVG path d attribute. Using this offset, here's a code snippet implementing the same:
var margin = {
top: 20,
right: 10,
bottom: 40,
left: 40
};
var padding = 20;
var height = 100;
var width = 400
var xAxisTimeScale = [];
for(var i = 8; i < 21; i++) {
xAxisTimeScale.push(i);
}
// scales
var xScale = d3.scaleLinear()
.domain([0, 12])
.range([padding, width - padding]);
var yScale = d3.scaleLinear()
.domain([0, 10])
.range([height, 0]);
function convertTimeToString(time) {
if(time > 12) {
return (time - 12) + "PM";
} else {
return time + "AM";
}
}
var xAxis = d3.axisBottom(xScale)
.ticks(13)
.tickFormat(function(d,i){ return convertTimeToString(xAxisTimeScale[i]);});
var yAxis = d3.axisLeft(yScale);
var svg = d3.select("body").append("svg")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// add axes
svg.append('g')
.call(yAxis);
svg.append('g').classed('x axis', true)
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
svg.select('.x.axis path.domain').attr('d', function() {
return 'M0,0.5V0.5H'+(width-padding);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>
Add a line explicitly from origin to the start-point of X-axis with a simple line code. Relevant code changes:
svg.append('line').classed('connecting-line', true)
.attr('y1', height+0.5).attr('y2', height+0.5).attr('x1', 0).attr('x2', padding).style('stroke', '#000');
0.5 has the same reason as above. Rest attributes are just based on height and width. Here's a code snippet implementing this:
var margin = {
top: 20,
right: 10,
bottom: 40,
left: 40
};
var padding = 20;
var height = 100;
var width = 400
var xAxisTimeScale = [];
for(var i = 8; i < 21; i++) {
xAxisTimeScale.push(i);
}
// scales
var xScale = d3.scaleLinear()
.domain([0, 12])
.range([padding, width - padding]);
var yScale = d3.scaleLinear()
.domain([0, 10])
.range([height, 0]);
function convertTimeToString(time) {
if(time > 12) {
return (time - 12) + "PM";
} else {
return time + "AM";
}
}
var xAxis = d3.axisBottom(xScale)
.ticks(13)
.tickFormat(function(d,i){ return convertTimeToString(xAxisTimeScale[i]);});
var yAxis = d3.axisLeft(yScale);
var svg = d3.select("body").append("svg")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// add axes
svg.append('g')
.call(yAxis);
svg.append('g').classed('x axis', true)
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
svg.append('line').classed('connecting-line', true)
.attr('y1', height+0.5).attr('y2', height+0.5).attr('x1', 0).attr('x2', padding).style('stroke', '#000');
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>
Adding an overlay rectangle either as a box OR as a rect styled with stroke-dasharray. But I think this wouldn't be that helpful as it would be a bit of overriding stuff.
Hope any of the above approaches serves the purpose. And I'm really sorry for not getting back on time (Friday night got me) :)
Given that you have a linear scale disguised as a time scale, the solution here will be an ad hoc one. Otherwise, the solution could be more idiomatic.
First, increase your xAxisTimeScale:
var xAxisTimeScale = d3.range(7, 23, 1)
Then, increase your domain...
.domain([0, 13])
... and remove the first tick:
.tickFormat(function(d, i) {
return i ? convertTimeToString(xAxisTimeScale[i]) : null;
});
Here is your code with those changes:
var margin = {
top: 20,
right: 10,
bottom: 40,
left: 40
};
var padding = 20;
var height = 100;
var width = 400
var xAxisTimeScale = d3.range(7, 23, 1)
// scales
var xScale = d3.scaleLinear()
.domain([0, 13])
.range([0, width - padding]);
var yScale = d3.scaleLinear()
.domain([0, 10])
.range([height, 0]);
function convertTimeToString(time) {
if (time > 12) {
return (time - 12) + "PM";
} else {
return time + "AM";
}
}
var xAxis = d3.axisBottom(xScale)
.ticks(13)
.tickFormat(function(d, i) {
return i ? convertTimeToString(xAxisTimeScale[i]) : null;
});
var yAxis = d3.axisLeft(yScale);
var svg = d3.select("body").append("svg")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// add axes
svg.append('g')
.call(yAxis);
svg.append('g')
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>
I am making a scatterplot in D3 with the code below and the textlabels of the right circles are being cut off, after putting textlabels on the X and Y axis.
Here is a working jsFiddle:
https://jsfiddle.net/chemok78/1vcat0s8/4/
Adding the X and Y axis labels seems to move the whole chart to the right and up, making it move a bit out of the containing div. Anyone can help me how to fix this?
var url = "https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/cyclist-data.json";
d3.json(url, function(json) {
var data = json;
var margin = {
top: 40,
right: 100,
bottom: 80,
left: 80
};
var w = 1000 - margin.left - margin.right;
var h = 800 - margin.top - margin.bottom;
var svg = d3.select("#chart")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var maxRank = d3.max(data, function(d) {
return d.Place;
});
var minSeconds = d3.min(data, function(d) {
return d.Seconds;
});
var maxSeconds = d3.max(data, function(d) {
return d.Seconds;
});
var formatTime = d3.time.format("%M:%S");
var formatSeconds = formatMinutes = function(d) {
return formatTime(new Date(2016, 0, 0, 0, 1, d));
};
var xScale = d3.scale.linear()
.domain([maxSeconds + 5, minSeconds])
.range([0, w]);
var yScale = d3.scale.linear()
.domain([0, maxRank + 2])
.range([0, h]);
This has nothing to do with "moving the chart". Here is the problem.
When you do this:
var labels = svg.selectAll("text")
You're selecting text elements that already exist in your SVG. Because of that, your "enter" selection will have less elements compared to what it should contain.
The solution is simple: select something that doesn't exist:
var labels = svg.selectAll("foo")
Or, alternatively, move the blocks that append the axes' labels to the bottom of the code.
Here is your updated fiddle: https://jsfiddle.net/mp7LxsL4/
I'm building a set of bar charts that will be updated dynamically with json data.
There will be occasions where the x.domain value is equal to zero or null so in those cases I don't want to draw the rectangle, and would like the overall height of my chart to adjust. However, the bars are being drawn based on data.length which may contain 9 array values, but some of those values are zeros, but render a white space within the graph.
I've attached an image of what is happening. Basically there are 9 data entries and only one of those actually contains the positive value, but the bars are still being drawn for all 9 points.
Here is my code:
d3.json("data/sample.json", function(json) {
var data = json.cand;
var margin = {
top: 10,
right: 20,
bottom: 30,
left: 0
}, width = parseInt(d3.select('#graphic').style('width'), 10),
width = width - margin.left - margin.right -50,
bar_height = 55,
num_bars = (data.length),
bar_gap = 18,
height = ((bar_height + bar_gap) * num_bars);
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.ordinal()
.range([height, 0]);
var svg = d3.select('#graphic').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'] + ')');
var dx = width;
var dy = (height / data.length) + 8;
// set y domain
x.domain([0, d3.max(data, function(d) {
return d.receipts;
})]);
y.domain(0, d3.max(data.length))
.rangeBands([0, data.length * bar_height ]);
var xAxis = d3.svg.axis().scale(x).ticks(2)
.tickFormat(function(d) {
return '$' + formatValue(d);
});
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.ticks(0)
.tickFormat(function(d) {
return '';
});
// set height based on data
height = y.rangeExtent()[1] +12;
d3.select(svg.node().parentNode)
.style('height', (height + margin.top + margin.bottom) + 'px');
// bars
var bars = svg.selectAll(".bar")
.data (data, function (d) {
if (d.receipts >= 1) {
return ".bar";
} else {
return null;
}
})
.enter().append('g');
bars.append('rect')
.attr("x", function(d, i) {
return 0;
})
.attr("y", function(d, i) {
if (d.receipts >= 1) {
return i * (bar_height + bar_gap - 4);
}else {
return null;
}
})
.attr("width", function(d, i) {
if (d.receipts >= 1) {
return dx * d.receipts;
} else {
return 0;
}
});
//y and x axis
svg.append('g')
.attr('class', 'x axis bottom')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis.orient('bottom'));
svg.append('g')
.call(yAxis.orient('left'));
});