Accessing object's property inside array Javascript AngularJS - javascript

I have an array of products, each product has a price. I want to create a filter to calculate the total price of all products. The problem is that I can't use forEach() since I'm in a callback function. My question is, is there a function that does something like myArray.(intheobject).price, or a way to manage the callback and get the right results?
this.productList = [
{
type: 'chocolate',
pack: '3',
price: 5,
checkState: false
},
{
type: 'chocolate',
pack: '5',
price: 7,
checkState: false
},
{
type: 'chocolate',
pack: '10',
price: 10,
checkState: false
},
{
type: 'honey',
pack: '3',
price: 5,
checkState: false
},
{
type: 'honey',
pack: '5',
price: 7,
checkState: false
},
{
type: 'honey',
pack: '10',
price: 10,
checkState: false
},
{
type: 'candy',
pack: '3',
price: 5,
checkState: false
},
{
type: 'candy',
pack: '5',
price: 7,
checkState: false
},
{
type: 'candy',
pack: '10',
price: 10,
checkState: false
}
]
.filter('calculateTotal', function(){
var totalCost = 0;
return function(input){
return totalCost + ???
}
})
After PierreDuc's answer, my filter is this:
.filter('calculateTotal', function(){
return function(input){
return input.reduce((total, item) => item.price + total, 0);
}
})

You can use the reduce method for this:
const totalPrice = [{
type: 'chocolate',
pack: '3',
price: 5,
checkState: false
},
{
type: 'chocolate',
pack: '5',
price: 7,
checkState: false
}].reduce((total, item) => item.price + total, 0);
Filter is used to, like the name suggests, filter the current array based on a the return value of the provided method.
With reduce you can transform your array based on an input, 0 in this case. This gets assigned to the total parameter of the passed method, where the item is every item in your array. The value you return from that method will be the new value for the total parameter.

The filter callback can access each element. So you need to create a global variable, outside the callback function and add the value of each element in the callback function to that global variable. See filter documentation.
this.totalCost = 0;
this.productList = [
{
type: 'chocolate',
pack: '3',
price: 5,
checkState: false
},
{
type: 'chocolate',
pack: '5',
price: 7,
checkState: false
},
{
type: 'chocolate',
pack: '10',
price: 10,
checkState: false
},
{
type: 'honey',
pack: '3',
price: 5,
checkState: false
},
{
type: 'honey',
pack: '5',
price: 7,
checkState: false
},
{
type: 'honey',
pack: '10',
price: 10,
checkState: false
},
{
type: 'candy',
pack: '3',
price: 5,
checkState: false
},
{
type: 'candy',
pack: '5',
price: 7,
checkState: false
},
{
type: 'candy',
pack: '10',
price: 10,
checkState: false
}
]
var self = this;
angular.module('myReverseFilterApp', [])
.filter('calculateTotal', function(){
return function(input) {
self.totalCost += input.price;
}
})
You can then access the totalCost variable in your html. It is not possbile to return the totalCost from a filter function becaus this function gets applied to each element of the array. It is possible that the syntax is not correct, but I guess you get the gist.

const productList = [
{
type: 'chocolate',
pack: '3',
price: 5,
checkState: false
},
{
type: 'chocolate',
pack: '5',
price: 7,
checkState: false
},
{
type: 'chocolate',
pack: '10',
price: 10,
checkState: false
},
{
type: 'honey',
pack: '3',
price: 5,
checkState: false
},
{
type: 'honey',
pack: '5',
price: 7,
checkState: false
},
{
type: 'honey',
pack: '10',
price: 10,
checkState: false
},
{
type: 'candy',
pack: '3',
price: 5,
checkState: false
},
{
type: 'candy',
pack: '5',
price: 7,
checkState: false
},
{
type: 'candy',
pack: '10',
price: 10,
checkState: false
}
]
let totalPrice = productList.reduce(function(sum, currentValue, currentIndex, array) {
return sum+currentValue['price']
},0);
console.log(totalPrice)

You can do as following, First get the list of all the prices and store it in an array, and then use the reduce function to perform the action on prices
also read the documentation of reduce function:
reduce documentation
let pricesList = [];
productList.forEach(product => {
pricesList.push(product['price']);
})
let sumPrice = pricesList.reduce((priceA, priceB) => priceA + priceB, 0);

Related

Cannot read properties of undefined (reading 'size')

I have 2 arrays. I'm using a for loop on arr1 and checking if arr2 contains some of the same shoe sizes as in arr1. If the condition checks that a shoe size from arr1 does not exist in arr2, then it will push an object ({size: '-'}) to the newArr, if it does exist it will just push the shoe object from arr2 into newArr
code:
const newArr = []
const arr1 = [
{ size: 'US 3' },
{ size: 'US 4' },
{ size: 'US 5' },
{ size: 'US 6' },
{ size: 'US 7' },
{ size: 'US 8' },
{ size: 'US 9' },
{ size: 'US 10' },
{ size: 'US 11' },
{ size: 'US 12' },
{ size: 'US 13' },
{ size: 'US 14' },
{ size: 'US 15' },
{ size: 'US 16' }
]
const arr2 = [
{ size: '4', cost: '170' },
{ size: '6', cost: '75' },
{ size: '7', cost: '75' },
{ size: '8', cost: '78' },
{ size: '9', cost: '80' },
{ size: '10', cost: '85' },
{ size: '11', cost: '73' },
{ size: '12', cost: '77' },
{ size: '14', cost: '100' }
]
for (const arr1Item in arr1) {
let arr1Size = arr1[arr1Item].size
let includes = arr1Size.includes(arr2[arr1Item].size);
if(includes) {
newArr.push(arr2[arr1Item])
} else {
newArr.push({size: '-'})
}
}
console.log(newArr)
The problem is whenever I run this I get this error: TypeError: Cannot read properties of undefined (reading 'size')
This error is happening because of this code here:
arr2[arr1Item].size
The thing I'm confused about is if I console.log(arr2[arr1Item]) INSIDE the for loop, it returns each object from arr2 with no error, only gives an error when I add .size.
I've been stuck on this for a bit, would appreciate any help. Thank you.
You iterate over arr1 using the index variable arr1Item, but then use that to index into arr2. As arr1 has more elements than arr2, you end up trying to accessarr2[9] === undefined and undefined does not have a size attribute hence the error TypeError: Cannot read properties of undefined (reading 'size').
Couple of other problems
newArr is not a const.
Array.protoype.includes() is used to see if, say, 4 of arr2.size matches arr.size like US 4 but this will also incorrectly (substring) match US 14.
Potential solution
Using the fact that both arr1 & arr2 are sorted by the (normalized) size attribute, and that the set of (normalized) arr2.size is a subset of (normalized) arr1.size you can do the transformation in linear time (O(arr1.length)):
const arr1 = [
{ size: 'US 3' },
{ size: 'US 4' },
{ size: 'US 5' },
{ size: 'US 6' },
{ size: 'US 7' },
{ size: 'US 8' },
{ size: 'US 9' },
{ size: 'US 10' },
{ size: 'US 11' },
{ size: 'US 12' },
{ size: 'US 13' },
{ size: 'US 14' },
{ size: 'US 15' },
{ size: 'US 16' }
]
const arr2 = [
{ size: '4', cost: '170' },
{ size: '6', cost: '75' },
{ size: '7', cost: '75' },
{ size: '8', cost: '78' },
{ size: '9', cost: '80' },
{ size: '10', cost: '85' },
{ size: '11', cost: '73' },
{ size: '12', cost: '77' },
{ size: '14', cost: '100' }
]
let newArr = []
for(let i1 = 0, i2 = 0; i1 < arr1.length; i1++) {
if(i2 < arr2.length && arr1[i1].size.substr(3) === arr2[i2].size) {
newArr.push(arr2[i2]);
i2++;
} else {
newArr.push({size: '-'});
}
}
console.log(newArr)
I think you want to use filter for matches.
let newArr = []
const arr1 = [
{ size: 'US 3' },
{ size: 'US 4' },
{ size: 'US 5' },
{ size: 'US 6' },
{ size: 'US 7' },
{ size: 'US 8' },
{ size: 'US 9' },
{ size: 'US 10' },
{ size: 'US 11' },
{ size: 'US 12' },
{ size: 'US 13' },
{ size: 'US 14' },
{ size: 'US 15' },
{ size: 'US 16' }
]
const arr2 = [
{ size: 'US 4', cost: '170' },
{ size: '6', cost: '75' },
{ size: '7', cost: '75' },
{ size: '8', cost: '78' },
{ size: '9', cost: '80' },
{ size: '10', cost: '85' },
{ size: '11', cost: '73' },
{ size: '12', cost: '77' },
{ size: '14', cost: '100' }
]
arr1.forEach(a1Item => {
let foundItems = arr2.filter(a2Item => a2Item.size === a1Item.size);
// OR PARTIAL MATCH (arr1.size '4' would match arr2.size 'US 4')
// let foundItems = arr2.filter(a2Item => a2Item.size.includes(a1Item.size));
if (foundItems.length > 0) {
foundItems.forEach(fItem => {
newArr.push(fItem);
});
} else {
// ADDED COST TO KEEP ITEMS CONSISTENT
newArr.push({size: '-', cost: '-'});
}
});
console.log(newArr);

How do you set tooltip labels for multiple series using Apache Echarts?

I'm trying to generate a line chart that uses date for the x-axis and two different y-axis. I have it mostly working, but I can't get the tooltip to display the label properly for the second line.
To see this, go to the ECharts Demo Editor and enter the following code:
option = {
xAxis: {
type: 'time'
},
yAxis: [
{
type: 'value'
},
{
type: 'value'
}
],
dataset: {
source: [
{ date: '2020-01-24', orders: 4, sales: 250 },
{ date: '2020-01-25', orders: 3, sales: 250 },
{ date: '2020-01-26', orders: 2, sales: 375 },
{ date: '2020-01-27', orders: 2, sales: 380 },
{ date: '2020-01-28', orders: 4, sales: 325 },
{ date: '2020-01-29', orders: 5, sales: 375 },
{ date: '2020-01-30', orders: 6, sales: 500 },
{ date: '2020-01-31', orders: 4, sales: 425 },
{ date: '2020-02-01', orders: 2, sales: 280 },
{ date: '2020-02-03', orders: 3, sales: 580 },
{ date: '2020-02-04', orders: 4, sales: 250 },
{ date: '2020-02-05', orders: 4, sales: 350 }
]
},
series: [
{
type: 'line',
yAxisIndex: 0,
dimensions: [
{
type: 'time',
name: 'date',
displayName: ''
},
{
type: 'float',
name: 'orders',
displayName: 'Orders'
}
]
},
{
type: 'line',
yAxisIndex: 1,
dimensions:[
{
type: 'time',
name: 'date',
displayName: ''
},
{
type: 'int',
name: 'sales',
displayName: 'Sales'
}
]
}
],
tooltip: {
trigger: 'axis'
}
};
As you can see, the lines render correctly, as do both y axes, but the label for the second series (green line) is empty rather than Sales. However, if I delete the first series from the array, Sales becomes the blue line and the label works correctly in the tooltip, so there seems to be something I'm missing when using multiple series.
I suspect the fix for this is quite simple and obvious, but I've spent a fair amount of time on it and haven't had any luck. Any help would be greatly appreciated.
Give name to series instead of displayName to dimension.
series: [
{
type: 'line',
yAxisIndex: 0,
name:'Orders', // Here
dimensions: [
{
type: 'time',
name: 'date'
},
{
type: 'float',
name: 'orders'
}
]
},
{
type: 'line',
yAxisIndex: 1,
name:'Sales', // Here
dimensions:[
{
type: 'time',
name: 'date'
},
{
type: 'int',
name: 'sales'
}
]
}
]

Highcharts drill down to detailed graph

I would like to create a drill-down highchart.
You can find the jsfiddle link which is not working but the sample data is in it.
data: [{
name: '6',
y: 14
}, {
name: '7',
y: 19
}, ...
}]
},
{
name: 'B1',
data: [{
name: '6',
y: 14
}, {
name: '7',
y: 19
}, ...
},
{
name: 'C1',
data: [{
name: '6',
y: 14
}, {
name: '7',
y: 19
}, ...
}
]
The vice versa is running here:
datanormal = [{
name: '6',
data: [{
name: 'A1',
y: 14,
drilldown: 'Details1'
}, {
name: 'B1',
y: 19,
drilldown: 'Details1'
}, {
name: 'C1',
y: 21,
drilldown: 'Details1'
}]
},
{
name: '7',
data: [{
name: 'A1',
y: 5,
drilldown: 'Details1'
} ...]
}];
datadrill =
[{
id: 'Details1',
name: 'Details1',
data: [
['D1', 4],
['D2', 2],
['D3', 1],
['D4', 4]
]
}];
I need the opposite, from the basic to complex.
This is the main column chart image
This is the detailed drill-down chart image
If you look fot the working example there is another object for the datadrill:
datadrill =
[{
id: 'Details1',
name: 'Details1',
data: [
['D1', 4],
['D2', 2],
['D3', 1],
['D4', 4]
]
}]
You need to do the same on your code.
Could you check this? Is that what you want?
This is jsfiddle link => https://jsfiddle.net/burakkp/ytkqzfos/2/
$(document).ready(function() {
var datadrill;
datadrill = [{
name: 'A1',
data: [{
name: '6',
y: 14
}, {
name: '7',
y: 19
}, {
name: '8',
y: 21
}, {
name: '9',
y: 34
}, {
name: '10',
y: 5
}, {
name: '11',
y: 9
}]
},
{
name: 'B1',
data: [{
name: '6',
y: 14
}, {
name: '7',
y: 19
}, {
name: '8',
y: 21
}, {
name: '9',
y: 34
}, {
name: '10',
y: 5
}, {
name: '11',
y: 9
}]
},
{
name: 'C1',
data: [{
name: '6',
y: 14
}, {
name: '7',
y: 19
}, {
name: '8',
y: 21
}, {
name: '9',
y: 34
}, {
name: '10',
y: 5
}, {
name: '11',
y: 9
}]
}];
Highcharts.chart('container', {
chart: {
type: 'column'
},
title: {
text: 'Highcharts multi-series drilldown'
},
subtitle: {
text: 'The <em>allowPointDrilldown</em> option makes point clicks drill to the whole category'
},
xAxis: {
type: 'category'
},
plotOptions: {
series: {
borderWidth: 0,
dataLabels: {
enabled: true
}
}
},
series: datadrill
});
});
Would you like to achieve something like this? Please test the drilldown only for the first point (A1).
Demo: https://jsfiddle.net/BlackLabel/wn65fkxj/
I did some changes in your data, like:
datadrill = [{
id: 'Details1',
name: 'A1',
data: [{
name: '6',
y: 14
}, {
name: '7',
y: 19
}, {
name: '8',
y: 21
}, {
name: '9',
y: 34
}, {
name: '10',
y: 5
}, {
name: '11',
y: 9
}]
},
{
id: 'Details2',
name: 'B1',
data: [{
name: '6',
y: 14
}, {
name: '7',
y: 19
}, {
name: '8',
y: 21
}, {
name: '9',
y: 34
}, {
name: '10',
y: 5
}, {
name: '11',
y: 9
}]
},
{
id: 'Details3',
name: 'C1',
data: [{
name: '6',
y: 14
}, {
name: '7',
y: 19
}, {
name: '8',
y: 21
}, {
name: '9',
y: 34
}, {
name: '10',
y: 5
}, {
name: '11',
y: 9
}]
}
];
Also, I added null points to trigger different drilldown to each.
API: https://api.highcharts.com/highcharts/drilldown.allowPointDrilldown

Filtering, and displaying nested object data

I have an array of objects that need to be filtered through, and displayed depending on which checkbox is selected. I can get it to work with a one dimension array, when I nest deeper I don't understand how to get everything working again.
This is the initial function that filters the array:
computed: {
selectedFilters: function() {
let filters = [];
let checkedFilters = this.shopFilters.filter(obj => obj.checked);
checkedFilters.forEach(element => {
filters.push(element.value);
});
return filters;
}
}
Tis is the data it pulls from:
shopFilters: [
{
name: 'price',
categories: [
{
checked: false,
value: 'Under $50'
},
{
checked: false,
value: '$50 to $100'
},
{
checked: false,
value: '$100 to $150'
},
{
checked: false,
value: '$150 to $200'
},
{
checked: false,
value: 'Over $200'
},
]
},
{
name: 'sports',
categories: [
{
checked: false,
value: 'lifestyle'
},
{
checked: false,
value: 'running'
},
{
checked: false,
value: 'basketball'
},
{
checked: false,
value: 'football'
},
{
checked: false,
value: 'soccer'
},
{
checked: false,
value: 'training & gym'
},
{
checked: false,
value: 'skateboarding'
},
{
checked: false,
value: 'baseball / softball'
},
{
checked: false,
value: 'golf'
}
]
}
]
This is the function that filters through the product data in another file to display on the page:
methods: {
getfilteredData: function() {
this.filteredData = data;
let filteredDataByfilters = [];
// first check if filters where selected
if (this.selectedFilters.length > 0) {
filteredDataByfilters= this.filteredData.filter(obj => this.selectedFilters.every(val => obj.indexOf(val) >= 0));
this.filteredData = filteredDataByfilters;
}
}
}
What the data looks like:
const data = [
{
name: 'SNKR 001',
gender: 'Men',
price: 100,
sport: 'running',
width: 'Wide',
colors: ['black', 'white', 'green', 'pink'],
sizes: [3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 14, 15],
image: '../assets/images/shoe-1.png'
},
{
name: 'SNKR 002',
gender: 'Men',
price: 100,
sport: 'running',
width: 'Wide',
colors: ['black', 'white', 'green', 'pink'],
sizes: [3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 14, 15],
image: '../assets/images/shoe-1.png'
},
{
name: 'SNKR 003',
gender: 'Men',
price: 100,
sport: 'training & gym',
width: 'Wide',
colors: ['black', 'white', 'green', 'pink'],
sizes: [3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 14, 15],
image: '../assets/images/shoe-1.png'
},
{
name: 'SNKR 004',
gender: 'Men',
price: 100,
sport: 'lifestyle',
width: 'Wide',
colors: ['black', 'white', 'green', 'pink'],
sizes: [3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 14, 15],
image: '../assets/images/shoe-1.png'
}
];
export default data;
You can merge all filter into 1 array:
const categories = this.shopFilters.map(item => item.categories)
const flatFilters = [].concat.apply([], categories)
then the filter will become flat:
flatFilters: [{
checked: false,
value: 'Under $50'
},
{
checked: false,
value: '$50 to $100'
},
{
checked: false,
value: '$100 to $150'
},
{
checked: false,
value: '$150 to $200'
},
{
checked: false,
value: 'Over $200'
},
{
checked: false,
value: 'lifestyle'
},
{
checked: false,
value: 'running'
},
{
checked: false,
value: 'basketball'
},
{
checked: false,
value: 'football'
},
{
checked: false,
value: 'soccer'
},
{
checked: false,
value: 'training & gym'
},
{
checked: false,
value: 'skateboarding'
},
{
checked: false,
value: 'baseball / softball'
},
{
checked: false,
value: 'golf'
}
]
then use can apply old logic
computed: {
selectedFilters: function() {
let filters = [];
const categories = this.shopFilters.map(item => item.categories)
const flatFilters = [].concat.apply([], categories)
let checkedFilters = flatFilters.filter(obj => obj.checked);
checkedFilters.forEach(element => {
filters.push(element.value);
});
return filters;
}

Highcharts Export PDF offline by clicking external button

I would like to create an external button named 'Export to PDF' outside the highcharts and hide the default buttons "Export" & "Print" in highchart export options.
I am using localhost for my project and have to export the highcharts to PDF offline. I have created an example at jsfiddle below.
jsfiddle
$(function () {
window.chart = new Highcharts.Chart('container', {
chart: {marginLeft: 400},
title: {
text: 'Report',
x: 50,
y: 130,
margin: 150
}, plotOptions: {
column: {
stacking: 'normal',
dataLabels: {
enabled: true,
formatter: function() {
var val = this.y;
if (val < 1) {
return '';
}
return val;
},
color: (Highcharts.theme && Highcharts.theme.dataLabelsColor) || 'white'
}
}
}, exporting:{
enabled: false,
}, xAxis: {
categories: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30']
},
labels: {
items: [{
//html: 'Total fruit consumption',
style: {
left: '50px',
top: '18px',
color: (Highcharts.theme && Highcharts.theme.textColor) || 'black'
}
}]
},
series: [{
type: 'column',
name: 'Jane',
data: [3, 2, 1, 3, 4, 3, 2, 1, 3, 4, 3, 2, 1, 3, 4, 3, 2, 1, 3, 4, 3, 2, 1, 3, 4, 3, 2, 1, 3, 4]
}, {
type: 'column',
name: 'John',
data: [2, 3, 5, 7, 6, 2, 3, 5, 7, 6, 2, 3, 5, 7, 6, 2, 3, 5, 7, 6, 2, 3, 5, 7, 6, 2, 3, 5, 7, 6],
center: [0, 100],
}, {
type: 'pie',
name: 'Total Visit',
data: [{
name: 'Jane',
y: 13,
color: Highcharts.getOptions().colors[0] // Jane's color
}, {
name: 'John',
y: 23,
color: Highcharts.getOptions().colors[1] // John's color
}],
center: [-250, 250],
size: 150,
showInLegend: false,
dataLabels: {
enabled: true
}
}]
}, function (chart) {
chart.renderer.text('Total Visit', 120, 320)
.css({
color: '#4572A7',
fontSize: '16px'
})
.add();
});
$('#export_PDF').click(function () {
chart.exportChart({
type: 'application/pdf',
sourceWidth: 1700,
sourceHeight: 600,
});
});
});
Can I know how to export the highcharts to PDF offline by clicking the external button that I have created?
If you need to have the pdf export to work offline, you need to setup the highcharts render on the server side.
Render charts on the server
Just edit your code for button on click with this,
$('#export_PDF').click(function () {
chart.exportChartLocal({
type: 'application/pdf',
sourceWidth: 1700,
sourceHeight: 600,
});
});

Categories

Resources