Consider the plot, pulled from the rCharts/NVD3 examples page
p6 <- nPlot(uempmed ~ date, data = economics, type = 'lineChart')
p6
I tried to get my y-axis to display percentages, so I tried the following code (following R: interactive plots (tooltips): rCharts dimple plot: formatting axis) but it returns a blank page
p6 <- nPlot(uempmed ~ date, data = economics, type = 'lineChart')
p6$yAxis(outputFormat = "%")
p6
I did some research and got the following to work:
p6 <- nPlot(uempmed ~ date, data = economics, type = 'lineChart')
p6$yAxis(tickFormat = "#! function(d) {return d*100 + '%' } !#")
p6
Now, I know about 0 javascript and want to stay in R as much as possible, at least in the short term. Could someone let me know
Why the first approach doesn't work, and
Whether the second approach is the best way to go about it
Since the answer to 1 is in the comment by #jdharrison, I thought I would quickly answer #2 with a complete code sample with comments demonstrating the different ways to achieve the same thing.
#turn off rstudio viewer
#since we will be making lots of charts
#we'll turn it back on at end
oldviewer = options("viewer")
options(viewer=NULL)
data(economics, package = "ggplot2")
#if we know we would like percent
#and R is our friend but javascript is not
#we could do some transformation in R
economics$uempmed <- economics$uempmed/100
p6 <- nPlot(uempmed ~ date, data = economics, type = 'lineChart')
p6
#then we could use d3.format just like the dimple example
#except dimple assumes the d3.format part so we just need "%"
#see https://github.com/PMSI-AlignAlytics/dimple/wiki/dimple.axis#tickFormat
#for more on d3.format
#see https://github.com/mbostock/d3/wiki/Formatting#d3_format
p6$yAxis( tickFormat = "#!d3.format('%')!#" )
p6
#however your method works fine also
p6$yAxis(tickFormat = "#! function(d) {return d*100 + '%' } !#")
p6
#but let's say we did not do the above transform
#dividing by 100
#here is how we could do it in javascript
data(economics, package = "ggplot2")
p6 <- nPlot(uempmed ~ date, data = economics, type = 'lineChart')
p6$yAxis(tickFormat = "#! function(d) {
//will change to .2% to show how to get two decimal
return d3.format('.2%')(d/100)
} !#")
p6
#turn rstudio viewer back on
options(viewer = oldviewer)
Related
I'm implementing a 2d chart using canvas. I want to reuse d3's logic for generating the chart's axes. d3 does quite a lot of good work in generating axes and I'd like to take advantage of it.
(Note: For backward-compatibility reasons I'm stuck using d3v3 for the time being.)
Consider the following d3 code:
let scale = d3.time.scale()
.range([0, width])
.domain([lo, hi]);
let axis = d3.svg.axis()
.scale(scale)
.ticks(num_ticks)
.tickSize(10)
.orient("bottom");
I can render this into a chart div with:
svg.selectAll('.x-axis').call(axis);
I want to be able to programmatically get the tick data out of axis, including the formatted labels, by writing a function that behaves as follows:
ticks = get_axis_ticks(axis)
ticks should hold each tick position (as a Date in this particular case) and the corresponding d3-generated label.
[[Date(...), "Wed 19"],
[Date(...), "Fri 21"],
[Date(...), "Apr 23"],
[Date(...), "Tue 25"],
...]
I could then use this data to paint an axis on my canvas.
I've dug into d3v3 source (in particular here: https://github.com/d3/d3/blob/v3.5.17/src/svg/axis.js) but I find it very difficult to tease apart the logic from the SVG manipulation.
Help would be much appreciated.
One idea I have is to use the scale function you have created to generate the ticks you desire and push them into an array.
As a very simple example, if you would like 10 ticks, each incrementing by a unit of 1, you could do something like this: https://jsfiddle.net/Q5Jag/3148/
//define dummy values
var lo = 1;
var hi = 10;
var width = 512
var scale = d3.time.scale()
.range([0, width])
.domain([lo, hi]);
//define your function
var get_x_axis = function() {
let axisArr = [];
for(var i = 0; i < 10; i++ ) {
//calculate your value
axisArr.push(scale(i))
}
return axisArr
}
//call it
let axisTicks = get_x_axis()
//log it
console.log(axisTicks)
I'm not sure if this is what you're looking for, but if you need further help just ask.
I was able to get this working. I found the time formatting strategy in the d3 docs: https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format_multi I believe this is the strategy that d3 itself uses by default when users do not provide a custom format.
I learned that simply calling scale.ticks(N) will return Nish ticks suitable for rendering on an axis. These tick values are chosen on natural boundaries. E.g., if you're working with a time axis, these ticks will be aligned on minute, hour, day boundaries.
Here's a solution:
let time_format = d3.time.format.multi([
[".%L", d => d.getMilliseconds()],
[":%S", d => d.getSeconds()],
["%I:%M", d => d.getMinutes()],
["%I %p", d => d.getHours()],
["%a %d", d => d.getDay() && d.getDate() !== 1],
["%b %d", d => d.getDate() !== 1],
["%B", d => d.getMonth()],
["%Y", () => true]
]);
let get_ticks = (width, lo, hi, num_ticks) => {
let scale = d3.time.scale().range([0, width]).domain([lo, hi]);
return scale.ticks(num_ticks).map(t => [t, time_format(t)]);
};
I'm displaying data using d3, dc.js etc. I have a working lineChart / geoChoroplethChart but for some reason I can't get the number display to work. I'm thinking the formatting might be to blame, I've got fairly large numbers ~billions. I tried setting the d3.format to ".0e" etc but no luck.
var ndx = crossfilter(tradeData);
var all = ndx.groupAll();
var tradeQuantityGroup = ndx.groupAll().reduceSum(function(d) {
return d["quantity"];
});
var totalTradeQuantity_ND = dc.numberDisplay("quantity-nd");
totalTradeQuantity_ND
.formatNumber(d3.format("d"))
.valueAccessor(function(d){return d; })
.group(tradeQuantityGroup)
.formatNumber(d3.format(".0e"));
I have converted a line chart into a cumulative line chart and its y values are not displayed correctly. The range of the y axis should be 80.00 - 140.00 but instead I get -0.08 - 0.20. Has anyone managed to tweak their normalization code below to make it work with all kinds of ranges?
line.values = line.values.map(function(point, pointIndex) {
point.display = {
'y': (lines.y()(point, pointIndex) - v) / (1 + v)
};
return point;
})
Any help will be greatly appreciated.
I know that this question is somewhat old, but I am convinced that the normalization code for the cumulative line chart is not conceptually correct. Furthermore, the NVD3 cumulative line chart implementation is actually an index chart implementation (see Mike Bostock's example). A cumulative line chart would be more like this, I think. The cumulative chart can be easily achieved using the NVD3 line chart and some quick modifications to the underlying data.
If we take Bostock to be correct, and we really do wish to achieve an indexed line chart, then the indexify function in NVD3 should be changed to:
/* Normalize the data according to an index point. */
function indexify(idx, data) {
if (!indexifyYGetter) indexifyYGetter = lines.y();
return data.map(function(line, i) {
if (!line.values) {
return line;
}
var indexValue = line.values[idx];
if (indexValue == null) {
return line;
}
var v = indexifyYGetter(indexValue, idx);
// TODO: implement check below, and disable series if series
// causes a divide by 0 issue
if ((Math.abs(v) < 1e-6) && !noErrorCheck) {
// P.S. You may have to set a higher threshold (~1e-6?).
// I don't know I didn't run any tests...
line.tempDisabled = true;
return line;
}
line.tempDisabled = false;
line.values = line.values.map(function(point, pointIndex) {
point.display = {
'y': (indexifyYGetter(point, pointIndex) - v) / v
};
return point;
});
return line;
})
}
I asked a related question to the authors of NVD3 and plan to submit a pull request. Note that percentage change charts are really only meaningful when all of the underlying data is positive. When you start throwing negative values into the mix, percentage change loses all of its meaning.
What I found works is to insert another point with a y value of 0 at the beginning of the sequence of points.
Given a list of data points in the form [ [x1,y1], [x2,y2], ... [xn,yn]] ],
something like values.upshift([0,0]) works for me (the x value is arbitrary, but i just use 0 or values[0][0]) to insert to the front of the list.
(I'm getting the same thing with that chart. I'm still looking into it, but I hope this helped.)
I am trying to render a dc.js barChart where my y-axis is percentage 0-100% and my x-axis are numbers, however I want to order the x-axis by date.
My data looks like this:
date,trend,percent
01/01/2014 13:00,238,53.6
01/01/2014 13:15,239,64.2
01/01/2014 13:30,219,43.1
01/01/2014 13:45,219.2,43.1
01/01/2014 14:00,237.4,50.6
...
I am adding the data to crossfilter
data.forEach(function (d) { d.date = parseDate(d.date); });
var ndx = crossfilter(data);
var trendDimension = ndx.dimension(function (d) { return d.trend; });
var trendGroup = trendDimension.group().reduce(
function (p, v) {
p.time = v.date.getTime();
p.trend = +v.trend;
p.percent = +v.percent;
return p;
},
...
).order(function (p) { return p.time; }); // ??? order by time rather than trend
When I graph the dimension and group, my x-axis is sorted by trend as my x domain looks like:
var minTrend = trendDimension.bottom(1)[0].trend;
var maxTrend = trendDimension.top(1)[0].trend;
...
chart.x(d3.scale.linear().domain([minTrend, maxTrend]);
...
chart.render();
Everything plots, however the bars a sorted in order of trend and I would like them sorted in order of date/time.
Is this possible?
EDIT
I also tried:
chart.ordering(function (d) { return d.value.time; });
but that does not seem to have an effect on the ordering...
Do you want to graph percent versus trend or percent versus time?
Right now your dimension is on trend, so it will not be possible to order it by date. Crossfilter will create a bin for each trend value (which may have many dates), and the way you have written your reduce function, it will simply replace the date entry for the bin, with the last date it sees.
If you want to order by date and then use trend to affect some other aesthetic (color for example), you should use a date dimension, group by some quantization of the date, not do anything with the date in your reduce, and use date scale/xUnits.
I have created a beautiful bubble chart using Google Charts. Here is a shot of the chart:
The numbers along the x-axis represent individual customers. The numbers along the y-axis represent individual products. As you all can see, there are about 23 customers and 7 products.
The problem is that the X and Y axes can only be numeric (as far as I know from the documentation). I wish to be able to display the string values for the customers and products along the axes instead of just representative integers. This will make it easier to understand what we are looking at.
Does anyone know how this can be accomplished?
I do have JS arrays which contain the customer and product strings. Their integer indices correspond to the numbers that show up on the chart. For example:
customers[6] = "Microsoft"
customers[7] = "Dell"
...
But right now just the integers show up.
Any help would be greatly appreciated! Thanks!
Here is the code I used to define the chart:
var options = {
'title':'Customer / Product Grid',
'width': 1000,
'height':500
};
//for customer product grid
var customer_product_grid_data_table = new google.visualization.DataTable();
customer_product_grid_data_table.addColumn('string', 'Customer and Product');
customer_product_grid_data_table.addColumn('number', 'Customer');
customer_product_grid_data_table.addColumn('number', 'Product');
customer_product_grid_data_table.addColumn('number', 'Profit Margin');
customer_product_grid_data_table.addColumn('number', 'Proportion of Sales');
for (var i = 1; i < customer_product_grid_data.length; i ++){
customer_product_grid_data_table.addRow([
'',
customer_product_grid_data[i][0],
customer_product_grid_data[i][1],
(customer_product_grid_data[i][3] - customer_product_grid_data[i][2]) / customer_product_grid_data[i][3] * 100,
customer_product_grid_data[i][3] / qnty_sell_total
]);
}
var chart = new google.visualization.BubbleChart(document.getElementById('customer_product_grid'));
chart.draw(customer_product_grid_data_table, options);
Judging from all the searching I did, and also the answer given here by jmac, I decided the only way to go was a Javascript hack to replace the axes numbers with words. The code I implemented is here:
/*
*
* The following 2 functions are a little hacky, they have to be done after calling the "draw" function
* The bubble chart originally displays only numbers along the x and y axes instead of customer or product names
* These 2 functions replace those numbers with the words for the customers and products
*
*/
for ( var i = -2; i < products.length + 1; i ++ ){
$('#customer_product_grid svg text[text-anchor="start"]:contains("'+i+'")').text(function(j,t){
if (t == i){
if (i >= products.length || i < 0){
return " ";
}
return products[i];
}
});
}
for ( var i = -2; i <= customers.length + 3; i ++ ){
$('#customer_product_grid svg text[text-anchor="end"]:contains("'+i+'")').text(function(j,t){
if (i >= customers.length + 1 || i <= 0){
return " ";
}else if (t == i){
return customers[i-1];
}
});
}
Basically, you just make a for loop that iterates through all the integers that you are showing on the x and y axes. Do some if...else stuff to either replace the integer with an element from the array, or just make it blank.
Keep in mind for the above code to work properly, you need to have the following property in the chart options -> vAxis: { textPosition: 'in' }
Unfortunately, there is no easy way to do this as bubble charts (or anything that uses numerical series for an axis value). You can work around it as explained here.
The general concept is to eliminate your axis labels on the 'main chart' and adjust the gridlines to match the amount of labels you want. Then create an additional dummy chart which shows only the categories, and use that to show the labels.
Unfortunately, this is how it has to be until Google decides to implement the entire ICU pattern set for chart axes...