Is it possible to get indices from x values in d3? - d3.js

I have a data set containing dates (x values). Later in the code I need to get index of the array element which contains that date. Is it possible to get a particular index of the array according to date input?
Data set:
{"date": "2006-12-01", "POPYFR": "6.32296e+07", "status": {}}, {"date": "2007-12-01", "POPYFR": "6.36451e+07", "status": {}
So if I have a date 2006-12-01 the function should return 0 etc.

Since you are using d3 try bisector or bisect
https://observablehq.com/#d3/d3-bisect
const data = {"date": "2006-12-01", "POPYFR": "6.32296e+07", "status": {}}, {"date": "2007-12-01", "POPYFR": "6.36451e+07", "status": {}
bisect = d3.bisector(d => moment(d.date, "YYYY-MM-DD").toDate())
const value = bisect(data, "2006-12-01");

Is there a reason you can't just iterate over the data? Something like
indexOfDate(date) {
for (var i=0; i<dataSet.length; i++) {
if (dataSet[i].date === date) return i;
}
return -1;
}
?

Related

Creating a histogram chart for each category

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.

How to change text color of label in segment controller?

I want to set different text color of label in each row SegmentControl programmatically.
Please check my ref. code.
var arrColors = [
{"color":"white"},
{"color":"orange"},
{"color":"blue"},
{"color":"yellow"},
{"color":"gray"}
];
this.view.segCont.widgetDataMap = {lblColorName: "color"};
this.view.segCont.setData(arrColors);
I want to do something like attached image.
Thanks in advance!!
I got solution from kony team.
1) Create different skin for different color label. See below image:
2) Set condition for as per your require color label.
var arrColors = [
{"color": "white"},
{"color": "orange"},
{"color": "blue"},
{"color": "yellow"},
{"color": "gray"}
];
for (i = 0; i < arrColors.length; i++) {
if (arrColors[i].color === "orange") {
arrColors[i].color = {
"skin": "sknLblOrange"
};
} else {
arrColors[i].color = {
"skin": "sknLblGreen"
};
}
}
this.view.segCont.widgetDataMap = {
lblColor: "color"
};
this.view.segCont.setData(arrColors);
Hope this helpful to you. Happy Coding :)
This is fine if your data is finite and static, or if the data array is always the same length, like with a menu.
However, if your data is dynamic you should consider instead this solution:
var arrColors = [
{"skin": "whiteRowSkin"},
{"skin": "orangeRowSkin"},
{"skin": "blueRowSkin"},
{"skin": "yellowRowSkin"},
{"skin": "grayRowSkin"}
];
this.view.segCont.widgetDataMap = {
lblColor: "color"
// plus any other properties you need for this data.
};
// Lets assume this getData function fetches your dynamic data from a service.
var segData = getData();
for (var i = 0; i < segData.length; i++) {
var colorIndex = i % arrColors.length;
segData[i].color = arrColors[colorIndex];
};
this.view.segCont.setData(segData);
The key above is the Modulus/Remainder % operator, which allows you to decide dynamically which of the colors/skins in the skin array to corresponds to each data row, even if the size of the data array varies.
Note: This obviates the fact that the data may be a matrix if you're using segment sections.

Dimensional Charting with Non-Exclusive Attributes

The following is a schematic, simplified, table, showing HTTP transactions. I'd like to build a DC analysis for it using dc, but some of the columns don't map well to crossfilter.
In the settings of this question, all HTTP transactions have the fields time, host, requestHeaders, responseHeaders, and numBytes. However, different transactions have different specific HTTP request and response headers. In the table above, 0 and 1 represent the absence and presence, respectively, of a specific header in a specific transaction. The sub-columns of requestHeaders and responseHeaders represent the unions of the headers present in transactions. Different HTTP transaction datasets will almost surely generate different sub-columns.
For this question, a row in this chart is represented in code like this:
{
"time": 0,
"host": "a.com",
"requestHeaders": {"foo": 0, "bar": 1, "baz": 1},
"responseHeaders": {"shmip": 0, "shmap": 1, "shmoop": 0},
"numBytes": 12
}
The time, host, and numBytes all translate easily into crossfilter, and so it's possible to build charts answering things like what was the total number of bytes seen for transactions between 2 and 4 for host a.com. E.g.,
var ndx = crossfilter(data);
...
var hostDim = ndx.dimension(function(d) {
return d.host;
});
var hostBytes = hostDim.group().reduceSum(function(d) {
return d.numBytes;
});
The problem is that, for all slices of time and host, I'd like to show (capped) bar charts of the (leading) request and response headers by bytes. E.g. (see the first row), for time 0 and host a.com, the request headers bar chart should show that bar and baz each have 12.
There are two problems, a minor one and a major one.
Minor Problem
This doesn't fit quite naturally into dc, as it's one-directional. These bar charts should be updated for the other slices, but they can't be used for slicing themselves. E.g., you shouldn't be able to select bar and deselect baz, and look for a resulting breakdown of hosts by bytes, because what would this mean: hosts in the transactions that have bar but don't have baz? hosts in the the transactions that have bar and either do or don't have baz? It's too unintuitive.
How can I make some dc charts one directional. Is it through some hack of disabling mouse inputs?
Major Problem
As opposed to host, foo and bar are non-exclusive. Each transaction's host is either something or the other, but a transaction's headers might include any combination of foo and bar.
How can I define crossfilter dimensions for requestHeaders, then, and how can I use dc? That is
var ndx = crossfilter(data);
...
var requestHeadersDim = ndx.dimension(function(d) {
// What should go here?
});
The way I usually deal with the major problem you state is to transform my data so that there is a separate record for each header (all other fields in these duplicate records are the same). Then I use custom group aggregations to avoid double-counting. These custom aggregations are a bit hard to manage so I built Reductio to help with this using the 'exception' function - github.com/esjewett/reductio
Hacked it (efficiently, but very inelegantly) by looking at the source code of dc. It's possible to distort the meaning of crossfilter to achieve the desired effect.
The final result is in this fiddle. It is slightly more limited than the question, as the fields of responseHeaders are hardcoded to foo, bar, and baz. Removing this restriction is more in the domain of simple Javascript.
Minor Problem
Using a simple css hack, I simply defined
.avoid-clicks {
pointer-events: none;
}
and gave the div this class. Inelegant but effective.
Major Problem
The major problem is solved by distorting the meaning of crossfilter concepts, and "fooling" dc.
Let's say the data looks like this:
var transactions = [
{
"time": 0,
"host": "a.com",
"requestHeaders": {"foo": 0, "bar": 1, "baz": 1},
"responseHeaders": {"shmip": 0, "shmap": 1, "shmoop": 0},
"numBytes": 12
},
{
"time": 1,
"host": "b.org",
"requestHeaders": {"foo": 0, "bar": 1, "baz": 1},
"responseHeaders": {"shmip": 0, "shmap": 1, "shmoop": 1},
"numBytes": 3
},
...
];
We can define a "dummy" dimension, which ignores the data:
var transactionsNdx = crossfilter(transactions);
var dummyDim = transactionsNdx
.dimension(function(d) {
return 0;
});
Using this dimension, we can define a group that counts the total foo, bar, and baz bytes of the filtered rows:
var requestHeadersGroup = dummyDim
.group()
.reduce(
/* callback for when data is added to the current filter results */
function (p, v) {
return {
"foo": p.foo + v.requestHeaders.foo * v.numBytes,
"bar": p.bar + v.requestHeaders.bar * v.numBytes,
"baz": p.baz + v.requestHeaders.baz * v.numBytes,
}
},
/* callback for when data is removed from the current filter results */
function (p, v) {
return {
"foo": p.foo - v.requestHeaders.foo * v.numBytes,
"bar": p.bar - v.requestHeaders.bar * v.numBytes,
"baz": p.baz - v.requestHeaders.baz * v.numBytes,
}
},
/* initialize p */
function () {
return {
"foo": 0,
"bar": 0,
"baz": 0
}
}
);
Note that this isn't a proper crossfilter group at all. It will not map the dimensions to their values. Rather, it maps 0 to a value which itself maps the dimensions to their values (ugly!). We therefore need to transform this group into something that actually looks like a crossfilter group:
var getSortedFromGroup = function() {
var all = requestHeadersGroup.all()[0].value;
all = [
{
"key": "foo",
"value": all.foo
},
{
"key": "bar",
"value": all.bar
},
{
"key": "foo",
"value": all.baz
}];
return all.sort(function(lhs, rhs) {
return lhs.value - rhs.value;
});
}
var requestHeadersDisplayGroup = {
"top": function(k) {
return getSortedFromGroup();
},
"all": function() {
return getSortedFromGroup();
},
};
We now can create a regular dc chart, and pass the adaptor group
requestHeadersDisplayGroup to it. It works normally from this point on.

Complex d3.nest() manipulation

I have an array of arrays that looks like this:
var arrays = [[1,2,3,4,5],
[1,2,6,4,5],
[1,3,6,4,5],
[1,2,3,6,5],
[1,7,5],
[1,7,3,5]]
I want to use d3.nest() or even just standard javascript to convert this data into a nested data structure that I can use with d3.partition.
Specifically, I want to create this flare.json data format.
The levels of the json object I want to create with d3.nest() correspond to the index positions in the array. Notice that 1 is in the first position in all the subarrays in the example data above; therefore, it is at root of the tree. At the next positions in the arrays there are three values, 2, 3, and 7, therefore, the root value 1 has 3 children. At this point the tree looks like this:
1
/ | \
2 3 7
At the third position in the subarrays there are four values, 3, 5, and 6. These children would be places into the tree as follows:
1
____|___
/ | \
2 3 7
/ \ / / \
3 6 6 3 5
How can I produce this data structure using d3.nest()? The full data structure with the example data I showed above should look like this:
{"label": 1,
"children": [
{"label": 2, "children": [
{"label": 3, "children": [
{"label": 4, "children": [
{"label": 5}
]},
{"label": 6, "children": [
{"label": 5}
]}
]},
{"label": 6, "children": [
{"label": 4, "children": [
{"label": 5}
]}
]},
{"label": 3, "children": [
{"label": 6, "children": [
{"label": 4, "children": [
{"label": 5}
]}
]}
]},
{"label": 7, "children": [
{"label": 3, "children": [
{"label": 5}
]},
{"label": 5}
]}
]}
]}
I'm trying to convert my array data structure above using something like this (very wrong):
var data = d3.nest()
.key(function(d, i) { return d.i; })
.rollup(function(d) { return d.length; })
I've been banging my head for a week to try and understand how I can produce this hierarchical data structure from an array of arrays. I'd be very grateful if someone could help me out.
#meetamit's answer in the comments is good, but in my case my tree is too deep to repeatedly apply .keys() to the data, so I cannot manually write a function like this.
Here's a more straightforward function that just uses nested for-loops to cycle through all the path instructions in each of your set of arrays.
To make it easier to find the child element with a given label, I have implemented children as a data object/associative array instead of a numbered array. If you want to be really robust, you could use a d3.map for the reasons described at that link, but if your labels are actually integers than that's not going to be a problem. Either way, it just means that when you need to access the children as an array (e.g., for the d3 layout functions), you have to specify a function to make an array out of the values of the object -- the d3.values(object) utility function does it for you.
The key code:
var root={},
path, node, next, i,j, N, M;
for (i = 0, N=arrays.length; i<N; i++){
//for each path in the data array
path = arrays[i];
node = root; //start the path from the root
for (j=0,M=path.length; j<M; j++){
//follow the path through the tree
//creating new nodes as necessary
if (!node.children){
//undefined, so create it:
node.children = {};
//children is defined as an object
//(not array) to allow named keys
}
next = node.children[path[j]];
//find the child node whose key matches
//the label of this step in the path
if (!next) {
//undefined, so create
next = node.children[path[j]] =
{label:path[j]};
}
node = next;
// step down the tree before analyzing the
// next step in the path.
}
}
Implemented with your sample data array and a basic cluster dendogram charting method:
http://fiddle.jshell.net/KWc73/
Edited to add:
As mentioned in the comments, to get the output looking exactly as requested:
Access the data's root object from the default root object's children array.
Use a recursive function to cycle through the tree, replacing the children objects with children arrays.
Like this:
root = d3.values(root.children)[0];
//this is the root from the original data,
//assuming all paths start from one root, like in the example data
//recurse through the tree, turning the child
//objects into arrays
function childrenToArray(n){
if (n.children) {
//this node has children
n.children = d3.values(n.children);
//convert to array
n.children.forEach(childrenToArray);
//recurse down tree
}
}
childrenToArray(root);
Updated fiddle:
http://fiddle.jshell.net/KWc73/1/
If you extend the specification of Array, it's not actually that complex. The basic idea is to build up the tree level by level, taking each array element at a time and comparing to the previous one. This is the code (minus extensions):
function process(prevs, i) {
var vals = arrays.filter(function(d) { return prevs === null || d.slice(0, i).compare(prevs); })
.map(function(d) { return d[i]; }).getUnique();
return vals.map(function(d) {
var ret = { label: d }
if(i < arrays.map(function(d) { return d.length; }).max() - 1) {
tmp = process(prevs === null ? [d] : prevs.concat([d]), i+1);
if(tmp.filter(function(d) { return d.label != undefined; }).length > 0)
ret.children = tmp;
}
return ret;
});
}
No guarantees that it won't break for edge cases, but it seems to work fine with your data.
Complete jsfiddle here.
Some more detailed explanations:
First, we get the arrays that are relevant for the current path. This is done by filtering out those that are not the same as prevs, which is our current (partial) path. At the start, prevs is null and nothing is filtered.
For these arrays, we get the values that corresponds to the current level in the tree (the ith element). Duplicates are filtered. This is done by the .map() and .getUnique().
For each of the values we got this way, there will be a return value. So we iterate over them (vals.map()). For each, we set the label attribute. The rest of the code determines whether there are children and gets them through a recursive call. To do this, we first check whether there are elements left in the arrays, i.e. if we are at the deepest level of the tree. If so, we make the recursive call, passing in the new prev that includes the element we are currently processing and the next level (i+1). Finally, we check the result of this recursive call for empty elements -- if there are only empty children, we don't save them. This is necessary because not all of the arrays (i.e. not all of the paths) have the same length.
Since d3-collection has been deprecated in favor of d3.array, we can use d3.groups to achieve what used to work with d3.nest:
var input = [
[1, 2, 3, 4, 5],
[1, 2, 6, 4, 5],
[1, 3, 6, 4, 5],
[1, 2, 3, 6, 5],
[1, 7, 5],
[1, 7, 3, 5]
];
function process(arrays, depth) {
return d3.groups(arrays, d => d[depth]).map(x => {
if (
x[1].length > 1 || // if there is more than 1 child
(x[1].length == 1 && x[1][0][depth+1]) // if there is 1 child and the future depth is inferior to the child's length
)
return ({
"label": x[0],
"children": process(x[1], depth+1)
});
return ({ "label": x[0] }); // if there is no child
});
};
console.log(process(input, 0));
<script src="https://d3js.org/d3-array.v2.min.js"></script>
This:
Works as a recursion on the arrays' depths.
Each recursion step groups (d3.groups) its arrays on the array element whose index is equal to the depth.
Depending on whether there are children or not, the recursion stops.
Here is the intermediate result produced by d3.groups within a recursion step (grouping arrays on there 3rd element):
var input = [
[1, 2, 3, 4, 5],
[1, 2, 6, 4, 5],
[1, 2, 3, 6, 5]
];
console.log(d3.groups(input, d => d[2]));
<script src="https://d3js.org/d3-array.v2.min.js"></script>
Edit - fixed
Here is my solution
Pro:It is all in one go (doesn't need objects converting to arrays like above)
Pro:It keeps the size/value count
Pro:the output is EXACTLY the same as a d3 flare with children
Con:it is uglier, and likely less efficient
Big Thanks to previous comments for helping me work it out.
var data = [[1,2,3,4,5],
[1,2,6,4,5],
[1,3,6,4,5],
[1,2,3,6,5],
[1,7,5],
[1,7,3,5]]
var root = {"name":"flare", "children":[]} // the output
var node // pointer thingy
var row
// loop through array
for(var i=0;i<data.length;i++){
row = data[i];
node = root;
// loop through each field
for(var j=0;j<row.length;j++){
// set undefined to "null"
if (typeof row[j] !== 'undefined' && row[j] !== null) {
attribute = row[j]
}else{
attribute = "null"
}
// using underscore.js, does this field exist
if(_.where(node.children, {name:attribute}) == false ){
if(j < row.length -1){
// this is not the deepest field, so create a child with children
var oobj = {"name":attribute, "children":[] }
node.children.push(oobj)
node = node.children[node.children.length-1]
}else{
// this is the deepest we go, so set a starting size/value of 1
node.children.push({"name":attribute, "size":1 })
}
}else{
// the fields exists, but we need to find where
found = false
pos = 0
for(var k=0;k< node.children.length ;k++){
if(node.children[k]['name'] == attribute){
pos = k
found = true
break
}
}
if(!node.children[pos]['children']){
// if no key called children then we are at the deepest layer, increment
node.children[pos]['size'] = parseInt(node.children[pos]['size']) + 1
}else{
// we are not at the deepest, so move the pointer "node" and allow code to continue
node = node.children[pos]
}
}
}
}
// object here
console.log(root)
// stringified version to page
document.getElementById('output').innerHTML = JSON.stringify(root, null, 1);
Working examples
https://jsfiddle.net/7qaz062u/
Output
{ "name": "flare", "children": [ { "name": 1, "children": [ { "name": 2, "children": [ { "name": 3, "children": [ { "name": 4, "children": [ { "name": 5, "size": 1 } ] } ] }, { "name": 6, "children": [ { "name": 4, "children": [ { "name": 5, "size": 1 } ] } ] } ] }, { "name": 3, "children": [ { "name": 6, "children": [ { "name": 4, "children": [ { "name": 5, "size": 1 } ] } ] }, { "name": 3, "children": [ { "name": 6, "children": [ { "name": 5, "size": 1 } ] } ] } ] }, { "name": 7, "children": [ { "name": 5, "size": 1 }, { "name": 3, "children": [ { "name": 5, "size": 1 } ] } ] } ] } ] }

d3 accessor of hierarchical array of objects

I'm feeding data to d3 via json in a format that looks like this:
[
{
"outcome_id":22,
"history":[
{
"time":"2013-05-06T16:38:55+03:00",
"balance_of_power":0.2
},
{
"time":"2013-05-07T00:38:55+03:00",
"balance_of_power":0.2222222222222222
},
{
"time":"2013-05-07T08:38:55+03:00",
"balance_of_power":0.36363636363636365
}
],
"winner":true,
"name":"Pauline"
},
{
"outcome_id":23,
"history":[
{
"time":"2013-05-06T16:38:55+03:00",
"balance_of_power":0.2
},
{
"time":"2013-05-07T00:38:55+03:00",
"balance_of_power":0.1111111111111111
},
{
"time":"2013-05-07T08:38:55+03:00",
"balance_of_power":0.09090909090909091
}
],
"winner":false,
"name":"Romain"
}
]
I use this data to draw both a multiple series line chart (to show the evolution of "balance_of_power" through time) and a donut chart to represent the latest value of "balance_of_power" for all series.
So each top-level array element is an object that has several attributes, one of them being "history", which is itself an array of objects (that have the time and balance_of_power attributes).
A working example can be found here.
To produce the data for the donut chart I use a function that takes the latest element from each history array (the data is sorted by time) and generate another attribute that's called "last_balance".
For example the first element becomes:
{
"outcome_id":22,
"history":[...],
"winner":true,
"name":"Pauline",
"last_balance":0.36363636363636365
}
And then I specify the right accessor from the pie layout value:
pie = d3.layout.pie().value(function(d) { return d.latest_balance; })
Now I'd like to get rid of the extra step and change the accessor function so that I can read the value directly form the initial data structure and also be able to access any balance_of_power for a time given as an argument.
Is there a way to do that with only modifying the accessor of pie value ?
EDIT
I changed the .value function to this:
.value(function(d) {
var h = d.history[0];
d.history.forEach(function(elt, i, a) {
console.log("======"); // start debug
console.log("search for:"+selected_time.toString());
console.log("current value:"+d.history[i].time.toString());
console.log("test:"+(d.history[i].time == selected_time));
console.log("======"); // end debug
if(d.history[i].time == selected_time) {
h = d.history[i];
}
});
return h.balance_of_power;
})
But the comparison always fails, even when the values seem to be identical, so the previous code always returns the initial value.
Here's what the javascript console shows for the last iteration:
====== final_balance_donut_chart.js?body=1:11
search for:Thu Jun 06 2013 16:06:00 GMT+0200 (CEST) final_balance_donut_chart.js?body=1:12
current value:Thu Jun 06 2013 16:06:00 GMT+0200 (CEST) final_balance_donut_chart.js?body=1:13
test:false final_balance_donut_chart.js?body=1:14
======
EDIT 2
For some reason I had to convert both times to string to make this work.
Here is the final code fore .value:
.value(function(d) {
var h = d.history[0];
d.history.forEach(function(elt) {
if(elt.time.toString() == selected_time.toString()) {
h = elt;
}
});
return h.balance_of_power;
})
Yes, your code would look something like this.
time = "...";
pie = d3.layout.pie()
.value(function(d) {
var h = d.history[0];
for(var i = 0; i < d.history.length; i++) {
if(d.history[i].time == time) {
h = d.history[i];
break;
}
}
return h.balance_of_power;
});
You will need to handle the case when the time is not in the history though.

Resources