Related
I have to make a network map in js in which there are components included in component (etc..). I use canvas to do this with the Fabric.js library.
I get a json from an api like this :
{
"name": "BAY_01",
"type": "Bay",
"_links": [{
"name": "SERVER_01",
"type": "Server",
"_links": [{
"name": "CPU"
}, {
etc...
}],
}]
}
I think the best way to implement the draw of these components is to make it recursive but I don't know how to do this.
Can anyone help me to solve my issue?
Here is an example of using recursion by name. Basically you need to determine if a property of the JSON object is an array then call recursion function again, else check if property name is "name" then call function to draw shape by name:
var obj = {
"name": "BAY_01",
"type": "Bay",
"_links": [{
"name": "SERVER_01",
"type": "Server",
"_links": [{
"name": "CPU"
}, {
"name": "SERVER_02",
"type": "Server",
"_links": [{
"name": "CPU2"
}]
}]
}]
};
function goThroughtObject(obj, name) {
var key;
if (obj instanceof Array) {
return obj.map(function(value) {
if (typeof value === "object") {
goThroughtObject(value, name)
}
return value;
})
} else {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (key === name){
drawByName(obj[key]);
}
if (obj[key] instanceof Array || (obj[key] !== null && obj[key].constructor === Object)) {
goThroughtObject(obj[key], name);
}
}
}
}
};
//implement fabricjs logic in this function
function drawByName (name) {
console.log("Fabricjs will draw a: " + name);
}
goThroughtObject(obj, 'name');
Please remember to use canvas.renderAll(); function after the recursion function as long as it will be better performance.
I am using stackByValue of amcharts to arranged a stack column chart . I would like to add a bullet point on each chart to check if they meet a certain target or not. Currently what happen is the bullet point is added to the stacked chart is there a way that I could do this without removing the stackByValue ?
Here is my JsFiddle: `http://jsfiddle.net/sky5rvdz/13/
$(document).ready(function() {
AmCharts.addInitHandler(function(chart) {
// Check if enabled
if (chart.valueAxes === undefined || chart.valueAxes.length === 0 || !chart.valueAxes[0].stackByValue)
return;
// Disable built-in stacking
chart.valueAxes[0].stackType = "none";
// Prepare all graphs
for (var i = 0; i < chart.graphs.length; i++) {
var graph = chart.graphs[i];
graph.originalValueField = graph.valueField;
graph.valueField = graph.originalValueField + "Close";
graph.openField = graph.originalValueField + "Open";
graph.clustered = false;
if (graph.labelText)
graph.labelText = graph.labelText.split("[[value]]").join("[[" + graph.originalValueField + "]]");
if (graph.balloonText)
graph.balloonText = graph.balloonText.split("[[value]]").join("[[" + graph.originalValueField + "]]");
}
// Go through each category and order values
for (var i = 0; i < chart.dataProvider.length; i++) {
// Assemble intermediate array of data point items
var dp = chart.dataProvider[i];
var items = [];
var sum = 0;
for (var x = 0; x < chart.graphs.length; x++) {
var graph = chart.graphs[x];
items.push({
"graph": graph,
"value": dp[graph.originalValueField]
});
}
var sortValue = 0;
// Order according to value
items.sort(function(a, b) {
if (sortValue == 0) {
return a.value - b.value;
} else {
return b.value - a.value;
}
});
// Calculate open and close fields
var offset = 0;
for (var x = 0; x < items.length; x++) {
var item = items[x];
dp[item.graph.openField] = offset;
dp[item.graph.valueField] = offset + dp[item.graph.originalValueField];
offset = dp[item.graph.valueField];
}
}
}, ["serial"]);
var response = [{
"name": "Jan",
"target": 2062186.74,
"USA": 0,
"MAN": 605873.95,
"PAN": 759763.5
}, {
"name": "Feb",
"target": 1492210.81,
"MAN": 499538.43,
"PAN": 559504.95,
"USA": 5850
}, {
"name": "Mar",
"target": 1455750,
"MAN": 403715.2,
"PAN": 694353.95,
"USA": 0
}, {
"name": "Apr",
"target": 2008623.96,
"USA": 0,
"MAN": 409993.3,
"PAN": 511030
}];
var graphs = Object.keys(response[0]).reduce(function(graphsArray, key) {
if (key !== "name" && key !== "target") {
graphsArray.push({
"balloonText": "<b>[[value]]</b>",
"balloonFunction": function(item, graph) {
var result = graph.balloonText;
for (var key in item.dataContext) {
if (item.dataContext.hasOwnProperty(key) && !isNaN(item.dataContext[key])) {
var formatted = AmCharts.formatNumber(item.dataContext[key], {
precision: chart.precision,
decimalSeparator: chart.decimalSeparator,
thousandsSeparator: chart.thousandsSeparator
}, 2);
result = result.replace("[[" + key + "]]", formatted);
}
}
return result;
},
"fillAlphas": 0.8,
"labelText": "[[title]]<br>",
"labelPosition": "middle",
"lineAlpha": 0.3,
"title": key,
"type": "column",
"color": "#000000",
//"showAllValueLabels": true,
"valueField": key
});
}
if (key === "target") {
graphsArray.push({
"balloonText": "<b>[[value]]</b>",
"balloonFunction": function(item, graph) {
var result = graph.balloonText;
for (var key in item.dataContext) {
if (item.dataContext.hasOwnProperty(key) && !isNaN(item.dataContext[key])) {
var formatted = AmCharts.formatNumber(item.dataContext[key], {
precision: chart.precision,
decimalSeparator: chart.decimalSeparator,
thousandsSeparator: chart.thousandsSeparator
}, 2);
result = result.replace("[[" + key + "]]", formatted);
}
}
return result;
},
"valueAxis": "v2",
"lineAlpha": 0,
"bullet": "round",
"bulletSize": 20,
"title": "target",
"type": "line",
"valueField": "target"
});
}
return graphsArray;
}, []);
var chart = AmCharts.makeChart("chartdiv", {
"type": "serial",
"theme": "light",
"legend": {
"horizontalGap": 10,
"maxColumns": 1,
"position": "right",
"useGraphSettings": true,
"markerSize": 10
},
"numberFormatter": {
"precision": 1,
"decimalSeparator": ".",
"thousandsSeparator": ","
},
"dataProvider": response,
"valueAxes": [{
"id": "v1",
"stackType": "regular",
/**
* A proprietary setting `stackByValue` which is not an
* official config option. It will be used by our custom
* plugin
*/
"stackByValue": true,
"axisAlpha": 0.3,
"gridAlpha": 0
}, , {
"id": "v2",
"axisAlpha": 0.3,
"gridAlpha": 0,
"position": "top",
"title": "Target"
}],
"gridAboveGraphs": true,
"startDuration": 0,
"graphs": graphs,
"categoryField": "name",
"categoryAxis": {
"gridPosition": "start",
"axisAlpha": 0,
"gridAlpha": 0,
"position": "left"
},
"export": {
"enabled": true
}
});
console.log(graphs);
console.log(response);
Object.keys(response[0]).forEach(key => {
console.log(key) // returns the keys in an object
// console.log(a[key]) // returns the appropriate value
})
});
The issue is that the sort by value plugin assumes that all graphs need to be sorted and modified to use the open/close fields to achieve this effect, which causes it to move your bullet to an incorrect location. Since you have multiple axes, you can modify the plugin to check if the graph belongs to the first axis and set a flag to be used to re-add the point correctly:
// Go through each category and order values
for (var i = 0; i < chart.dataProvider.length; i++) {
// ...
for (var x = 0; x < chart.graphs.length; x++) {
var graph = chart.graphs[x];
items.push({
"graph": graph,
// check if this graph's data points need to be omitted from the sorting process.
"ignoreSort": (graph.valueAxis && graph.valueAxis !== chart.valueAxes[0].id),
"value": dp[graph.originalValueField]
});
}
// ...
// Calculate open and close fields
var offset = 0;
for (var x = 0; x < items.length; x++) {
var item = items[x];
if (!item.ignoreSort) {
//process the pont as normal if it doesn't have the flag set with open/value fields
dp[item.graph.openField] = offset;
dp[item.graph.valueField] = offset + dp[item.graph.originalValueField];
offset = dp[item.graph.valueField];
} else {
//otherwise treat the point as a normal graph and use the value field
dp[item.graph.valueField] = dp[item.graph.originalValueField]
}
}
}
You'll also want to synchronize the axes' min/max values so that your target is correctly placed with respect to your stacked bars. You can achieve this using another custom plugin through addInitHandler:
//synchronizes axes' min/max values using a custom synchronizeValueAxes property
//(while synchronizeGrid exists, it doesn't work with this particular chart)
AmCharts.addInitHandler(function(chart) {
if (chart.synchronizeValueAxes) {
setTimeout(function() {
var max = chart.valueAxes.reduce(function(max, axis) {
if (!isNaN(axis.max)) {
return Math.max(max, axis.max);
} else {
return max;
}
}, Number.MIN_VALUE);
var min = chart.valueAxes.reduce(function(min, axis) {
if (!isNaN(axis.min)) {
return Math.min(min, axis.min);
} else {
return min;
}
}, Number.MAX_VALUE);
chart.valueAxes.forEach(function(axis) {
axis.maximum = max;
axis.minimum = min;
axis.strictMinMax = true;
});
chart.validateData();
}, 500);
}
}, ["serial"]);
Updated fiddle
I'm new in AmCharts.js. I want to create a chart with multiple value axis which represents occurences of prices on different websites for one product according to datetime (up to hours or better minutes) (not date).
So I need to draw chart with multiple lines which doesn't depends on each other. So when I one is null value, the value of second line is still drawn.
Every product can have different number of occurences so I can't hardcode colors and another properties of datasets.
One of the best approaches I found is AmStockChart because there can be drawn multiple lines. But there are multiple problems. One of them is that it needs to "compare" one line to another lines so if there is no value for datetime xxx, the value of line2 is not shown for this datetime.
The datetimes can differ (for one line is it 12.01 13:00, for another is it 14:00 etc).
This is my solution which doesn't work correctly since it has to be compared.
The JSON is: {'web_name':[[[year,month,day,hour...],price],[[[year,month....}
<script>
var lines = [];
var dataSets = [];
generateChartData();
function generateChartData() {
var google_chart_json = JSON;
var loopcounter = -1;
$.each(google_chart_json, function (key, val) {
var line = [];
loopcounter = loopcounter + 1;
$.each(val, function (_, scan) {
var year = scan[0][0];
var month = scan[0][1];
var day = scan[0][2];
var hour = scan[0][3];
var minute = scan[0][4];
var price = scan[1];
var data = {
'date': new Date(year, month - 1, day, hour, minute),
'value': price
};
line.push(data);
});
line.sort(function (lhs, rhs) {
return lhs.date.getTime() - rhs.date.getTime();
});
lines.push([key, line]);
});
console.log('LINES');
console.log(lines);
$.each(lines, function (_, name_line) {
var dict = {
'title': name_line[0],
"fieldMappings": [{
"fromField": "value",
"toField": "value"
}],
"dataProvider": name_line[1],
"categoryField": "date"
};
dataSets.push(dict);
});
}
console.log(dataSets)
var chart = AmCharts.makeChart("chartdiv", {
"allLabels": [
{
"text": "Free label",
"bold": true,
"x": 20,
"y": 20
}
],
categoryAxesSettings: {
minPeriod: "hh",//(at least that is not grouped)
groupToPeriods: ["DD", "WW", "MM"]//(Data will be grouped by day,week and month)
},
"type": "stock",
"theme": "light",
"dataSets": dataSets,
"panels": [{
"showCategoryAxis": false,
"title": "Value",
"percentHeight": 70,
"stockGraphs": [{
"id": "g1",
"valueField": "value",
"comparable": true,
"compareField": "value",
"balloonText": "[[date]][[title]]:<b>[[value]]</b>",
"compareGraphBalloonText": "[[title]]:<b>[[value]]</b>"
}],
"stockLegend": {
"periodValueTextComparing": "[[percents.value.close]]%",
"periodValueTextRegular": "[[value.close]]"
}
}],
{#https://docs.amcharts.com/javascriptcharts/ChartScrollbar#}
"chartScrollbarSettings": {
"graph": "g1",
"color": "#333333"
},
"chartCursorSettings": {
"valueBalloonsEnabled": true,
"fullWidth": true,
"cursorAlpha": 0.1,
"valueLineBalloonEnabled": true,
"valueLineEnabled": true,
"valueLineAlpha": 0.5
},
"periodSelector": {
"position": "left",
"periods": [{
"period": "MM",
"selected": true,
"count": 1,
"label": "1 month"
}, {
"period": "YYYY",
"count": 1,
"label": "1 year"
}, {
"period": "YTD",
"label": "YTD"
}, {
"period": "MAX",
"label": "MAX"
}]
},
"dataSetSelector": {
"position": "left",
},
"export": {
"enabled": true
}
});
chart.panelsSettings.recalculateToPercents = "never";
</script>
When I put the same datetimes for the values, it shows lines. But when each value has different datetime, it shows nothing except the first line:
Another solution (Line chart FiddleJS) has hardcoded lines which I can't do because there are different numbers of them. But the main problem is that they have own value axises.
Could you tell me what what to do in my code to achieve not compared multiple line chart with allowed different datetimes for different values and lines? Or if you know - recommend some type of amchart which can do this all?
The comparison requires that every date/time has to match or it won't show every point, as you noticed. In the AmCharts knowledge base, there's a demo that implements a mini-plugin that syncs the timestamps in your data prior to initializing the chart:
/**
* amCharts plugin: sync timestamps of the data sets
* ---------------
* Will work only if syncDataTimestamps is set to true in chart config
*/
AmCharts.addInitHandler(function(chart) {
// check if plugin is enabled
if (chart.syncDataTimestamps !== true)
return;
// go thorugh all data sets and collect all the different timestamps
var dates = {};
for (var i = 0; i < chart.dataSets.length; i++) {
var ds = chart.dataSets[i];
for (var x = 0; x < ds.dataProvider.length; x++) {
var date = ds.dataProvider[x][ds.categoryField];
if (dates[date.getTime()] === undefined)
dates[date.getTime()] = {};
dates[date.getTime()][i] = ds.dataProvider[x];
}
}
// iterate through data sets again and fill in the blanks
for (var i = 0; i < chart.dataSets.length; i++) {
var ds = chart.dataSets[i];
var dp = [];
for (var ts in dates) {
if (!dates.hasOwnProperty(ts))
continue;
var row = dates[ts];
if (row[i] === undefined) {
row[i] = {};
var d = new Date();
d.setTime(ts);
row[i][ds.categoryField] = d;
}
dp.push(row[i]);
}
dp.sort(function(a,b){
return new Date(a[ds.categoryField]) - new Date(b[ds.categoryField]);
});
ds.dataProvider = dp;
}
}, ["stock"]);
Just add this before your chart code and set the custom syncDataTimestamps property to true in the top level of your chart config and it will run upon initialization.
For each node, i have a circle which I color with the help of a function ColorType(d):
node.append("circle")
.attr("r", 20)
.attr("y", -25)
.style("fill", function(d) { return ColorType(d); })
.style("stroke-width",0.5)
.style("stroke",'black')
.attr("opacity", "1");
My ColorType function is
function ColorType(d){
for (var i = 0; i < TypesTab.length; i++) {
if (d.type == TypesTab[i].type) { return ColorAction;}
}
}
in the above function, d.type is the type of my node (see below the json file strucutre). And TypesTab[i].type is each of my types stored separately in types, checking if the node type is the same as one of the value of the type in types, if so then applies the ColorAction which colors the node circle.
and here is the ColorAction code, which is embedded in each color picker container that is appended to each type in types, the list which is inserted to #filterColor html dom. so each type has a color picker container which is supposed to color its own type only.
$(document).ready(function () {
$.getJSON("databeta.json", function (obj) {
$('#filterColor').data('types', obj.types.map(function (o) {
// console.log(o.type);
return o.type;
})).append(obj.types.map(function (o) {
return '<li>' + o.type + '<input class="color-picker" type="text"/></li>';
}).join(''));
var data = $('#filterColor').data('types'); //stores all types
mynodes = obj.nodes;
console.log("mynodes : ", mynodes); //array of all my nodes
console.log("mynodes : ", mynodes[3].type); //reading the type of the fourth object in the nodes array
$("#filterColor .color-picker").each(function(){
$(this).spectrum({
color: (function (m, s, c) {
return (c ? arguments.callee(m, s, c - 1) : '#') +
s[m.floor(m.random() * s.length)]
})(Math, '0123456789ABCDEF', 5), //generate random initial color for each container
preferredFormat: "rgb",
showInput: true,
showPalette: true,
showAlpha: true,
palette: [["red", "rgba(0, 255, 0, .5)", "rgb(0, 0, 255)"]],
change: function(color) {
MyNode = d3.select("#node").selectAll(".entreprise").select("circle");
MyNode.style("fill", function(d) {
return d3.rgb(color.toHexString())
});
Coloration = d3.rgb(color.toHexString());
}
});
});
});
});
the problem is that when i hardcode the type in the ColorType(d) function,
if (d.type == "school") { return ColorAction;}
it successfully colors the school typed nodes only. However, if i want to make it dynamic so that it colors the nodes with the type that the color picker is assigned to, it fails, because I can't make the connection with the each of o.type. So the question is to pass the each of o.type into the ColorAction and/or ColorType(d) so that each container only colors the nodes of its own type.
Here is an unsuccessfull attempt, because it doesn't take into consideration the o.type and reads the type in types from a global variable (TypesTab) that holds all types in types:
function ColorType(d){
for (var i = 0; i < TypesTab.length; i++) {
if (d.type == TypesTab[i].type) { return ColorAction;}
}
}
The below is the json structure:
{
"nodes": [
{
"type": "school",
"country": "US",
"name": "saint peter's",
"id": 1006
},
{
"type": "univeristy",
"country": "Brazil",
"name": "saint joseph's",
"id": 1007
}
...
],
"links": [
{
"source": 1006,
"target": 1007,
"value": 20
},
...
],
"types": [
{
"type": "school",
"image": "image01"
},
{
"type": "univeristy",
"image": "image02"
},
{
"type": "company",
"image": "image03"
},
...
]
}
This how I would read / import JSON. Inside the for in is basic, so you would need to change that part to suit your needs.
d3.json("data.json", function (error, data) {
console.log(d3.values(data)); // do this as a check
for (var d in data) {
d.nodes = +d.nodes;
d.links = +d.links;
d.types = +d.types;
}
// svg can go here
})
I have an array of objects that I would like to trim down based on a specific key:value pair. I want to create an array that includes only one object per this specific key:value pair. It doesn't necessarily matter which object of the duplicates is copied to the new array.
For example, I want to trim based on the price property of arrayWithDuplicates, creating a new array that only includes one of each value:
var arrayWithDuplicates = [
{"color":"red",
"size": "small",
"custom": {
"inStock": true,
"price": 10
}
},
{"color":"green",
"size": "small",
"custom": {
"inStock": true,
"price": 30
}
},
{"color":"blue",
"size": "medium",
"custom": {
"inStock": true,
"price": 30
}
},
{"color":"red",
"size": "large",
"custom": {
"inStock": true,
"price": 20
}
}
];
Would become:
var trimmedArray = [
{"color":"red",
"size": "small",
"custom": {
"inStock": true,
"price": 10
}
},
{"color":"green",
"size": "small",
"custom": {
"inStock": true,
"price": 30
}
},
{"color":"red",
"size": "large",
"custom": {
"inStock": true,
"price": 20
}
}
];
Is there a JavaScript or Angular function that would loop through and do this?
EDIT: The property to filter on is nested within another property.
This function removes duplicate values from an array by returning a new one.
function removeDuplicatesBy(keyFn, array) {
var mySet = new Set();
return array.filter(function(x) {
var key = keyFn(x), isNew = !mySet.has(key);
if (isNew) mySet.add(key);
return isNew;
});
}
var values = [{color: "red"}, {color: "blue"}, {color: "red", number: 2}];
var withoutDuplicates = removeDuplicatesBy(x => x.color, values);
console.log(withoutDuplicates); // [{"color": "red"}, {"color": "blue"}]
So you could use it like
var arr = removeDuplicatesBy(x => x.custom.price, yourArrayWithDuplicates);
I don't think there's a built-in function in Angular, but it isn't hard to create one:
function removeDuplicates(originalArray, objKey) {
var trimmedArray = [];
var values = [];
var value;
for(var i = 0; i < originalArray.length; i++) {
value = originalArray[i][objKey];
if(values.indexOf(value) === -1) {
trimmedArray.push(originalArray[i]);
values.push(value);
}
}
return trimmedArray;
}
Usage:
removeDuplicates(arrayWithDuplicates, 'size');
Returns:
[
{
"color": "red",
"size": "small"
},
{
"color": "blue",
"size": "medium"
},
{
"color": "red",
"size": "large"
}
]
And
removeDuplicates(arrayWithDuplicates, 'color');
Returns:
[
{
"color": "red",
"size": "small"
},
{
"color": "green",
"size": "small"
},
{
"color": "blue",
"size": "medium"
}
]
Use Array.filter(), keeping track of values by using an Object as a hash, and filtering out any items whose value is already contained in the hash.
function trim(arr, key) {
var values = {};
return arr.filter(function(item){
var val = item[key];
var exists = values[val];
values[val] = true;
return !exists;
});
}
You can use underscore for this:
//by size:
var uSize = _.uniqBy(arrayWithDuplicates, function(p){ return p.size; });
//by custom.price;
var uPrice = _.uniqBy(arrayWithDuplicates, function(p){ return p.custom.price; });
You can use lodash to remove duplicate objects:
import * as _ from 'lodash';
_.uniqBy(data, 'id');
Here 'id' is your unique identifier
Try the following function:
function trim(items){
const ids = [];
return items.filter(item => ids.includes(item.id) ? false : ids.push(item.id));
}
using lodash you can filter it out easily
the first parameter will be your array and second will be your field with duplicates
_.uniqBy(arrayWithDuplicates, 'color')
it will return an array with unique value
Simple solution although not the most performant:
var unique = [];
duplicates.forEach(function(d) {
var found = false;
unique.forEach(function(u) {
if(u.key == d.key) {
found = true;
}
});
if(!found) {
unique.push(d);
}
});
for (let i = 0; i < arrayWithDuplicates.length; i++) {
for (let j = i + 1; j < arrayWithDuplicates.length; j++) {
if (arrayWithDuplicates[i].name === students[j].name) {
arrayWithDuplicates.splice(i, 1);
}
}
}
this will work perfectly...and this will delete first repeated array.
To delete last repeated array we only have to change
arrayWithDuplicates.splice(i, 1) ; into
arrayWithDuplicates.splice(j, 1);
Off the top of my head there is no one function that will do this for you as you are dealing with an array of objects and also there is no rule for which duplicate would be removed as duplicate.
In your example you remove the one with size: small but if you were to implement this using a loop you'd most likely include the first and exclude the last as you loop through your array.
It may very well be worth taking a look at a library such as lodash and creating a function that uses a combination of it's API methods to get the desired behaviour you want.
Here is a possible solution you could use making use of basic Arrays and a filter expression to check whether a new item would be considered a duplicate before being attached to a return result.
var arrayWithDuplicates = [
{"color":"red", "size": "small"},
{"color":"green", "size": "small"},
{"color":"blue", "size": "medium"},
{"color":"red", "size": "large"}
];
var reduce = function(arr, prop) {
var result = [],
filterVal,
filters,
filterByVal = function(n) {
if (n[prop] === filterVal) return true;
};
for (var i = 0; i < arr.length; i++) {
filterVal = arr[i][prop];
filters = result.filter(filterByVal);
if (filters.length === 0) result.push(arr[i]);
}
return result;
};
console.info(reduce(arrayWithDuplicates, 'color'));
You can check out some literature on Array filtering here
If you need to provide a preference on which item to remove you could define extra parameters and logic that will make extra property checks before adding to a return value.
Hope that helps!
Here is the typescript way
public removeDuplicates(originalArray:any[], prop) {
let newArray = [];
let lookupObject = {};
originalArray.forEach((item, index) => {
lookupObject[originalArray[index][prop]] = originalArray[index];
});
Object.keys(lookupObject).forEach(element => {
newArray.push(lookupObject[element]);
});
return newArray;
}
And
let output = this.removeDuplicates(yourArray,'color');
This is just another 'feature' based on yvesmancera's solution (after I started tinkering for my own solution) Also noted we are only allowed to currently use IE 11, so limited ES5 is allowed.
var newArray = RemoveDuplicates(myArray,'Role', 2);
function RemoveDuplicates(array, objKey, rtnType) {
var list = [], values = [], value;
for (var i = 0; i < array.length; i++) {
value = array[i][objKey];
if(values.indexOf(value) === -1){
list.push(array[i]);
values.push(value);
}
}
if(rtnType == 1)
return list;
return values;
};
Hoping this will work for most, if not all arrays when filtering out objects based on a single object property value.