finding d3 extent of multiple values in object array - javascript

My data array:
var data = [{glazed: 3.50, jelly: 4.50, powdered: 1.00, sprinkles: 3.50, age: 21, responses: 2,name:"test"},
{glazed: 2.83, jelly: 3.50, powdered: 1.83, sprinkles: 4.50, age: 22, responses: 6,name:"test"},
{glazed: 3.25, jelly: 4.75, powdered: 2.25, sprinkles: 3.50, age: 23, responses: 4,name:"test"},
{glazed: 1.50, jelly: 4.00, powdered: 2.50, sprinkles: 4.00, age: 25, responses: 2,name:"test"}];
If I wanted to find extent of either glazed or jelly or powdered or sprinkles to use for scaling, I would use a code as below..
var x = d3.scale.linear()
.domain(d3.extent(data, function (d) {
return d.glazed;//or jelly etc..
}))
.range([0, width]);
What would I need to do to get the extent of all values in glazed, jelly, powdered and sprinkles rather all values which are not age, responses and name.
This is because json file gets created dynamically and so I will have no idea of the key values except age, responses and name.
So, my requirement is that it should give me min as 1.5 (from glazed) and max as 4.75 (from jelly)
Any help is sincerely appreciated..
Thanks

var x = d3.scale.linear()
.domain(d3.extent(data.map(function (item) {
return (item.glazed);
})))
.range([0, width]);
map() returns [3.5, 2.83, 3.25, 1.5]
extent() returns [1.5, 3.5]
In case you need absolute minimum and maximum for all properties of the data, you should concatenate arrays:
var x = d3.scale.linear()
.domain(d3.extent(
[].concat(data.map(function (item) {
return (item.glazed);
}), data.map(function (item) {
return (item.jelly);
}), data.map(function (item) {
return (item.powdered);
}), data.map(function (item) {
return (item.sprinkles);
}))))
.range([0, width]);
Finally, if you have a list of valuable properties, you should replace [].concat(...) expression by anonymous function and call it immediately like this: function(array, names){...}(data, temp). You should know, in JavaScript array.property and array["property"] -- are the same calls.
var temp = ["glazed", "jelly", "powdered", "sprinkles"];
var width = 1000;
var data = [{glazed: 3.50, jelly: 4.50, powdered: 1.00, sprinkles: 3.50, age: 21, responses: 2,name:"test"},
{glazed: 2.83, jelly: 3.50, powdered: 1.83, sprinkles: 4.50, age: 22, responses: 6,name:"test"},
{glazed: 3.25, jelly: 4.75, powdered: 2.25, sprinkles: 3.50, age: 23, responses: 4,name:"test"},
{glazed: 1.50, jelly: 4.00, powdered: 2.50, sprinkles: 4.00, age: 25, responses: 2,name:"test"}];
var x = d3.scale.linear()
.domain(d3.extent(
function(array, names){
var res = [];
array.forEach(function(item){
names.forEach(function(name){
res = res.concat(item[name]);
});
});
return(res);
}(data, temp)
))
.range([0, width]);
DEMO: http://jsfiddle.net/v5qzuuhj/

Assuming that you want to be able to handle any key values except age, responses and name, and that others in addition to glazed, jellied, powdered & sprinkled might appear, this approach should work to calculate max and min values:
var keys_to_ignore = ["age", "responses", "name"]
data.forEach( function (row)
{
//Use d3.entries to create key\values
var data_entries = d3.entries(row)
// Add the 'current maximum' and 'current_min' based off the previous rows
// If this is the first row, the extent will be undefined
if (typeof extent !== "undefined") {
data_entries.push({"key":"current_max", "value":extent[1]})
data_entries.push({"key":"current_min", "value":extent[0]})
}
// now calculate the extent (max / min)
extent = d3.extent(data_entries, function (d) {
// Ignore specified keys
if (keys_to_ignore.indexOf(d.key) == -1) {
return d.value;
}
});
});
console.log(extent)
Using d3.entries will create objects with a key and value attribute, making the data easier to manage.
Working fiddle here: http://jsfiddle.net/henbox/aa4d0z81/
I feel like there's a more elegant way to do this, but I haven't managed to figure it out

Related

.sort not sorting from lowest to highest

while (true) {
let texts = await page.evaluate(() => {
let data = [];
let elements = document.getElementsByClassName("my-2 card__price tw-truncate");
for (var element of elements) {
var floor = Number(element.textContent.split("SOL")[0])
data.push(floor)
}
return data.sort()
});
floorpricearray = texts
if (texts !== undefined) {
console.log(floorpricearray)
}
}
The output
[
10, 11, 5.3, 5.3,
5.38, 5.4, 5.4, 5.4321,
5.4321, 5.5, 5.6942, 5.8,
5.95, 6.2, 7, 7.95,
7.99, 8.05, 8.3, 9
]
You need to give the native .sort method a comparator function like so, otherwise the values will converted to strings and then sorted by the characters ASCII value.
data.sort((a, b) => {
return a - b;
});
You can try this
var a = [10, 11, 5.3, 5.3,
5.38, 5.4, 5.4, 5.4321,
5.4321, 5.5, 5.6942, 5.8,
5.95, 6.2, 7, 7.95,
7.99, 8.05, 8.3, 9
];
a.sort(function(a, b) {
return a - b;
});
alert(a);
It is sorting the array considering the array elements as string. To get it sorted from lowest to highest it needs to be a number.

Function will not return an integer when multiplying two variables with numerical values (JS)

I am having issues getting my function to return product of two variables with numerical values.
The directions are: You want to write a function that takes in the name of a stock as a string and returns for you the total amount of money you have invested in that stock. e.g. if your function is given ‘AMZN’ it should return 4000.
This is my function, I keep returning NaN when I should be returning 4000.
let investments = [
{stock: "AMZN", price: 400, numOfShares: 10},
{stock: "AAPL", price: 300, numOfShares: 5},
{stock: "BIDU", price: 250, numOfShares: 4}
];
calculateInvestedAmt = (stock) => {
const totalAmt = investments.price * investments.numOfShares;
return totalAmt;
};
calculateInvestedAmt("AMZN");
investments.price and investments.numOfShares is undefined. That's why it is producing NaN, because you are multiplying two undefined values. You should first iterate over investments to find the stock you want.
You have to use find method
const getStockPrice = (name) => {
const stock = investments.find(s => s.stock === name);
if (!stock) return undefined; // can't find stock
returb stock.price * stock.numOfShares;
}
let investments = [
{stock: "AMZN", price: 400, numOfShares: 10},
{stock: "AMZN", price: 400, numOfShares: 10},
{stock: "AAPL", price: 300, numOfShares: 5},
{stock: "BIDU", price: 250, numOfShares: 4}
];
calculateInvestedAmt = (stockName) => {
var totalAmt;
var investedItem = investments.filter(invst => invst.stock === stockName);
investedItem.map(item => {
totalAmt = item.price * item.numOfShares;
});
return totalAmt;
};
console.log(calculateInvestedAmt("AMZN"));

Easy way to sort 2 arrays in Javascript?

Sorry if this is a stupid question, but I'm learning JavaScript and was wondering if there was an easy way to sort 2 lists like the ones here:
var names=["item", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9", "item10"];
var points=[12, 12345, 5765, 123, 3, 567765, 99, 87654, 881, 101];
How would I display the items from 'names' according to the corresponding value in 'points'? For example, for the above item6 would displayed first and item5 would be displayed last.
I don't know if it's easy enough, but you could make an array of objects, sort it by the value prop and just map to get only the name props.
let names = ["item", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9", "item10"],
points = [12, 12345, 5765, 123, 3, 567765, 99, 87654, 881, 101],
res = names.map((v, i) => ({ name: v, val: points[i] }))
.sort((a, b) => b.val - a.val)
.map(v => v.name);
console.log(res);
Here's a somewhat lengthy solution (with a much more concise version below). The basic idea is to:
Sort the points array in descending order
Loop through the sorted array, and find each value's position in the original points array
Grab the corresponding item from the names array
Push the value into a new array
var names=["item", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9", "item10"];
var points=[12, 12345, 5765, 123, 3, 567765, 99, 87654, 881, 101];
const sortedPoints = points.slice().sort(function(a, b) {
return b - a;
});
const sortedNames = [];
sortedPoints.forEach(function(val) {
const position = points.indexOf(val);
sortedNames.push(names[position]);
})
console.log(sortedNames)
For a more concise solution, following the same process above but taking advantage of some shortcuts:
const names = ["item", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9", "item10"];
const points = [12, 12345, 5765, 123, 3, 567765, 99, 87654, 881, 101];
const sortedNames = points.slice().sort((a, b) => b - a).map(val => names[points.indexOf(val)]);
console.log(sortedNames)
Javascript doesn't have a zip function natively. But that is most of what you want to do here. A little utility library like underscore is pretty handy. You can view the annotated source if you just want to replicate a zip function yourself.
var zippedAndSorted = _.zip(names, points)
.sort(function(a, b) {
return b - a;
});
Then you can iterate over each pair:
zippedAndSorted.forEach(function(namePoint) {
console.log('Name: ' + namePoint[0] + ' Points: ' + namePoint[1]);
});

Inserting dynamic value into array of objects, javascript

I have an array of objects that look something like this;
[
{Number: 5002000, Origin: 123456, Count: 128},
{Number: 5002300, Origin: 900231, Count: 52},
{Number: 5002022, Origin: 534323, Count: 269}
]
Now I'm trying to multiply the "Count" value with a value from a designated price pool.
Which looks something like this;
[
{Prefix: 50023, Price: 20},
{Prefix: 50020, Price: 10},
{Prefix: 5002, Price: 60},
]
Currently there's an horrendous for loop with if-statements.
for (var key in sData) {
if (sData[key].Origin.startsWith('50023')) {
sData[key].sum = (sData[key].Count * 20);
}
else if (sData[key].Origin.startsWith('50020')) {
sData[key].sum = (sData[key].Count * 10);
}
// continues...
}
startsWith is a function that simply checks if the value starts with the (value).
Is there already a function in JS to map two arrays of objects? (I'm also having issues with the logic since the "Prefix" value basically has to go from the top down as not to land on the default "5002"-prefix.)
You should use nested loops in this situation. Also switch to Array.forEach method.
sData.forEach(function(item_, key) {
prices.forEach(function(item) {
if (sData[key].Origin.startsWith(item.Prefix)) {
sData[key].sum = (sData[key].Count * item.Price);
}
});
})
Assuming that second array can be transformed into the hash:
var tiers = {
50023: {Prefix: 50023, Price: 20},
50020: {Prefix: 50020, Price: 10},
5002: {Prefix: 5002, Price: 60},
}
You may make it look like this:
for (var key in sData) {
var key = String(sData[key])
var keyIndex = key.slice(5)
if (tiers.hasOwnProperty(keyIndex)) {
var price = tiers[keyIndex].Price
sData[key].sum = (sData[key].Count * price)
} else {
// Fallback solution
}
}
Going further you may even think of some recursive solution for fallback.

Invalid value for <circle> attribute r="NaN" in D3 scatterplot code

I am getting a NaN error on the r value in a d3 scatterplot.
Console error: Error: Invalid value for attribute r="NaN"
From this section of the code:
g.selectAll(".response")
.attr("r", function(d){
return responseScale(d.responses);
})
.attr("cx", function(d){
return x(d.age);
})
.attr("cy", function(d){
return y(d.value);
})
Here is how the scale is set up:
var responseScale = d3.scale.linear()
.domain(d3.extent(data, function(d){
return d.responses;
}))
.range(2, 15);
Here is a sample of the data:
var data = [
{glazed: 3.14, jelly: 4.43, powdered: 2.43, sprinkles: 3.86, age: 18, responses: 7},
{glazed: 3.00, jelly: 3.67, powdered: 2.67, sprinkles: 4.00, age: 19, responses: 3},
{glazed: 2.00, jelly: 4.00, powdered: 2.33, sprinkles: 4.33, age: 20, responses: 3},
I have tried putting a plus sign in front of d.responses and using parseFloat().
The code is an example used in this course, Learning to Visualize Data with D3.js (chapter on Creating a Scatterplot)
Any suggestions would be greatly appreciated!
In your code:
var responseScale = d3.scale.linear()
.domain(d3.extent(data, function(d){
return d.responses;
}))
.range(2, 15);
The parameter for the range() function should be an array of values, like this: .range([2,15]);
corrected scale:
var responseScale = d3.scale.linear()
.domain(d3.extent(data, function(d){
return d.responses;
}))
.range([2, 15])
;
More info on scales can be found here. If you are still in trouble, let me know!

Categories