So I have json response containing data formatted for creating a chart (using Canvas.js), it's an array of objects, each of which contains y value (which is int) and label (which is SQL date, like 2016-02-06).
What I need is to group these values by dates and get average from them. Data are sorted by date. The problem is, there can be random number of points for one day, and the other day may not have any points at all. So, I need kind of week index (calculating the week of the year is not a solution, because there can be multiple years). For example, I could have the following array:
[0] {y: 2; label: 2016-04-01} // this is week 1 from the start of the array
[2] {y: 6; label: 2016-04-02} // this is still week 1
[3] {y: 1; label: 2016-04-13} // this is week 2
[4] {y: 10; label: 2016-04-28} // this is week 3, if we count weeks only by appearance in the array, not by their actual position
[5] {y: 2; label: 2016-05-01} // this is week 4
[6] {y: 4; label: 2016-05-02} // still week 4
So I need to get these weeks somehow, and then find the average y value, so from the array above I would get:
[0] {y: 4; label: 2016-04-01}
[2] {y: 1; label: 2016-04-13}
[3] {y: 10; label: 2016-04-28}
[4] {y: 3; label: 2016-05-01}
How could this be handled? I suppose I should use PHP methods and give up trying make it on front-end, or may be there are more simple ways to handle this? Or maybe there are js chart plugins that allow automatic grouping? I would highly appreciate any possible help!
In Javascript, you could use an object for collecting and an array for the result.
This proposal utilised the answer of RobG to Get week of year in JavaScript like in PHP.
The later code is a grouping part, which takes the year and the week number and collect the data for the average calculation.
// taken from https://stackoverflow.com/a/6117889/1447675
Date.prototype.getWeekNumber = function () {
var d = new Date(+this);
d.setHours(0, 0, 0);
d.setDate(d.getDate() + 4 - (d.getDay() || 7));
return Math.ceil((((d - new Date(d.getFullYear(), 0, 1)) / 8.64e7) + 1) / 7);
};
var data = [{ y: 2, label: '2016-04-01' }, { y: 6, label: '2016-04-02' }, { y: 1, label: '2016-04-13' }, { y: 10, label: '2016-04-28' }, { y: 2, label: '2016-05-01' }, { y: 4, label: '2016-05-02' }],
avg = [];
data.forEach(function (a) {
var year = a.label.slice(0, 4),
week = new Date(a.label).getWeekNumber(),
key = year + '|' + week;
if (!this[key]) {
this[key] = { count: 0, sum: 0, result: { y: 0, label: a.label } };
avg.push(this[key].result);
}
this[key].count++;
this[key].sum += a.y;
this[key].result.y = this[key].sum / this[key].count;
}, Object.create(null));
console.log(avg);
You can try something like this:
$weeks = array();
foreach ($myData as $info) {
$info = json_decode($info, true);
$dateTime = new DateTime($info['label']);
$weeksKey = $dateTime->format('W-Y');
if (!isset($weeks[$weeksKey])) {
$weeks[$weeksKey] = array('y' => $info['y'], 'label' => $dateTime, 'count' => 1);
} else {
$weeks[$weeksKey]['y'] += $info['y'];
$weeks[$weeksKey]['count']++;
$weeks[$weeksKey]['label'] = $weeks[$weeksKey]['label'] > $dateTime
? $dateTime
: $weeks[$weeksKey]['label'];
}
}
$weeks = array_map(function($week) {
return json_encode(array(
'y' => $week['y'] / $week['count'],
'label' => $week['label']->format('Y-m-d')
));
}, $weeks);
var_dump($weeks); // or var_dump(array_values($weeks));
Related
I'm populating a series with "date-value pairs like this:
series: [{
name: "Price",
data:[
[1486684800000, 38],
[1486771200000, 0],
[1486857600000, 0],
[1486944000000, 0],
[1487030400000, 0],
[1487116800000, 58]]
},
But, when for instance I'm populating the series array with "missing dates" like this:
series: [{
name: "Price",
data:[
[1486684800000, 38],
[1487116800000, 58]]
},
ApexCharts will automatically fill the line, and will not fill the missing dates with zero values.
Any ideas on how to force ApexCharts to show missing dates with zero values and not "ignore" them?
I know this question is 2 years old, but I had simillar problem. My backend returns date formatted like YYYY-MM-DD, but you can change this pretty easy.
var startDate = new Date()
startDate.setDate(startDate.getDate() - 30)
var getDaysArray = function (start, end) {
for (var arr = [], dt = new Date(start); dt <= new Date(end); dt.setDate(dt.getDate() + 1)) {
const currDate = new Date(dt).toISOString().split('T')[0]
var amount = response.data.monthly_logs.find(monthlyLog => monthlyLog.created_at__date === currDate) || 0
if (amount) {
amount = amount.price
}
arr.push({ x: new Date(dt), y: amount })
}
return arr
}
var result = getDaysArray(startDate, new Date())
This question already has answers here:
Finding the max value of an attribute in an array of objects and return the entire object
(5 answers)
Finding the max value of an attribute in an array of objects
(21 answers)
Closed 4 years ago.
I have object that looks like this:
peaks =
0: {intervalId: 7, time: 1520290800000, value: 54.95125000000001}
1: {intervalId: 7, time: 1520377200000, value: 49.01083333333333}
and so on.
How do I find peak that has Max value?
I tried to do it like this
this.loadPeak = peaks.map(a => Math.max(a.value));
but I just got bunch of peaks array with value (instead of all intervalId, time, value) and not the max value.
**Thank you so much for everyone, every solution was working, sadly can't accept all. **
The main issue with sorting your array, is it causes many needless iterations through your array. This gets drastically slower the bigger your array is, sorting to try and move elements up and down. With reduce(), we can handle this in the minimum amount of steps needed, simply replacing the previous value if the current element's value is greater than the previous:
var peaks = [
{intervalId: 7, time: 1520290800000, value: 54.95125000000001},
{intervalId: 7, time: 1520377200000, value: 49.01083333333333}
];
const maxPeak = peaks.reduce((p, c) => p.value > c.value ? p : c);
console.log(maxPeak);
You could spread only the value value for Math.max.
var peaks = [{ intervalId: 7, time: 1520290800000, value: 54.95125000000001 }, { intervalId: 7, time: 1520377200000, value: 49.01083333333333 }]
max = Math.max(...peaks.map(({ value }) => value)),
object = peaks.find(({ value }) => value === max);
console.log(max);
console.log(object);
The simple way is just to use a loop:
this.loadPeak = null;
for (const peak of peaks) {
if (!this.loadPeak || peak.value > this.loadPeak.value) {
this.loadPeak = peak;
}
}
Live Example:
const peaks = [
{intervalId: 7, time: 1520290800000, value: 54.95125000000001},
{intervalId: 7, time: 1520377200000, value: 49.01083333333333}
];
let loadPeak = null;
for (const peak of peaks) {
if (!loadPeak || peak.value > loadPeak.value) {
loadPeak = peak;
}
}
console.log(loadPeak);
As with nearly any array operation, you can shoe-horn it into a reduce call if you like:
this.loadPeak = peaks.reduce((maxPeak, peak) => !maxPeak || maxPeak.value < peak.value ? peak : maxPeak, null);
const peaks = [
{intervalId: 7, time: 1520290800000, value: 54.95125000000001},
{intervalId: 7, time: 1520377200000, value: 49.01083333333333}
];
const loadPeak = peaks.reduce((maxPeak, peak) => !maxPeak || maxPeak.value < peak.value ? peak : maxPeak, null);
console.log(loadPeak);
I should have realized earlier this was a duplicate question. I've found the dupetarget, marked it, and made this a CW answer.
You can sort your peaks in descending order of value, then pick the first of the sorted array.
let peaks = [{
intervalId: 7,
time: 1520290800000,
value: 54.95125000000001
},
{
intervalId: 7,
time: 1520377200000,
value: 49.01083333333333
}
]
let sortedPeaks = peaks.sort((a, b) => b.value - a.value)
let loadPeak = sortedPeaks[0];
console.log(loadPeak);
I found similar questions but approved answers do not work for my problem.
I have an input: array of range objects each containing:
start: Integer, start of range,
end: Integer, end of the range.
Output should be:
An array of non-overlapping range objects covering the same range(s) as input ordered from smallest start to largest start. Two ranges are not overlapping if:
range1.start <= range2.start, and
range1.end >= range2.start
Input:
[
{ start: 8, end: 10 },
{ start: 5, end: 7 },
{ start: 9, end: 12 },
{ start: 2, end: 6 },
]
output:
[
{ start: 2, end: 7 },
{ start: 8, end: 12 }
]
As I mentioned, I tried applying solutions on the web for Merging overlapping intervals but they do not do the job.
Thank you.
You could sort the array by start and end and iterate the sorted array with a check for the ranges if they overlap.
var data = [{ start: 8, end: 10 }, { start: 5, end: 7 }, { start: 9, end: 12 }, { start: 2, end: 6 }],
result = data
.sort(function (a, b) { return a.start - b.start || a.end - b.end; })
.reduce(function (r, a) {
var last = r[r.length - 1] || [];
if (last.start <= a.start && a.start <= last.end) {
if (last.end < a.end) {
last.end = a.end;
}
return r;
}
return r.concat(a);
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var ranges = [
{ start: 8, end: 10 },
{ start: 5, end: 7 },
{ start: 9, end: 12 },
{ start: 2, end: 6 }
];
function merge(ranges) {
// first, sort the ranges
ranges.sort((a, b) => a.start - b.start);
// take two ranges, and merges them together
var mergeFn = (a, b) => ({start: Math.min(a.start, b.start), end: Math.max(a.end, b.end)});
// check if two ranges overlap
var overlapFn = (a, b) => (b.start <= a.end);
// make current the first item of the array (start the array from 1 to not check the first item against itself)
var current = ranges[0];
var result = [];
for(var i = 1; i < ranges.length; i++) {
if(overlapFn(current, ranges[i])) // if the current range overlapping with this range
current = mergeFn(current, ranges[i]); // merge them into the current range
else { // if not
result.push(current); // add the current accumulated range as result
current = ranges[i]; // start accumulating another one from this range
}
}
result.push(current); // add the last result
return result;
}
console.log(merge(ranges));
This question already has answers here:
Weighted random selection from array
(15 answers)
Closed 6 years ago.
I have an array of objects that represent creatures in a game I'm trying to develop. These objects have (among others) a unique identifier and a weight (or probability) to spawn.
I'm trying to develop an algorithm to spawn creatures randomly but I fail to come up with a way to use the weights (I really don't know how to do it).
Can anybody help?
An example of creatures array could be:
var creatures = [
{id: 1, weight: 25},
{id: 2, weight: 15},
{id: 3, weight: 5},
{id: 4, weight: 45},
{id: 5, weight: 10}
]
I found this nice algorithm implemented in PHP in this blog that I think migth suit your needs.
I just adopted it to JS.
var creatures = [{
id: 1,
weight: 25
}, {
id: 2,
weight: 15
}, {
id: 3,
weight: 5
}, {
id: 4,
weight: 45
}, {
id: 5,
weight: 10
}],
sumOfWeights = creatures.reduce(function(memo, creature) {
return memo + creature.weight;
}, 0),
selectedWeigths = {};
function getRandom(sumOfWeights) {
var random = Math.floor(Math.random() * (sumOfWeights + 1));
return function(creature) {
random -= creature.weight;
return random <= 0;
};
}
for (var i = 0; i < 1000; i++) {
var creature = creatures.find(getRandom(sumOfWeights));
selectedWeigths[creature.weight] = (selectedWeigths[creature.weight] || 0) + 1;
}
console.log(selectedWeigths);
Hope it helps.
Create a new array and add the id for each creature to the array the number of times it is weighted. Then get a random number between 0 and the size of the array and return the id number at that position.
var creatureIds = [];
for(var i=0;i<creatures.length;i++){
for(var x=0;x<creatures[i].weight;x++){
creatureIds.push(creatures[i].id);
}
}
// get a random index between 0 and the ids length.
var min = 0;
var max = creatureIds.length;
var index = Math.floor(Math.random() * (max - min + 1)) + min;
var randomWeightedCreatureId = creatureIds[index];
I am trying to group timeslots by overlap but I can't figure out how to do it exactly.
I have a pretty simple array in the form of [{start_at: Date, end_at: Date, etc.etc. }]
And I lay them out in my view like this
<---slot1----><----slot5----><--slot6-->
<--slot2-><--slot4---> <--slot7-->
<----slot3---->
Finding directly overlapping slots isn't that hard, I just compare a slot with the next one with (StartA <= EndB) and (EndA >= StartB)
from here.
Now I want to group my overlapping slots (slot 1, 2, 3, 4 and 5) but not include slot 6 and 7, and put those two in their own group. into something like [[Slot (has 1 through 5)][Slot (has 6 and 7)]]
I am kind of lost with this problem right now and I hope anybody here can help me.
I'd suggest creating a Slot object that holds:
an array of items in the slot,
the earliest start_at date of those items,
the latest end_at of those items.
By keeping an up to date slot-range, you don't have to compare a new item to each of the slot's items. You'll only have to compare to the slot itself.
Now, you'll have to sort your items by start_at. You can then reduce the array by:
Create a Slot for the first item
Set the Slot's start_at and end_at to mimic those of the first item
Go to the second item, check for overlap with the first Slot
If it overlaps,
push the second item to the Slot's items array, and
Set start_at to the minimum of Slot.start_at and item2.start_at
Do the same (max) for end_at
If it does not overlap,
Create a new Slot for the second item, repeat with this Slot and item3 (et cetera)
A sample implementation (I'd advice you to rewrite it based on your personal preferences. I didn't make any neat classes/prototypes/etc., nor did I test it thoroughly)
function createSlot(initialItem) {
var slot = {
items: [initialItem],
start: initialItem.start,
end: initialItem.end
};
slot.addItem = function(item) {
slot.items.push(item);
slot.start = Math.min(slot.start, item.start);
slot.end = Math.max(slot.end, item.end);
}
return slot;
};
function itemsOverlap(item1, item2) {
return item1.start <= item2.end &&
item1.end >= item2.start;
};
var slots = [];
var items = randomItems(10);
items.slice(1).reduce(function(currentSlot, item) {
if (itemsOverlap(currentSlot, item)) {
currentSlot.addItem(item);
return currentSlot;
}
slots.push(currentSlot);
return createSlot(item);
}, createSlot(items[0]));
console.log(
slots.map(function(slot) { return slot.items.length; }));
// Create random data
function randomItems(n) {
var arr = [];
for (var i = 0; i < n; i += 1) {
arr.push(generateRandomItem());
}
return arr.sort(function(a, b) { return a.start - b.start; });
};
function randomHourTimespan() {
return Math.random() * 60 * 60 * 1000;
};
function randomHalfDayTimespan() {
return randomHourTimespan() * 12;
};
function generateRandomItem() {
var start = Date.now() + randomHalfDayTimespan();
var end = start + randomHourTimespan();
return { start: new Date(start), end: new Date(end) };
}
I implemented a simple algorithm to group the slots regarding to the start and end values.
Here is a working fiddle https://jsfiddle.net/LeoAref/gg6q0mby/, and you will find a visual presentation for the grouping.
var timeSlots = [
{start: 0, end: 3},
{start: 1, end: 2},
{start: 2, end: 4},
{start: 4, end: 6},
{start: 4, end: 8},
{start: 5, end: 6}
];
timeSlots.forEach((slot, index) => {
var slotElem = document.createElement('div');
slotElem.classList.add('slot');
slotElem.style.top = index * 25 + 'px';
slotElem.style.left = slot.start * 30 + 'px';
slotElem.style.width = (slot.end - slot.start) * 30 + 'px';
document.body.appendChild(slotElem);
});
var groups = [];
timeSlots.forEach(slot => {
added = false;
if (groups.length) {
var index = 0;
do {
group = groups[index];
if (slot.start >= group.start && slot.start < group.end ||
slot.end <= group.end && slot.end > group.start
) {
group.slots.push(slot);
group.start = Math.min(slot.start, group.start);
group.end = Math.max(slot.end, group.end);
added = true;
}
} while (!added && ++index < groups.length);
if (!added) {
groups.push({start: slot.start, end: slot.end, slots: [slot]});
}
} else {
groups.push({start: slot.start, end: slot.end, slots: [slot]});
}
})
groups.forEach(group => {
var groupElem = document.createElement('div');
groupElem.classList.add('group');
groupElem.style.left = group.start * 30 + 'px';
groupElem.style.width = (group.end - group.start) * 30 - 2 + 'px';
document.body.appendChild(groupElem);
})
#user3297291's description/algorithm of a time interval grouping function is really good. Here's a function that was created/posted on GitHub by the user 'blaston' from several years ago that follows the algorithm. I'm posting it here in case the content/link disappears. I started with blaston's function for its simplicity to follow and swapped array groups in blaston's function for slot objects from #user3297291's post.
// Group all overlaping intervals
// * * * * * * *
// This is an approach to a problem the engineers at Google Calandar/ Outlook probably faced.
// You have events that may overlap and you want to display them in such a way that
// they don't overlap with each other. One approach is to distribute them into columns.
// Each column has events that don't overlap with each other.
// Cost: O(n*log n) if the interval aren't sorted by the starting time,
// O(n) otherwise.
// Sample run: groupOverlapingIntervals([ [2, 5], [5, 6],[3, 4] ])
// Output: [ [ [2, 5], [3, 4], [5, 6] ] ]
function groupOverlapingIntervals(intervals) {
intervals.sort(function(a, b) {
return a[0] - b[0];
});
var groups = [
[intervals[0]]
];
var j = 0;
var end = intervals[0][1];
for (var i = 1; i < intervals.length; i++) {
if (intervals[i][0] <= end) {
if (intervals[i][1] > end) {
end = intervals[i][1];
}
groups[j].push(intervals[i]);
} else {
groups.push([intervals[i]]);
j++;
end = intervals[i][1];
}
}
return groups;
}
var intervals = [
[2, 5],
[5, 6],
[3, 4],
[7, 8],
[6.5, 9],
[10, 11.5]
];
var groups = groupOverlapingIntervals(intervals);
console.log(groups);