I am trying to create histogram having ages in groups like this (0-10), (10-20), ...(90,100)
Dataset look like this:
0: {agebracket: "20", currentstatus: "Recovered", dateannounced: "30/01/2020"}
1: {agebracket: "45", currentstatus: "Confirmed", dateannounced: "02/03/2020"}
2: {agebracket: "24", currentstatus: "Recovered", dateannounced: "02/03/2020"}
.
.
.
99: {agebracket: "58", currentstatus: "Hospitalized", dateannounced: "20/03/2020"}
I was able to create histogram but that was on the whole dataset. I didn't take account of
"currentstatus" --> "Recovered", "Hospitalized", "Deceased"
On whole dataset:
I tried to create histogram by currentstatus but it look like this:
This is what I have tried:
var binwidth = 10;
var dim = cf.dimension(function(d) {
return parseInt(d.agebracket); });
var age_by_cases= dim.group().reduce(
// add
(p, v) => {
p[v.currentstatus] = (p[v.currentstatus] || 0) + 1;
return p;
},
// remove
(p, v) => {
p[v.currentstatus] -= 1;
return p;
},
// init
() => ({})
);
barChart
.height(300)
.width(500) //give it a width
.dimension(dim)
.group(age_by_cases, type)
.elasticY(true)
.valueAccessor(function(p) {
return p.value[type_c];
// return (binwidth * Math.floor(parseInt(p.value[type_c])/binwidth)) ;
})
.x(d3.scaleLinear().domain([1,101]))
.xUnits(dc.units.fp.precision(binwidth))
.elasticX(true);
Line no. 170-184 and Line no. 227-243
https://blockbuilder.org/ninjakx/8e2c0b407fdb1991c9cc5e81e447ebe2
I just got struck at this badly. I don't know how to solve it.
Usually you will define the binning in the dimension key function:
var dim = cf.dimension(function(d) {
return binwidth * Math.floor(parseInt(p.agebracket)/binwidth);
};
And then, use the bin width (or other binning spec) in xUnits, as you have it:
.xUnits(dc.units.fp.precision(binwidth))
This causes crossfilter to sort each row into the bin with the value rounded down by binwidth, and it tells dc.js to calculate the bar width also using the binwidth.
I am unable to render a dc.js stacked bar chart successfully and I receive a console error
unable to read property 'Total' of undefined
I am new to the library and suspect my group or reduce is not successfully specified.
How do I resolve this issue?
$scope.riskStatusByMonth = function(){
var data = [
{"Month":"Jan","High":12},{"Month":"Jan","Med":14},{"Month":"Jan","Low":2},{"Month":"Jan","Closed":8},
{"Month":"Feb","High":12},{"Month":"Feb","Med":14},{"Month":"Feb","Low":2},{"Month":"Feb","Closed":8},
{"Month":"Mar","High":12},{"Month":"Mar","Med":14},{"Month":"Mar","Low":2},{"Month":"Mar","Closed":8},
{"Month":"Apr","High":12},{"Month":"Apr","Med":14},{"Month":"Apr","Low":2},{"Month":"Apr","Closed":8},
{"Month":"May","High":12},{"Month":"May","Med":14},{"Month":"May","Low":2},{"Month":"May","Closed":8},
{"Month":"Jun","High":12},{"Month":"Jun","Med":14},{"Month":"Jun","Low":2},{"Month":"Jun","Closed":8},
{"Month":"Jul","High":12},{"Month":"Jul","Med":14},{"Month":"Jul","Low":2},{"Month":"Jul","Closed":8},
{"Month":"Aug","High":12},{"Month":"Aug","Med":14},{"Month":"Aug","Low":2},{"Month":"Aug","Closed":8},
{"Month":"Sep","High":12},{"Month":"Sep","Med":14},{"Month":"Sep","Low":2},{"Month":"Sep","Closed":8},
{"Month":"Oct","High":12},{"Month":"Oct","Med":14},{"Month":"Oct","Low":2},{"Month":"Oct","Closed":8},
{"Month":"Nov","High":12},{"Month":"Nov","Med":14},{"Month":"Nov","Low":2},{"Month":"Nov","Closed":8},
{"Month":"Dec","High":8},{"Month":"Dec","Med":6},{"Month":"Dec","Low":13},{"Month":"Dec","Closed":8},
]
data.forEach(function(x) {
x.Total = 0;
});
var ndx = crossfilter(data)
var xdim = ndx.dimension(function (d) {return d.Month;});
function root_function(dim,stack_name) {
return dim.group().reduce(
function(p, v) {
p[v[stack_name]] = (p[v[stack_name]] || 0) + v.High;
return p;},
function(p, v) {
p[v[stack_name]] = (p[v[stack_name]] || 0) + v.Med;
return p;},
function(p, v) {
p[v[stack_name]] = (p[v[stack_name]] || 0) + v.Low; <-------------------here is where error occurs
return p;},
function(p, v) {
p[v[stack_name]] = (p[v[stack_name]] || 0) + v.Closed;
return p;},
function() {
return {};
});}
var ydim = root_function(xdim,'Total')
function sel_stack(i) {
return function(d) {
return d.value[i];
};}
$scope.monthlyRiskStatus = dc.barChart("#risk-status-by-month");
$scope.monthlyRiskStatus
.x(d3.scaleLinear().domain(xdim))
.dimension(xdim)
.group(ydim, '1', sel_stack("Jan"))
.xUnits(dc.units.ordinal);
month = [null,'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
for(var i = 2; i<=12; ++i)
$scope.monthlyRiskStatus.stack(ydim, ''+i, sel_stack(month[i]));
$scope.monthlyRiskStatus.render();
}
group.reduce() takes three arguments: add, remove, init.
You are passing 5.
Looks like it is trying to call the third one as the initializer, with no arguments, so therefore v is undefined.
how to stack by level
It looks like what you're really trying to do is group by month (X axis) and then stack by status or level. Here's one way to do that.
First, you're on the right track with a function that takes a stack name, but we'll want it to take all of the stack names:
function root_function(dim,stack_names) {
return dim.group().reduce(
function(p, v) {
stack_names.forEach(stack_name => { // 1
if(v[stack_name] !== undefined) // 2
p[stack_name] = (p[v[stack_name]] || 0) + v[stack_name] // 3
});
return p;},
function(p, v) {
stack_names.forEach(stack_name => { // 1
if(v[stack_name] !== undefined) // 2
p[stack_name] = (p[v[stack_name]] || 0) + v[stack_name] // 3
});
return p;},
function() {
return {};
});}
In the add and reduce functions, we'll loop over all the stack names
Stack names are fields which may or may not exist in each row. If the stack name exists in the current row...
We'll add or subtract the row field stack_name from the field with the same name in the current bin.
We'll define both levels and months arrays. levels will be used for stacking and months will be used for the ordinal X domain:
var levels = ['High', 'Med', 'Low', 'Closed']
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
When we define the group, we'll pass levels to root_function():
var ygroup = root_function(xdim,levels)
I see you had some confusion between the English/math definition of "dimension" and the crossfilter dimension. Yes, in English "Y" would be a dimension, but in crossfilter and dc.js, "dimensions" are what you aggregate on, and groups are the aggregations that often go into Y. (Naming things is difficult.)
We'll use an ordinal scale (you had half ordinal half linear, which won't work):
$scope.monthlyRiskStatus
.x(d3.scaleOrdinal().domain(months))
.dimension(xdim)
.group(ygroup, levels[0], sel_stack(levels[0]))
.xUnits(dc.units.ordinal);
Passing the months to the domain of the ordinal scale tells dc.js to draw the bars in that order. (Warning: it's a little more complicated for line charts because you also have to sort the input data.)
Note we are stacking by level, not by month. Also here:
for(var i = 1; i<levels.length; ++i)
$scope.monthlyRiskStatus.stack(ygroup, levels[i], sel_stack(levels[i]));
Let's also add a legend, too, so we know what we're looking at:
.margins({left:75, top: 0, right: 0, bottom: 20})
.legend(dc.legend())
Demo fiddle.
I am trying to place a string on the x axis of a line graph, initially the x axis is a date variable, but now I must add a string that differentiates me if it is morning 'AM', afternoon 'PM' or night 'ZM' .
I am using nvd3 v1.8.1 with angularjs. The initial code is as follows
tchart.xAxis
// Chart x-axis settings
.axisLabel('Muestras')
.tickFormat( function(d){
return d3.time.format('%y-%m-%d %p')
(Utils.utcDateToLocalDate(d))
});
chart.xScale(d3.time.scale.utc());
tchart.yAxis // Chart y-axis settings
.axisLabel('Temperatura').tickFormat(
d3.format('.02f'));
var tdata = getData('temperatura');
d3.select('#temperatureChart svg').datum(tdata).call(tchart);
var getData = function(_prop) {
var _data = [];
$scope.registros.forEach(function(_item, _index) {
var _cfecha = _item.fecha.split('-');
var _fecha = new Date(Date.UTC(
parseInt(_cfecha[0]),
parseInt(_cfecha[1]),
parseInt(_cfecha[2]),
(_item.horario === 'AM') ? 1: 13,0, 0));
_data.push({
x : _fecha,
y : _item[_prop]
});
});
return [ {
values : _data,
key : _prop,
color : '#ff7f0e'
} ];
};
With which I obtain the result of graph follow.
Result1
Then I increased a variable of 'label' in the data to use them in thickformat, but not all the values are shown in the graph2
//fill data array
_data.push({
x : _fecha,
y : _item[_prop],
label : _lfecha
});
//tchar.xAxis
.tickFormat(function(d,i) {
return tdata[0].values[i].label;
}
graph2
I also used an ordinal scale with the values of the data matrix, but only shows the start day and the end day, the other values are omitted (graph3)
var x = d3.extent(tdata[0].values, function(d) { console.log(d); return d.label; });
tchart.xScale(d3.scale.ordinal().domain(x));
grahp3
The result I expect is the same as the first graphic but the axis labels allow me to concatenate the date with the abbreviations AM-PM-ZM
I'm working on a new chart type for NVD3 called lineWithFocusPlusSecondary. It has two graphs on top of each other. It's working well except for one problem: if the x values are dates, when you zoom in, the graph gets cut off in an unpleasant manner. This doesn't happen with the default lineChart so I've definitely done something wrong.
I've put my code in this plnkr: https://plnkr.co/edit/9GzI0Jxi5qXZas3ljuBQ?p=preview
Would love some help :) It seems like the issue in the screenshot is that the x-axis domain goes until ~7:05pm but we don't have a data point until 7pm.
It could be something something to do with my onBrush function:
function onBrush(extent) {
var processedData = processData(container.datum()),
dataPrimary = processedData.dataPrimary,
dataSecondary = processedData.dataSecondary,
seriesPrimary = processedData.seriesPrimary,
seriesSecondary = processedData.seriesSecondary;
updateChartData(
getIntegerExtent(extent),
dataPrimary,
dataSecondary,
seriesPrimary,
seriesSecondary
);
}
function getIntegerExtent(extent) {
return [Math.ceil(extent[0]), Math.floor(extent[1])];
}
function updateAxes(extent) {
primaryXAxis.scale(primaryChart.xScale());
primaryXAxis.domain(extent);
g
.select('.nv-primary .nv-x.nv-axis')
.transition()
.duration(transitionDuration)
.call(primaryXAxis);
g
.select('.nv-secondary .nv-ySecondary.nv-axis')
.transition()
.duration(transitionDuration)
.call(yAxisSecondary);
g
.select('.nv-primary .nv-yPrimary.nv-axis')
.transition()
.duration(transitionDuration)
.call(yAxisPrimary);
}
function updateChartData(currentExtent, dataPrimary, dataSecondary) {
updateAxes(currentExtent);
var primaryDatasetsWithinBrushExtent = !dataPrimary.length
? [
{
values: []
}
]
: dataPrimary.map(function(d) {
var restrictedDataset = Object.assign({}, d);
restrictedDataset.values = d.values.filter(function(d, i) {
return (
primaryChart.x()(d, i) >= currentExtent[0] &&
primaryChart.x()(d, i) <= currentExtent[1]
);
});
return restrictedDataset;
});
var primaryChartWrap = g
.select('.nv-primary .nv-linesWrap')
.datum(primaryDatasetsWithinBrushExtent);
var secondaryDatasetsWithinExtent = !dataSecondary.length
? [
{
values: []
}
]
: dataSecondary.map(function(d) {
var restrictedDataset = Object.assign({}, d);
restrictedDataset.values = d.values.filter(function(d, i) {
return (
secondaryChart.x()(d, i) >= currentExtent[0] &&
secondaryChart.x()(d, i) <= currentExtent[1]
);
});
return restrictedDataset;
});
var focusSecondaryChartWrap = g
.select('.nv-secondary .nv-secondaryChartWrap')
.datum(secondaryDatasetsWithinExtent);
primaryChart.xDomain(currentExtent);
secondaryChart.xDomain(currentExtent);
primaryChartWrap
.transition()
.duration(transitionDuration)
.call(primaryChart);
focusSecondaryChartWrap
.transition()
.duration(transitionDuration)
.call(secondaryChart);
}
I discovered the issue was that I was trying to set the xDomain in multiple locations. This seems to mess up NVD3's logic. After I removed all of the .domain/.xDomain it worked perfectly :)
Debugging approach was to carefully read through the lineChart.js code and notice what it didn't have that I had.
How do I create my own scale() function in d3?
I am trying to replace the nice linear scale in d3 d3.scale.linear() with a different function that I would like to create myself. My new scale would be based on a cumulative distribution function, so that the median value would appear in the center of the x axis, and a value that was two standard deviations from the median would appear twice as far from the center of the x axis as something that was one standard deviation from the mean.
Here is a link to my jsfiddle page: http://jsfiddle.net/tbcholla/kR2PS/3/ (I would appreciate any other comments you might have about my code as well!)
right now I have:
var x = d3.scale.linear()
.range([0, width])
.domain(d3.extent([0, data.length]));
I've seen scale.pow() and scale.log(). Now I'd like to create a new function!
Thanks!
EDIT: I found the function scale.quantile(), which might hold the solution for me. My related question: Plotting a line graph with scale.quantile()
This is an example how we can add new functionality in d3.scale.liner(). For null values my function returns null (d3.scale.liner() returns 0 in this case). The primary approach is to wrap the original scale and all his methods.
I didn't test this function for all cases. But for basic functionality it's working. Unfortunately I didn't found easier way to do it :(
/**
* d3.scale.linear() retrun 0 for null value
* I need to get null in this case
* This is a wrapper for d3.scale.linear()
*/
_getLinearScaleWithNull: function() {
var alternativeScale = function(origLineScale) {
var origScale = origLineScale ? origLineScale : d3.scale.linear();
function scale(x) {
if (x === null) return null; //this is the implementation of new behaviour
return origScale(x);
}
scale.domain = function(x) {
if (!arguments.length) return origScale.domain();
origScale.domain(x);
return scale;
}
scale.range = function(x) {
if (!arguments.length) return origScale.range();
origScale.range(x);
return scale;
}
scale.copy = function() {
return alternativeScale(origScale.copy());
}
scale.invert = function(x) {
return origScale.invert(x);
}
scale.nice = function(m) {
origScale = origScale.nice(m);
return scale;
}
scale.ticks = function(m) {
return origScale.ticks(m);
};
scale.tickFormat = function(m, Format) {
return origScale.tickFormat(m, Format);
}
return scale;
}
return alternativeScale();
},