I'm not entirely sure if a for loop is the right thought the process to handle what my issue is. I am creating a line graph to display the % growth between two years for 10 years. I am able to get the data showing on the graph as expect but I unable to think of another way to make it more efficient. My current code works but I would like to handle the calculations for the entire array of data together rather than handling it separately.
my current logic:
const numerator is the index of the year I would to display a percentage growth I am subtracting it from const denominator
I am removing the commas in the first two lines since the value are higher numbers such as 1,323,349
Finally, the third line perChange is the calculation for the actual percentage. I am using 2 indexes at a time and using the larger number of the two to subtract the smaller one for example. index 1 - index 0; index 3 - index 2, index 5 - index 4 etc...
This is my current code:
const numeratorOne = Number(caArray[1].DataValue.replace(/,/g, ''))
const denominatorOne = Number(caArray[0].DataValue.replace(/,/g, ''))
const perChangeOne = 100 * Math.abs((denominatorOne - numeratorOne) / ((denominatorOne + numeratorOne ) /2 ) )
const numeratorTwo = Number(caArray[3].DataValue.replace(/,/g, ''))
const denominatorTwo = Number(caArray[2].DataValue.replace(/,/g, ''))
const perChangeTwo = 100 * Math.abs((denominatorTwo - numeratorTwo) / ((denominatorTwo + numeratorTwo) / 2))
const numeratorThree = Number(caArray[5].DataValue.replace(/,/g, ''))
const denominatorThree = Number(caArray[4].DataValue.replace(/,/g, ''))
const perChangeThree = 100 * Math.abs((denominatorThree - numeratorThree) / ((denominatorThree + numeratorThree) / 2))
const numeratorFour = Number(caArray[7].DataValue.replace(/,/g, ''))
const denominatorFour = Number(caArray[8].DataValue.replace(/,/g, ''))
const perChangeFour = 100 * Math.abs((denominatorFour - numeratorFour) / ((denominatorFour + numeratorFour) / 2))
const numeratorFive = Number(caArray[9].DataValue.replace(/,/g, ''))
const denominatorFive = Number(caArray[8].DataValue.replace(/,/g, ''))
const perChangeFive = 100 * Math.abs((denominatorFive - numeratorFive) / ((denominatorFive + numeratorFive) / 2))
const numeratorSix = Number(caArray[11].DataValue.replace(/,/g, ''))
const denominatorSix = Number(caArray[10].DataValue.replace(/,/g, ''))
const perChangeSix = 100 * Math.abs((denominatorSix - numeratorSix) / ((denominatorSix + numeratorSix) / 2))
const numeratorSeven = Number(caArray[13].DataValue.replace(/,/g, ''))
const denominatorSeven = Number(caArray[12].DataValue.replace(/,/g, ''))
const perChangeSeven = 100 * Math.abs((denominatorSeven - numeratorSeven) / ((denominatorSeven + numeratorSeven) / 2))
const numeratorEight = Number(caArray[15].DataValue.replace(/,/g, ''))
const denominatorEight = Number(caArray[16].DataValue.replace(/,/g, ''))
const perChangeEight = 100 * Math.abs((denominatorEight - numeratorEight) / ((denominatorEight + numeratorEight) / 2))
const numeratorNine = Number(caArray[17].DataValue.replace(/,/g, ''))
const denominatorNine = Number(caArray[16].DataValue.replace(/,/g, ''))
const perChangeNine = 100 * Math.abs((denominatorNine - numeratorNine) / ((denominatorNine + numeratorNine) / 2))
Highcharts.chart('container', {
chart: {
type: 'line'
},
title: {
text: 'Gross State Product in California'
},
xAxis: {
categories: ['1998', '2000', '2002', '2004', '2006', '2008', '2010', '2012', '2014', '2016', '2018'],
title: {
text: 'Year'
},
},
yAxis: {
categories: ['2', '4', '6', '8', '10'],
title: {
text: 'Percentage Change (%)'
}
},
plotOptions: {
line: {
dataLabels: {
enabled: false
},
enableMouseTracking: false
}
},
series: [{
name: 'California',
data: [perChangeOne, perChangeTwo, perChangeThree, perChangeFour, perChangeFive, perChangeSix, perChangeSeven, perChangeEight, perChangeNine, 8.3, 3.9],
color: '#002F65'
}, {
name: 'US',
color: '#0B7070',
data: [3.9, 4.2, 5.7, 8.5, 1.9, 5.2, 7.0, 6.6, 4.2, 5.3, 10]
}]
});
}
The desired outcome is a way to get the same results without having to create 10 different variables as I will need to continue to add more data to this graph in the future.
I have attempted this:
for (let i = 0; i < caArray.length; i++) {
let removeComma = Number(caArray.DataValue.replace(/,/g, ''))
}
for (let i = 0; i < removeComma.length; i += 2) {
// math logic here which I do not know how to work out
}
Iterate over the caArray first, pushing to either a numerators or denominators array, depending on the index of the item you're iterating over. Then map the numerators array to get the associated denominator and calculate the change, and you'll have the array you can put into the series.data property:
const numerators = [];
const denominators = [];
caArray.forEach(({ DataValue }, i) => {
const arrToPushTo = i % 2 === 0 ? denominators : numerators;
arrToPushTo.push(Number(DataValue.replace(/,/g, '')));
});
const changes = numerators.map((numerator, i) => {
const denominator = denominators[i];
return 100 * Math.abs((denominator - numerator) / ((denominator + numerator) / 2));
});
series: [{
name: 'California',
data: [...changes, 8.3, 3.9],
const caArray = [
{ DataValue: '10' },
{ DataValue: '20' },
{ DataValue: '30' },
{ DataValue: '40' },
];
const numerators = [];
const denominators = [];
caArray.forEach(({ DataValue }, i) => {
const arrToPushTo = i % 2 === 0 ? denominators : numerators;
arrToPushTo.push(Number(DataValue.replace(/,/g, '')));
});
const changes = numerators.map((numerator, i) => {
const denominator = denominators[i];
return 100 * Math.abs((denominator - numerator) / ((denominator + numerator) / 2));
});
console.log(changes);
If i understood correctly you just need one loop. You need to iterate by two each and then you have all the data in your current iteration. Something like this.
const changes = []
for(let i = 0; i < caArray.length; i+=2) {
if (!caArray[i + 1]) {
break
}
const denominator = Number(caArray[i].DataValue.replace(/,/g, ''));
const numerator = Number(caArray[i + 1].DataValue.replace(/,/g, ''));
const perChange = 100 * Math.abs((denominator - numerator) / ((denominator + numerator ) /2 ) )
changes.push(perChange);
}
This should work.
Related
I am using Chart.js to draw graphs in typescript.
I want to get a dynamic weight and bring a minimum and a maximum. And with maxTicksLimit as 5, I want to keep 5 Ticks no matter what data comes in.
The decimal point of body weight is taken to the first decimal point.
ex) 50.3
I want to show the difference between minimum and maximum as much as possible.
please help me!!!
ex1) maximum weight: 74.5, minimum weight: 71
result
Y Axis maximum weight: 76 , Y Axis minimum weight: 71
ex1 result image
enter image description here
ex2) maximum weight: 76.9, minimum weight: 62
result
Y Axis maximum weight: 76 , Y Axis minimum weight: 61
ex2 result image
enter image description here
The beforeBuildTicks callback also works to create a dynamic tick step size. Here is the code:
{
maintainAspectRatio: false,
tooltips: {
mode: 'label',
position: 'nearest',
},
scales: {
xAxes:[
{...}
],
yAxes: [
{
position: 'left',
id: 'y-axis-0',
scaleLabel: {
display: true,
labelString: 'label string goes here',
fontSize: 16,
},
ticks: {
beginAtZero: true,
stepSize: .25,
},
beforeBuildTicks: function(axis) {
if (axis.max >= 17) {
axis.options.ticks.stepSize = 1;
} else if (axis.max >= 10) {
axis.options.ticks.stepSize = .5;
}
}
}
]
}
}
I solved this problem in the following way.
There was a callback function of afterBuildTicks in chart.js
※ chart.js documents link.
It becomes possible to customize the ticks.
//... middle code skip
const getChartMaxAndMin = (
maxValue,
minValue
): { max: number; min: number } => {
// Convert all decimal places to integers
let max = Math.ceil(maxValue);
let min = Math.floor(minValue);
// a multiple of 5
const MULTIPLES = 5;
const DIVIDED_REMAINING_VALUE_LIMIT = 3;
// Maximum to Minimum difference
const diff = max - min;
const diffDividedRemainingValue = diff % MULTIPLES;
const remainingValue =
MULTIPLES *
(diffDividedRemainingValue > DIVIDED_REMAINING_VALUE_LIMIT
? Math.floor(diff / MULTIPLES) + 2
: Math.floor(diff / MULTIPLES) + 1) -
diff;
if (remainingValue % 2 !== 0) {
max = max + Math.floor(remainingValue / 2) * 2;
min = min - Math.floor(remainingValue % 2);
return { max, min };
}
max = max + remainingValue / 2;
min = min - remainingValue / 2;
return { max, min };
};
const customizedAxesTicks = (axis) => {
const EQUAL_PARTS = 5; // set 5 parts
const max = axis.max;
const min = axis.min;
const steps = (max - min) / EQUAL_PARTS;
const ticks = [];
for (let i = min; i <= max; i += steps) {
ticks.push(i);
}
axis.ticks = ticks;
return;
};
const {max, min} = getChartMaxAndMin(68,57);
const chartOptions = {
//... code skip
scales: {
yAxes: [
{
//... code skip
ticks: {
max: max,
min: min
},
afterBuildTicks: customizedAxesTicks,
//... code skip
};
I would appreciate it if you could let me know if there is a better way😀
So we have that :
{TierLowerBound : 2,
TierUpperBound : 5,
TierName : 'Tier 1',
TierDiscount : 0.15},
{TierLowerBound : 6,
TierUpperBound : 10,
TierName : 'Tier 2',
TierDiscount : 0.40}
And given number for example let i=5;
What can be the best way to find out to which of the tiers our number i belongs to?
I created simple function isBetween, some objects etc. but I am asking, because there are maybe some great ways in JS to deal with that kind of situation?
I'm using foreach loop to check every tier, so it's not as efficient as switch, but switch relays on fixed values, so it would be hard to evalute for instance this situation :
lowerBound = 5;
upperBound = 10;
number = 7;
Looking forward for an answer:)
ANSWER
getDiscount : function(number){
let foundTier = tier.tiersArray.filter(function(object){
let isBetween = number >= object.TierLowerBound &&
( number <= object.TierUpperBound || object.TierUpperBound ==
'noLimit')
return isBetween;
}).values().next().value;
return foundTier ? foundTier.TierDiscount : 0;
}
You can use filter:
const yourArrayOfObjects = [
{
TierLowerBound: 2,
TierUpperBound: 5,
TierName: 'Tier 1',
TierDiscount: 0.15
},
{
TierLowerBound : 6,
TierUpperBound: 10,
TierName: 'Tier 2',
TierDiscount: 0.40
}
];
const returnObjectsWhereNumberFitsInBounds = n => {
return yourArrayOfObjects.filter(element => element.TierLowerBound < n && element.TierUpperBound > n)
}
const magicNumberToTest = 4;
console.log(returnObjectsWhereNumberFitsInBounds(magicNumberToTest));
Assuming that you have an array of objects with tier information, here is an approach:
const tiers = [{
TierLowerBound: 2,
TierUpperBound: 5,
TierName: 'Tier 1',
TierDiscount: 0.15
},
{
TierLowerBound: 6,
TierUpperBound: 10,
TierName: 'Tier 2',
TierDiscount: 0.40
}
]
function isBetween(numberGiven) {
let categoryFound = {}
tiers.forEach(function(arrayItem) {
if (numberGiven >= arrayItem.TierLowerBound && numberGiven <= arrayItem.TierUpperBound) {
console.log("The number requested, " + numberGiven + " is in " + arrayItem.TierName + " category!");
categoryFound = arrayItem;
}
});
return categoryFound;
}
isBetween(3);
isBetween(6);
isBetween(9);
// if you want to use the category in another place, call it like this:
const objInRange = isBetween(7);
console.log("objInRange: " + JSON.stringify(objInRange, null, 2));
So for example I have an array with 3 waypoints:
[ [ 526, 1573, 24 ], [ 2224, 809, -1546 ], [ 6869, 96, -3074 ] ]
I also know I want to rest for lets say n times between between arriving at the first and last waypoint. So in the end I want an array of n points.
How do I go about finding those n resting-points in JS?
Thanks in advance!
EDIT: Note this is not a single object! Imagine each axis being one person. They have to stop the same amount of time and at the same time but they do not have to be at the same place obviously.
You want to use linear interpolation.
A quick example:
const POINTS = [ [ 526, 1573, 24 ], [ 2224, 809, -1546 ], [ 6869, 96, -3074 ] ];
const N = 10;
function getDistance(point1, point2) {
// speed in 3d space is mutated according only to the X distance,
// to keep speed constant in X dimension
return Math.abs(point1[0] - point2[0]);
}
function go(points, n) {
const pointDistances = points.slice(1).map((point, index) => getDistance(points[index], point));
const fullDistance = pointDistances.reduce((sum, distance) => sum + distance, 0);
const distancePerSection = fullDistance / n;
return points.slice(1)
.reduce((last, point, index) => {
const thisDistance = pointDistances[index];
const numRestPoints = Math.max(0, Math.floor(thisDistance / distancePerSection) - 1);
if (!numRestPoints) {
return last.concat([point]);
}
const thisYVector = point[1] - points[index][1];
const thisZVector = point[2] - points[index][2];
return last.concat(new Array(numRestPoints).fill(0)
.reduce((section, item, restIndex) => {
return section.concat([[
points[index][0] + (restIndex + 1) * distancePerSection,
points[index][1] + (restIndex + 1) * thisYVector * distancePerSection / thisDistance,
points[index][2] + (restIndex + 1) * thisZVector * distancePerSection / thisDistance
]]);
}, [])
.concat([point])
);
}, points.slice(0, 1));
}
function test() {
const result = go(POINTS, N);
if (result.length !== N) {
throw new Error('Must be N length');
}
if (!result[0].every((value, index) => value === POINTS[0][index])) {
throw new Error('Doesn\'t start at the first point');
}
if (!result[N - 1].every((value, index) => value === POINTS[POINTS.length - 1][index])) {
throw new Error('Doesn\'t end at the last point');
}
if (!POINTS.slice(1, N - 1).every(point =>
result.some(resultPoint => resultPoint.every((value, index) => value === point[index]))
)) {
throw new Error('Doesn\'t go through every provided point');
}
console.log(result.slice(1).map((point, index) => getDistance(point, result[index])));
console.log('The result passed the tests!');
console.log(JSON.stringify(result, null, 2));
}
test();
I'm basically going through the list of points, and determining if there should exist any rest points between them, inserting them if so.
Please comment if you want further clarification!
I also solved this problem now with linear interpolation:
My solution:
var waypoints = [[526,1573,24],[2224,809,-1546],[6869,96,-3074]];
var pauses = 20;
generateWaypopints();
function generateWaypopints(){
var newWaypoints = [];
var progressAtMainPoints = 1 / (waypoints.length - 1)
var pausesBetweenWaypoints = pauses * progressAtMainPoints;
var progressAtPauses = 1 / pausesBetweenWaypoints;
newWaypoints.push(waypoints[0]);
var sector = 0;
var pausesInSector = 0;
for(var i = 0; i < pauses; i++){
var progress = progressAtPauses * (pausesInSector + 1)
var x = Math.round(waypoints[sector][0] + (waypoints[sector + 1][0] - waypoints[sector][0]) * progress);
var y = Math.round(waypoints[sector][1] + (waypoints[sector + 1][1] - waypoints[sector][1]) * progress);
var z = Math.round(waypoints[sector][2] + (waypoints[sector + 1][2] - waypoints[sector][2]) * progress);
if(progress >= 1){
sector++;
pausesInSector = 0;
}else
pausesInSector++;
newWaypoints.push([x,y,z]);
}
console.log(newWaypoints);
return newWaypoints;
}
I have a number (let's say 525). I would like to take this number and split it into an array of chunks with a max of 100 each value. If I took 525 and split it into an array, it would look like:
[
100,
100,
100,
100,
100,
25
]
Here's what I've tried so far:
var number = 525;
var array = [];
while (number > 0) {
number = number - 100;
array.push(Math.min(number, 100));
}
That doesn't get me far. It just returns [ 100, 100, 100, 100, 25, -75 ]. I know that using while isn't the best way to go, but that is what I could think of off the top of my head. Does anyone have any other way that improve my code and be more efficient?
You can figure the number of times that number is divisible by 100 then programmatically create an array of that length:
var number = 525;
var array = new Array(Math.floor(number / 100)).fill(100).concat(number % 100))
// [ 100, 100, 100, 100, 100, 25 ]
You can extend this to chunk by any number:
function chunkBy(number, n) {
var chunks = Array(Math.floor(number / n)).fill(n);
var remainder = number % n;
if (remainder > 0) {
chunks.append(remainder);
}
return chunks;
}
Alternatively, simply push the element before performing your subtraction:
var number = 525;
var array = [];
while (number > 0) {
array.push(Math.min(number, 100));
number = number - 100;
}
// [ 100, 100, 100, 100, 100, 25 ]
Using ES6 with arrow functions:
const chunkBy = (n) => number => {
var chunks = new Array(Math.floor(number / n)).fill(n);
var remainder = number % n;
console.log('CHUNKS = ', chunks);
if (remainder > 0) {
chunks.push(remainder);
}
return chunks;
};
const chunkBy50 = chunkBy(50);
const chunkBy100 = chunkBy(100);
const chunkBy77 = chunkBy(77);
console.log(chunkBy50(500));
// [ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 ]
console.log(chunkBy100(500));
// [ 100, 100, 100, 100, 100 ]
console.log(chunkBy77(500));
// [ 77, 77, 77, 77, 77, 77, 38 ]
Alternatively, you could use integer division to get the number of occurrences for the "max" divisor and then use the "mod" operation to get the final chunk or remainder.
const number = 525;
const max = 100;
const maxCount = Math.floor(number / max); // 5
const remainder = 525 % max; // 25
You should move the line number = number - 100; after you push the number, so it will count the 1st 100, and stop before pushing the -75:
var number = 525;
var array = [];
while (number > 0) {
array.push(Math.min(number, 100));
number = number - 100; // remove number after pushing
}
console.log(array);
And a fancy one using Array#from:
const number = 525;
const get100s = (number) => Array.from({ length: Math.ceil(number / 100) }, (_, i) => {
const leftover = number - i * 100;
return leftover > 100 ? 100 : leftover;
});
console.log('525: ', get100s(525));
console.log('500: ', get100s(500));
console.log('10: ', get100s(10));
The problem here is that you are substracting 100 to the number after adding it to the array.
The code should be like this:
var number = 525;
var array = [];
while (number > 0) {
array.push(Math.min(number, 100));
number = number - 100;
}
Apparently I think completely different about this kind of problem. I would have divided the number by the section size and rounded down/truncated to get the number of sections, added that many sections to the final array, and then added the modulus of the section size and the number as the final value.
var value = 525;
function splitValue(val, sectionSize)
{
var sections = Math.floor(val/sectionSize);
var finalValue = val%sectionSize;
var splitValues = [];
for (var i = 0; i < sections; i++) {
splitValues.push(sectionSize);
}
splitValues.push(finalValue);
return splitValues;
}
var valueArray = splitValue(value, 100);
console.log(valueArray);
You can also use modulus.
let x = 556;
let mod = 100;
let res = [];
for (let i = 0; i < parseInt(x / mod); i++) res.push(mod);
res.push(x % mod);
I was playing around with the waterfall series of the jqxChart.
According to its API, the following piece of code defines the values of the axis, in this case it's the y-axis:
valueAxis:
{
title: {text: 'Population<br>'},
unitInterval: 1000000,
labels:
{
formatFunction: function (value) {
return value / 1000000 + ' M';
}
}
}
Is it possible to define the intervals not with absolute values, but with relative values. So that the interval are e.g. 10% and the overall value is 100%?
Simply doing unitInterval: '10%' doesn't work.
This is how it should look like:
Here is a fiddle.
I think you're looking for these options :
logarithmicScale: true,
logarithmicScaleBase: 1.10,
Example:
valueAxis:
{
title: {text: 'Population<br>'},
logarithmicScale: true,
logarithmicScaleBase: 1.10,
labels:
{
formatFunction: function (value) {
return value / 1000000 + ' M';
}
}
},
Edit:
var accuracy = 2;
var first = data[0].population;
var last = data[data.length - 2].population;
var unit = (100 / last);
// convert raw data to differences
for (var i = 0; i < data.length - 2; i++)
data[i].population = (data[i].population * unit).toFixed(accuracy);