I have the following data structure from the API, it comes in as an array of Data, each Data element is intervaled by 1 hour.
interface Data {
time_bucket: string // ISO8601 string
aggregated_value: number // 0 and up
}
My plan is to format this for it to work with d3 to plot as a barchart. The barchart has a selector for you to group data into week, day, month, and year. I decided to create a function called groupBy that works quite like lodash's groupBy. It groups data to specified groups you want. This is the function
export function groupBy<T, K extends keyof any> (list: T[], criteria: (item: T) => K): Record<K, T[]> {
return list.reduce<Record<K, T[]>>((prev, curr) => {
const group = criteria(curr)
// eslint-disable-next-line
if (!prev[group]) {
prev[group] = []
}
prev[group].push(curr)
return prev
// eslint-disable-next-line
}, {} as Record<K, T[]>)
}
The problem is that the x scale of the graph is constructed in YYYY-MM-DD format. I wanted to group the data into each day while keeping the date format to YYYY-MM-DD. What I get right now from running the function looks like this in the snippets.
const data = [
{
time_bucket: '2021-06-01T16:00:00.000Z',
aggregated_value: 20
},
{
time_bucket: '2021-06-01T18:00:00.000Z',
aggregated_value: 20
},
{
time_bucket: '2021-06-02T16:00:00.000Z',
aggregated_value: 40
},
{
time_bucket: '2021-06-02T20:00:00.000Z',
aggregated_value: 40
},
{
time_bucket: '2021-06-03T05:00:00.000Z',
aggregated_value: 60
}
]
function groupBy(list, criteria) {
return list.reduce((prev, curr) => {
const group = criteria(curr)
if (!prev[group]) {
prev[group] = []
}
prev[group].push(curr)
return prev
}, {})
}
console.log(groupBy(data, (item) => dayjs.utc(item.time_bucket).get('date')))
<script src="https://unpkg.com/dayjs#1.8.21/dayjs.min.js"></script>
<script src="https://unpkg.com/dayjs#1.8.21/plugin/utc.js"></script>
<script>dayjs.extend(window.dayjs_plugin_utc)</script>
You can see that the output looks like this:
{
"1": [
{
"time_bucket": "2021-06-01T16:00:00.000Z",
"aggregated_value": 20
},
{
"time_bucket": "2021-06-01T18:00:00.000Z",
"aggregated_value": 20
}
],
"2": [
{
"time_bucket": "2021-06-02T16:00:00.000Z",
"aggregated_value": 40
},
{
"time_bucket": "2021-06-02T20:00:00.000Z",
"aggregated_value": 40
}
],
"3": [
{
"time_bucket": "2021-06-03T05:00:00.000Z",
"aggregated_value": 60
}
]
}
This is what I wanted
{
"2021-06-01": [
{
"time_bucket": "2021-06-01T16:00:00.000Z",
"aggregated_value": 20
},
{
"time_bucket": "2021-06-01T18:00:00.000Z",
"aggregated_value": 20
}
],
"2021-06-02": [
{
"time_bucket": "2021-06-02T16:00:00.000Z",
"aggregated_value": 40
},
{
"time_bucket": "2021-06-02T20:00:00.000Z",
"aggregated_value": 40
}
],
"2021-06-03": [
{
"time_bucket": "2021-06-03T05:00:00.000Z",
"aggregated_value": 60
}
]
}
What I wanted from the function is to be able to group the data into specified range, while still keeping the format of the date in YYYY-MM-DD format for me to still map it to the d3 x scale that I have generated. Is there any function in dayjs that could do this or is there any workaround. Thank you very much for the response.
After quite a while of research. I decided to round any date to the start of each time range.
I would be using it like this in the example.
const data = [
{
time_bucket: '2021-06-01T16:00:00.000Z',
aggregated_value: 20
},
{
time_bucket: '2021-06-01T18:00:00.000Z',
aggregated_value: 20
},
{
time_bucket: '2021-06-02T16:00:00.000Z',
aggregated_value: 40
},
{
time_bucket: '2021-06-02T20:00:00.000Z',
aggregated_value: 40
},
{
time_bucket: '2021-06-03T05:00:00.000Z',
aggregated_value: 60
}
]
function groupBy(list, criteria) {
return list.reduce((prev, curr) => {
const group = criteria(curr)
if (!prev[group]) {
prev[group] = []
}
prev[group].push(curr)
return prev
}, {})
}
console.log(groupBy(data, (item) => dayjs.utc(item.time_bucket).startOf('day').format('YYYY-MM-DD')))
<script src="https://unpkg.com/dayjs#1.8.21/dayjs.min.js"></script>
<script src="https://unpkg.com/dayjs#1.8.21/plugin/utc.js"></script>
<script>dayjs.extend(window.dayjs_plugin_utc)</script>
This way I can use the .format() that got exposed from startOf() to group the data into each time range.
Related
How can I find out the min and the max date from an object?
Currently, I am getting an array like this: min date should be '2010-02-24' and max date should be '2022-10-04'.
Is there any built-in function to do this? Thanks in advance.
{
"2010":[
{
"id":1243,
"eventName":"sample_01",
"categoryType":"CUSTOM_NOTES",
"tags":"tag19",
"startDate":"2010-02-24",
"endDate":"2010-02-26",
"attachments":[
]
}
],
"2022":[
{
"id":1244,
"eventName":"sample_02",
"categoryType":"CUSTOM_NOTES",
"tags":"tag1, tag12, tag3, tag52, tag19",
"startDate":"2022-10-04",
"endDate":"2022-12-12",
"attachments":[
]
},
{
"id":1245,
"eventName":"hello_03",
"categoryType":"CUSTOM_NOTES",
"tags":"tag1, tag12",
"startDate":"2022-06-01",
"endDate":"2010-06-26",
"attachments":[
]
}
]
}
filterEventsByDates = () => {
const filterDateFn = (a, b) => a.startDate.localeCompare(b.startDate);
setDateFiltersToState(filterDateFn);
}
setDateFiltersToState = (filterDateFn) => {
this.setState(state => {
const events = {};
for (const [year, items] of Object.entries(state.events)) {
events[year] = items.slice().filter(filterDateFn);
}
return { events };
});
}
A sort will do the job here, by packing all dates into an array first:
const values = {
"2010":[
{
"id":1243,
"eventName":"sample_01",
"categoryType":"CUSTOM_NOTES",
"tags":"tag19",
"startDate":"2010-02-24",
"endDate":"2010-02-26",
"attachments":[
]
}
],
"2022":[
{
"id":1244,
"eventName":"sample_02",
"categoryType":"CUSTOM_NOTES",
"tags":"tag1, tag12, tag3, tag52, tag19",
"startDate":"2022-10-04",
"endDate":"2022-12-12",
"attachments":[
]
},
{
"id":1245,
"eventName":"hello_03",
"categoryType":"CUSTOM_NOTES",
"tags":"tag1, tag12",
"startDate":"2022-06-01",
"endDate":"2010-06-26",
"attachments":[
]
}
]
};
// include startDate only
const dates = Object.values(values).flatMap(v =>
v.map(({startDate}) => startDate)).sort();
console.log(dates[0], dates.pop());
// include startDate and endDate
const datesAny = Object.values(values).flatMap(v =>
v.flatMap(({startDate, endDate}) => [startDate, endDate])).sort();
console.log(datesAny[0], datesAny.pop());
Here you can make use of reduce function. & check if the date is less or more(with the one stored in accumulator).
const obj = {"2010":[{"id":1243,"eventName":"sample_01","categoryType":"CUSTOM_NOTES","tags":"tag19","startDate":"2010-02-24","endDate":"2010-02-26", "attachments":[ ]} ], "2022":[{ "id":1244, "eventName":"sample_02", "categoryType":"CUSTOM_NOTES", "tags":"tag1, tag12, tag3, tag52, tag19", "startDate":"2022-10-04", "endDate":"2022-12-12", "attachments":[ ] }, { "id":1245, "eventName":"hello_03", "categoryType":"CUSTOM_NOTES", "tags":"tag1, tag12", "startDate":"2022-06-01", "endDate":"2010-06-26", "attachments":[ ] } ] };
const result = Object.values(obj).flat().reduce((a,e)=>{
if(new Date(e.startDate)<new Date(a.min) || !a.min) a.min=e.startDate;
if(new Date(e.startDate)>new Date(a.max)) a.max=e.startDate;
return a;
},{min:null, max:null});
console.log(result);
I am using angular-highcharts for my project.I am used https://www.highcharts.com/demo/column-basic this chart for my data.I have below data format.
[
{
"project": "train project1",
"hours": {
"AD": 58265
}
},
{
"project": "butify",
"hours": {
"AD": 3940
}
},
{
"project": "skler",
"hours": {
"AD": 563250
}
},
{
"project": "Internal Application",
"hours": {
"AD": 33325,
"DAM": 328095
}
},
{
"project": "train2project",
"hours": {
"AD": 137215
}
},
{
"project": "CELLProje1",
"hours": {
"DAM": 488470
}
},
{
"project": "rangeselector",
"hours": {
"AD": 3015,
"DAM": 71175
}
},
{
"project": "Android dev",
"hours": {
"AD": 99160
}
},
{
"project": "Web Application",
"hours": {
"AD": 72720
}
}
];
The values inside "hours" will be one or more.I have added my fiddle tried so far.I struggling to form json for series data.Also I need form X -axis for the graph that should be in an array.
Ex:
categories: [
'train project1',
'beautify',
'skler',
'Internal Application',
'train project2',
'rangeselector',
'Android',
'Web Application'
],
X-axis formation will be right?
http://jsfiddle.net/harifrais/uxpvs8fw/34/
You're trying to get data into this format
[
{
name:"series-name",
data:[ ... ]
}
]
But to use categories, there must be the same number of elements in each series as there are categories. And as every hours element in your input data does not contain all the same you need to do a little more work.
Get a distinct list of all the keys from hours
Loop over every element and use project as a category
Fill in zeros where any element does not occur in hours
So you can do this in a fairly simple 2-step process using reduce and map.
var data = [{"project":"train project1","hours":{"AD":58265}},{"project":"butify","hours":{"AD":3940}},{"project":"skler","hours":{"AD":563250}},{"project":"Internal Application","hours":{"AD":33325,"DAM":328095}},{"project":"train2project","hours":{"AD":137215}},{"project":"CELLProje1","hours":{"DAM":488470}},{"project":"rangeselector","hours":{"AD":3015,"DAM":71175}},{"project":"Android dev","hours":{"AD":99160}},{"project":"Web Application","hours":{"AD":72720}}];
// get a distinct list of hour keys
var seriesData = data.reduce( (acc, {hours}) => {
Object.keys(hours).forEach(key => {
if(!acc[key]) acc[key] = [];
})
return acc;
},{});
// reduce the original data to get categories and series values
// filling in zeros where necessary
var result = data.reduce( (acc, {project,hours}) => {
acc.categories.push(project);
Object.keys(acc.seriesData).forEach(s => {
acc.seriesData[s].push(hours[s] || 0);
});
return acc;
},{categories:[],seriesData:seriesData});
// shape the data to how highcharts wants it
var categories = result.categories;
var series = Object.entries(result.seriesData).map( e => ({
name: e[0],
data:e[1]
}));
console.log(categories);
console.log(series);
Here's an updated fiddle to show highcharts/your data: https://jsfiddle.net/u7opL2dw/2/
Here is my idea how to parse your data to using in the Highcharts library, no matter how many properties will be in the data.hours object.
Demo: http://jsfiddle.net/BlackLabel/31tp0mkw/
const categories = sampleJson.map(data => data.project);
const getSeriesNames = sampleJson.map(data => {
for (let i in data.hours) {
return i
}
}).filter((item, i, ar) => ar.indexOf(item) === i);
const series = getSeriesNames.map(name => {
let output = {
name: name,
data: []
};
sampleJson.forEach(data => {
if (data.hours[name]) {
output.data.push(data.hours[name])
} else {
output.data.push(null)
}
});
return output
})
Here's what the data "valueContainer" looks like:
{
"totalValue": 0,
"subValues1": [
{ "value": 20 },{ "value": 30 }
],
"subValues2": [
{ "value": 10 },{ "value": 40 }
]
}
I'm trying to gather the values from 'subValues1' and 'subValues2' and store them in the 'totalValue' field? I've been trying to use computed but the for loop isn't working, here's an example of trying to accumulate values from the first sub object
computed: {
totalValue: function() {
let total;
for (let v in this.valueContainer.subValues1) {
total = total + v.value;
}
return total;
}
}
As written I'm getting NaN for the totalValue. How can I loop through the values, accumulate the values, and store them as totalValue?
Link to codepen: https://codepen.io/connorontheweb/pen/dyPvwmE
You could get the keys, filter by unwanted and iterate the values arrays. Then add the values to total.
var data = { valueContainer: { totalValue: 0, subValues1: [{ value: 20 }, { value: 30 }], subValues2: [{ value: 10 }, { value: 40 }] } };
Object
.keys(data.valueContainer)
.filter(v => v !== 'totalValue')
.forEach(k => data.valueContainer[k].forEach(({ value }) => data.valueContainer.totalValue += value));
console.log(data);
Here's a working component:
<template>
<div>{{ totalValue }}</div>
</template>
const sum = list => list.reduce((acc, value) => acc + value, 0);
export default {
data() {
return {
valueContainer: {
subValues1: [{ value: 20 }, { value: 30 }],
subValues2: [{ value: 10 }, { value: 40 }],
},
};
},
computed: {
totalValue() {
const { subValues1, subValues2 } = this.valueContainer;
const values = [...subValues1, ...subValues2].map(({ value }) => value);
return sum(values);
},
},
};
If you are using lodash, you could simplify this a bit with sumBy:
import sumBy from 'lodash/sumBy';
export default {
data() {
return {
valueContainer: {
subValues1: [{ value: 20 }, { value: 30 }],
subValues2: [{ value: 10 }, { value: 40 }],
},
};
},
computed: {
totalValue() {
const { subValues1, subValues2 } = this.valueContainer;
return sumBy([...subValues1, ...subValues2], 'value');
},
},
};
{
"rResponse":{
"rDetailsList":[
{
"rDate":"April 01, 2018",
"rList":[
{
"aName":"GOKQG C HQFUDHFPX",
"aNumber":"P3799838628"
},
{
"aName":"IGNDPJR D EKYJYC",
"aNumber":"P3899820579"
}
]
},
{
"rDate":"Jan 01, 2018",
"rList":[
{
"aName":"",
"aNumber":"A39A4035073"
},
{
"aName":"YVTLW K SIGLC",
"aNumber":"A270M040558"
}
]
}
]
}
}
getFilteredResult(rDetails, searchText) {
const regex = new RegExp(searchText, 'i');
let result= rDetails.filter(a =>
a.rList.some(rItem=>
(rItem.aName.search(regex) > -1) ||
(rItem.aNumber.search(regex) > -1)
))
console.log(result,"filteredResults")
return result;
}
let result=getFilteredResult(rResponse.rDetailsList, "A270M040558"):
I am using the above function for filtering the data based on search string.
I want to filter the nested array of object keep the structure of the object same
The output of the above function is below, where i am getting all object of a list instead of getting only one object which matches the search text
{
"rResponse": {
"rDetailsList": [{
"rDate": "Jan 01, 2018",
"rList": [{
"aName": "",
"aNumber": "A39A4035073"
},
{
"aName": "YVTLW K SIGLC",
"aNumber": "A270M040558"
}
]
}]
}
}
The expected Output is
{
"rResponse": {
"rDetailsList": [{
"rDate": "Jan 01, 2018",
"rList": [
{
"aName": "YVTLW K SIGLC",
"aNumber": "A270M040558"
}
]
}]
}
}
You have 2 arrays, so you need to filter the first one then the second one :
const rDetailsList = [
{
"rDate":"April 01, 2018",
"rList":[
{
"aName":"GOKQG C HQFUDHFPX",
"aNumber":"P3799838628"
},
{
"aName":"IGNDPJR D EKYJYC",
"aNumber":"P3899820579"
}
]
},
{
"rDate":"Jan 01, 2018",
"rList":[
{
"aName":"",
"aNumber":"A39A4035073"
},
{
"aName":"YVTLW K SIGLC",
"aNumber":"A270M040558"
}
]
}
];
const myFilter = (arr, num) => {
const rDetails = arr.filter(det => !!det.rList.find(l => l.aNumber === num));
return rDetails.map(det => {
det.rList = det.rList.filter(l => l.aNumber === num);
return det;
});
};
console.log(myFilter(rDetailsList, 'A270M040558'));
const res = _.chain(rDetailsList)
.map(rDetail => _.assign( // iterate array and set filtered rList
{}, // use new object to avoid mutations
rDetail,
{ rList: _.filter(rDetail.rList, { aNumber: 'A270M040558' }) }
))
.reject(rDetail => _.isEmpty(rDetail.rList)) // remove elements with empty rList
.value();
I have an array
[
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 20 },
{ price: 20 },
]
and I want it transformed into
[
{ numElements: 4, price: 10 },
{ numElements: 2, price: 20 },
]
I have tried using arr.reduce((prev, curr) => ..., []) to accomplish this, but I can't figure out how to do it.
A traditional method might use a for/loop to wrangle the data, but these days JavaScript has a number of functional methods that can help. This code uses reduce and map. To get your data in the format you want is a two stage process.
First, use reduce to create a hash table using the price as a key (because you know the each price is going to be unique:
const obj = arr.reduce((p, c) => {
// If price exists as a key its value by 1
// otherwise set it to 1.
p[c.price] = ++p[c.price] || 1;
return p;
}, {});
OUTPUT
{
"10": 4,
"20": 2
}
As it stands you've got a perfectly good object that you can access by the key/price and I would probably just stop there:
obj['10'] // 4
But if you want to get that data into the format in your question, map over the object keys to return an array of new objects.
const out = Object.keys(obj).map(key => {
return { price: +key, numElements: obj[key] };
});
DEMO
var hash = {}, result = [];
arr.forEach(function(el){
if(hash[el.price]){
hash[el.price].numElements++;
}else{
result.push(hash[el.price]={price:el.price,numElements:1});
}
});
Run
May use a hash table for price lookup. Or with reduce and find:
arr.reduce((res,{price})=>
(( res.find(el=>el.price===price) || res[res.push({price,numElements:0})-1] )
.numElements++,res)
);
Run
You can use try this:
let arr = [
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 20 },
{ price: 20 },
]
let result = []
let counter = {}
arr.forEach( el => {
if (!counter[el.price]) counter[el.price] = 1
else counter[el.price]++
console.log(counter[el.price])
})
for (let id in counter) {
result.push({numElements: counter[id], price: id})
}
Assuming that the data comes sorted on price property, with a single .reduce() you may do as follows;
var data = [{ price: 10 }, { price: 10 }, { price: 10 }, { price: 10 }, { price: 20 }, { price: 20 }],
result = data.reduce((r,d,i) => i ? r[r.length-1].price === d.price ? (r[r.length-1].numElemenets++, r)
: (r.push(Object.assign({}, d, {numElemenets: 1})),r)
: [Object.assign({}, d, {numElemenets: 1})], {});
console.log(result);
You could look up the price in the result array and if not found insert a new object.
var data = [{ price: 10 }, { price: 10 }, { price: 10 }, { price: 10 }, { price: 20 }, { price: 20 }],
grouped = data.reduce((r, { price }) => {
var t = r.find(p => price === p.price);
t || r.push(t = { numElements: 0, price });
t.numElements++;
return r;
}, []);
console.log(grouped);