Parsing x-axis with D3 error for column chart - parsing
I'm new to D3 having problems parsing the x-axis for a positive and negative value bar chart, as seen here:http://bl.ocks.org/mbostock/2368837
Instead of showing the actual year, it is repeating '1970' which is when the time code starts counting. I think it might be an error where the year is not being converted to a string, but it doesn't seem to be a simple d3.timescale since bar charts require the ordinal axis.
var data = [
{"date":19990101,"change":123000},
{"date":19990102,"change":409000},
{"date":19990103,"change":108000},
{"date":19990104,"change":373000},
{"date":19990105,"change":213000},
{"date":19990106,"change":263000},
{"date":19990107,"change":293000},
{"date":19990108,"change":191000},
{"date":19990109,"change":203000},
{"date":19990110,"change":403000},
{"date":19990111,"change":291000},
{"date":19990112,"change":300000},
{"date":20000101,"change":233000},
{"date":20000102,"change":130000},
{"date":20000103,"change":471000},
{"date":20000104,"change":287000},
{"date":20000105,"change":225000},
{"date":20000106,"change":-46000},
{"date":20000107,"change":166000},
{"date":20000108,"change":2000},
{"date":20000109,"change":127000},
{"date":20000110,"change":-12000},
{"date":20000111,"change":223000},
{"date":20000112,"change":138000},
{"date":20010101,"change":-32000},
{"date":20010102,"change":69000},
{"date":20010103,"change":-29000},
{"date":20010104,"change":-281000},
{"date":20010105,"change":-41000},
{"date":20010106,"change":-126000},
{"date":20010107,"change":-122000},
{"date":20010108,"change":-156000},
{"date":20010109,"change":-244000},
{"date":20010110,"change":-327000},
{"date":20010111,"change":-296000},
{"date":20010112,"change":-172000},
{"date":20020101,"change":-143000},
{"date":20020102,"change":-135000},
{"date":20020103,"change":-22000},
{"date":20020104,"change":-83000},
{"date":20020105,"change":-6000},
{"date":20020106,"change":52000},
{"date":20020107,"change":-92000},
{"date":20020108,"change":-14000},
{"date":20020109,"change":-58000},
{"date":20020110,"change":124000},
{"date":20020111,"change":7000},
{"date":20020112,"change":-162000},
{"date":20030101,"change":89000},
{"date":20030102,"change":-158000},
{"date":20030103,"change":-215000},
{"date":20030104,"change":-51000},
{"date":20030105,"change":-10000},
{"date":20030106,"change":-3000},
{"date":20030107,"change":20000},
{"date":20030108,"change":-44000},
{"date":20030109,"change":105000},
{"date":20030110,"change":197000},
{"date":20030111,"change":13000},
{"date":20030112,"change":119000},
{"date":20040101,"change":159000},
{"date":20040102,"change":43000},
{"date":20040103,"change":333000},
{"date":20040104,"change":247000},
{"date":20040105,"change":306000},
{"date":20040106,"change":78000},
{"date":20040107,"change":37000},
{"date":20040108,"change":125000},
{"date":20040109,"change":155000},
{"date":20040110,"change":343000},
{"date":20040111,"change":65000},
{"date":20040112,"change":128000},
{"date":20050101,"change":130000},
{"date":20050102,"change":240000},
{"date":20050103,"change":135000},
{"date":20050104,"change":362000},
{"date":20050105,"change":168000},
{"date":20050106,"change":246000},
{"date":20050107,"change":372000},
{"date":20050108,"change":192000},
{"date":20050109,"change":65000},
{"date":20050110,"change":81000},
{"date":20050111,"change":335000},
{"date":20050112,"change":158000},
{"date":20060101,"change":274000},
{"date":20060102,"change":316000},
{"date":20060103,"change":280000},
{"date":20060104,"change":181000},
{"date":20060105,"change":21000},
{"date":20060106,"change":80000},
{"date":20060107,"change":210000},
{"date":20060108,"change":179000},
{"date":20060109,"change":159000},
{"date":20060110,"change":-3000},
{"date":20060111,"change":205000},
{"date":20060112,"change":169000},
{"date":20070101,"change":234000},
{"date":20070102,"change":90000},
{"date":20070103,"change":186000},
{"date":20070104,"change":76000},
{"date":20070105,"change":141000},
{"date":20070106,"change":80000},
{"date":20070107,"change":-35000},
{"date":20070108,"change":-24000},
{"date":20070109,"change":77000},
{"date":20070110,"change":86000},
{"date":20070111,"change":111000},
{"date":20070112,"change":93000},
{"date":20080101,"change":14000},
{"date":20080102,"change":-85000},
{"date":20080103,"change":-79000},
{"date":20080104,"change":-215000},
{"date":20080105,"change":-186000},
{"date":20080106,"change":-169000},
{"date":20080107,"change":-216000},
{"date":20080108,"change":-270000},
{"date":20080109,"change":-459000},
{"date":20080110,"change":-472000},
{"date":20080111,"change":-775000},
{"date":20080112,"change":-705000},
{"date":20090101,"change":-794000},
{"date":20090102,"change":-695000},
{"date":20090103,"change":-830000},
{"date":20090104,"change":-704000},
{"date":20090105,"change":-352000},
{"date":20090106,"change":-472000},
{"date":20090107,"change":-351000},
{"date":20090108,"change":-210000},
{"date":20090109,"change":-233000},
{"date":20090110,"change":-170000},
{"date":20090111,"change":-21000},
{"date":20090112,"change":-220000},
{"date":20100101,"change":-13000},
{"date":20100102,"change":-40000},
{"date":20100103,"change":154000},
{"date":20100104,"change":229000},
{"date":20100105,"change":521000},
{"date":20100106,"change":-130000},
{"date":20100107,"change":-86000},
{"date":20100108,"change":-37000},
{"date":20100109,"change":-43000},
{"date":20100110,"change":228000},
{"date":20100111,"change":144000},
{"date":20100112,"change":95000},
{"date":20110101,"change":69000},
{"date":20110102,"change":196000},
{"date":20110103,"change":205000},
{"date":20110104,"change":304000},
{"date":20110105,"change":115000},
{"date":20110106,"change":209000},
{"date":20110107,"change":78000},
{"date":20110108,"change":132000},
{"date":20110109,"change":225000},
{"date":20110110,"change":166000},
{"date":20110111,"change":174000},
{"date":20110112,"change":230000},
{"date":20120101,"change":311000},
{"date":20120102,"change":271000},
{"date":20120103,"change":205000},
{"date":20120104,"change":112000},
{"date":20120105,"change":125000},
{"date":20120106,"change":87000},
{"date":20120107,"change":153000},
{"date":20120108,"change":165000},
{"date":20120109,"change":138000},
{"date":20120110,"change":160000},
{"date":20120111,"change":247000},
{"date":20120112,"change":219000},
{"date":20130101,"change":148000},
{"date":20130102,"change":332000},
{"date":20130103,"change":142000},
{"date":20130104,"change":199000},
{"date":20130105,"change":195000},
{"date":20130106,"change":195000}
];
var margin = {top: 34, right: 0, bottom: 30, left: 60},
width = 900;
height = 420 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%d%m").parse;
formatValue = d3.format(",");
formatTime = d3.time.format("%Y");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.2);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.tickPadding(10)
.ticks(20)
.orient("top")
.tickFormat(function(d) { return d3.time.format('%Y')(new Date(d)); });
// .tickFormat(d3.time.format("%Y"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// });
x.domain(data.map(function(d) {
return +d.date;
}));
y.domain(d3.extent(data, function(d) {
return d.change;
})).nice();
// add grid lines
function make_X_axis() {
return d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(6);
}
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function(d) { return d.change < 0 ? "bar negative" : "bar positive"; })
.attr("y", function(d) {
if (d.change < 0) {
return y(0);
} else {
return y(d.change);
}
})
.attr("x", function(d) {
return x(d.change);
})
.attr("height", function(d) {
return Math.abs(y(d.change) - y(0));
})
.attr("width", x.rangeBand())
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.select(this).attr("x")) + x.rangeBand() / 2;
var yPosition = parseFloat(d3.select(this).attr("y")) / 2 + height / 2;
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.style("right", xPosition + "px")
.select("#value")
// .text(formatValue(d.change)) + formatTime(d.date);
.text(formatValue(d.change));
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
});
// Update the inner dimensions.
var g = svg.select("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Regular x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis.orient("bottom").tickValues([1999, 20000101, 20010101, 20020101, 20030101, 20040101, 20050101, 20060101, 20070101, 20080101, 20090101, 20100101, 20110101, 20120101, 20130101]))
.style("text-anchor", "start");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("x", -25)
.attr("y", -25)
.style("text-anchor", "end")
.text("Jobs");
svg.append("g")
.attr("class", "grid")
.call(make_Y_axis()
.tickSize(-width, 0, 0)
.tickFormat(""));
// //Zero axis
svg.append("g")
.attr("transform", "translate(0," + Y0() + ")")
.call(xAxis.tickFormat("").tickSize(1));
// add grid lines
function make_X_axis() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(6);
}
function make_Y_axis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
}
function Y0() {
return y(0);
}
You need to fix your representation of dates. There are several common ways to represent dates in JavaScript:
As a number (e.g., 1375284578036) representing the number of milliseconds since UNIX epoch.
As an arbitrary string (e.g., "2013-07-31"); this is typically parsed with d3.time.format.
As a bonafide Date object (e.g., new Date(2013, 6, 31)).
In general, your strategy should be to get things into Date objects as quickly as possible, because this is the canonical representation in JavaScript. You should only use a number or a string to represent (“serialize”) dates in a data file, such as CSV or JSON. In sum, always represent dates as Date objects in-code, and convert strings or numbers to Date objects on load as necessary.
In your code, you appear to be using a number of the form YYYYMMDD (with the exception of the first tick value of your x-axis, which is YYYY). Because there are no leading zeroes in the years you are using (i.e., they are all four digits), it’s technically possible to represent a date this way and then coerce it to a string before parsing it with d3.time.format—but this is a bad idea. You should use a string to serialize your dates, rather than a number; only use a number for milliseconds since UNIX epoch.
Notice in your x-axis tick format you are saying new Date(d). This is taking one of the domain values (a number of the form YYYYMMDD) and then converting it to a Date object using the Date constructor. But when the Date constructor is passed a single number, it assumes this is milliseconds since epoch, which is why all of your dates are in the 1970s.
If you convert all your dates to Date objects first when the data is loaded, then the domain of your x-scale will be Date objects, and you won’t need to coerce to dates as part of formatting ticks. You can simply use d3.time.format("%Y") as your tick format.
Related
Highcharts Tooltip pointFormat and formatter
I am trying to get the following output in highcharts... Have a list of supporting values for a specific point and show in the tooltip all of those supporting values on that single point. my solution is to use the formatter() formatter: function () { var res = 'The value for <b>' + this.x + '</b> is <b>' + this.y + '</b><br/>'; for(var i = 0; i < this.point.supportingData.list.length; i++){ res += this.point.supportingData.list[i].name + '<br/>'; } return res; } example.... point (x,y) has 12, 17, 53 and 27 I want all of those valuse to appear on the hover over on point (x,y). The problem is that I want my formatter to be split like this. http://jsfiddle.net/207xt4an/ Where the x value is on the bottom of the chart and everything else that comes with that jsfiddle. Is there a way to accomplish this using formatter? Or vise versa, grab all of the supporting in the pointFormat?
highcharts tooltip formatter: how to access adjacent points [duplicate]
This question already has an answer here: How to get next point in Highcharts tooltip (1 answer) Closed 7 years ago. I have a line chart with cumulative values. What I try to do in tooltip: show value of ( current point.y - previous point.y ). But I don't know how to get the y value of the previous point in a tooltip formatter function.
You need to use loop over each point in current serie and compare current point with loop point. If are the same, then extract point with index-1 from array of points. tooltip: { formatter: function () { var x = this.point.x, y = this.y, series = this.series, each = Highcharts.each, txt = 'The value for <b>' + this.x + '</b> is <b>' + this.y + '</b>'; each(series.data, function(p, i){ if(p.x === x && series.data[i-1]) { txt += ' Previous: ' + series.data[i-1].y; } }); return txt; } }, Example: http://jsfiddle.net/3qw6ry06/
Plotting a line using d3 and an JSON data object passed from rails
I'm pushing the boundaries of experience here and cant see what I am doing wrong. I'm pulling a bunch of data from a DB in rails, converting it to JSON and trying to plot it in d3. The data is a simple JSON of {date => number} and the data is fine. I'm doing something wrong with d3 in the loop at the bottom of the code but cant see what. The axes plot fine but I can't get the line to draw. Here is my code: var data = <%= #signups.to_json.html_safe %>; var date, signups var margin = {top: 20, right: 20, bottom: 30, left: 50}, width = 500 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var parseDate = d3.time.format("%d-%b-%Y").parse; var x = d3.time.scale() .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(date) .y(signups) .interpolate("linear"); var svg = d3.select(".container").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 + ")"); // Find range of data for domain var min_date = Infinity, max_date = -Infinity; var min_signups = Infinity, max_signups = -Infinity; var x; $.each(data, function(key, value) { temp = parseDate(key) if( temp < min_date) min_date = temp; if( temp > max_date) max_date = temp; if( +value < min_signups) min_signups = +value; if( +value > max_signups) max_signups = +value; }); x.domain([min_date, max_date]); y.domain([0, max_signups]); 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("Price ($)"); svg.append("path") $.each(data, function(key, value) { date = parseDate(key); signups = value; d3.select("path") .append("svg") .attr("d", d3.svg.line() .x(date) .y(signups) .interpolate("linear")); }); Any help would be greatly appreciated
The D3 way would be to pass all your data to a suitable line generator. This is fairly simple in your case, you just need to convert the object that contains all the data to an array that D3 can work with using d3.entries(). var line = d3.svg.line() .x(function(d) { return x(parseDate(d.key)); }) .y(function(d) { return y(d.value); }); svg.selectAll("path").data([d3.entries(data)]) .enter().append("path").attr("d", line); As you have only a single line, you could also use .datum() to bind the data, but using .data() leaves you the option of passing in data for more lines later. Complete jsfiddle here. When modifying this, make sure that you create the line before adding the axes, otherwise D3's data matching won't work with the default matching and you won't get any lines. To fix this, you could supply a matching function, but to start with it's much easier to keep to this order.
highcharts tooltip format millions billions
I want to display couple of points on a highcharts line chart which are big numbers. e.g. 100,000, 10,000,000, 1,000,000,000 When I display these, the y axis automatically formats the number into 100 k, 10 M, 1,000 M etc but the tooltip still shows the actual big number. Is it possible to show 1,000,000,000 as 1 B or 1000 M in the tooltip itself. Example - http://jsfiddle.net/ynCKW/1/ I am trying to play with the numberFormat function but I dont think its the right function. Highcharts.numberFormat(this.y,0) Do I have to write a custom function which would do this formatting in the tooltip?
You can use the same logic as implemented in Highcharts core: tooltip: { formatter: function () { var ret = '', multi, axis = this.series.yAxis, numericSymbols = ['k', 'M', 'G', 'T', 'P', 'E'], i = numericSymbols.length; while (i-- && ret === '') { multi = Math.pow(1000, i + 1); if (axis.tickInterval >= multi && numericSymbols[i] !== null) { ret = Highcharts.numberFormat(this.y / multi, -1) + numericSymbols[i]; } } return ret; } }, And jsFiddle: http://jsfiddle.net/ynCKW/2/ EDIT for Highcharts v6: We can call build-in method, which should be easier to maintain: http://jsfiddle.net/BlackLabel/ynCKW/104/ tooltip: { valueSuffix: '', formatter: function () { var axis = this.series.yAxis; return axis.defaultLabelFormatter.call({ axis: axis, value: this.y }); } },
Piggybacking off #Pawel Fus, a slight tweak allows you to have negative currency values as well but with the negative outside the $ (i.e. -$100K versus -$-100k). function () { var isNegative = this.value < 0 ? '-' : ''; var absValue = Math.abs(this.value); return isNegative + '$' + this.axis.defaultLabelFormatter.call({ axis: this.axis, value: absValue }); } Here is a jsFiddle: http://jsfiddle.net/4yuo9mww/1/
how do i put a draggable back in it's droppable using only code
The basic concept of my app is to map a group of pallets onto a truck. I have a page that allows the user to drag a draggable div "pallet" and place it in a droppable div "slot" on the truck. The weights for each row, left and right columns and the truck total weight are calculated on the fly. I then pass all of the information to a page to print a table of pallets and slots so an operator can then load the truck. The problem is restoring the load if the user only loads half the truck and then wants to come back at a latter time to finish. I know how to save any number of data points, but i cannot find a way to put the "pallets" back in their "slots" when i rebuild the page. Here are the div's for the pallets, some of the slots and the drop procedure (i'm not sure what else is needed) //load the playpen with the pallets that belong on this truck for (var i = 0; i < noPallets; i++) { $('<div id="' + PalletIDs[i] + '">' + PalletIDs[i] + '<br>' + Storage[i] + ':' + Weights[i] + '</div>').data('pallet', { ID: PalletIDs[i], Weight: Weights[i], columnNo: 0, rowNo: 0, tierNo:0 }).appendTo('#playpen').draggable({ containment: '#content', stack: '#playpen div', cursor: 'move', start: handlePalletStart, revert: true }); } // Create the pallet slots for truck left var words = ['C<br>one', 'C<br>two', 'C<br>three', 'C<br>four', 'C<br>five', 'C<br>six', 'C<br>seven', 'C<br>eight', 'C<br>nine', 'C<br>ten', 'C<br>eleven']; for (var i = 1; i <= 11; i++) { position = 1000 + (i * 10) + 3 $('<div id="' + position + '">' + words[i - 1] + '</div>').data('position', { columnNo: 1, rowNo: i, tierNo: 3 }).appendTo('#truckleftC').droppable({ accept: '#playpen div', hoverClass: 'hovered', drop: handlePalletDrop }); } function handlePalletDrop(event, ui) { // this is the truck location we are about to drop into var columnNo = $(this).data('position').columnNo; var rowNo = $(this).data('position').rowNo; var tierNo = $(this).data('position').tierNo; // this is pallet information var palletID = ui.draggable.data('pallet').ID; var weight = ui.draggable.data('pallet').Weight; // if the pallet was already in a positio, we need to zero that position if (ui.draggable.data('pallet').columnNo != 0) { oldposition = ui.draggable.data('pallet').columnNo * 1000 + ui.draggable.data('pallet').rowNo * 10 + ui.draggable.data('pallet').tierNo; // alert(oldposition); for (var h = 0; h <= 65; h++) { if (aryTWeight[h][0] == oldposition) { aryTWeight[h][1] = 0; } } } // set the position in the truck for this pallet ui.draggable.data('pallet').columnNo = columnNo; ui.draggable.data('pallet').rowNo = rowNo; ui.draggable.data('pallet').tierNo = tierNo; // set the weight for this position in the truck position = columnNo * 1000 + rowNo * 10 + tierNo // alert(position); for (var h = 0; h <= 65; h++) { if (aryTWeight[h][0] == position) { aryTWeight[h][1] = weight; } } // alert(columnNo + ' : ' + rowNo + ' : ' + tierNo); // set the hidden text box with the palletid so data can be posted to next page for (var i = 0; i < document.forms[0].elements.length; i++) { element = document.forms[0].elements[i]; if (element.name == oldposition) { element.value = 0; }; if (element.name == position) { element.value = palletID; }; } // set all of the weights for rows, columns and totals calcWeights(); ui.draggable.addClass('correct'); ui.draggable.position({ of: $(this), my: 'left top', at: 'left top' }); ui.draggable.draggable('option', 'revert', false); }
You could store x and y coordinates of the loaded pallets and then when you're rebuilding the page set the palettes' css position: absolute; and set top and left to the x and y stored. And append them to the droppable div programmatically. It all depends on how your slots work, if you have lots of droppables and each droppable can only contain one draggable then just store the pairs and append the draggable to droppable with jquery when you're rebuilding the page. var html = '<div id="myDraggableDiv"></div>' $('#' + droppableId).append(html); If you need any special css like exact top and left add it in style attribute or with jquery after the element is created.