I`m using a d3.js linechart with the following code:
var data = [{"age":"3","compositions":"1"},{"age":"5","compositions":"6"},{"age":"6","compositions":"5"},{"age":"7","compositions":"4"},{"age":"8","compositions":"9"},{"age":"9","compositions":"7"},{"age":"10","compositions":"16"},{"age":"11","compositions":"11"},{"age":"12","compositions":"11"},{"age":"13","compositions":"12"},{"age":"14","compositions":"19"},{"age":"15","compositions":"15"},{"age":"16","compositions":"30"},{"age":"17","compositions":"29"},{"age":"18","compositions":"21"},{"age":"19","compositions":"22"},{"age":"20","compositions":"29"},{"age":"21","compositions":"24"},{"age":"22","compositions":"28"},{"age":"23","compositions":"19"},{"age":"24","compositions":"13"},{"age":"25","compositions":"25"},{"age":"26","compositions":"36"},{"age":"27","compositions":"29"},{"age":"28","compositions":"23"},{"age":"29","compositions":"26"},{"age":"30","compositions":"24"},{"age":"31","compositions":"30"},{"age":"32","compositions":"33"},{"age":"33","compositions":"20"},{"age":"34","compositions":"9"},{"age":"35","compositions":"30"}]
var margin = {
top : 70,
right : 20,
bottom : 50,
left : 50
}, width = 460, height = 230;
var x = d3.scale.linear().range([0, width ]);
var y = d3.scale.linear().range([ height, 0 ]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left");
var line = d3.svg.line().x(function(d) {
return x(d.age);
}).y(function(d) {
return y(d.compositions);
});
var svg = d3
.select(".linechartAgePublicationsComposer")
.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 + ")");
data.forEach(function(d) {
d.age = d.age;
d.compositions = +d.compositions;
});
x.domain(d3.extent(data, function(d) {
return d.age;
}));
y.domain(d3.extent(data, function(d) {
return d.compositions;
}));
svg.append("g").attr("class", "x axis").attr("transform",
"translate(0," + height + ")").call(xAxis);
svg.append("g").attr("class", "y axis").call(yAxis).append("text")
.attr("transform", "rotate(-90)").attr("y", 6).attr("dy",
".71em").style("text-anchor", "end").text(
"Compositions");
svg.append("path").datum(data).attr("class", "line")
.attr("d", line);
when I execute it the line is out of the set range as in the picture and the y-axis has the wrong kind of direction.
Why is the line not within the range and goes from 9 to 10 only?
You should convert the age attribute to number, as you are doing with the composition attribute:
data.forEach(function(d) {
d.age = +d.age;
d.compositions = +d.compositions;
});
The d3.extent will return the minimum and maximum value in the given array using natural order. In your case you're not casting strings to numbers, so obviously the lowest string is "10" and the highest is "9". Thus, to fix the issue with the range, just cast the age to numbers as in the following example. I'm suggesting using parseInt
x.domain(d3.extent(data, function(d) {
return parseInt(d.age, 10);
}));
Related
how can i create a time scale over seconds, minutes, hours, days, months and years. in my code i get a second line when the seconds overlap.
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%dT%H:%M:%S");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the 0 line
var valueline0 = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value0); });
// append the svg object to the id="chart" of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom + 75 )
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Get the data
d3.csv("/trace/O00.csv", function(error, data) {
if (error) throw error;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.value0 = +d.value0;
});
// 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 Math.max(d.value0); })]);
// Add the valueline0 path.
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "steelblue")
.attr("d", valueline0);
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%Y-%m-%d %H:%M:%S")))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
O00.csv:
date,value0
2020-07-14T14:03:51,35.66
2020-07-14T14:04:01,23.56
2020-07-14T14:03:11,32.64
2020-07-14T14:03:21,22.55
2020-07-14T14:03:31,28.60
2020-07-14T14:03:41,38.70
2020-07-14T14:03:51,35.66
2020-07-14T14:04:01,23.56
2020-07-14T14:04:11,21.54
chart with 2lines
the second line starts with the 7th data record (2020-07-14T14:03:51,35.66) because the seconds (51) from the 1st data record (2020-07-14T14:03:51,35.66) are repeated.
Thanks in advance, Onka
There is "only one line". You have Dates with multiple values. If you don't want one of the values, then you have to remove that value from your dataset, by filtering the data in some way.
If you want to remove the extra datapoint you'll have to figure out which one is the correct value. For instance if we say, "Let's use the max value", convert this code:
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.value0 = +d.value0;
});
To this
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.value0 = +d.value0;
});
const dataMap = {};
let dupCount = 0;;
data.forEach((d, index) => {
if (!dataMap[d.date]) {
dataMap[d.date] = true;
} else {
// remove the duplicate from the CSV
data.splice(index - dupCount, 1);
dupCount++;
}
});
Alternative, and much simpler, would be to first filter the data from the CSV using the csv parser: https://www.npmjs.com/package/csv-parser and then passing that to the .data(filteredCsvData) function rather than using the builtin d3.csv() which doesn't contain what you need.
the problem was due to the non-consecutive records in the csv file. if the sequence is correct, everything works as desired! done! thanks for all...
I'm trying to follow this guide, but implementing my own data:
https://www.d3-graph-gallery.com/graph/stackedarea_basic.html
Here is my function
getStackedAreaChart: function(pod) {
//eval is sligtly heavily used here.
var cssName = ".stackedareachart-" + pod;
var podData = eval("this.StackedAreaChartData" + pod);
var ListName = eval("this.List" + pod);
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 30, left: 60 },
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3
.select(cssName)
.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 sumstat = d3
.nest()
.key(function(d) {
return d.time;
})
.entries(podData);
console.log(sumstat);
// Stack the data: each group will be represented on top of each other
var mygroups = ListName; // list of group names
var mygroup = []; // list of group names
for (let i = 1; i <= mygroups.length; i++) {
mygroup.push(i);
}
console.log(mygroups);
console.log(mygroup);
var stackedData = d3
.stack()
.keys(mygroup)
.value(function(d, key) {
return d.values[key].interactionCount;
})(sumstat);
// Add X axis --> it is a date format
var x = d3
.scaleLinear()
.domain(
d3.extent(podData, function(d) {
return d.time;
})
)
.range([0, width]);
svg
.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5));
// Add Y axis
var y = d3
.scaleLinear()
.domain([
0,
d3.max(podData, function(d) {
return +d.interactionCount;
})
])
.range([height, 0]);
svg.append("g").call(d3.axisLeft(y));
// color palette
var color = d3
.scaleOrdinal()
.domain(mygroups)
.range([
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33",
"#a65628",
"#f781bf",
"#999999"
]);
// Show the areas
svg
.selectAll("mylayers")
.data(stackedData)
.enter()
.append("path")
.style("fill", function(d) {
name = mygroups[d.key - 1];
return color(name);
})
.attr(
"d",
d3
.area()
.x(function(d, i) {
return x(d.data.key);
})
.y0(function(d) {
return y(d[0]);
})
.y1(function(d) {
return y(d[1]);
})
);
}
}
Here is where I get the error:
TypeError: Cannot read property 'interactionCount' of undefined
var stackedData = d3
.stack()
.keys(mygroups)
.value(function(d, key) {
return d.values[key].interactionCount;
})(sumstat);
For some reason if I make the list mygroup have one less element in the array, I don't get this error. BUT, my chart doesn't come out looking right.
I've followed the guide word for word line by line and I have no problems replicating the chart. But, when using my own data, I run into issues. Here is the json data:
[{"interactionCount":0,"time":951,"pod":"POD2","client":"C1"},{"interactionCount":6,"time":951,"pod":"POD2","client":"C2"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C3"},{"interactionCount":14,"time":951,"pod":"POD2","client":"C4"},{"interactionCount":44,"time":951,"pod":"POD2","client":"C5"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C6"},{"interactionCount":8,"time":951,"pod":"POD2","client":"C7"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C8"},{"interactionCount":5,"time":951,"pod":"POD2","client":"C9"},{"interactionCount":2,"time":951,"pod":"POD2","client":"C10"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C11"},{"interactionCount":13,"time":951,"pod":"POD2","client":"C12"},{"interactionCount":6,"time":951,"pod":"POD2","client":"C13"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C14"},{"interactionCount":6,"time":951,"pod":"POD2","client":"C15"}]
I was thinking maybe the error was when interactionCount was 0. This is not a problem. I did test this out.
Although I'm following the guide line by line. What am I doing wrong to receive the error?
NOTE my data is json data. The user uses CSV data. Could this be my problem?
I'm new to D3 and I am trying to display a simple d3 bar chart that changes which data attribute it is visualizing based on a dropdown menu - the data remains the same and I am displaying the same labels (x-axis) with each dropdown selection, just the labels should transition/change their ordering and the bar values should transition/change based on which attribute they are showing.
When the dropdown menu changes however, the transition (update) selection isn't getting called - it is only called when the chart loads for the first time. Therefore, based on the code, the y-Axis is changing its numerical values, but the heights always remain the same as they are initiated so the bars don't animate at all despite the labels changing.
updateChart(menuSelection) { // called when dropdown selection changes, and initially upon page load with default menuSelection
// I sort the data by the attribute of the dropdown item selected
this.myData.sort(function(a,b){
if(menuSelection == 1) return b.count - a.count;
else if(menuSelection == 2) return b.positiveCount - a.positiveCount;
else return b.negativeCount - a.negativeCount;
});
var m = this.myData.length;
var svg = d3.select(".chart"),
margin = {top: 40, right: 25, bottom: 40, left: 25},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var g = svg.select("g.rectGroup").attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("class", "rectGroup");
if(g.empty()) {
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("class", "rectGroup");
}
var x = d3.scaleBand()
.domain(myData.map(function(d) { return d._id; }))
.range([0, width])
.padding(0.08);
var yMax = d3.max(this.myData, function(d) {
if(this.menuSelection == 1) return d.count;
else if(this.menuSelection == 2) return d.positiveCount;
else return d.negativeCount;
});
var y = d3.scaleLinear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.axisBottom()
.scale(x);
var yAxis = d3.axisLeft()
.scale(y);
var yz = getHeights(m, menuSelection, this.myData); // ARRAY OF HEIGHTS DEPENDING ON MENU DROP DOWN SELECTION
var bars = g.selectAll(".bar")
.data(this.myData, function(d) {
return d._id; // KEY IS ITEM NAME FOR OBJECT CONSTANCY; ALL ITEMS ARE DISPLAYED REGARDLESS OF ATTRIBUTE SELECTED, BUT ORDER/VALUES CHANGE FOR EACH ITEM
})
.enter().append("rect")
.attr("class", "bar")
.attr("height", 0)
.attr("y", height);
bars.transition().duration(700)
.attr("x", function(d) { return x(d._id); })
.attr("width", x.bandwidth())
.attr("y", function(d, i) { return y(yz[i])})
.attr("height", function(d, i) {
return height - y(yz[i])
});
bars.exit().remove();
svg.selectAll(".axis").remove();
var height_to_use = +svg.attr("height") - margin.bottom
var xAxis_g = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + margin.left + "," + height_to_use + ")")
.call(xAxis)
.selectAll(".tick text")
.call(wrap, x.bandwidth());
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
function getHeights(m, menuSelection, data) {
var values = [];
for (var i = 0; i < m; ++i) {
if(menuSelection == 1) {
values[i] = data[i].count;
} else if(menuSelection == 2) {
values[i] = data[i].positiveCount;
} else {
values[i] = data[i].negativeCount;
}
}
return values;
}
}
Actually, you don't have an update selection in your code.
For having an update selection, break up your "enter" selection, like this:
//this is the update selection
var bars = g.selectAll(".bar")
.data(data, function(d) {
return d._id;
});
//and the remainder is the enter selection
bars.enter().append("rect")
.attr("class", "bar")
.attr("height", 0)
.attr("y", height);
Also, it's worth mentioning that, since you don't have an update selection in your code, this...
bars.exit().remove();
... is useless.
With help from https://bl.ocks.org histogram example I try to create a histogram with JSON from AJAX.
It seems like my data is not suitable for the histogram() function.
My Data in dev tools (top = my data; bottom = bins from the histogram):
My data is not in histogram bins. The array objects are missing.
Here are the data from bl.ocks.org working example:
...and the bins from histogram from bl.ocks.org example:
You can see it clearly. In my experiment, the data is not in the bins. In the working example of bl.ocks.org you can see the additional objects as an array from index 1 to 13 in the histogram bins.
Here is my full source code:
$(function () {
var updateStatistic = function () {
var dateFrom = $('#date_from').val();
var dateTo = $('#date_to').val();
var parseDate = d3.timeParse('%Y-%m-%d %H:%M:%S'), formatCount = d3.format(',.0f');
var margin = {top: 10, right: 10, bottom: 20, left: 10},
width = 1800 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var dataset = [];
d3.json('/statistic-sizearchive/' + dateFrom + '/' + dateTo, function (data) {
dataset = data.sizeArchive;
dataset.columns = ['date'];
var datetimeFrom = parseDate(dataset[0].archive_time_sql);
var datetimeTo = parseDate(dataset[dataset.length - 1].archive_time_sql);
$(dataset).each(function (index, element) {
element.date = parseDate(element.archive_time_sql);
delete element.archive_time_sql;
});
console.log(dataset);
var x = d3.scaleTime()
.domain([datetimeFrom, datetimeTo])
.rangeRound([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var histogram = d3.histogram()
.value(function (d) {
return d.length;
})
.domain(x.domain())
.thresholds(x.ticks(d3.timeWeek));
var bins = histogram(dataset);
console.log(bins);
y.domain([0, d3.max(bins, function (d) {
return d.length;
})]);
/*
* ### SVG
*/
var svg = d3.select('#statistic_size_archive').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 + ")");
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
var bar = svg.selectAll(".bar")
.data(bins)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function (d) {
return "translate(" + x(d.x0) + "," + y(d.length) + ")";
})
;
bar.append("rect")
.attr("x", 1)
.attr("width", function (d) {
return x(d.x1) - x(d.x0); // x(d.x1) - x(d.x0) - 1
})
.attr("height", function (d) {
return height - y(d.length); // height - y(d.length)
});
bar.append("text")
.attr("dy", ".75em")
.attr("y", 0)
.attr("x", function (d) {
return (x(d.x1) - x(d.x0)) / 2;
})
.attr("text-anchor", "middle")
.text(function (d) {
return formatCount(d.length);
});
});
};
updateStatistic();
$('button#update_statistic').click(function () {
updateStatistic();
});
});
I do not see anything that I'm doing wrong.
Without your actual data, I'm not able to test this code... however, it appears that your histogram call function is returning the wrong value from the data. Instead of returning d.length, shouldn't the code be:
var histogram = d3.histogram()
.value(function (d) {
return d.date;
})
...
This way, the histogram will put each data point into a bin determined by its date?
i have some values in my csv file and i show a graph with values on y axis and dates on x axis.
For first graph i have following values
date,close
13-Jul-16,0.8736701869033555
15-Jul-16,0.3631761567983922
17-Jul-16,0.4795564555162078
19-Jul-16,0.3754827857186281
21-Jul-16,0.4355941951068847
23-Jul-16,0.34393804366457353
25-Jul-16,0.40967947088135176
27-Jul-16,0.2707818657230363
29-Jul-16,0.34430251610420176
31-Jul-16,0.28089496856221585
For second graph i have following values
date,close
11-Jul-16,0.766705419439816
15-Jul-16,0.7353651170975812
17-Jul-16,0.41531502169603063
19-Jul-16,0.5927871032351933
21-Jul-16,0.7986419920511857
23-Jul-16,0.7904979990272231
25-Jul-16,0.817690401573838
27-Jul-16,0.8433545168648027
29-Jul-16,0.8612307965742473
31-Jul-16,0.806498303188971
But in second graph x axis does not contain all dates.. As an example i put a printscreen of my output graphs myoutput to here.
This is my code which takes datas from csv file and visualize it.
var selectedMonth=document.getElementById('selectedMonth').value;
var selectedTopic=document.getElementById('selectedTopic').value;
var userFileDirectory="../documents/";
userFileDirectory=userFileDirectory+selectedMonth+"/"+selectedTopic+"/"+"dataCs.csv";
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%b-%y").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
// Adds the svg canvas
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
d3.csv(userFileDirectory, function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
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; })]);
// Add the valueline path.
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
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("text")
.text("("+selectedMonth+" "+selectedTopic+")");
});
I would try setting the tick values explicitly, using tickValues:
https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#tickValues
ticks(5) will suggest 5 ticks, but will be adapted based on the scale's domain. Alternative to tickValues(), you could try ticks(d3.time.day, 2) to have a tick every 2 days.