I am working on a layout in VisJS that involves a lot of nodes. When edges are hovered over, I have text that I want to appear and then hide on leave.
When the nodes are crowded and the edge labels run into it, they are currently displaying so that they are underneath the nodes, instead of above. Is there any way I can set the z-index of the edge label so that they appear on top of the nodes rather than underneath them?
I have read the docs extensively and haven't seen anything that would be likely to change the position in the z index. any hacks or workaround would be welcomed. Thanks!
working JSFiddle
My visjs code:
const root = document.querySelector('#graph');
const nodeData = [
{
type: "A",
name: 'A Node 1',
index: 0,
x: -50,
y: -50
},
{
type: 'A',
name: 'A node 2',
index: 1,
x: -50,
y: 50
},
{
type: 'A',
name: 'A node 3',
index: 2,
x: -150,
y: 50
},
{
type: 'A',
name: 'A Node 4',
index: 3,
x: 50,
y: 50
},
{
type: 'A',
name: 'A Node 5',
index: 4,
x: -50,
y: 150
},
{
type: "B",
name: 'B Node 1',
index: 5
},
{
type: "B",
name: 'B Node 2',
index: 6
},
{
type: 'B',
name: 'B Node ',
index: 7
},
{
type: 'B',
name: 'B Node ',
index: 8
},
{
type: 'B',
name: 'B Node ',
index: 9
},
{
type: 'B',
name: 'B Node ',
index: 10
},
{
type: 'B',
name: 'B Node ',
index: 11
},
{
type: 'B',
name: 'B Node ',
index: 12
}, {
type: 'B',
name: 'B Node ',
index: 13
},
];
const edgeData = [
{from: 0, to: 1},
{from: 1, to: 2},
{from: 1, to: 3},
{from: 1, to: 4},
{from: 2, to: 5},
{from: 3, to: 6},
{from: 4, to: 7},
{from: 4, to: 8},
{from: 4, to: 9},
{from: 4, to: 10},
{from: 4, to: 11},
{from: 4, to: 12},
{from: 4, to: 13},
]
const options = {
physics: {
barnesHut: {
avoidOverlap: 1,
springConstant: 0.3
},
solver: 'hierarchicalRepulsion'
},
layout: {
randomSeed: '0:0'
}
};
const nodes = new vis.DataSet(nodeData.map(d => {
const newNode = {
id: d.index,
label: d.name,
fixed: d.type === 'A'
}
if (typeof d.x === 'number') {
newNode.x = d.x;
}
if (typeof d.y === 'number') {
newNode.y = d.y;
}
if(d.type === 'A') {
newNode.color = 'orange';
newNode.mass = 3;
}
return newNode;
}));
const edges = new vis.DataSet(edgeData.map(e => {
return {...e, smooth: false, label: 'edge from node to node'}
}));
const network = new vis.Network(root, {nodes, edges}, options);
network.on('stabilizationIterationsDone', () => network.setOptions({physics: false}));
Related
I have an array of objects with dynamic keys and values (so not all objects have category_id, size and so on) but I simplified it to this:
let products_row = [
{
id: 1,
category_id: 11,
options: {
'modelName1': {
size: '2 feet',
colour: 'red',
is_new: true
},
'modelName2': {
size: '4 feet',
colour: 'black',
is_new: true
},
}
},
{
id: 2,
category_id: 21,
options: {
'modelName11': {
size: '2 feet',
colour: 'white',
is_new: false
},
'modelName12': {
size: '4 feet',
colour: 'white',
is_new: false
},
}
},
{
id: 3,
category_id: 31,
options: {
'modelName21': {
size: '8 feet',
colour: 'white',
is_new: false
},
'modelName22': {
size: '4 feet',
colour: 'black',
is_new: true
},
}
},
{
id: 4,
category_id: 41,
options: {
'modelName31': {
size: '8 feet',
colour: 'red',
is_new: true
},
'modelName32': {
size: '8 feet',
colour: 'red',
is_new: true
},
}
}
]
the result data structure needs to be like this:
let resultArray = [
{
id: 1,
category_id: 11,
model: 'modelName1',
size: '2 feet',
colour: 'red',
is_new: true
},
{
id: 1,
category_id: 11,
model: 'modelName2',
size: '4 feet',
colour: 'black',
is_new: true
},
{
id: 2,
category_id: 21,
model: 'modelName11',
size: '2 feet',
colour: 'white',
is_new: false
},
{
id: 2,
category_id: 21,
model: 'modelName12',
size: '4 feet',
colour: 'white',
is_new: false
},
{
id: 3,
category_id: 31,
model: 'modelName21',
size: '8 feet',
colour: 'white',
is_new: false
},
{
id: 3,
category_id: 31,
model: 'modelName22',
size: '4 feet',
colour: 'black',
is_new: true
},
{
id: 4,
category_id: 41,
model: 'modelName31',
size: '8 feet',
colour: 'red',
is_new: true
},
{
id: 4,
category_id: 41,
model: 'modelName32',
size: '8 feet',
colour: 'red',
is_new: true
},
]
This is what I have tried:
let productsData = [];
products_row
.map((product, p) => Object.entries(product.options || {})
.filter((model, i) => {
return productsData.push(
{
model: model[0],
[Object.keys(product).filter(el => delete product.options)[i]]: Object.values(product)[i],
[Object.keys(model[1] || [])[i]]: Object.values(model[1] || [])[i],
}
)
})
)
console.log(productsData)
But it returns not all data, which is expected because I can't figure out how to keep previous key-values:
[
{
model: 'modelName1',
id: 1,
size: '2 feet',
},
{
model: 'modelName2',
category_id: 11,
colour: 'black',
},
{
model: 'modelName11',
id: 2,
size: '2 feet',
},
{
model: 'modelName12',
category_id: 21,
colour: 'white',
},
{
model: 'modelName21',
id: 3,
size: '8 feet',
},
{
model: 'modelName22',
category_id: 31,
colour: 'black',
},
{
model: 'modelName31',
id: 4,
size: '8 feet',
},
{
model: 'modelName32',
category_id: 41,
colour: 'red',
},
]
I am completely stuck, any help is appreciated. Thank you.
you can use flatMap and map
What flatMap does is if the returned array of map looks like
[
[{...1},{...2}],
[{...3},{...4}]
]
it will flatten it and give
[
{...1},{...2},
{...3},{...4}
]
let products_row = [{id: 1,category_id: 11,options: {'modelName1': {size: '2 feet',colour: 'red',is_new: true},'modelName2': {size: '4 feet',colour: 'black',is_new: true},}},{id: 2,category_id: 21,options: {'modelName11': {size: '2 feet',colour: 'white',is_new: false},'modelName12': {size: '4 feet',colour: 'white',is_new: false},}},{id: 3,category_id: 31,options: {'modelName21': {size: '8 feet',colour: 'white',is_new: false},'modelName22': {size: '4 feet',colour: 'black',is_new: true},}},{id: 4,category_id: 41,options: {'modelName31': {size: '8 feet',colour: 'red',is_new: true},'modelName32': {size: '8 feet',colour: 'red',is_new: true},}}]
let x = products_row.flatMap(({options,...rest}) => Object.entries(options).map(([k,v]) => ({...v,...rest,model:k})))
console.log(x)
It is quite hard to analyze your solution and reason about your idea, so I cannot fix your code.
What you want to do is to extract options from each object and attach the rest of object, in other words you want to iterate over each option for each product row.
There are numerous ways to achieve this, you can use flatMap as #cmgchess suggested. Easier to understand is something like this:
let result = [];
products_row.forEach(({ options, ...rest }) =>
Object.values(options).forEach((b) => result.push({ ...rest, ...b })),
);
I want to sort my nodes in Sankey chart. How can I do that?
For example, for the following data:
const data0 = {
nodes: [
{ name: "L0" },
{ name: "L1" },
{ name: "L2" },
{ name: "L3" },
{ name: "L4" },
{ name: "R5" },
{ name: "R6" },
{ name: "R7" },
{ name: "R8" },
{ name: "R9" }
],
links: [
{ source: 0, target: 5, value: 30 },
{ source: 1, target: 8, value: 99 },
{ source: 1, target: 7, value: 20 },
{ source: 1, target: 6, value: 15 },
{ source: 4, target: 5, value: 6 },
{ source: 2, target: 8, value: 30 },
{ source: 0, target: 6, value: 15 },
{ source: 2, target: 9, value: 11 },
{ source: 3, target: 9, value: 8 },
{ source: 3, target: 8, value: 23 },
{ source: 2, target: 5, value: 20 }
]
};
For this data, the Sankey chart will be as follows:
But I want to sort the nods on each side. I want nodes to be on each side in this way:
How can I do that?
I want to have highchart drilldown with more than 3 levels.
Also, I have tried referring below articles, but I was not able to figure out.
Drilldown multiple levels Highchart
Highcharts - drill down to multiple series
I could do only till 3 levels. After that, bar is not clickable.
Not sure where the mistake is, but this is what I have done so far.
2 Level Drill down - Works fine
Multilevel - Works only till Month level, cannot filter Day level
https://jsfiddle.net/foodiepanda/2ec7d6fz/9/
Below is code (only jquery)
/*Start*/
$('#chart1').highcharts({
chart: {type: 'column'},
title: {text: 'Multi Drilldown'},
xAxis: {type: 'category'},
legend: {enabled: false},
plotOptions:
{
series:
{
dataLabels:
{
enabled: true, //Shown at top of column
}
}
},
series:
[
{
name: 'Year',
//colorByPoint: false,
data:
[
{name: '2019',y: 200,drilldown: '2019'}, //200 clicks in 2018
{name: '2020',y: 450,drilldown: '2020'}, //450 clicks in 2019
]
}
],
drilldown:
{
series:
[
{
id: '2019', //For 2019
name: 'Quarter', //Splitting 200 as 50,100,20,30
data:
[
{
name: 'Q1',
y: 50,
drilldown: 'Q1'
},
{
name: 'Q2',
y: 100,
drilldown: 'Q2'
},
{
name: 'Q3',
y: 20,
drilldown: 'Q3'
},
{
name: 'Q4',
y: 30,
drilldown: 'Q4'
}
]
},
{
id: 'Q1',
name: 'Month', //Splitting 50 as 10,30,20
data:
[
{
name: 'Jan',
y: 10,
drilldown: 'Jan'
},
{
name: 'Feb',
y: 30,
drilldown: 'Feb'
},
{
name: 'Mar',
y: 20,
drilldown: 'Mar'
}
]
},
{
id: 'Jan',
name: 'Day', //Splitting 10 as ...[days]
data:
[
{name:'1', y: 0},
{name:'2', y: 0},
{name:'3', y: 2},
{name:'4', y: 0},
{name:'5', y: 0},
{name:'6', y: 0},
{name:'7', y: 0},
{name:'8', y: 0},
{name:'9', y: 0},
{name:'10', y: 0},
{name:'11', y: 1},
{name:'12', y: 2},
{name:'13', y: 0},
{name:'14', y: 1},
{name:'15', y: 0},
{name:'16', y: 0},
{name:'17', y: 0},
{name:'18', y: 0},
{name:'19', y: 0},
{name:'20', y: 0},
{name:'21', y: 0},
{name:'22', y: 0},
{name:'23', y: 0},
{name:'24', y: 0},
{name:'25', y: 2},
{name:'26', y: 0},
{name:'27', y: 0},
{name:'28', y: 0},
{name:'29', y: 0},
{name:'30', y: 1},
{name:'31', y: 1}
]
},
{
id: 'Q2',
name: 'Month', //Splitting 100 as 80,10,10
data:
[
['Apr', 80],
['May', 10],
['Jun', 10]
]
},
{
id: 'Q3',
name: 'Month', //Splitting 20 as 5,10,5
data:
[
['Jul', 5],
['Aug', 10],
['Sep', 5]
]
},
{
id: 'Q4',
name: 'Month', //Splitting 30 as 5,15,10
data:
[
['Oct', 5],
['Nov', 15],
['Dec', 10]
]
},
//For 2020
{
id: '2020',
name: 'Quarter', //Splitting 450 as 50,100,50,250
data:
[
{
name: 'Q1',
y: 50,
drilldown: 'Q1'
},
{
name: 'Q2',
y: 100,
drilldown: 'Q2'
},
{
name: 'Q3',
y: 50,
drilldown: 'Q3'
},
{
name: 'Q4',
y: 250,
drilldown: 'Q4'
}
]
},
{
id: 'Q1',
name: 'Month', //Splitting 50 as 10,35,5
data:
[
['Jan', 10],
['Feb', 35],
['Mar', 5]
]
},
{
id: 'Q2',
name: 'Month', //Splitting 100 as 40,35,25
data:
[
['Apr', 40],
['May', 35],
['Jun', 25]
]
},
{
id: 'Q3',
name: 'Month', //Splitting 50 as 5,25,20
data:
[
['Jul', 5],
['Aug', 25],
['Sep', 20]
]
},
{
id: 'Q4',
name: 'Month', //Splitting 250 as 75,125,50
data:
[
['Oct', 75],
['Nov', 125],
['Dec', 50]
]
},
] //End Series
} //End Year Drilldown
}); //End Highchart function
//Explicitly change Y axis
var curChart = $('#chart1').highcharts();
curChart.yAxis[0].update({
title:{
text:"Number of Hits"
}
});
/*End*/
I was able to figure out what the issue was.
The name parameter and drilldown parameter were having same values.
I just renamed name parameter from 'Q1' to '_Q1' and it worked like a charm.
{
name: 'Q1',
y: 50,
drilldown: '_Q1'
}
Updated JSFiddle :
https://jsfiddle.net/foodiepanda/2ec7d6fz/11/
For exemple I have a simple network:
var options = {
style = {
hierarchical = {
sortMethod = 'directed'
}
}
}
var nodes = new vis.DataSet([
{id: 1, label: 'Node 1'},
{id: 2, label: 'Node 2'},
{id: 3, label: 'Node 3'},
{id: 4, label: 'Node 4'},
{id: 5, label: 'Node 5'}
]);
var edges = new vis.DataSet([
{from: 1, to: 2},
{from: 2, to: 3},
{from: 4, to: 3},
{from: 5, to: 3}
]);
Is there a way to indicate that I want the node 2 to be rendered between node 4 and 5?
I'm afraid the only way is to set coordinates of the nodes explicitly:
var nodes = new vis.DataSet([
{id: 1, label: 'Node 1', x: 0, y: -100},
{id: 2, label: 'Node 2', x: 0, y: 0},
{id: 3, label: 'Node 3', x: 0, y: 100},
{id: 4, label: 'Node 4', x: 100, y: 0},
{id: 5, label: 'Node 5', x: -100, y: 0}
]);
The point is, physics works only with existing nodes and edges. Still, you can try one of the following tricks:
add hidden edges between 2 and 4 and 2 and 5 – this way, you may hope that because nodes in those pairs are attracted to each other, node 2 ends up at least closer to both 4 and 5 than 4 to 5;
or instead of setting coordinates explicitly, use some pre-defined layout to put nodes in the desired order; however, as far as I know, no existing layout really garantees that this will be so (see these, these issues: this is vis.js-version-dependent and not reliable)
Using the SlickGrid view from recline.js, I've been trying to bind an click event to a row without success. It's easy enough to $('.slick-row').click(function() {...etc}), but I was hoping to use the backbone-esque view to handle my events. Nothing seems to fire. Context seems right. Is there something obvious I'm overlooking?
My HTML container:
<div id="mygrid" style="height: 400px"></div>
My JS:
$(function() {
var data = [
{id: 0, date: '2011-01-01', x: 1, y: 2, z: 3, country: 'DE', geo: {lat:52.56, lon:13.40} },
{id: 1, date: '2011-02-02', x: 2, y: 4, z: 24, country: 'UK', geo: {lat:54.97, lon:-1.60}},
{id: 2, date: '2011-03-03', x: 3, y: 6, z: 9, country: 'US', geo: {lat:40.00, lon:-75.5}},
{id: 3, date: '2011-04-04', x: 4, y: 8, z: 6, country: 'UK', geo: {lat:57.27, lon:-6.20}},
{id: 4, date: '2011-05-04', x: 5, y: 10, z: 15, country: 'UK', geo: {lat:51.58, lon:0}},
{id: 5, date: '2011-06-02', x: 6, y: 12, z: 18, country: 'DE', geo: {lat:51.04, lon:7.9}}
];
var dataset = new recline.Model.Dataset({
records: data
});
var $el = $('#mygrid');
var grid = new recline.View.SlickGrid({
model: dataset,
el: $el,
state: {
gridOptions: '',
},
// Click handler?
events: {
'click .slick-row': 'handleClick',
'click': 'handleClick',
},
handleClick: function(e) {
console.log('row clicked');
}
});
grid.visible = true;
grid.render();
});