how to use string values ​in x-axis of line graph d3.js v3.5.6 - d3.js

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

Related

crossfilter reduction is crashing

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.

Append Circles to 1 Line in a D3 Multi-Line chart

I have a multi-line chart representing 8 different series of values, for a given date:
http://bl.ocks.org/eoiny/8548406
I have managed to filter out series1 and append circles for each data-point for series1 only, using:
var filtered = city
.filter(function(d){
return d.name == "series1"
})
filtered
.selectAll('circle')
.data(
function(d){return d.values}
)
.enter().append('circle')
.attr({
cx: function(d,i){
return x(d.date)
},
cy: function(d,i){
return y(d.pindex)
},
r: 5
})
However I am trying to append 4 circles to my series1 line, one for each of the following values only:
min value in series1,
max value in series1,
1st value in series1,
last value in series1.
I approached this problem by looking at the "filtered" array and I tried using something like this to catch the min & max values to start with:
.attr("visibility", function(d) {
if (d.pindex == d3.max(filtered, function(d) { return d.pindex; })) {return "visible"}
if (d.pindex == d3.min(filtered, function(d) { return d.pindex; })) {return "visible"}
else { return "hidden" }
;})
But I'm somehow getting muddled up by the fact that the data I need is in an object within the filtered array. I know that filtered should look like this:
[{
name: "series1",
values: [{date: "2005-01-01",
pindex: "100"},
{date: "2005-02-01"
pindex: "100.4"}, ...etc for all data points i.e. dates
]
}]
So I tried something like this:
d.pindex == d3.max(filtered, function(d) { return d.values.pindex; })
but I'm still getting a bit lost. Does anyone have any ideas?
In general, you probably want to filter your data rather than DOM elements. So instead of using city.filter you might use cities.filter to get the data array you're interested in. More importantly, you probably want to filter the data passed to the new circle selection, rather than creating all circles and then selectively showing or hiding them. I might try:
filtered
.selectAll('circle')
.data(function(d){
var points = d.values;
// create the array of desired points, starting with the easy ones
var circleData = [
// first
points[0],
// last
points[points.length - 1]
];
// now find min and max
function getValue(d) { return d.pindex; }
// d3.max returns the max value, *not* the object that contains it
var maxVal = d3.max(points, getValue);
// Note that you might have multiple points with the max. If you
// don't want them all, just take maxPoints[0]
var maxPoints = points.filter(function(d) { return d.pindex === maxVal; });
// same for min
var minVal = d3.min(points, getValue);
var minPoints = points.filter(function(d) { return d.pindex === minVal; });
// stick them all together
return circleData.concat(maxPoints).concat(minPoints);
})
.enter().append('circle')
// etc
Key points:
Filter your data, not your DOM. It's less expensive processing, easier to debug, and generally much easier to get your head around.
d3.min and d3.max don't return the object with the max value, they return the value itself, hence your TypeError.

custom linear scales in D3

I have a linear scale and I am using it for one of my axis in a chart.
I want to define a custom format only showing positive numbers and following this example I created a customFormat in this way:
function yFormat(formats) {
return function(value) {
var i = formats.length - 1, f = formats[i];
while (!f[1](value)) f = formats[--i];
return f[0](value);
};
}
var customFormat = yFormat([
[d3.format("?????"), function() { return true }],
[d3.format("d"), function(d) { return d >= 0 }]
]);
but I don't know how to define an empty format for Numbers (link to doc)
Does anybody have an idea? Thanks
EDIT
d3.format("") seems to work for time scales but not for linear scale. In a console:
> var emptyTime = d3.time.format("");
undefined
> emptyTime("something");
""
> var emptyInt = d3.format("");
undefined
> emptyInt(5);
"5"
ANSWER
I am just using tickValues on the axis itself. So, something like:
yAxis.tickValues(
d3.range(
0,
d3.max(y.domain()),
shift
)
);

How do I create a new scale() function in d3.js? I would like to create a cumulative distribution function

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();
},

Charting a D3 line chart with an array of objects

I am trying to create a line chart using D3.js but I am pretty stumped. I've followed this tutorial, but I don't see how I can get my data into the chart.
Here's what my data looks like:
[
{ name : "SomeKey", timestamp : "2013-05-07T12:00:00.052Z", value : "1" }
{ name : "SomeKey", timestamp : "2013-05-07T12:10:00.052Z", value : "4" }
{ name : "SomeKey", timestamp : "2013-05-07T12:40:00.052Z", value : "2" }
{ name : "SomeKey", timestamp : "2013-05-07T12:41:00.052Z", value : "2" }
{ name : "SomeKey", timestamp : "2013-05-07T12:44:00.052Z", value : "2" }
... etc. there can be loads of objects in this array
]
As mentioned there can be any number of objects in the array, the name key always holds the same value for the array, what I need is the timestamp on the x-axis and the value is what would plot the chart, but I can't quite figure it out.
I looked at a couple of other questions that might be similar to this one on SO, but the difference for me is that there is only one value per object.
You need to make a few changes to specify the property within your object you want the data to be based on, in your case "value".
First get the max Y.
maxY = d3.max(data, function (d) {
return d.value;
});
You want to modify this line from the tutorial and replace d3.max(data) with maxY.
y = d3.scale.linear().domain([0, d3.max(data)]).range([0 + margin, h - margin])
Then you'll need to modify this code to specify your values as well
var line = d3.svg.line()
.x(function(d,i) { return x(i); })
.y(function(d) { return -1 * y(d.value); })

Resources