Use dates as numbers, but format them as dates in UI - d3.js

Let's take this example.
https://d3fc.io/examples/series-canvas-candlestick/
It defines X scale as a time scale.
const xScale = d3
.scaleTime()
.domain(fc.extentDate().accessors([d => d.date])(data));
To prevent multiple tick to date conversion in my zoom handler, I'd like to always work with number, but keep UI showing these numbers as dates. So, I change the code above to linear scale.
const xScale = d3
.scaleLinear()
.domain(fc.extentLinear().accessors([d => d.date.getTime()])(data));
Everything works fine, but now I have an X scale showing numbers instead of dates. The question is, how to add mapper or formatter that would show these numbers as dates in UI? I see some examples of d3.tickFormat, but not sure how to apply it in d3fc.
https://observablehq.com/#d3/axis-ticks

You supply your scales to the D3FC cartesian chart, which then couples these with the axes that it creates for you.
As mentioned in the documentation the chart exposes the properties of the X and Y axes with an x and y prefix. So, if you want to change the tick format for the X axis, which you would typically do via the tickFormat property, you instead do the following:
var chart = fc.chartCartesian(
d3.scaleLinear(),
d3.scaleLinear()
)
.xTickFormat(/* ... formatter goes here */);

Related

Using Custom Scale function for ReCharts or d3-scale

I want to display Weibull analysis plot using Recharts. For Weibull analysis, formula for Y-Axis Scale is as:
ln(-ln(1-p))
where p=(i-0.3)/(n+0.4) and i is the rank of the observation. This scale is chosen in order to linearize the resulting plot for Weibull data. You can have a look at the Y Axis log scale as shown in the example graph below.
I tried standard scaleLog() but I need to customize the log function.
const y_scale = scaleLog().range([0, 100]);
Also, one of the recharts issue shows an option of function while defining scale, but that also does not seem to work.
https://github.com/recharts/recharts/issues/305
Would be great if someone could help with the way to implement custom scale functions.
Example of Weibull Graph:
Recharts seems to have added exactly what you were searching on the documentation of YAxis
https://recharts.org/en-US/api/YAxis#scale
import { scaleLog } from 'd3-scale';
const scale = scaleLog().base(Math.E);
...
<YAxis scale={scale} />
...

Dynamic colors in DC charts

I'm building a data dashboard using DC.js and was wondering if it was possible to change the color of the slices in a pie chart dynamically based on the value in the field it is referring to.
Basically I've built a pie chart aggregating the costume colors of different superheroes and I'd love to be able to color each slice with the color it is referring to - so the slice for 'Black' is colored black, the slice for 'Green' is colored green and so forth.
I'm fairly new to DC.js so accept that it may not be possible, but wanted to throw it out there and see if it could be done!
I tried including an array within .ordinalColors but couldn't figure out if there was a way to pull in the data from the field dynamically. I'm assuming that I'd have to change the data in the .csv file to a string that could be recognised as a color reference, but not sure how to go about doing that.
function show_costume_color(ndx) {
var costume_color_dim = ndx.dimension(dc.pluck('Costume Colour'));
var costume_color = costume_color_dim.group();
dc.pieChart('#costume-color')
.width(500)
.height(500)
.radius(500)
.innerRadius(100)
.slicesCap([7])
.transitionDuration(1500)
.dimension(costume_color_dim)
.group(costume_color);
}
CSV data comes in the below format
ID,name,Gender,Eye color,Race,Hair color,Publisher,Alignment,Superpower,Superpower Strength Level,Costume
Colour
0,A-Bomb,Male,Yellow,Human,No Hair,Marvel Comics,Good,Superhuman
Strength,10,None
1,Abin Sur,Male,Blue,Ungaran,No Hair,DC Comics,Good,Cosmic Power,40,Green
Yes, of course. Everything is specified dynamically in dc.js.
Assuming you are using dc.js v3 (and d3 v4+) the way I would suggest doing this is by creating another CSV file with the color assignments you want, something like
Name, RGB
Red, #ff1122
Blue, #1133ff
...
Then you can load the second file in parallel with your data using Promise.all(),
Promise.all([d3.csv('data.csv'), d3.csv('colors.csv')])
.then(function(data, colors) {
// rest of code will go here
});
ordinalColors is a nice convenience method, but if you want complete control, and to understand exactly what's going on, it's better to supply your own color scale. In this case, we want an ordinal scale, which maps specific discrete values to specific colors.
Under the covers, dc.js always deals with colors by using the colorAccessor to fetch a value for the the item, and then mapping this value using a color scale. You can think of the value that the accessor returns as a "color name", which is pretty convenient because it's exactly what you want here.
So you can populate a d3.scaleOrdinal with the domain of color names and the range of RGB colors:
var colorScale = d3.scaleOrdinal()
.domain(colors.map(row => row.Name))
.range(colors.map(row => row.RGB));
Now supply it to your chart using .colors():
chart.colors(colorScale);
What's really handy about this approach is that you can supply the same color scale for multiple charts, in order to make sure they are consistent. This is something that you don't get automatically in dc.js, because charts don't know very much about each other.
So, I managed to figure it out through an extensive period of trial and error and now I'm off and away with my dashboard. Thanks for your help, Gordon - it really made the difference! It needs a bit of tidying up but my working test code is below.
// Bring in data from both csv files
Promise.all([d3.csv("../data/heroes_information.csv"),
d3.csv("../data/costume_colors.csv")])
.then(function(data) {
// Tidy up data before use
data.forEach(function(d) {
d.Height = +d.Height;
d.Weight = +d.Weight;
d.Strength = +d.Strength;
});
// Bring in colorScale to dynamically color pie chart slices
var ndxcol = crossfilter(data[1]);
var colorScale = d3.scaleOrdinal()
.domain(data[1].map(row => row.Name))
.range(data[1].map(row => row.RGB));
// Bring in superhero data
var ndx = crossfilter(data[0]);
// Define chart types
var publisherSelector = dc.selectMenu('#publisher-selector')
var genderChart = dc.rowChart('#gender-balance');
// Define chart dimensions
var publisherChoice = ndx.dimension(dc.pluck('Publisher'));
var genderBalance = ndx.dimension(dc.pluck('Gender'));
// Define chart groups
var genderNumber = genderBalance.group();
var publisherNumber = publisherChoice.group();
// Draw charts
publisherSelector
.dimension(publisherChoice)
.group(publisherNumber);
genderChart
.width(500)
.height(200)
.margins({ top: 30, right: 30, bottom: 30, left: 30 })
.dimension(genderBalance)
.group(genderNumber)
.gap(6)
.colors(colorScale)
.transitionDuration(500)
.x(d3.scaleOrdinal())
.elasticX(true);
dc.renderAll();
});

XY Amchart XY chart showing data on hovering perfectly on bullet

How can I show the data in balloon text while cursor is in chart in when using xy type chart?
I want to show my data like this:
https://www.amcharts.com/demos/chart-with-gaps-in-data/
I am using type: xy amchart, with x axis values as date, and y axis data as integer .
The amCharts demo uses a chartCursor (https://docs.amcharts.com/3/javascriptcharts/ChartCursor) for this behavior. Unfortunately you cannot get the same behavior for an XY chart, meaning for an XY chart, balloons will only show when hovering exactly over a data point/bullet.
But if your X-axis is a date based axis and Y is an integer (value) axis, you could use a Serial Chart to get that same chartCursor behavior.

NVD3.js multiChart x-axis labels is aligned to lines, but not bars

I am using NVD3.js multiChart to show multiple lines and bars in the chart. All is working fine, but the x-axis labels is aligned only to the line points, not bars. I want to correctly align labels directly below the bars as it should. But I get this:
With red lines I marked where the labels should be.
I made jsFiddle: http://jsfiddle.net/n2hfN/
Thanks!
As #Miichi mentioned, this is a bug in nvd3...
I'm surprised that they have a TODO to "figure out why the value appears to be shifted" because it's pretty obvious... The bars use an ordinal scale with .rangeBands() and the line uses a linear scale, and the two scales are never made to relate to one another, except in that they share the same endpoints.
One solution would be to take the ordinal scale from the bars, and simply adjust it by half of the bar width to make the line's x-scale. That would put the line points in the center of the bars. I imagine that something similar is done in the nv.models.linePlusBarChart that #LarsKotthoff mentioned.
Basically, your line's x-scale would look something like this:
var xScaleLine = function(d) {
var offset = xScaleBars.rangeBand() / 2;
return xScaleBars(d) + offset;
};
...where xScaleBars is the x-scale used for the bar portion of the chart.
By combing through the source code for nvd3, it seems that this scale is accessible as chart.bars1.scale().
Maybe someday the authors of nvd3 will decide that their kludge of a library deserves some documentation. For now, I can show you the kind of thing that would solve the problem, by making a custom chart, and showing how the two scales would relate.
First, I'll use your data, but separate the line and bar data into two arrays:
var barData = [
{"x":0,"y":6500},
{"x":1,"y":8600},
{"x":2,"y":17200},
{"x":3,"y":15597},
{"x":4,"y":8600},
{"x":5,"y":814}
];
var lineData = [
{"x":0,"y":2},
{"x":1,"y":2},
{"x":2,"y":4},
{"x":3,"y":6},
{"x":4,"y":2},
{"x":5,"y":5}
];
Then set up the scales for the bars. For the x-scale, I'll use an ordinal scale and rangeRoundBands with the default group spacing for nvd3's multiBar which is 0.1. For the y-scale I'll use a regular linear scale, using .nice() so that the scale doesn't end on an awkward value as it does by default in nvd3. Having some space above the largest value gives you some context, which is "nice" to have when trying to interpret a chart.
var xScaleBars = d3.scale.ordinal()
.domain(d3.range(barData.length))
.rangeRoundBands([0, w], 0.1);
var yScaleBars = d3.scale.linear()
.domain([0, d3.max(barData, function(d) {return d.y;})])
.range([h, 0])
.nice(10);
Now here's the important part. For the line's x-scale, don't make a separate scale, but just make it a function of the bars' x-scale:
var xScaleLine = function(d) {
var offset = xScaleBars.rangeBand() / 2;
return xScaleBars(d) + offset;
};
Here's the complete example as a JSBin. I've tried to document the major sections with comments so it's easy to follow the overall logic of it. If you can figure out from the nvd3 source code exactly what each of the elements of the multiChart are called and how to set the individual scales of the constituent parts, then you might be able to just plug in the new scale.
My feeling on it is that you need to have a pretty good handle on how d3 works to do anything useful with nvd3, and if you want to customize it, you're probably better off just rolling your own chart. That way you have complete knowledge and control of what the element classes and variable names of the parts of your chart are, and can do whatever you want with them. If nvd3 ever gets proper documentation, maybe this will become a simple fix. Good luck, and I hope this at least helps you get started.

NVD3 X axis incorrect ordering (dates)

I'm trying to visualize 2 series but when I visualize them together, the dates don't go in sequential order anymore.
Here is the fiddle: http://jsfiddle.net/hohenheim/6R7mu/21/ Notice the weird x-axis.
Is there a way to fix the x axis on nvd3?
The data looks like this:
data1 = [{
"date": 1396828800,
"impressions": 49145385
}, {
"date": 1396915200,
"impressions": 46704447
} ....
The NVD3 "multiBarChart" uses an ordinal (category) scale, so it will only display the x-values you give it, in the order in which they are added to the scale. Because your two series only partially overlap on the x axis that's causing problems.
Unlike other NVD3 chart types, the multiBarChart doesn't give you the option of setting your own scale -- it needs to use an ordinal scale in order to generate even widths for the bars. However, you can set the x domain (the list of categories to use for each bar), by calling chart.xDomain(arrayOfXValues).
The array will need to be an ordered array of Date values that spans your data. In order to generate it, you'll need the d3 time intervals range functions. You might also need the d3.extent function to find your max and min values.

Resources