I'm struggling to understand how I can use the new d3.group and d3.groups methods. My goal is to add a dropdown menu to my d3.chart. Specifically, when I select an option, the chart displays the data for this particular individual. My problem is that I don't understand how I can access the inner array for the selected individual. For example, if I select bib nr. 5, I want to access this individual's ratio score.
I have been stuck for this problem for a long time. I appreciate your help :)
const data = d3.range(20).map(i => ({
bib: Math.floor(i / 5) + 1,
ratio: -1 + Math.random() * 5,
run: [1, 2, 3, 4, 5][i % 5],
name: ['GIRL1', 'GIRL2', 'GIRL3', 'GIRL4'][Math.floor(i / 5)]
}));
// Now I want to use d3.groups
const skiers = d3.groups(data, d => d.bib)
//Logging this gives me a nested array.
console.log(skiers)
// I want to access the inner array for a particular individual, but how can I accomplish this?
// I have tried to use .map()
console.log(skiers.map(d => d.run)) //This gives me undefines
<script src="https://unpkg.com/d3#6.2.0/dist/d3.min.js"></script>
Use filter() to get all the objects where d[0] equals the bib you're trying to collect;
const data = d3.range(20).map(i => ({
bib: Math.floor(i / 5) + 1,
ratio: -1 + Math.random() * 5,
run: [1, 2, 3, 4, 5][i % 5],
name: ['GIRL1', 'GIRL2', 'GIRL3', 'GIRL4'][Math.floor(i / 5)]
}));
const skiers = d3.groups(data, d => d.bib)
const bibToFind = 3;
// Array with objects
const bibFound = skiers.filter(d => d[0] === bibToFind);
// Objects only
const bibObjects = bibFound[0][1];
console.log(bibFound, bibObjects);
<script src="https://unpkg.com/d3#6.2.0/dist/d3.min.js"></script>
Whe you use .map you're iterating only the first level of nested array
[
[ // "d" is this array in your map function
1,
[
{
"bib": 1,
"ratio": 0.6494187230703661,
"run": 1,
"name": "GIRL1"
},
{
"bib": 1,
"ratio": -0.2740398059491158,
"run": 2,
"name": "GIRL1"
},
{
"bib": 1,
"ratio": 3.3705616486650136,
"run": 3,
"name": "GIRL1"
},
{
"bib": 1,
"ratio": 0.9649181479003297,
"run": 4,
"name": "GIRL1"
},
{
"bib": 1,
"ratio": 1.7259011213023032,
"run": 5,
"name": "GIRL1"
}
]
]
]
const data = d3.range(20).map(i => ({
bib: Math.floor(i / 5) + 1,
ratio: -1 + Math.random() * 5,
run: [1, 2, 3, 4, 5][i % 5],
name: ['GIRL1', 'GIRL2', 'GIRL3', 'GIRL4'][Math.floor(i / 5)]
}));
// Now I want to use d3.groups
const skiers = d3.groups(data, d => d.bib)
//Logging this gives me a nested array.
console.log(skiers[0]) // first level
console.log(skiers[0][1]) // second level take second element
console.log(skiers[0][1][0]) // here you have the first object
// so you can map over the specific level of the nested array
console.log(skiers[0][1].map(d => d.run)) // no mo undefined
//or you can convert the nested array to a simple array if if that what are you looking
console.log("flatmap",skiers.flatMap(d=> d[1]))
// and the acces each run property
console.log("flatmap",skiers.flatMap(d=> d[1]).map(d => d.run))
<script src="https://unpkg.com/d3#6.2.0/dist/d3.min.js"></script>
The only difference between d3.group and d3.groups is that the former returns a Map, while the latter returns an array. Therefore, if you use d3.group, the only thing you'll need to get the array (say bib 1) is:
skiers.get(1);
Here is the demo:
const data = d3.range(20).map(i => ({
bib: Math.floor(i / 5) + 1,
ratio: -1 + Math.random() * 5,
run: [1, 2, 3, 4, 5][i % 5],
name: ['GIRL1', 'GIRL2', 'GIRL3', 'GIRL4'][Math.floor(i / 5)]
}));
// Now I want to use d3.groups
const skiers = d3.group(data, d => d.bib)
const innerArray = skiers.get(1);
console.log(innerArray);
<script src="https://unpkg.com/d3#6.2.0/dist/d3.min.js"></script>
Related
I have the following object that I am trying to sort so that the labels are "High, mid, low" all of the time. The order I get them are not always the same so I wanted to add another layer of ordering to ensure that I get "high, mid, low"
Before:
status:{
label:['mid', 'high', 'low'],
data:[4, 3, 1]
}
After:
status:{
label:['high', 'mid', 'low'],
data:[3, 4, 1]
}
The easiest way to sort those two arrays "linked" is by temporarily combining them into one array:
const status = {
label: ['mid', 'high', 'low'],
data: [4, 3, 1]
};
// Combine the two arrays into an array of pairs
const pairs = status.label.map((label, index) => [label, status.data[index]]);
console.log('pairsBefore', pairs); // [ ['mid', 4 ], ['high', 3 ], ['low', 1 ]]
// Used for sorting
const ORDER = ['high', 'mid', 'low'];
// Sort the pairs
pairs.sort((a, b) => {
const [labelA, dataA] = a;
const [labelB, dataB] = b;
// Gives 0 for 'high', 1 for 'mid' and 2 for 'low'
const indexA = ORDER.indexOf(labelA);
const indexB = ORDER.indexOf(labelB);
// Substract for A and B, see how Array.prototype.sort works
return indexA - indexB;
});
console.log('pairsAfter', pairs); // [ ['high', 3 ], ['mid', 4 ], ['low', 1 ]]
// Split it back into two arrays
const statusSorted = {
label: pairs.map(pair => pair[0]),
data: pairs.map(pair => pair[1]),
};
console.log('statusSorted', statusSorted);
//{
// label: ['high', 'mid', 'low'],
// data: [3, 4, 1],
//}
I have an array of arrays that I am trying to map. But I am having an issue with my mapping because mapping all indexes not just the one I want it to be mapping. I figured I can work my way around this by creating a new array of only of the data I am trying to map.
How can I push all index 2 into a new array? I have not been able to find an example of what I am attempting to do.
I am expecting an outcome of a new array that equals [231, 431, 481]
Here is an example of my code:
const array = [ ["name", 1, 231], [ "name", 2, 431], ["name", 3, 481] ]
console.log(array)
let percentage = array.map(function (num, i) {
return 100 * ((num - array[i - 1]) / (array[i - 1]));
});
You can do this
const array = [ ["name", 1, 231], [ "name", 2, 431], ["name", 3, 481] ]
const arrayNew = array.map(x => x[2])
// arrayNew === [231, 431, 481]
Check the inline comments
const array = [
["name", 1, 231],
["name", 2, 431],
["name", 3, 481],
];
// If you know, there will be 3 elements in array
console.log(array.map(([, , last]) => last));
console.log(array.map(({ 2: last }) => last));
// For any size of sub array, get the last element in map
console.log(array.map((arr) => arr[arr.length - 1]));
I am from C++ background.
I am trying to translate a C++ code to JavaScript.
in C++ we have vector < pair < int,int > > to store pairs.
in JS i have a situation. i want to store 2D coordinates. i actually want to push new coordinates to the array.
i did like
first I created a Object
const coordinate = {
x= 9,
y= 10
}
Then i tried to push that object into the array CoordinateStorage that i want this object to get stored
CoordinatesStorage.unshift({X : coordinate.x, Y : coordinates.y});
I know this code shown above is absolutely wrong to store an object into the array. I searched out for sources but i got nothing useful.
Please recommend some sources that i can refer for such translation related problems if possible.
Generally speaking, we should use the .push method for an array.
There are other methods available you can find them here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array but the .push method for your case is more suitable.
Basically, as a result, we want to have something like this:
[ {x: 2, y: 4 }, { x: 2, y: 4 } ] We have an array of objects.
Or we could also have an array of arrays:
[[1, 2], [3, 4], [4, 6]] Not sure if it okay for your case, but maybe as an option.
Also, we could create a class Vector and we might have something like
[ Vector { x: 1, y: 2 }, Vector { x: 3, y: 4 }, Vector { x: 4, y: 6 } ]
Let's take a look at the examples:
Using the plain object for the vector:
const coordinate1 = {
x: 2,
y: 4
};
const coordinate2 = {
x: 3,
y: 4
};
const coordinatesStorage = [];
coordinatesStorage.push(coordinate1);
coordinatesStorage.push(coordinate2);
If you will do console.log(coordinatesStorage) you will see [ { x: 2, y: 4 }, { x: 3, y: 4 } ]
Using the array to store a vector:
const coordinate1 = [1, 2];
const coordinate2 = [3, 4];
const coordinatesStorage = [];
coordinatesStorage.push(coordinate1);
coordinatesStorage.push(coordinate2);
The coordinatesStorage will be [ [ 1, 2 ], [ 3, 4 ] ]
Using the Vector class:
Maybe in your case, it would be more helpful to operate with a class Vector:
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
const coordinatesStorage = [];
coordinatesStorage.push(new Vector(1, 2));
coordinatesStorage.push(new Vector(3, 4));
coordinatesStorage.push(new Vector(4, 6));
And here in the console you will see [ Vector { x: 1, y: 2 }, Vector { x: 3, y: 4}, Vector { x: 4, y: 6 } ]
Take a look at the Vector implementations in JS:
https://gist.github.com/jjgrainger/808640fcb5764cf92c3cad960682c677
https://github.com/maxkueng/victor/blob/master/index.js
I hope this helps. Good luck!
First initialize the array
var CoordinatesStorage = [];
//create object
const coordinate = {
x: 9, // Note - the operator is colon : not = as in the question
y: 10
}
// push to array
CoordinatesStorage.push(coordinate);
Now your array will be like this [{x:9, y:10}] if you push again the array will be [{x:9, y:10}, {x:9, y:10}]
Tip: Arrays are denoted by square brackets eg: ['math', 'science', 'english']
Objects are denoted by key-value pairs wrapped in curly brackets
eg: var student = {
name: "John", // string value
age: 6, // integer value
sex: "M",
phone: [123456789 , 564654654] // value is of array of 2 items
}
I'm trying to transpose a matrix using recursion. Now, I know that under normal circumstances this isn't a good idea and a nested loop/nested map, or a similar approach is superior, but I need to learn this for educational purposes.
To show that I did my homework, here is the nested loop approach:
const arrMatrix = [
[3, 6, 7, 34],
[6, 3, 5, 2],
[2, 6, 8, 3]
];
const transposedMatrix = []
for (let i = 0; i < arrMatrix[0].length; i++) {
const tempCol = [];
for (let j = 0; j < arrMatrix.length; j++) {
tempCol.push(arrMatrix[j][i]);
}
transposedMatrix.push(tempCol);
}
console.log(transposedMatrix);
Here's another approach using nested maps:
const arrMatrix = [
[3, 6, 7, 34],
[6, 3, 5, 2],
[2, 6, 8, 3]
];
const transposedMatrix = arrMatrix[0].map((_, i) =>
arrMatrix.map((_, j) => arrMatrix[j][i])
);
console.log(transposedMatrix);
Here are some resources that I went through but didn't manage to come up with a solution using them.
If possible, in addition to the algorithm/code, please give me some explanation and resources to learn more about it.
const map = ([head, ...tail], mapper) => tail.length ? [mapper(head), ...map(tail, mapper)] : [mapper(head)];
const transpose = matrix =>
matrix[0].length
? [map(matrix, row => row.shift()), ...transpose(matrix)]
: [];
How it works:
From a given matrix, we always take out the first column (matrix.map(row => row.shift()), then we continue recursively:
[[1, 1, 1], -> [[1, 1], -> [[1], -> [[],
[2, 2, 2], [2, 2], [2], [],
[3, 3, 3]] [3, 3]] [3]] []]
Then the base case gets reached, the matrix is empty (matrix[0].length is 0 = falsy) and an empty array gets returned. Now at every step, the column taken out gets added to that array, and thus it's a row now:
[[1, 2, 3], <- [[1, 2, 3], <- [[1, 2, 3]] <- []
[1, 2, 3], [1, 2, 3]]
[1, 2, 3]]
Note: This destroys the original array.
const transpose = matrix => {
const row = (x) => x >= matrix[0].length ? [] : [col(x, 0), ...row(x + 1)];
const col = (x, y) => y >= matrix.length ? [] : [matrix[y][x], ...col(x, y + 1)];
return row(0);
};
That version does not mutate the original array. You can take that even a step further, than it is purely functional, but thats a bit overkill:
const transpose = matrix => (
(row, col) => row(row)(col)(0)
)(
row => col => (x) => x >= matrix[0].length ? [] : [col(col)(x, 0), ...row(row)(col)(x + 1)],
col => (x, y) => y >= matrix.length ? [] : [matrix[y][x], ...col(col)(x, y + 1)]
);
This is very similar to hitmands' solution, and would likely be less performant, but I think it's slightly cleaner to avoid working with column indices:
const head = xs => xs[0]
const tail = xs => xs.slice(1)
const transpose = (m) => head(m).length
? [m.map(head), ...transpose(m.map(tail))]
: []
const matrix = [
[3, 6, 7, 34],
[6, 3, 5, 2],
[2, 6, 8, 3]
]
console .log (
transpose (matrix)
)
This version transposes the first column into a row (via .map(head)) and then recurs on the remaining matrix (via .map(tail)), bottoming out when the first row is empty.
You can inline those helper functions if you choose, so that it looks like this:
const transpose = (m) => m[0].length
? [m.map(xs => xs[0]), ...transpose(m.map(xs => xs.slice(1)))]
: []
..but I wouldn't recommend it. The first version seems more readable, and head and tail are easily reusable.
Update
user633183 suggests an alternative escape condition. It's a good question whether or not it's a better result for ill-formed data, but it's certainly a useful possible variant:
const head = xs => xs[0]
const tail = xs => xs.slice(1)
const empty = xs => xs.length == 0
const transpose = (m) => m.some(empty)
? []
: [m.map(head), ...transpose(m.map(tail))]
(This could also be done with m.every(nonempty) by reversing the consequent and alternative in the conditional expression, but I think it would be slightly harder to read.)
I would write it like this,
assuming that all the rows inside the matrix have the same length:
check if there still are rows to process
create a row from each colum at the given index
increment column index by 1
call transpose with the new index
const transpose = (m, ci = 0) => ci >= m[0].length
? []
: [m.map(r => r[ci]), ...transpose(m, ci + 1)]
;
const matrix = [
[3, 6, 7, 34],
[6, 3, 5, 2],
[2, 6, 8, 3]
];
console.log(
transpose(matrix),
);
I had an idea to write transpose using the Maybe monad. I'll start using functional operations and then refactor to clean up the code -
Dependencies -
const { Just, Nothing } =
require("data.maybe")
const safeHead = (a = []) =>
a.length
? Just(a[0])
: Nothing()
const tail = (a = []) =>
a.slice(1)
Without refactors -
const column = (matrix = []) =>
matrix.reduce
( (r, x) =>
r.chain(a => safeHead(x).map(x => [ ...a, x ]))
, Just([])
)
const transpose = (matrix = []) =>
column(matrix)
.map(col =>
[ col, ...transpose(matrix.map(tail)) ]
)
.getOrElse([])
Refactor column using generic append and lift2 -
const append = (a = [], x) =>
[ ...a, x ]
const lift2 = f =>
(mx, my) =>
mx.chain(x => my.map(y => f(x, y)))
const column = (matrix = []) =>
matrix.reduce
( (r, x) =>
lift2(append)(r, safeHead(x))
, Just([])
)
const transpose = (matrix = []) =>
column(matrix)
.map(col =>
[ col, ...transpose(matrix.map(tail)) ]
)
.getOrElse([])
Refactor column again using generic transducer mapReduce -
const mapReduce = (map, reduce) =>
(r, x) => reduce(r, map(x))
const column = (matrix = []) =>
matrix.reduce
( mapReduce(safeHead, lift2(append))
, Just([])
)
const transpose = (matrix = []) =>
column(matrix)
.map(col =>
[ col, ...transpose(matrix.map(tail)) ]
)
.getOrElse([])
transpose stays the same in each refactoring step. It produces the following outputs -
transpose
( [ [ 1, 2, 3, 4 ]
, [ 5, 6, 7, 8 ]
, [ 9, 10, 11, 12 ]
]
)
// [ [ 1, 5, 9 ]
// , [ 2, 6, 10 ]
// , [ 3, 7, 11 ]
// , [ 4, 8, 12 ]
// ]
transpose
( [ [ 1, 2, 3, 4 ]
, [ 5 ]
, [ 9, 10, 11, 12 ]
]
)
// [ [ 1, 5, 9 ] ]
I have been trying to use a node.js script to turn some data into music. The script is only returning a single note for some reason:
The orignal script on github: https://github.com/wbkd/from-data-to-sound had res.concat(scribble.scale('c', but the threw an error Invalid Scale name.
const scribble = require('scribbletune');
// example data
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
const min = Math.min(...data);
const octaves = [...Array(5)].map((d, i) => i + 1); // [1, 2, 3, 4, 5]
// creates array of notes like 'c1', 'd1', 'e1', 'gb1', 'ab1', 'bb1', 'c2', ...
const notes = octaves.reduce((res, octave) =>
res.concat(scribble.scale('c1 major', 'whole tone', octave, false))
, []);
const midiData = scribble.clip({
notes: data.map(value => notes[value - min]),
pattern: 'x',
noteLength: '1/16',
});
// write the MIDI file 🎵🎵🎵
scribble.midi(midiData, 'data-sonification.mid');
From scribbletune doc:
each x implies a note on event
scribbletune docs/core/clip
Since you're passing only 1 'x' as a pattern in scribble.clip, it only plays 1 note. In order for all the notes to be played, you can try something like this:
const midiData = scribble.clip({
notes: data.map(value => notes[value - min]),
- pattern: 'x', // only play 1 note
+ pattern: 'x'.repeat(data.length), // repeat this pattern for each note in data
noteLength: '1/16',
});