I have a scatter plot created using d3.js that I am trying to add an x axis to which will range from 12AM to 12AM i.e. spanning 24 hours. The nature of the graph is that the data will change depending on user input however I keep receiving the same error for however I try to append the axis.
I receive the error:
d3.v4.min.js:2 Error: attribute transform: Expected number,
"translate(NaN,0)".
Here is my code in which I have tried to include only that which is important for my query. (timeLog is my array of data)
var parseTime = d3.utcParse("%H:%M");
var midnight = parseTime("00:00");
var height = $("#scatter").height();
var width = $("#scatter").width();
var max = Math.max(timeLog);
var min = Math.min(timeLog);
var yScale = d3.scale.linear()
.domain([0,d3.max(timeLog)])
.range([0,height]);
var xScale = d3.scaleBand()
.domain(d3.range(0,timeLog.length))
.range([0,width]);
d3.select("#scatter").append('svg')
.attr('width',width)
.attr('height',height)
.style('background', '#f4f4f4')
.selectAll('circle')
.data(timeLog)
.enter().append('circle')
.style('fill', 'black')
.style('stroke','none')
.attr('cx',function(d, i){
return xScale(i);
})
.attr('cy',function(d){
return height - yScale(d);
})
.attr('r',2);
var hScale = d3.scaleUtc()
.domain([midnight,d3.time.day.offset(midnight,1)])
.range([0,width]);
var xAxis = d3.axisBottom()
.scale(hScale)
.tickFormat(d3.time.format.utc("%I %p"))
var xGuide = d3.select('svg')
.append('g')
.attr("class", "axis axis--x")
xAxis(xGuide)
xGuide.attr('transform','translate(0,' + height + ')')
xGuide.selectAll('path')
.style('fill','black')
.style('stroke','black')
xGuide.selectAll('line')
.style('stroke','black')
I am fairly new to d3.js and am attempting to teach myself the basics so any feedback that would help me identify the cause of the error would be greatly appreciated.
Edit
I have made some changes after discovering some of my syntax was outdated and updated the above code, I am now not receiving any error messages however the axis is still not displaying. Is there perhaps some attribute I am missing?
Thanks for any feedback.
Related
I need some help with scaling with D3.In my codepen I am attempting to create a graph with some retrieve GDP data.
The data is retrieved and displayed correctly, but when I attempt to scale the graph only one vertical bar is displayed.
Can someone point me in the right direction?
Here is a link to project codepen:
https://codepen.io/henrycuffy/pen/gKVdgv
The main.js:
$(document).ready(function() {
$.getJSON(
'https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json',
function(data) {
const dataset = data.data;
const w = 1000;
const h = 500;
var maxX = d3.max(dataset, d => d[1]);
var minDate = new Date(dataset[0][0]);
var maxDate = new Date(dataset[dataset.length - 1][0]);
var xScale = d3
.scaleTime()
.domain([minDate, maxDate])
.range([0, w]);
var yScale = d3
.scaleLinear()
.domain([0, maxX])
.range([h, 0]);
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
const svg = d3
.select('body')
.append('svg')
.attr('width', w)
.attr('height', h);
svg
.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.attr('x', (d, i) => xScale(i * 10))
.attr('y', d => h - yScale(d[1]))
.attr('width', 2)
.attr('height', d => yScale(d[1]))
.attr('fill', 'navy');
}
);
});
You are using a time scale for the x axis. But you aren't positioning the bars based on a time:
.attr('x', (d, i) => xScale(i * 10))
You are positioning each bar based on its index. The scale expects you to feed a date to it (it is taking the provided number and treating it as a date, which is pretty near the beginning of the epoch (Jan 1, 1970), which explains the positioning. The bars appear as one because each one is placed 10 milliseconds out from the previous one on a scale that covers decades, an imperceptible difference).
Instead let's feed the x scale the date in the data:
.attr('x', d => xScale(new Date(d[0]) )
Since the datum contains a string representation of the date, I'm converting to a date object here. You could do this to the data once it is loaded, but to minimize changes I'm just doing it when assigning the x attribute.
Here's an updated plunkr.
I'm having trouble on this.. I'm working on a line chart using d3.js. I'm having trouble to display the X axis tick text that uses date values ONLY in the data spreadsheet. When I develop the line chart, I saw it automatic generated the date values between the values from the Data spreadsheet. Here is a quick example of the data.
date,close
16-Dec-12,53.98
16-Dec-12,67.00
16-Dec-12,89.70
16-Dec-12,99.00
16-Dec-12,130.28
23-Dec-12,166.70
23-Dec-12,234.98
23-Dec-12,345.44
23-Dec-12,443.34
23-Dec-12,543.70
23-Dec-12,580.13
30-Dec-12,605.23
30-Dec-12,622.77
30-Dec-12,626.20
30-Dec-12,628.44
30-Dec-12,636.23
30-Dec-12,633.68
So in this dataset, it has 3 different date values.
In the D3 line chart, I want to display those only the 3 different date values which are Weeks in the x axis tick text. However, the chart is generated other dates in between those data date values. Example below.
I'm trying to display like this that only display the date values from the Dataset.
I hope this makes sense. Is this possible? I tried to use .tick() but it only display '16-Dec-12' which it confuses me. I'm pretty new into line charts for d3.js =/
here is my snippet code. I hope this helps.
function getExtent(member) {
var extents = [];
dataset.forEach(function(arry){
extents = extents.concat(d3.extent(arry,function(d){return d[member]}));
});
return d3.extent(extents);
}
var xScale = d3.time.scale().domain(getExtent('x')).range([0,width]);
var yScale = d3.scale.linear().domain(getExtent('y')).range([height,0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
xAxis.scale(xScale)
.tickFormat(d3.time.format('%b %d'));
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
var lineFunc = d3.svg.line()
.x(function(d){return xScale(d.x)})
.y(function(d){return yScale(d.y)})
.interpolate('linear');
var g = svg.append('g')
.attr('width',width)
.attr('height',height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Use this group for drawing the lines
g.append('g')
.attr('class', 'line-group');
// Axes
g.append('g')
.attr('class', 'usps-multiline axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
.selectAll("text")
.attr("transform", "translate(-40,20) rotate(315)");
g.append('g')
.attr('class', 'usps-multiline axis axis--y')
.call(yAxis);
**Please let me know if you can view the sample pic.
This is the expected behaviour for a time scale. In D3, the axis is automatically generated, you don't have much control on the ticks.
The easiest alternative seems to be passing an array of the dates you have in your data to tickValues:
var axis = d3.axisBottom(scale)
.tickValues(uniqueValues);
Here, uniqueValues is an array with the dates you have in your CSV, filtered to only unique dates (otherwise you'll have several ticks in the same position).
here is the demo with the CSV you shared:
var svg = d3.select("svg");
var csv = `date,close
16-Dec-12,53.98
16-Dec-12,67.00
16-Dec-12,89.70
16-Dec-12,99.00
16-Dec-12,130.28
23-Dec-12,166.70
23-Dec-12,234.98
23-Dec-12,345.44
23-Dec-12,443.34
23-Dec-12,543.70
23-Dec-12,580.13
30-Dec-12,605.23
30-Dec-12,622.77
30-Dec-12,626.20
30-Dec-12,628.44
30-Dec-12,636.23
30-Dec-12,633.68`;
var data = d3.csvParse(csv, function(d) {
d.date = d3.timeParse("%d-%b-%y")(d.date);
return d
});
var uniqueValues = [...new Set(data.map(function(d) {
return d.date.getTime()
}))].map(function(d) {
return new Date(d);
});
var scale = d3.scaleTime()
.range([30, 570])
.domain(d3.extent(data, function(d) {
return d.date
}));
var axis = d3.axisBottom(scale)
.tickValues(uniqueValues);
var gX = svg.append("g")
.attr("transform", "translate(0,50)")
.call(axis);
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="100"></svg>
PS: I'm using D3 v4 in the demo, but the principle is the same.
If .ticks(3) doesn't work, you can pass a custom function into .ticks to ensure you get the ticks you want.
Here is a fairly comprehensive axis tutorial.
I have constructed a stacked bar chart with approx 700 bars. Everything function as it should but I am getting really frustrated with the stripes that appear when the chart is drawn. Below is a screenshot with the default view and a zoomed view.
zoomed view to the left, default to the right
I suspect that the stripes come from the padding between the bars. I've tampered with the bar width to try and eliminate the padding but the stripes are still there. Currently the bar width code looks like this:
.attr("width",((width-(padding+xPadding))/data.length)+0.01)
The "+0.01" removes the padding and if I increase it further to, say 1, the stripes are gone. However, now the bars are stacked on each other noticably, which I do not want. I suspect there is some quick fix to this(maybe css or something other trivial) but I cannot find it myself. So, how do I solve this?
Thanks in advance.
EDIT 1:
Tried using scalebands as suggested in comments but it had no effect on the stripes.
same behaviour with scalebands
EDIT 2:
Added relevant code used to draw rectangles. Note the code does not run, snippet is just for viewing the code.
d3.csv("vis_temp.csv", function(d, i, columns) {
for (i = 1, t = 0; i < columns.length-1; ++i){ //calculate total values. ignore last column(usecase)
t += d[columns[i]] = +d[columns[i]];
}
d.total = t;
return d;
}, function(error,data){
if(error){
console.log(error);
return;
}
console.log(data);
dataset = data; // save data outside of d3.csv function
header = data.columns.slice(1); //prop1, prop2..... no sample
header.splice(header.length-1,1); //remove usecase from header
stack = d3.stack().keys(header);
maxValue = d3.max(data,function(d){
return d.total;});
samples = data.map(function(d){
return d.sample;});
xScale = d3.scaleLinear()
.domain([1,samples.length+1])
.range([padding+1,width-xPadding]);
/* using scalebands
xScale = d3.scaleBand()
.domain(d3.range(data.length))
.range([padding+1,width-xPadding]);
*/
yScale = d3.scaleLinear()
.domain([0,maxValue])
.range([height-padding,padding]);
zScale = d3.scaleOrdinal()
.domain(header)
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); // low profile, stylish colors
xAxis = d3.axisBottom()
.scale(xScale)
.ticks(nbrOfXTicks);
yAxis = d3.axisLeft()
.scale(yScale)
.ticks(nbrOfYTicks);
svg.append("text")
.attr("class","chart_item")
.attr("x",(width-padding-xPadding-20)/2)
.attr("y",padding/2)
.text("measurement");
svg.append("text")
.attr("class","chart_item")
.attr("x",padding/3)
.attr("y",height/2)
.attr("transform","rotate(270,"+padding/3+","+height/2+")")
.text("Time [ms]")
svg.append("text")
.attr("class","chart_item")
.attr("x",(width-padding-xPadding)/2)
.attr("y",height-7)
.text("Sample");
svg.append("g")
.attr("class","axis")
.attr("id","x_axis")
.attr("transform","translate(0,"+(height-padding)+")")
.call(xAxis);
svg.append("g")
.attr("class","axis")
.attr("id","y_axis")
.attr("transform","translate("+padding+",0)")
.call(yAxis);
svg.append("g").attr("class","data");
svg.select(".data")
.selectAll("g")
.data(stack(data))
.enter()
.append("g")
.attr("class","data_entry")
.attr("id",function(d){
return d.key;})
.attr("fill",function(d){
return zScale(d.key);})
.selectAll("rect")
.data(function(d,i){
return d;})
.enter()
.append("rect")
.attr("id",function(d){
return "bar_"+d.data.sample;})
.style("opacity",function(d){
return d.data.usecase=="E" ? val1 : val2;})//some bars opacity change
.attr("width",((width-(padding+xPadding))/data.length)+0.01) // +0.01 to remove whitespace between bars
//.attr("width",xScale.bandwidth()) use this with scalebands
.attr("height",function(d){
return (yScale(d[0])-(yScale(d[1])));
})
.attr("x",function(d){
return xScale(d.data.sample);})
.attr("y",function(d){
return yScale(d[1]);})
.on("mouseover",mouseover) //tooltip on mouseover
.on("mouseout", function() {
d3.select("#tooltip").classed("hidden", true);
});
When using ordinal scale for x axis, you can set the bar padding in the range.
For example:
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeBands([0, width], 'padding');
A regular padding value would be around 0.1, but you can set to 0 since you don't want padding.
Now, you can set your width attr like this: .attr("width", xScale.rangeBand())
I'm trying to to build a time-series line in d3, using date for the x axis and the number of entries per date as the y axis. I'm having trouble moving the date part of the data object through a date formatter, then a scale, then into my line.
See it in Codepen http://codepen.io/equivalentideas/pen/HaoIs/
Thanks in advance for your help!
var data = [{"title":"1","date":"20140509"},{"title":"2)","date":"20140401"},{"title":"3","date":"20140415"},{"title":"4","date":"20140416"},{"title":"5","date":"20140416"},{"title":"6","date":"20140422"},{"title":"7","date":"20140422"},{"title":"8","date":"20140423"},{"title":"9","date":"20140423"},{"title":"10","date":"20140423"},{"title":"11","date":"20140502"},{"title":"12","date":"20140502"}
var width = "100%",
height = "8em";
var parseDate = d3.time.format("%Y%m%d").parse;
// X Scale
var x = d3.time.scale()
.range([0, width]);
// Y Scale
var y = d3.scale.linear()
.range([height, 0]);
// define the line
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(+d);
})
data.forEach(function(d) {
d.date = parseDate(d.date);
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d; }));
// build the svg canvas
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// build the line
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
Currently I get a js console error
Error: Invalid value for <path> attribute d="MNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaN"
You have not used parseDate. You are missing this :
data.forEach(function(d) {
d.date = parseDate(d.date);
});
Have a look at this example.
Some obvious visible problems:
1) You are not appending your svg to any part of the body or div. You should have a line look like this:
d3.select("body").append("svg").attr("width", width).attr("height", height);
2) I doubt d3 can understand your definition for width and
height. The width and height is the definition of chart size
3) I think there has no need for the dateParse as d3 will internally do it for you.
Finally, check the example provided by Niranjan.
There's a few other issues going on here. First, the width/height are not numbers, so the yScale and xScale ranges are invalid (that's why you get the "NaN" in the line path).
This is bad:
var width = "100%",
height = "8em";
Because these will not have valid, numerical ranges as required by the following scale definitions:
// X Scale
var x = d3.time.scale().range([0, width]);
// Y Scale
var y = d3.scale.linear().range([height, 0]);
...what does "8em" to 0 mean in a numerical svg path coordinate? So, make them numbers instead:
var width = 500,
height = 100;
After you fix that, you'll still have errors because your mapping for the y values isn't going to work. You want a histogram of the counts for the different dates. You should generate the data that way and feed it into the line generator.
var generateData = function(data){
var newData = [];
var dateMap = {};
data.forEach(function(element){
var newElement;
if(dateMap[element.date]){
newElement = dateMap[element.date];
} else {
newElement = { date: parseDate(element.date), count: 0 };
dateMap[element.date] = newElement;
newData.push(newElement);
}
newElement.count += 1;
});
newData.sort(function(a,b){
return a.date.getTime() - b.date.getTime();
});
return newData;
};
Once you fix those two things it should work. Here's a jsFiddle: http://jsfiddle.net/reblace/j3LzY/
I'm attempting to create a simple line chart with d3, however for some reason it's filling between the line and some midpoint. Here's the output:
My javascript is the following:
var width = 500,
height = 500,
padding = 10;
var extentVisits = d3.extent(visits, function(obj){
return obj['visits'];
});
var extentDates = d3.extent(visits, function(obj){
return obj['datestamp'];
});
var yScale = d3.scale.linear()
.domain(extentVisits)
.range([height - padding, padding]);
var xScale = d3.time.scale()
.domain(extentDates)
.range([0, width]);
var line = d3.svg.line()
.x(function(d) {
return xScale(d['datestamp']);
})
.y(function(d) {
return yScale(d['visits']);
})
d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height)
.append("g")
.attr("transform", "translate(5,5)")
.append('path')
.datum(visits)
.attr("class", "line")
.attr('d', line);
Where visits is of the form:
visits = [{'datestamp': timestampA, 'visits': 1000},
{'datestamp': timestampB, 'visits': 1500}]
I'm pretty new at d3 so I'm sure it's something simple, but it's driving me crazy.
Thanks in advance.
The midpoint you're seeing is just the connection of the first and last point. This is because the path you've created has (by default) a black fill. Even though it's an open path (i.e., the first and last point are not actually connected), if filled, it will appear closed:
The fill operation fills open subpaths by performing the fill operation as if an additional "closepath" command were added to the path to connect the last point of the subpath with the first point of the subpath.
Source: SVG specification via this SO answer
The solution here is to eliminate the fill and instead set a stroke. You can do this directly in JS with d3 or through CSS.
path.line
{
fill: none;
stroke: #000;
}
Demo showing both the CSS and JS method (commented out) on jsFiddle