Node hierarchy is like:
{
"nodes":[
{
"assetId":"cfe-3a2b-47e7-b7e9-e2e090ca0d34",
"assetName":"IRCTC",
"assetType":"Company"
},
{
"assetId":"32d9-05b8-4293-af55-2ee4617c6ffe",
"assetName":"Northern Railway Fleet",
"assetType":"Fleet"
},
{
"assetId":"15-b76c-426c-a272-6485359c5836",
"assetName":"Vande Bharat Express",
"assetType":"Train"
}
],
"edges":[
{
"source":"cfe-3a2b-47e7-b7e9-e2e090ca0d34",
"destination":"32d9-05b8-4293-af55-2ee4617c6ffe",
"relation":"HAS"
},
{
"source":"32d9-05b8-4293-af55-2ee4617c6ffe",
"destination":"15-b76c-426c-a272-6485359c5836",
"relation": "HAS"
}
]
}
Basically nodes contain set of assets list and edges contain their mapping or relation. We need to traverse nodes first and say I create a function called
createBreadcrumbs("15-b76c-426c-a272-6485359c5836");
It should check for this node ID in edges object and retrieve its parent.
Breadcrumb would be:
"IRCTC > Northern Railway Fleet > Vande Bharat Express"
You can do that by creating intermediate objects nodeIdToName and nodeIdToParentId for easy lookup in a while loop:
const hierarchy = {
nodes: [ { assetId:"cfe-3a2b-47e7-b7e9-e2e090ca0d34", assetName:"IRCTC", assetType:"Company" }, { assetId:"32d9-05b8-4293-af55-2ee4617c6ffe", assetName:"Northern Railway Fleet", assetType:"Fleet" }, { assetId:"15-b76c-426c-a272-6485359c5836", assetName:"Vande Bharat Express", assetType:"Train" } ],
edges: [ { source:"cfe-3a2b-47e7-b7e9-e2e090ca0d34", destination:"32d9-05b8-4293-af55-2ee4617c6ffe", relation:"HAS" }, { source:"32d9-05b8-4293-af55-2ee4617c6ffe", destination:"15-b76c-426c-a272-6485359c5836", relation: "HAS" } ]
};
const nodeIdToName = hierarchy.nodes.reduce((acc, obj) => {
acc[obj.assetId] = obj.assetName;
return acc;
}, {});
const nodeIdToParentId = hierarchy.edges.reduce((acc, obj) => {
acc[obj.destination] = obj.source;
return acc;
}, {});
function createBreadcrumbs(id) {
let breadcrumb = [];
while(nodeIdToName[id]) {
breadcrumb.push(nodeIdToName[id]);
id = nodeIdToParentId[id];
}
return breadcrumb.reverse().join(' > ');
}
const breadcrumb = createBreadcrumbs("15-b76c-426c-a272-6485359c5836");
console.log({
nodeIdToName,
nodeIdToParentId,
breadcrumb
});
Output:
{
"nodeIdToName": {
"cfe-3a2b-47e7-b7e9-e2e090ca0d34": "IRCTC",
"32d9-05b8-4293-af55-2ee4617c6ffe": "Northern Railway Fleet",
"15-b76c-426c-a272-6485359c5836": "Vande Bharat Express"
},
"nodeIdToParentId": {
"32d9-05b8-4293-af55-2ee4617c6ffe": "cfe-3a2b-47e7-b7e9-e2e090ca0d34",
"15-b76c-426c-a272-6485359c5836": "32d9-05b8-4293-af55-2ee4617c6ffe"
},
"breadcrumb": "IRCTC > Northern Railway Fleet > Vande Bharat Express"
}
Note: nodeIdToName and nodeIdToParentId added in output just for illustration
You could create 2 Map objects. One maps each node's assetId -> assetName. And another Map object which maps each edge's destination -> to it's source.
Create a function which takes an id. Get the source and node name for the current id.
If the source is not null, then recursively call the function and append the current crumb at the end. Else, return the ,current crumb
const input = {nodes:[{assetId:"cfe-3a2b-47e7-b7e9-e2e090ca0d34",assetName:"IRCTC",assetType:"Company"},{assetId:"32d9-05b8-4293-af55-2ee4617c6ffe",assetName:"Northern Railway Fleet",assetType:"Fleet"},{assetId:"15-b76c-426c-a272-6485359c5836",assetName:"Vande Bharat Express",assetType:"Train"}],edges:[{source:"cfe-3a2b-47e7-b7e9-e2e090ca0d34",destination:"32d9-05b8-4293-af55-2ee4617c6ffe",relation:"HAS"},{source:"32d9-05b8-4293-af55-2ee4617c6ffe",destination:"15-b76c-426c-a272-6485359c5836",relation:"HAS"}]},
nodeMap = new Map ( input.nodes.map(o => [o.assetId, o.assetName]) ),
edgeMap = new Map ( input.edges.map(o => [o.destination, o.source]) )
function getCrumb(id) {
const crumb = nodeMap.get(id),
source = edgeMap.get(id);
return source
? [getCrumb(source), crumb].join(' > ')
: crumb
}
console.log( getCrumb("15-b76c-426c-a272-6485359c5836") )
console.log( getCrumb("32d9-05b8-4293-af55-2ee4617c6ffe") )
I'll present a solution that uses the same idiomatic data structure as the other answers for this problem: an associative array — however, this answer focuses on using functional programming to arrive at the solution:
The first step is to produce a Map of doubly-linked list nodes from the initial input, which will give you all the relational data that you need to solve the described problem, and to derive any other kinds of relational insights from the example input that you presented:
function linkAssets (graph) {
const map = new Map();
for (const asset of graph.nodes) {
map.set(asset.assetId, {value: asset});
}
for (const edge of graph.edges) {
if (edge.relation === "HAS") {
const previous = map.get(edge.source);
const next = map.get(edge.destination);
if (previous && next) {
previous.next = next;
next.previous = previous;
}
}
}
return map;
}
Then, you can use the map of linked list objects to build your breadcrumb path:
function createBreadcrumbs (
graph,
lastAssetId,
{
delimiter = " > ",
transformFn = (asset) => asset.assetName,
} = {},
) {
const map = linkAssets(graph);
const assetPath = [];
let node = map.get(lastAssetId);
while (node) {
assetPath.unshift(transformFn(node.value));
node = node.previous;
}
return assetPath.join(delimiter);
}
Using it looks like this:
const result = createBreadcrumbs(input, "15-b76c-426c-a272-6485359c5836");
console.log(result); // "IRCTC > Northern Railway Fleet > Vande Bharat Express"
And customizing the output is possible as well:
const result2 = createBreadcrumbs(input, "15-b76c-426c-a272-6485359c5836", {
delimiter: " ➡️ ",
transformFn: a => `${a.assetName} (${a.assetType})`,
});
console.log(result2); // "IRCTC (Company) ➡️ Northern Railway Fleet (Fleet) ➡️ Vande Bharat Express (Train)"
Here's a working code snippet example with the code above and your question input:
"use strict";
function linkAssets (graph) {
const map = new Map();
for (const asset of graph.nodes) {
map.set(asset.assetId, {value: asset});
}
for (const edge of graph.edges) {
if (edge.relation === "HAS") {
const previous = map.get(edge.source);
const next = map.get(edge.destination);
if (previous && next) {
previous.next = next;
next.previous = previous;
}
}
}
return map;
}
function createBreadcrumbs (
graph,
lastAssetId,
{
delimiter = " > ",
transformFn = (asset) => asset.assetName,
} = {},
) {
const map = linkAssets(graph);
const assetPath = [];
let node = map.get(lastAssetId);
while (node) {
assetPath.unshift(transformFn(node.value));
node = node.previous;
}
return assetPath.join(delimiter);
}
const input = {"nodes":[{"assetId":"cfe-3a2b-47e7-b7e9-e2e090ca0d34","assetName":"IRCTC","assetType":"Company"},{"assetId":"32d9-05b8-4293-af55-2ee4617c6ffe","assetName":"Northern Railway Fleet","assetType":"Fleet"},{"assetId":"15-b76c-426c-a272-6485359c5836","assetName":"Vande Bharat Express","assetType":"Train"}],"edges":[{"source":"cfe-3a2b-47e7-b7e9-e2e090ca0d34","destination":"32d9-05b8-4293-af55-2ee4617c6ffe","relation":"HAS"},{"source":"32d9-05b8-4293-af55-2ee4617c6ffe","destination":"15-b76c-426c-a272-6485359c5836","relation":"HAS"}]};
const result = createBreadcrumbs(input, "15-b76c-426c-a272-6485359c5836");
console.log(result);
const result2 = createBreadcrumbs(input, "15-b76c-426c-a272-6485359c5836", {
delimiter: " ➡️ ",
transformFn: a => `${a.assetName} (${a.assetType})`,
});
console.log(result2);
Code in the TypeScript Playground
I'm trying to figure out how to update a chart.js chart. Google's returned with a lot of answers and I think some are outdated because I can't seem to get any of the solutions to work. The documentation page says just use chartname.update() but it doesn't seem to work for me. I already checked console to make sure the chart object was updating. For some reason the chart itself on the page just isn't changing.
let chartContainer = document.getElementById('charts');
let overview = {
create: function () {
let chartCanvas = document.createElement('canvas');
chartCanvas.id = 'overviewChart';
chartCanvas.appendChild(document.createTextNode('test'));
chartContainer.appendChild(chartCanvas);
let overviewChart = document.getElementById('overviewChart').getContext('2d');
renderChart = new Chart(overviewChart, {
type: 'bar',
data: {
labels:subjectList,
datasets: [{
barThickness: 'flex',
label: 'Completed Credits',
data: []
}]
},
options: {
}
})
},
reload: function() {
console.log('reloaded overview chart');
renderChart.data.datasets.data = [];
for (subject in classes) {
console.log('adding: ' + classes[subject].count)
renderChart.data.datasets.data.push(classes[subject].count);
}
renderChart.update();
}
}
function reloadCharts() {
overview.reload();
}
overview.create();
There are problems in your reload function where you access renderChart.data.datasets.
Please note that renderChart.data.datasets is an array. Therefore, you need to make the following changes:
reload: function() {
// renderChart.data.datasets.data = []; // old
renderChart.data.datasets[0].data = []; // new
for (subject in classes) {
console.log('adding: ' + classes[subject].count)
// renderChart.data.datasets.data.push(classes[subject].count); // old
renderChart.data.datasets[0].data.push(classes[subject].count); // new
}
renderChart.update();
}
I'm using Bootstrap vue table with contentful's API and could use some help with my code. I'm attempting to use a for loop to iterate over an array and get the property values. The console.info(episodes); call prints out each iteration for the var episodes, but now how do I bind this to my variable episodes. Using return only returns one result even outside of the for each loop. Any help or suggestions on another implementation is greatly appreciated. Full Template below.
<template>
<div>
<h1>Bootstrap Table</h1>
<b-table striped responsive hover :items="episodes" :fields="fields"></b-table>
</div>
</template>
<style>
</style>
<script>
import axios from "axios";
// Config
import config from "config";
// Vuex
import store from "store";
import { mapGetters, mapActions } from "vuex";
// Services
import { formatEntry } from "services/contentful";
// Models
import { entryTypes } from "models/contentful";
// UI
import UiEntry from "ui/Entry";
import UiLatestEntries from "ui/LatestEntries";
const contentful = require("contentful");
const client = contentful.createClient({
space: "xxxx",
environment: "staging", // defaults to 'master' if not set
accessToken: "xxxx"
});
export default {
name: "contentful-table",
data() {
return {
fields: [
{
key: "category",
sortable: true
},
{
key: "episode_name",
sortable: true
},
{
key: "episode_acronym",
sortable: true
},
{
key: "version",
sortable: true
}
],
episodes: []
};
},
mounted() {
return Promise.all([
// fetch the owner of the blog
client.getEntries({
content_type: "entryWebinar",
select: "fields.title,fields.description,fields.body,fields.splash"
})
])
.then(response => {
// console.info(response[0].items);
return response[0].items;
})
.then(response => {
this.episodes = function() {
var arrayLength = response.length;
var episodes = [];
for (let i = 0; i < arrayLength; i++) {
// console.info(response[i].fields.title + response[i].fields.splash + response[i].fields.description + response[i].fields.body );
var episodes = [
{
category: response[i].fields.title,
episode_name: response[i].fields.splash,
episode_acronym: response[i].fields.description,
version: response[i].fields.body
}
];
// episodes.forEach(category => episodes.push(category));
console.info(episodes);
}
return episodes;
};
})
.catch(console.error);
}
};
</script>
You can use the map method on the response array to return all the elements.
In your current example you keep re-setting the episodes variable, instead of the push() you actually want to do. The map method is still a more elegant way to solve your problem.
this.episodes = response.map((item) => {
return {
category: item.fields.title,
episode_name: items.fields.splash,
episode_acronym: item.fields.description,
version: item.fields.body
}
})
You can update the last then to match the last then below
]).then(response => {
return response[0].items;
})
.then((response) => {
this.episodes = response.map((item) => {
return {
category: item.fields.title,
episode_name: items.fields.splash,
episode_acronym: item.fields.description,
version: item.fields.body
};
});
})
.catch(console.error)
You do have an unnecessary second then, but I left it there so that you could see what I am replacing.
I created a chart using vue-chartjs which renders some data from a JSON endpoint.
The problem is that the chart is not rendered after the page reloads.
This is the HTML that calls the chart component.
<absentee-chart v-if="isLoaded==true" :chart-data="this.chartData"></absentee-chart>
This is the absenteeChart.js file that renders the chart data.
import { Bar, mixins } from 'vue-chartjs'
export default {
extends: Bar,
mixins: [mixins.reactiveProp],
data: () => ({
options: {
responsive: true,
maintainAspectRatio: false
}
}),
mounted () {
this.renderChart(this.chartData, this.options)
}
}
And finally my .vue file.
created () {
axios
.get(constants.baseURL + "absentee-reports/graph", auth.getAuthHeader())
.then(response => {
this.graph = response.data;
var males = {
data: []
};
var females = {
data: []
};
var len = this.graph.length;
for (var i = 0; i < len; i++) {
if (this.graph[i].year == "2009") {
this.labels.push(this.graph[i].month);
//push to males
this.datasets[0].data.push(this.graph[i].males);
//push to females
this.datasets[1].data.push(this.graph[i].females);
}
}
this.isLoaded = true;
this.chartData.labels = this.labels;
this.chartData.datasets = this.datasets;
});
}
UPDATE: The chart appears after I resize my browser page.
The solution to this was separating all my chart logic from my .vue file and omitting the use of mixins and reactiveProp. From what I could tell, this issue lies deep within the core of chart.js
Hope this helps somebody.
Cheers :)
My code is not working well, i don't know how to have a handsontable in my .js file. I wrote code in html last time, which i know it's wrong in meteor, but now i changed all of the code.
I saw Meteor Handsontable example which is what i need, but i use https://github.com/awsp/handsontable-meteor instead of using the source code of handsontable, because the last version does not work. i have no error from this, but i don't see any table, i don't know why.
body.js
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { Cells } from './cells.js';
import './main.html';
if (Meteor.isClient) {
Template.handsontable.rendered = function () {
var myData = []; // Need this to create instance
var container = document.getElementById("hot");
var hot = new Handsontable(container, { // Create Handsontable instance
data: myData,
startRows: 5,
startCols: 5,
colHeaders: true,
minSpareRows: 1,
contextMenu: true,
afterChange: function (change, source) { // "change" is an array of arrays.
if (source !== "loadData") { // Don't need to run this when data is loaded
for (i = 0; i < change.length; i++) { // For each change, get the change info and update the record
var rowNum = change[i][0]; // Which row it appears on Handsontable
var row = myData[rowNum]; // Now we have the whole row of data, including _id
var key = change[i][1]; // Handsontable docs calls this "prop"
var oldVal = change[i][2];
var newVal = change[i][3];
var setModifier = {$set: {}}; // Need to build $set object
setModifier.$set[key] = newVal; // So that we can assign 'key' dynamically using bracket notation of JavaScript object
Cells.update(row._id,setModifier);
}
}
}
});
console.log(hot);
Tracker.autorun( function () { // Tracker function for reactivity
myData = Cells.find().fetch(); // Tie in our data
hot.loadData(myData);
});
};
}
FlowRouter.route('/data-view/', {
name: 'data-view',
action() {
BlazeLayout.render('handsontable');
}
});
main.html
<template name="handsontable">
<div class="handsontable" id="hot"></div>
</template>
cells.js
import { Mongo } from 'meteor/mongo';
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';
class CellsCollection extends Mongo.Collection {
}
export const Cells = new CellsCollection('Cells');
Cells.schema = new SimpleSchema({
x: {
type: Number
},
y: {
type: Number
},
data: {
type: String
}
});
Cells.attachSchema(Cells.schema);
Meteor.methods({
'updateCell': function(y, x, data){
cell = Cells.findOne({x: x, y: y});
if (cell != undefined){
if (data != null){
Cells.update({_id: cell._id}, {$set: {x: x, y: y, data: data}});
}else{
Cells.remove({_id: cell._id});
}
}
},
'createCell': function(y, x, data){
if (Cells.findOne({x: x, y: y}) == undefined) {
if (data != null) {
Cells.insert({x: x, y: y, data: data});
}
} else{
Meteor.call("updateCell", y, x, data);
}
}
});
How to solve it? Thanks.