How to sum duplicates in an array - javascript

I am creating a javascriptchart, which I have done succesfully but I am lot having duplicates.
Below is a sample xmlwhere I am retrieving data from to display on my Bar chart.
How do I sum the duplicate values retrieved from the xml?
xml:
<counts>
<serial>3123111</serial>
<scans>3</scans>
<prints>1</prints>
<copies>0</copies>
</counts>
<counts>
<serial>3123111</serial>
<scans>0</scans>
<prints>2</prints>
<copies>0</copies>
</counts>
<counts>
<serial>AHTSD111</serial>
<scans>0</scans>
<prints>1</prints>
<copies>2</copies>
</counts>
<counts>
<serial>AHTSD111</serial>
<scans>0</scans>
<prints>1</prints>
<copies>2</copies>
</counts>
Expected result below which I use for the barchart:
<counts>
<serial>3123111</serial>
<scans>3</scans>
<prints>3</prints>
<copies>0</copies>
</counts>
<counts>
<serial>AHTSD111</serial>
<scans>0</scans>
<prints>2</prints>
<copies>4</copies>
</counts>
Data retrieved and pushed to the Chart data, I know i have to do a loop to sum the values of scan, prints, copies for each Serial Number but I still keep getting duplicate values, I just left the code out.
$.ajax({
url: "http://localhost5/api/",
dataType: 'xml',
method: "GET",
success: function (data) {
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(data, "text/html");
data = xmlDoc.getElementsByTagName("JobCounts");
var items = [];
var serial, prints, copies, scans;
function getAsText(parent, name) {
return parent.getElementsByTagName(name)[0].textContent
}
function getAsInt(parent, name) {
return parseInt(getAsText(parent, name));
}
for (i = 0; i < data.length; i++) {
items.push({
serial: getAsText(data[i], "serial"),
prints: getAsInt(data[i], "prints"),
copies: getAsInt(data[i], "copies"),
scans: getAsInt(data[i], "scans")
});
}
items = items.reduce((a, c) => {
var same = a.find(v => v.serial == c.serial);
console.log(same);
if (same) {
same.prints += c.prints;
same.copies += c.copies;
same.scans += c.scans;
} else {
a.push(c);
}
return a;
}, []);
// "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js"
// JS chart library I am using
var chartdata = {
labels: serial,
title: "Date",
datasets: [
{
label: 'Copies',
backgroundColor: 'rgba(0,255,0,0.6)',
data: copies
}
]
};
var ctx = $("#mycanvas");
var options = {
responsive: true,
title: {
display: true,
position: "top",
text: "DashBoard",
fontSize: 12,
fontColor: "#111"
},
legend: {
display: true,
position: "bottom",
labels: {
fontSize: 9
}
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
};
var barGraph = new Chart(ctx, {
'type': 'bar',
data: chartdata,
options : options,
"categoryField": "date",
"categoryAxis": {
"autoGridCount": false,
"gridCount": chartdata.length,
"gridPosition": "start",
"labelRotation": 90,
"startOnAxis": false,
"title":"Date"
}
});
},
error: function (data) {
console.log(data);
}
});
}

Instead of storing the info in 3 different arrays, you can stored it in a single array of objects which will make it easier to merge objects that have the same serial number.
Here is what I mean:
var items = [];
function getAsText(parent, name){
return parent.getElementsByTagName(name)[0].textContent
}
function getAsInt(parent, name){
return parseInt(getAsText(parent, name));
}
for (i = 0; i < data.length; i++) {
items.push({serial: getAsText(data[i], "serial"),
prints: getAsInt(data[i], "prints"),
copies: getAsInt(data[i], "copies"),
scans: getAsInt(data[i], "scans")});
}
// Merge items with the same `serial` value;
items = items.reduce((a, c) => {
var same = a.find(v => v.serial == c.serial);
if(same){
same.prints += c.prints;
same.copies += c.copies;
same.scans += c.scans;
}else{
a.push(c);
}
return a;
}, []);

You can iterate each counts element .children, .map() each .tagName and .textContent to an object, push the object to an array, iterate the array and increment each SCANS, PRINTS, and COPIES property if SERIAL matches.
Convert JSON to elements by iterating resulting array using .forEach() and Object.entries().
let data = `<counts>
<serial>3123111</serial>
<scans>3</scans>
<prints>1</prints>
<copies>0</copies>
</counts>
<counts>
<serial>3123111</serial>
<scans>0</scans>
<prints>2</prints>
<copies>0</copies>
</counts>
<counts>
<serial>AHTSD111</serial><scans>0</scans><prints>1</prints><copies>2</copies>
</counts>
<counts>
<serial>AHTSD111</serial><scans>0</scans><prints>1</prints><copies>2</copies>
</counts>`;
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(data, "text/html");
let res = [];
for (let {counts:{SERIAL, SCANS, COPIES, PRINTS}} of
[...xmlDoc.querySelectorAll("counts")]
.map(el =>
({counts:[...el.children]
.reduce((o, {tagName, textContent}) =>
Object.assign(o, {[tagName]:textContent}), {})
}))) {
let curr = res.find(({counts:{SERIAL:s}}) => s === SERIAL);
if (!curr) {
res.push({counts:{SERIAL, SCANS, COPIES, PRINTS}})
} else {
curr = curr.counts;
curr.SCANS = +curr.SCANS + +SCANS;
curr.PRINTS = +curr.PRINTS + +PRINTS;
curr.COPIES = +curr.COPIES + +COPIES;
}
}
res.forEach(o => {
let key = Object.keys(o).pop();
let parentNode = document.createElement(key);
Object.entries(o[key])
.forEach(([tagName, textContent]) => {
let childNode = document.createElement(tagName)
childNode.textContent = textContent;
parentNode.appendChild(childNode);
parentNode.appendChild(document.createElement("br"));
document.body.appendChild(parentNode);
})
document.body.appendChild(document.createElement("br"));
})

Related

Display data for specific release selected in release filter at the top of page in rally dashboard

This is my code and in this the data displayed in chart is hole project data but in rally dashboard there is release filter at the top of your page. and i want my chart to show data of the the release selected by that filter and my sdk version in code is 1.33
<!DOCTYPE HTML\>
<script
src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js">
var WORKSPACE_OID = "__WORKSPACE_OID__";
var PROJECT_OID = "__PROJECT_OID__";
var PROJECT_SCOPING_UP = "__PROJECT_SCOPING_UP__";
var PROJECT_SCOPING_DOWN = "__PROJECT_SCOPING_DOWN__";
var MILS_IN_DAY = 86400000;
var DAYS_BACK = 30;
var filterChart;
var currentProjectDataSource;
var fromDate = new Date(new Date().getTime() - (MILS_IN_DAY * DAYS_BACK));
var allDefects = [];
// var currentRelease;
var onLoadAllIssues = function (result) {
// var defects = result.defects.filter(function (defect) {
// return defect.Release && defect.Release.\_refObjectName === currentRelease.Name;
// });
var labels = [];
var openDefects = [];
var closedDefects = [];
var defects = result.defects;
for (var count = 0; count < defects.length; count++) {
allDefects[allDefects.length] = defects[count];
var defect = defects[count];
labels.push(defect.CreationDate.split('T')[0]);
if (defect.ClosedDate !==null) {
closedDefects.push(defect.ClosedDate.split('T')[0]);
}
}
closedDefects.sort();
const counts = {};
labels.forEach(function (x) { counts[x] = (counts[x] || 0) + 1; });
const closedcounts = {};
closedDefects.forEach(function (x) { closedcounts[x] = (closedcounts[x] || 0) + 1; });
mychart(counts,closedcounts,labels)
};
var createCharts = function () {
var loadAllDefectsQuery = {
type: 'defect',
key: 'defects',
fetch: 'CreationDate,ClosedDate,ObjectID,FormattedID,Name,State,Priority',
order: 'CreationDate',
query: '((CreationDate != "null") OR (CreationDate > "' + dojo.date.stamp.toISOString(fromDate, { zulu: true }) +
'"))'
};
currentProjectDataSource.findAll(loadAllDefectsQuery, onLoadAllIssues);
};
var initPage = function () {
currentProjectDataSource = new rally.sdk.data.RallyDataSource(WORKSPACE_OID, PROJECT_OID, PROJECT_SCOPING_UP,
PROJECT_SCOPING_DOWN);
createCharts();
};
rally.addOnLoad(initPage);
function mychart(counts,closedcounts,labels) {
const pielable = labels;
const piedata = counts;
const closedcountsdata = closedcounts;
const data = {
datasets: [
{
label: 'Number of opened defects',
data: piedata,
},
{
label: 'Number of closed defects',
data: closedcountsdata,
}
]
};
const config = {
type: 'line',
data: data,
options: {
scales: {
x: {
min:"2022-01-01",
max:"2022-12-31",
type: 'time',
time:{
unit:'day',
},
},
y: {
beginAtZero: true,
grace: 5,
ticks: {
stepSize: 1,
},
},
},
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'Defect Burndown Chart'
},
tooltip: {
yAlign: 'bottom',
titleMarginBottom: 0,
callbacks: {
title: function (context) {
return( `${context[0].label.slice(0, -13).replace(/,/g, " ")}`)
},
}
}
}
}
};
const myChart = new Chart(
document.getElementById('myChart'),
config
)
filterChart= function filterChart(date){
const year = date.value.substring(0,4);
const month = date.value.substring(5);
const lastday = (y,m)=>{
return new Date(y,m,0).getDate();
}
const startDate = `${date.value}-01`;
const endDate = `${date.value}-${lastday(year,month)}`;
myChart.config.options.scales.x.min=startDate;
myChart.config.options.scales.x.ma`your text`x=endDate;
myChart.update();
}}
</script>

Icons not Displaying on Deck.gl

I think there's an error with the data format that I using but I can't seen to solve it, at the current format no icons are displaying.
var coord = [];
export default function App({
function coordGet() {
for (let i = 0; i < IconPositions.length; i++) {
coord["ID" + i] = [{
name: "ID" + i,
address: "Address" + i,
exits: "Exits" + i,
position: [parseFloat(IconPositions[i][0]), parseFloat(IconPositions[i][1])]
}];
}
coordGet();
}
const iconVar = { data: coordGet() };
function updateLayers() {
const layers = [
new IconLayer({
id: "icon-layer",
data: iconVar.data,
pickable: true,
iconAtlas: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/icon-atlas.png',
iconMapping: ICON_MAPPING,
getIcon: d => 'marker',
getPosition: d => d.position,
sizeScale: 200,
getSize: d => 7,
getColor: d => [Math.sqrt(d.exits), 140, 0],
})
];
return layers;
}

ChartJS combine two arrays with some similarities in one plot

i need to combine two arrays into one barplot without manual manipulation of the arrays.
Example of arrays:
result1 = {a:2,b:5,c:52}
result2 = {a:4,b:3,d:47}
//WHERE (to elaborate):
result1.labels = {a, b, c};
result1.data = {2, 5, 52};
result2.labels = {a, b, d};
result2.data = {4, 3, 47};
var myChart = new Chart(ctx, {
type: 'bar',
data: {
datasets: [{
label: 'Dataset 1',
data: result1.data,
order: 1,
labels: result1.labels
}, {
label: 'Dataset 2',
data: result2.data,
order: 2,
labels: result2.labels
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
Right now i will just get 3 stacks of bars where it will merge the 3. entry of result1 and result2, but i need it to make 4 bars "a, b, c, d".
I know i can manually fill into result1 and add "d:0" and to result2 add "c:0" but as it is getting the data from a database that constantly changes the returned array size, that is not a good solution.
Thanks
You can create a set of all keys and then loop over the object and check if the key already exists. If not add it.
const result1 = {a:2,b:5,c:52};
const result2 = {a:4,b:3,d:47};
const getUniqueKeys = (...objs) => {
// Create a set of all unique keys
const keys = objs.reduce((keys, obj) => {
for(key in obj) {
keys.add(key)
}
return keys;
}, new Set());
// Convert set to array
return [...keys];
}
const addMissingValues = (keys, obj, fill = 0) => {
// Create copy to prevent changing the original object
const copy = {...obj};
for(const key of keys) {
// Add key if it doesn't exist
if(!copy.hasOwnProperty(key)) {
copy[key] = fill;
}
}
// Return copy
return copy;
}
const keys = getUniqueKeys(result1, result2);
const correct1 = addMissingValues(keys, result1);
const correct2 = addMissingValues(keys, result2);
console.log(correct1);
console.log(correct2);

How to separate the difference from an array object?

My existing array object are
//existing object
var existing = [
{
'ProviderCode':'aa',
'msg':'....',
},{
'ProviderCode':'bb',
'msg':'....',
},{
'ProviderCode':'cc',
'msg':'....',
},{
'ProviderCode':'dd',
'msg':'....',
},{
'ProviderCode':'ee',
'msg':'....',
}];
new object I'm comparing to
var new = [
{
'ProviderCode':'bb',
'msg':'....',
},{
'ProviderCode':'cc',
'msg':'....',
},{
'ProviderCode':'ee',
'msg':'....',
},{
'ProviderCode':'ff',
'msg':'....',
},{
'ProviderCode':'gg',
'msg':'....',
}];
I would like to generate same, remove and add array based on the two array objects, I can get the same array object but not the remove and add object from the objects.
var same = []; //bb, cc, ee //these will be the match
var remove = []; //aa , dd //will be remove from existing
var add = []; //ff, gg //will be consider as add
//I can get the same using below:
e.forEach(function(ev,ei,ea){
n.forEach(function(nv,ni,na){
if( ev.ProviderCode === nv.ProviderCode ){
s.push({ProviderCode:ev.ProviderCode,msg:"Same, do nothing"});
}
});
});
/* //output:
[{
"ProviderCode": "bb",
"msg": "Same, do nothing"
}, {
"ProviderCode": "cc",
"msg": "Same, do nothing"
}, {
"ProviderCode": "ee",
"msg": "Same, do nothing"
}]
*/
//but how do I get remove and add array object?
//remove will be:
/* //output:
[{
"ProviderCode": "aa",
"msg": "removed"
}, {
"ProviderCode": "dd",
"msg": "removed"
}]
*/
//add will be:
/* //output:
[{
"ProviderCode": "ff",
"msg": "added"
}, {
"ProviderCode": "gg",
"msg": "added"
}]
*/
You can use Array.prototype.filter & Array.prototype.find for this:
let existing = [{ProviderCode:'aa'},{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'dd'},{ProviderCode:'ee'}];
let newData = [{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'ee'},{ProviderCode:'ff'},{ProviderCode:'gg'}];
let added = newData.filter(d => !existing.find(e => d.ProviderCode === e.ProviderCode));
console.log("ADDED:", added);
let removed = existing.filter(d => !newData.find(e => d.ProviderCode === e.ProviderCode));
console.log("REMOVED:", added);
let same = newData.filter(d => existing.find(e => d.ProviderCode === e.ProviderCode));
console.log("SAME:", same);
With a library like lodash this is a bit easier:
let existing = [{ProviderCode:'aa'},{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'dd'},{ProviderCode:'ee'}];
let newData = [{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'ee'},{ProviderCode:'ff'},{ProviderCode:'gg'}];
console.log("ADDED:" , _.differenceBy(newData, existing, 'ProviderCode'));
console.log("REMOVED:", _.differenceBy(existing, newData, 'ProviderCode'));
console.log("SAME:" , _.intersectionBy(newData, existing, 'ProviderCode'));
<script src="https://unpkg.com/lodash#4.17.15/lodash.min.js"></script>
You can use this little "library" that provides set operations for JS Map objects:
function mapUnion(m1, m2) {
let m = new Map();
for (let [k, v] of m1)
m.set(k, v);
for (let [k, v] of m2)
m.set(k, v);
return m;
}
function mapIntersection(m1, m2) {
let m = new Map();
for (let [k, v] of m1)
if (m2.has(k))
m.set(k, v);
return m;
}
function mapDifference(m1, m2) {
let m = new Map();
for (let [k, v] of m1)
if (!m2.has(k))
m.set(k, v);
return m;
}
Having this, you can convert both your arrays to Maps:
let m1 = new Map(oldArray.map(x => [x.ProviderCode, x]))
let m2 = new Map(newArray.map(x => [x.ProviderCode, x]))
and do whatever you want with these, for example,
console.log(mapIntersection(m1, m2)) // bb=>..., cc=>..., ee=>...
console.log(mapDifference(m1, m2)) // aa=>..., dd=>...
If you need arrays as results:
commonObjects = Array.from(mapIntersection(m1, m2).values())
You could take some sets and get the wanted items for each change.
const
providerCode = ({ ProviderCode }) => ProviderCode;
var existingData = [{ ProviderCode: 'aa' }, { ProviderCode: 'bb' }, { ProviderCode: 'cc' }, { ProviderCode:'dd' }, { ProviderCode: 'ee' }],
newData = [{ ProviderCode: 'bb' }, { ProviderCode: 'cc' }, { ProviderCode: 'ee' }, { ProviderCode: 'ff' }, { ProviderCode: 'gg' }],
existingSet = new Set(existingData.map(providerCode)),
newSet = new Set(newData.map(providerCode)),
same = [...existingSet].filter(Set.prototype.has, newSet),
add = [...newSet].filter(v => !existingSet.has(v)),
remove = [...existingSet].filter(v => !newSet.has(v));
console.log(...same);
console.log(...add);
console.log(...remove);

Chart.js : Is there a way to name each bubble in chart?

I have created a bubble chart using chart.js,which looks like the below
Is there a way to name each and every bubble in the chart? I am planning to put a data box below this chart. On clicking each bubble data box should display info associated with each bubble. Each bubble will have its own data like maturity_date,bond_type,credit_rating,symbol,etc... How can I name each bubble? These bubbles are created dynamically. This is the code I use to create the chart
$(document).ready(function(){
$.ajax({url: "xxxxxxxx.x.xx", success: function(result){
var dataObj = {};
dataObj.datasets = [];
var object = {};
object.label = 'First Dataset';
object.backgroundColor = [];
object.hoverBackgroundColor = [];
object.data = [];
var resultData = result.data;
var currentYear = new Date().getFullYear();
for (var i=0; i<resultData.length; i++) {
if(resultData[i].hasOwnProperty("maturity_date") && resultData[i].hasOwnProperty("ask_ytm")) {
var maturity_date = resultData[i].maturity_date.split("-");
var matYear = new Date(maturity_date[1]+"-"+maturity_date[0]+"-"+maturity_date[2]).getFullYear();
if (resultData[i].bond_type == "Tax Free" )
{
object.backgroundColor.push("#34A10C");
object.hoverBackgroundColor.push("#34A10C");
}
else
{
object.backgroundColor.push("#1130E8");
object.hoverBackgroundColor.push("#1130E8");
}
object.data.push({x: (matYear - currentYear), y: resultData[i].ask_ytm, r: 4});
}
}
dataObj.datasets.push(object);
var ctx = document.getElementById("myChart");
var myBubbleChart = new Chart(ctx,{
type: 'bubble',
data : dataObj,
legend: {
display: false
},
responsive: true,
maintainAspectRatio: true,
}
});
}});
});
In your data declaration, you can add custom properties if you need to :
data: [{
x: 20,
y: 30,
r: 15,
symbol: "£",
bond_type: "corporate"
}, {
x: 40,
y: 10,
r: 10,
symbol: "$",
bond_type: "james"
} /* ... */]
Since this data is dynamic, you need to do it from your back-end of course.
Afterwards, you can access these new properties in your callback (onClick event for instance) :
options: {
onClick: function(e) {
var element = this.getElementAtEvent(e);
if (element.length > 0) {
var data = this.config.data.datasets[element[0]._datasetIndex].data[element[0]._index];
console.log(data);
// You can have the following for instance :
// data -> { x:40, y:10, r:10, symbol:"$", bond_type:"james" }
}
}
}

Categories