I have tried everything from https://observablehq.com/#d3/styled-axes and other tutorials but no chance: formatting the x-axis doesn't work, for instance with .call(d3.axisBottom(x).ticks(10)). My chart always shows every tick (> 100) and so it's overlapping. The x-axis displays dates, like 2020-03-06.
Here is my code:
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 80, left: 80},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).paddingInner(0.15),
y = d3.scaleLinear().rangeRound([height, 0]);
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("10.json").then(function (data) {
x.domain(data.map(function (d) { return d.TIMESTAMP; }));
y.domain([0, d3.max(data, function (d) { return Number(d.TURNOVER); })]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(10));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(10));
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.TIMESTAMP); })
.attr("y", function(d) { return y(d.TURNOVER); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.TURNOVER); });
})
.catch((error) => {
throw error;
});
</script>
You should use date & time scale for your x scale which is x = d3.scaleTime().
In ticks(10), You might want to slice it by month, or days. For example .ticks(d3.timeDay.every(4)). This will slice to every 4 days.
You also want to change your date format. You can use d3.timeFormat("%m/%d"), which will give you "02/06".
Here is some example. Hope this helps.
// Let create some json dataset
var data = [];
for (var i = 0; i < 1000; ++i) {
let date = parseInt(Math.random() * 30) + 1;
let dateStr = (date < 10) ? '0' + date : date
data.push({
TIMESTAMP: '2020-02-' + dateStr.toString(),
TURNOVER: Math.random() * 100
});
}
// Create svg
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 80, left: 80},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().rangeRound([height, 0]);
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Find min and max date
var minDate = d3.min(data.map(function(d) { return new Date(d.TIMESTAMP); }));
var maxDate = d3.max(data.map(function(d) { return new Date(d.TIMESTAMP); }));
// Set x and y data domains
x.domain([minDate, maxDate]);
y.domain([0, d3.max(data, function(d) { return d.TURNOVER; })]);
// Create x axis
var xAxis = d3.axisBottom(x)
.ticks(d3.timeDay.every(4)) // Slice time every 4 days
.tickFormat(d3.timeFormat("%m/%d")); // Change time format as m/d
// Add x axis
g.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0, 10)')
.call(xAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="100"></svg>
Related
I have an issue with D3 scatterplot where the data are not correctly plot
(plotted to 1 horizontal line rather than a scattered plot, the actual data is also scattered)
and the x-axis not able to show up.
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%dT%H:%M:%S.%L%Z");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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 + ")");
// Get the data
data = data.rows;
// format the data
data.forEach(function(d) {
var momentTemp = moment(d[0]).format("YYYY-MM-DDTHH:mm:ss.SSSZ");
var parseTemp = parseTime(momentTemp);
d.date = parseTemp;
d.close += d[1];
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.close;
})]);
var xValue = function(d) {
return d.date;
}
var yValue = function(d) {
return d.close;
}
// Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 1.5)
.attr("cx", function(d) {
return x(d.date);
})
.attr("cy", function(d) {
return y(d.close);
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.attr("transform", "translate(" + margin.left + " ,0)")
.call(d3.axisLeft(y));
That happens when your data is invalid. Are you sure the field close is correct? Your data calls the column ratio. I made some sample data and everything works:
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%dT%H:%M:%S.%L%Z");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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 + ")");
var data = new Array(100)
.fill(undefined)
.map(function(d, i) {
return {
date: new Date(Number(new Date("01/01/2000")) + i * 24 * 60 * 60 * 1000),
close: Math.random(),
};
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.close;
})]);
var xValue = function(d) {
return d.date;
}
var yValue = function(d) {
return d.close;
}
// Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 1.5)
.attr("cx", function(d) {
return x(d.date);
})
.attr("cy", function(d) {
return y(d.close);
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.attr("transform", "translate(" + margin.left + " ,0)")
.call(d3.axisLeft(y));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
I am displaying the following boxplot and would like to give it style features like: resize it to 400x800px , display it in the middle of the html file... Now I cant see the x axis labels for example because of the size... Any help?
Please check the complete code on this plunker.
The script is starting like this:
var labels = true; // show the text labels beside individual boxplots?
var margin = {top: 25, right: 25, bottom: 25, left: 25};
var width = 800 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var min = 0,
max = 10;
// parse in the data
d3.csv("boxplot_year.csv", function(error, csv) {
var data = [];
data[0] = [];
data[1] = [];
data[2] = [];
data[3] = [];
data[4] = [];
data[5] = [];
data[0][0] = "Y2010";
data[1][0] = "Y2011";
data[2][0] = "Y2012";
data[3][0] = "Y2013";
data[4][0] = "Y2014";
data[5][0] = "Y2015";
data[0][1] = [];
data[1][1] = [];
data[2][1] = [];
data[3][1] = [];
data[4][1] = [];
data[5][1] = [];
csv.forEach(function(x) {
var v1 = Math.floor(x.Y2010),
v2 = Math.floor(x.Y2011),
v3 = Math.floor(x.Y2012),
v4 = Math.floor(x.Y2013),
v5 = Math.floor(x.Y2014),
v6 = Math.floor(x.Y2015);
var rowMax = Math.max(Math.max(v1,v2), Math.max(Math.max(v3,v4), Math.max(v5,v6)));
var rowMin = Math.min(Math.min(v1,v2), Math.min(Math.min(v3,v4), Math.min(v5,v6)));
data[0][1].push(v1);
data[1][1].push(v2);
data[2][1].push(v3);
data[3][1].push(v4);
data[4][1].push(v5);
data[5][1].push(v6);
if (rowMax > max) max = rowMax;
if (rowMin < min) min = rowMin;
});
var boxplot = d3.box()
.whiskers(iqr(1.5))
.height(height)
.domain([min, max])
.showLabels(labels);
var svg = d3.select("#boxplot").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("class", "box")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// the x-axis
var x = d3.scale.ordinal()
.domain( data.map(function(d) { console.log(d); return d[0]; } ) )
.rangeRoundBands([0 , width], 0.7, 0.3);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// the y-axis
var y = d3.scale.linear()
.domain([min, max])
.range([height + margin.top, 0 + margin.top]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// draw the boxplots
svg.selectAll(".box")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x(d[0]) + "," + margin.top + ")"; } )
.call(boxplot.width(x.rangeBand()));
// draw y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text") // and text1
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "middle")
.style("font-size", "10px")
.text("Grade");
// draw x axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height + margin.top + 10) + ")")
.call(xAxis)
.append("text") // text label for the x axis
.attr("x", (width / 2) )
.attr("y", 10 )
.attr("dy", ".71em")
.style("text-anchor", "middle")
.style("font-size", "10px")
.text("Year");
});
// Returns a function to compute the interquartile range.
function iqr(k) {
return function(d, i) {
var q1 = d.quartiles[0],
q3 = d.quartiles[2],
iqr = (q3 - q1) * k,
i = -1,
j = d.length;
while (d[++i] < q1 - iqr);
while (d[--j] > q3 + iqr);
return [i, j];
};
}
You can adjust the bottom margin to 100. This is sufficient to show the bottom y-axis.
var margin = {top: 25, right: 25, bottom: 100, left: 25};
Based on your comment, to make both axis unite at 0 add a transform to your y-axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate (0,10)")
.call(yAxis)
To get rid of the Y from Y2010 add a slice to your labels like below. However be aware that it is changing your data so in your console log you will see years as 2010, 2011 etc.
// the x-axis
var x = d3.scale.ordinal()
.domain( data.map(function(d) { d[0] = d[0].slice(1); console.log(d); return d[0]; } ) )
.rangeRoundBands([0 , width], 0.7, 0.3);
Hope this helps.
Remove the width and height attributes on your svg and make it expand to full size of its outer container using the viewBox attribute, replace the var svg part as follows, I hard coded the viewBox, change it as you see fit:
var svg = d3.select("#boxplot").append("svg")
.attr("viewBox","0 0 800 800")
.attr("preserveAspectRatio","none")
.style("display","block")
.style("width","100%")
.style("height","100%")
.attr("class", "box")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Since you now make your svg expand as much as possible, only modify the outer div as you see fit, either on "resize", periodically etc. To better understand what viewBox does: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox
I am trying to create a Line chart for stock data like Open, High, Low, Close and Volume but when i am trying to pass these data in Line chart it does not display correct graph its only represent date and close. But i want to pass Open High Low Close and volume in Line chart please help me.
I just want to pass some more data.
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.timeParse("%d-%b-%y");
// Set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// Define the axes
var xAxis = d3.axisBottom().scale(x)
.tickSizeInner(-height)
.tickSizeOuter(0)
.tickPadding(10);
var yAxis = d3.axisLeft().scale(y)
.tickSizeInner(-width)
.tickSizeOuter(0)
.tickPadding(10);
var zAxis = d3.axisRight(y);
// Define the line
var valueline = d3.line()
.x(function (d) { return x(d.Date); })
.y(function (d) { return y(d.Close); });
// Adds the svg canvas
var svg = d3.select("#DivChartShow")
.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 + ")");
// Get the data
d3.csv("/Content/data2.csv", function (error, data) {
// for filter data by days
if (days != undefined) {
var today = new Date();
today.setDate(today.getDate() - days);
var dd = today.getDate();
var mm = today.getMonth() + 1;
var yyyy = today.getFullYear();
if (dd < 10) {
dd = '0' + dd;
}
if (mm < 10) {
mm = '0' + mm;
}
var today = mm + '/' + dd + '/' + yyyy;
var cutoffDate = moment(today).format('DD-MMM-YY');
data = data.filter(function (i) {
return parseDate(i.Date) > parseDate(cutoffDate);
});
}
data.forEach(function (d) {
d.Date = parseDate(d.Date);
d.Open = +d.Open;
d.High = +d.High;
d.Low = +d.Low;
d.Close = +d.Close;
});
// Scale the range of the data
x.domain(d3.extent(data, function (d) { return d.Date; }));
y.domain([0, d3.max(data, function (d) { return d.Close; })]);
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
if (clStatus == true) {
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
}
if (crStatus == true) {
svg.append("g")
.attr("class", "z axis")
.attr("transform", "translate(" + width + " ,0)")
.call(yAxis);
}
});
</script>
I'm only posting this as an answer because it's too much for a comment. From your code:
var valueline = d3.line()
.x(function (d) { return x(d.Date); })
.y(function (d) { return y(d.Close); });
This line will ONLY draw d.Close on the y axis. Either: write multiple line functions for each property(d.Open, d.High, etc) OR nest the data by property and draw a line using forEach() with some sort of color scale. I think this version may be viable if all the properties use the same value (dollars): https://bl.ocks.org/mbostock/3884955
QUESTION:
Why are there no ticks on my X (time) axis xAxis?
CODE:
<script type="text/javascript">
d3.json("https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json", function(error, json) {
if (error) {
return console.warn(error);
}
visualizeIt(json);
});
function visualizeIt(data) {
const dataset = data.data;
const margin = {
top: 10,
right: 6,
bottom: 20,
left: 70
}
const w = 900 - margin.left - margin.right;
const h = 500 - margin.top - margin.bottom;
const barWidth = Math.ceil(w / dataset.length);
const format = d3.timeFormat("%Y-%m");
const mindate = dataset[0][0];
const maxdate = dataset[274][0];
const xScale = d3.scaleTime()
.domain([mindate, maxdate])
.range([0, w]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[1])])
.range([h, 0]);
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale)
const svg = d3.select("#results")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom);
svg.append("g")
.attr("transform", "translate(50," + (h+margin.top) + ")")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(50," + margin.top + ")")
.call(yAxis);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d,i) => 50+barWidth*i)
.attr("y", (d, i) => yScale(d[1]) + margin.top)
.attr("width", barWidth)
.attr("height", (d, i) => h - yScale(d[1]))
.attr("fill", "navy")
.attr("class", "bar");
}
</script>
There are some problems in your code, but I'll address only those related to the x-axis ticks:
You want to parse the strings and return dates, not the other way around. Thus, instead of using timeFormat, your conts format should use timeParse:
const format = d3.timeParse(specifier)
That brings us to the second problem: the specifier in your const format is wrong. You're missing %d here, which is the day, since your strings are like this: "1947-01-01". So, it should be:
const format = d3.timeParse("%Y-%m-%d")
You have to use the parser in your dates:
const mindate = format(dataset[0][0]);
Do the same for maxdate.
Here is your updated CodePen: https://codepen.io/anon/pen/GmjRzY?editors=1010
I'm using Polymer to render some d3 charts. When the Polymer is initially rendered I only draw a graph with axes and no data, since the data comes later once the API calls succeed. However, when I get around to selecting the 'rect' elements in the svg, calling data() on that fails. Here's my code:
dataChanged: function() {
var data = this.data;
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = this.width - margin.left - margin.right,
height = this.height - margin.top - margin.bottom;
// format the data
data.forEach(function(d) {
d.date = d3.isoParse(d.date);
});
// set the ranges
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.date; }))
.rangeRound([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var svg = d3.select(this.$.chart).transition();
var histogram = d3.histogram()
.value(function(d) { return d.date; })
.domain(x.domain())
.thresholds(x.ticks(d3.timeMonth));
var bins = histogram(data);
y.domain([0, d3.max(bins, function(d) { return d.length; })]);
svg.selectAll("rect")
.data(bins)
.enter().append("rect")
.attr("class", "bar")
.attr("x", 1)
.attr("transform", function(d) {
return "translate(" + x(d.x0) + "," + y(d.length) + ")";
})
.attr("width", function(d) { return x(d.x1) - x(d.x0) -1 ; })
.attr("height", function(d) { return height - y(d.length); });
svg.select(".xAxis")
.duration(300)
.call(d3.axisBottom(x));
svg.select(".yAxis")
.duration(300)
.call(d3.axisLeft(y));
},
ready: function() {
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = this.width - margin.left - margin.right,
height = this.height - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleTime()
.domain([new Date(2010, 6, 3), new Date(2012, 0, 1)])
.rangeRound([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
// Add the SVG to my 'chart' div.
var svg = d3.select(this.$.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 + ")");
// Add the X Axis
svg.append("g")
.attr("class","xAxis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.attr("class","yAxis")
.call(d3.axisLeft(y));
}
ready() gets called upon rendering, dataChanged() when the parent component passes a chunk of data down. Everything works fine until svg.selectAll("rect").data(bins) is called when it crashes with Uncaught TypeError: svg.selectAll(...).data is not a function. bins has the right data in it, so that's not empty. I know that there are no rect elements for it to select, but in the example I followed here there are no rect elements prior to them being appended by this call anyway, so I'm confused as to why this doesn't work.
What's the purpose of this line:
var svg = d3.select(this.$.chart).transition();
This would make svg a transition, your code is implying it should be selection. So, just drop it the .transition:
var svg = d3.select(this.$.chart);
...
svg.selectAll("rect")
.data(bins)
...