I'm trying to create a chart which uses an input set of data which I then turn into percentages so that the chart is always between the range of -100 and 100.
From the array of JSON data I want to work out the max number (positive value, generally) and min number (likely to be a negative but turned into a positive using a - before the variable).
I then want to work out which of those two 'positive' values is higher and mark that as the top value.
I then for each value from the data, want to divide the (timeline_value by the top_value) multiplied by 100 and rounded.
For each of those values, I need to add them to individual arrays. One of them is to calculate the rectangle width chartWidth, one to work out the rect start position chartStart (for positive values its 152, for negative values, it is 148 minus chartWidth).
For chartColor, I want to have it predefined that revenue is always green (#28CE6D), expenses is always red (#DF3456) and profit is always blue (#4DC7EC). Below is the data set that is driving my chart that I need to create from the input data:
var chartStart = [152, 84, 152]
var chartWidth = [100, 64, 36]
var chartNames = ["$7,110 Revenue", "$4,539 Expenses", "$2,571 Profit"]
var chartColor = ["#28CE6D", "#DF3456", "#4DC7EC"]
Here's the bit I'm stuck on where I'm trying to create the above set from. Thanks
var input_data = {
timeline_balances: [
{
timeline_name: "Revenue",
timeline_value: 7110,
currency: "$"
},
{
timeline_name: "Expenses",
timeline_value: -4539,
currency: "$"
},
{
timeline_name: "Profit",
timeline_value: 2571,
currency: "$"
}
]
}
var max_value = input_data.timeline_balances.timeline_value.max_value
var min_value = -input_data.timeline_balances.timeline_value.min_value
if (max_value > min_value) {
top_value = max_value;
} else {
top_value = min_value;
}
You have to use a loop to get values from the object as the object is inside an array
var input_data = {
timeline_balances: [{
timeline_name: "Revenue",
timeline_value: 7110,
currency: "$"
},
{
timeline_name: "Expenses",
timeline_value: -4539,
currency: "$"
},
{
timeline_name: "Profit",
timeline_value: 2571,
currency: "$"
}
]
}
var top_value=-10;
input_data.timeline_balances.forEach(e=>{
var max_value = e.timeline_value
var min_value = -1*e.timeline_value
if (max_value > min_value) {
if(max_value>top_value)
top_value = max_value;
} else {
if(min_value>top_value)
top_value = min_value;
}
})
console.log(top_value)
We can utilize a custom sort on the array. In our custom sort function we'll use Math.abs() to get the value regardless of sign and sort using that.
In the example below I've done the sort in descending order, so the first value in the array has the highest absolute value. (I've tweaked your negative value to make it our maximum for the purposes of this example)
var input_data = {
timeline_balances: [{
timeline_name: "Revenue",
timeline_value: 7110,
currency: "$"
},
{
timeline_name: "Expenses",
timeline_value: -14539,
currency: "$"
},
{
timeline_name: "Profit",
timeline_value: 2571,
currency: "$"
}
]
}
/*Make a copy of the object as sort happens in place*/
/*See: https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript*/
var absolute_sorted_data = JSON.parse(JSON.stringify(input_data));
/*Sort by absolute value - decending*/
absolute_sorted_data.timeline_balances.sort(function(a,b){
return Math.abs(b.timeline_value) - Math.abs(a.timeline_value);
});
/*First value has the highest absolute value*/
var max_val = Math.abs(absolute_sorted_data.timeline_balances[0].timeline_value);
console.log(max_val);
/*Update chart arrays - also set new properties in your object.
This is cleaner but your charting engine may not support that*/
var chartStart = []; //[152, 84, 152]
var chartWidth = []; //[100, 64, 36]
var chartNames = []; //["$7,110 Revenue", "$4,539 Expenses", "$2,571 Profit"]
var chartColor = []; //["#28CE6D", "#DF3456", "#4DC7EC"]
input_data.timeline_balances.forEach(function(el){
var val = el.timeline_value;
el.chartStart = val >= 0 ? 152 : 84;
el.chartWidth = Math.round(val/max_val*100);
el.chartName = `${el.currency}${Math.abs(val)} ${el.timeline_name}`;
switch(el.timeline_name) {
case "Revenue":
el.chartColor = "#28CE6D";
break;
case "Expenses":
el.chartColor = "#DF3456";
break;
case "Profit":
el.chartColor = "#4DC7EC";
break;
}
chartStart.push(el.chartStart);
chartWidth.push(el.chartWidth);
chartNames.push(el.chartName);
chartColor.push(el.chartColor);
});
console.log(input_data);
{//
<script>
'use strict';
let input_data =
{
timeline_balances :
[
{
timeline_name : "Revenue",
timeline_value : 7110,
currency : "$"
},
{
timeline_name : "Expenses",
timeline_value : -4539,
currency : "$"
},
{
timeline_name : "Profit",
timeline_value : 2571,
currency : "$"
}
]
};
let maxValue = 0;
let minValue = 0;
for (let key in input_data.timeline_balances) {
{
//alert(input_data.timeline_balances[key].timeline_value)
if(maxValue < input_data.timeline_balances[key].timeline_value)
maxValue = input_data.timeline_balances[key].timeline_value;
if(minValue > input_data.timeline_balances[key].timeline_value)
minValue = input_data.timeline_balances[key].timeline_value;
}
} //end for loop
alert("Maximum value: " + maxValue);
alert("Minimum value: " + minValue);
//}
Related
I am trying to maap and filter the key and value
AvailblePointsToBuy = 100;
lifePriority = { "cloth":1,"bike":2,"cycle":3 }
availbility = {"cycle":40,"car":80,"cloth":10,"bike":50 }
i need to create one response based on the "lifePriority" priority and total point should not less than AvailblePointsToBuy.
output be like : { "cloth":10,"bike":50 ,"cycle":40,} based on the lifePriority need to sort with total points not to excude 100 points.
Logic
Loop through lifePriority.
Start fom the priority value 1. (OR minimum in list)
Find the item from lifePriority with the required priority and its matching value from availbility.
Add the key to the output, with value minimum of the product vailability or the total availability.
Decrease the total availabiity as minimum of the product vailability or zero.
AvailblePointsToBuy = 100;
const lifePriority = { "cloth": 1, "bike": 2, "cycle": 3 };
const availbility = { "cycle": 40, "car": 80, "cloth": 10, "bike": 50 };
const output = {};
let priority = 1;
Object.keys(lifePriority).forEach((key) => {
if(lifePriority[key] === priority) {
output[key] = AvailblePointsToBuy > availbility[key] ? availbility[key] : AvailblePointsToBuy;
AvailblePointsToBuy = (AvailblePointsToBuy - availbility[key]) > 0 ? (AvailblePointsToBuy - availbility[key]) : 0;
}
priority++;
})
console.log(output);
I need to find the range between lowest and higest value . So if I have following datasets
Australia : 454545,
India : 56655,
China: 8989898,
Usa: 545444
Here range will be (8989898-56666 = 8933243) which is the difference between the lowest and highest values. I managed to find that in the following way
let marks ={Australia : 100,India : 89,China: 78,Usa: 45}
let max=0;
let min=9999;
for (let m in marks) {
let mark=marks[m];
if(mark>max){
max=mark;
}
if(mark<min){
min=mark;
}
}
console.log(max);
console.log(min);
console.log(max-min);
But my problem here is that i have specified minimum arbitrary number 9999 for comparison , it will work most of the case but it wont if the datasets are greater than 9999, so what is the best way to achieve this
Similar to how Math.min operates, you can set the initial value to Infinity:
console.log(Math.min());
let marks = {
Australia: 100,
India: 89,
China: 78,
Usa: 45
}
let max = 0;
let min = Infinity;
for (let m in marks) {
let mark = marks[m];
if (mark > max) {
max = mark;
}
if (mark < min) {
min = mark;
}
}
console.log(max);
console.log(min);
console.log(max - min);
Or you could just invoke Math.min and Math.max:
let marks ={Australia : 100,India : 89,China: 78,Usa: 45}
const vals = Object.values(marks);
console.log(
Math.max(...vals) - Math.min(...vals)
);
let marks = { Australia : 100, India : 89, China: 78,Usa: 45 };
var min = Infinity, max = -Infinity, x;
for( x in marks) {
if( marks[x] < min) min = marks[x];
if( marks[x] > max) max = marks[x];
}
console.log(max);
console.log(min);
console.log(max - min);
I'm making a simple line chart for a client using Chart.js, but the values shown are all above millions, making the labels take up a lot of space in the chart, as below:
I would like to shorten the labels to show an M instead of six 0s, for instance.
I've looked into the documentation and and have not found anything of such.
You could override the ticks.callback method as documented here: https://www.chartjs.org/docs/latest/axes/labelling.html#creating-custom-tick-formats
For example, to abbreviate the y-axis zeros to simply 'M':
var chart = new Chart(ctx, {
type: 'line',
data: data,
options: {
scales: {
yAxes: [{
ticks: {
// Abbreviate the millions
callback: function(value, index, values) {
return value / 1e6 + 'M';
}
}
}]
}
}
});
My fiddle: https://jsfiddle.net/robhirstio/hsvxbjkg/17/
Adding commarize feature for k, M, B and T
function commarize(min) {
min = min || 1e3;
// Alter numbers larger than 1k
if (this >= min) {
var units = ["k", "M", "B", "T"];
var order = Math.floor(Math.log(this) / Math.log(1000));
var unitname = units[(order - 1)];
var num = Math.floor(this / 1000 ** order);
// output number remainder + unitname
return num + unitname
}
// return formatted original number
return this.toLocaleString()
}
In chart JS you could use config property ticks into yAxes
var chart = new Chart(ctx, {
type: 'line',
data: data,
options: {
scales: {
yAxes: [{
ticks: {
// Include a dollar sign in the ticks
callback: function(value, index, values) {
return String(value).commarize();
}
}
}]
}
}
});
Chart JS Reference https://www.chartjs.org/docs/latest/axes/labelling.html
Commarize reference https://gist.github.com/MartinMuzatko/1060fe584d17c7b9ca6e
Support 'K', 'M', 'B':
This is my solution, to be generic when you use the same options object for multiple charts, that possibly contain lower numbers or negative numbers.
formatNumbers(value) {
if (value >= 1000000000 || value <= -1000000000 ) {
return value / 1e9 + 'B';
} else if (value >= 1000000 || value <= -1000000) {
return value / 1e6 + 'M';
} else if (value >= 1000 || value <= -1000) {
return value / 1e3 + 'K';
}
return value;
}
My fiddle:
https://jsfiddle.net/epsilontal/v0qnsbwk/45/
Example:
I was trying to figure it out how can I extend and then sort the items by the created extension variable.
if(score === 'Overall Score'){
// let midtermSort = _.sortBy(overAll, 'overall_score');
_.each(students, function(elem) {
_.extend(elem, {overall_score : (elem.midterm_score + elem.final_score) / 2});
_.sortBy(elem, 'overall_score');
console.log(elem.firstname + " " + elem.overall_score);
});
}
As you can see on my code, I iterate to the students and then extend a new column w/c is overall_score. So right now I need to sort the items via overall_score.
Here's what I got:
As you can see the overall score does not SORT them properly. Anything i was doing wrong? Please help.
UPDATE Side Note:
I tried to mixed it up with each function and it works but it was a long process. Any idea how to refactor it a little bit?
if(score === 'Overall Score'){
let overAllScore = _.each(students, function(elem) {
_.extend(elem, {overall_score : (elem.midterm_score + elem.final_score) / 2});
});
let sorted = _.sortBy(overAllScore, 'overall_score');
_.each(sorted, function(elem) {
console.log(elem.firstname + " " + elem.final_score);
});
}
This will do what you want, assuming of course that you wanted them listed from lowest to highest overall score. Note that I've indented the code for readability:
var score = 'Overall Score';
var students = [
{
firstname: 'Frank',
midterm_score: 80,
final_score: 80
},
{
firstname: 'Julie',
midterm_score: 50,
final_score: 65
},
{
firstname: 'Eddie',
midterm_score: 100,
final_score: 73
},
{
firstname: 'Bill',
midterm_score: 60,
final_score: 67
}
];
if (score === 'Overall Score') {
_.each(
_.sortBy(
_.map(
students,
function(elem) {
return _.extend(elem, {overall_score : (elem.midterm_score + elem.final_score) / 2});
}
),
'overall_score'
),
function(elem) {
console.log(elem.firstname + " " + elem.overall_score);
}
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
_.sortBy, according to the documentation, returns a sorted copy of the Array. This is unlike the standard Array.sort().
You need to keep this value:
elem = _.sortBy(elem, ...)
Or, if you want to keep the entire dataset, use map instead of each:
students = _.map(students, function(elem) {
...
return _.sortBy(elem, ...)
})
This is the example data:
100 items or less = $20
200 items or less = $15
500 items or less = $10
Example scenario:
user inputs 150 items -> price is $15 per item
And this is how far I get:
http://jsfiddle.net/ByPh5/
<script type="text/javascript">
$(function(){
var tier_prices = {
'100':'20',
'200':'15',
'500':'10'
}
var user_input = $('#user_input').val();
var price_output = 0;
/*
calculate
*/
$('#price_output').text(price_output)
})
</script>
<input type="text" id="user_input" value="150"/>
<p id="price_output"></p>
any help is much appreciated
(Note: Since you left some ambiguity, I'll assume that 500+ items also cost $20.)
Instead of messing with that data-structure, you can do something simpler. First the code, then the explanation (if the comments aren't enough.)
function determine_price ( qty ) {
var prices = [
20, //0 to 99
20, //100 to 199
15, //200 to 299
15, //300 to 399
15, //400 to 499
10 //500+
];
//divide by the common denominator
//0-99 turn into 0,
//100-199 turns into 1
//200-299 turns into 2
//and so on
qty = Math.floor( qty / 100 );
//check for 600+, which are the same as 500 (last array item)
return prices[ qty ] || prices.pop();
}
100, 200 and 500 have something in common: They're multiples of 100. So we take an array, and treat each element as if it's a range of 100: the first element (0) is 0 to 99 items, second element (1) is 100 to 199 items and so forth. Then, for each input quantity, we divide by that common denominator, to find out in which range it falls, and grab the price corresponding to that quantity.
In the case of ambiguity, which is what happens for 600+ elements (the last element, element #6 is for 500-599), we simply take the last range's price.
No need for loops, just simple math.
First, instead of specifying the max quantity for a price tier, specify the min quantity. And define it as a sorted array so you can iterate through it.
var tier_prices = [
{ minQty: 0, unitPrice: 20 },
{ minQty: 101, unitPrice: 15 },
{ minQty: 201, unitPrice: 10 }
];
Then, loop through the values until you get to a minimum quantity that is greater than the entered quantity:
var qty = +$('#user_input').val();
var price;
for (var i = 0; i < tier_prices.length && qty >= tier_prices[i].minQty; i++) {
price = tier_prices[i].unitPrice;
}
$('#price_output').text(price * qty);
http://jsfiddle.net/gilly3/ByPh5/3/
Objects are nice but a bit annoying since you're not guaranteed to go through the values in order.
http://jsfiddle.net/radu/6MNuG/
$(function() {
var tier_prices = {
'100': '20',
'200': '15',
'500': '10'
};
$('#user_input').change(function() {
var num = parseInt($(this).val(), 10),
price = 0,
prevTier = 0,
maxTier = 0;
for (var tier in tier_prices) {
if (tier_prices.hasOwnProperty(tier) && num <= tier) {
if (tier < prevTier || prevTier == 0) {
price = tier_prices[tier];
prevTier = tier;
}
}
if (tier > maxTier) maxTier = tier;
}
if (num > maxTier) price = tier_prices[maxTier];
$('#price_output').text(price * num);
});
});
Example with a multidimensional array: http://jsfiddle.net/radu/6MNuG/
$(function() {
var tier_prices = [
[100, 20],
[200, 15],
[500, 10]
];
$('#user_input').change(function() {
var num = parseInt($(this).val(), 10),
price = 0,
n = tier_prices.length - 1;
if (num > tier_prices[n][0]) {
price = tier_prices[n][1];
} else {
for (var i = 0; i <= n; i++) {
if (num <= tier_prices[i][0]) {
price = tier_prices[i][1];
break;
}
}
}
$('#price_output').text(price * num);
});
});
Try:
var tier_prices = {
'100': '20',
'200': '15',
'500': '10'
}
var price_output = 0;
var multiplier = 1;
$('#user_input').change(function() {
var user_input = parseInt($('#user_input').val(),10);
for (tier in tier_prices) {
if (user_input <= tier) {
multiplier = tier_prices[tier];
break;
}
}
$('#price_output').text(user_input * multiplier);
});
jsFiddle example
UPDATE
Here's an example forgoing the object you had with a simple switch/case since the idea of the object isn't very popular or functional. Note that I added a case for quantities greater than 500:
$('#user_input').change(function() {
var user_input = parseInt($('#user_input').val(), 10);
switch (true) {
case user_input >= 0 && user_input <= 100:
$('#price_output').text(user_input * 20);
break;
case user_input > 100 && user_input <= 200:
$('#price_output').text(user_input * 15);
break;
case user_input > 200 && user_input <= 500:
$('#price_output').text(user_input * 10);
break;
case user_input > 500:
$('#price_output').text(user_input * 5);
}
});
jsFiddle example
Was looking for a similar thing and decided to do it slightly different, ran it in js fiddle and it seems to work pretty well. As gilly3 pointed out you probably don't want an upper limit, so you might want to do '1 or more, 100 or more...500 or more'. The vars and div names I used were different but you can see what I'm trying to do and adapt it to your needs:
JSFiddle: https://jsfiddle.net/vx4k2vdh/5/
(function() {
const tiers = {
0: 20,
100: 15,
200: 10,
500: 5
}
/**
* Take qty and return the first appropriate
* tier that it encounters, break when
* tier has been identified so you don't
* waste time iterating if u've already found tier
**/
function calculatePriceTier(qty) {
var selectedTier;
for (var tier in tiers) {
if (tiers.hasOwnProperty(tier)) {
if (qty < tier) break;
selectedTier = tier;
}
}
return selectedTier;
}
$(function() {
/**
* Every time a new number is selected
* run calculations and grab tier, total
**/
$('#items').on('input', 'input', function() {
var qty = +$(this).val(),
tier = calculatePriceTier(qty),
total = qty * tiers[tier];
$('#total-price span').text(total);
});
});
})();
What is described in the OP's request is volume pricing.
All the examples given here are for volume pricing not tier pricing.
Tire pricing example in ruby
https://repl.it/repls/IndigoRightDisks
def cost(qty)
tier = {
10 => 100,
50 => 97,
100 =>82,
200 =>71,
300 =>66,
400 =>64,
500 =>27,
1000 =>12
}
cs = []
for cnt in 1..qty
d = tier.keys.find{|x| cnt <= x ?cnt <= x : tier.keys.last == x }
cs << tier[d]
end
return cs.reduce{|y,x| y+x};
end