Cytoscape.js Preset Layout Documentation - javascript

I have a cytoscape.js graph that renders. I'm interested in leveraging the preset layout to place the nodes. The cytoscape.js documentation shows the following for the preset layout:
var options = {
name: 'preset',
positions: undefined, // map of (node id) => (position obj); or function(node){ return somPos; }
zoom: undefined, // the zoom level to set (prob want fit = false if set)
pan: undefined, // the pan level to set (prob want fit = false if set)
fit: true, // whether to fit to viewport
padding: 30, // padding on fit
animate: false, // whether to transition the node positions
animationDuration: 500, // duration of animation in ms if enabled
animationEasing: undefined, // easing of animation if enabled
ready: undefined, // callback on layoutready
stop: undefined // callback on layoutstop
};
Can some one help me understand or give an example of what the documentation means when it says the following
// map of (node id) => (position obj); or function(node){ return somPos; }
I store all the nodes in a MySQL database table with the following columns
id, origin, destination, x position, y position
Does the cytoscape.js positions take a dictionary that looks like this:
{'id': 1, {'x':10, 'y':45}}, {'id': 2, {'x':21, 'y':32}} etc?

This means when the positions is set as undefined you need to create a function to get the positions of each node.
For example:
cy.nodes().forEach(function(n){
var x = n.data("x");
var y = n.data("y");
});
This should return your node position.
EDIT
You can set the node position when you are creating it. For example:
var elements;
elements: [{"data":{"id":"yourID","name":"name"},"position"{"x":"0.0","y":"0.0"}}];
For more, see this Demo on Cytoscape js site. Here they set the position manually.

Related

ChartJS hover/tooltips: selecting the correct points in the datasets based on x-value

I'm using ChartJS (v 2.9.4) and I want the hover interaction / tooltip system (they work the same way from what I can tell) to select the point nearest (relative to the x axis) to the mouse pointer on all datasets.
Now, if the x-axis of the chart is operating in the default category mode (i.e.: data points with the same index in the dataset have the same position on the x-axis) this is easily achieved with this configuration:
options: {
hover: {
mode: 'index',
intersect: false
}
//other options...
}
However, if the x-axis is of type linear (i.e.: each data point in each dataset has a specific x-value) this doesn't work properly, since the decision on which point to select is based on its index in the dataset, rather than its x-axis value.
I've created an example here:
https://jsfiddle.net/parhwv73/5/
as you can see by hovering the mouse around, you can easily find yourself in situations like this, where the points selected in the two datasets are very far apart:
while what I would like is this:
in other words: I would like to select the point in each dataset nearest to the mouse pointer (relative to the x-axis).
I've tried playing around with the mode and intersect options, but I found no combination that works, mainly because most other modes only select a point in one single dataset, rather than all of them, index mode is the closest one to being correct, but not quite as I've explained.
Is there a way to achieve this natively?
Or if there isn't, can anyone give some pointers on how to implement a plugin of some sort that can achieve this?
If someone has the same problem in a more recent version (like 3.7.0), you can just modify the interaction mode: instead of 'index', use this:
options: {
interaction: {
intersect: false,
mode: 'nearest',
axis: 'x'
},
}
found on the docs
Well, in the end I had to modify the ChartJS source code to achieve this. Fortunately, it wasn't too hard. All I had to do was add this function to the core.interaction.js file:
function xPositionMode(chart, e, options) {
var position = getRelativePosition(e, chart);
// Default axis for index mode is 'x' to match old behaviour
options.axis = options.axis || 'x';
var distanceMetric = getDistanceMetricForAxis(options.axis);
var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
var elements = [];
if (!items.length) {
return [];
}
const findClosestByX = function(array, element) {
let minDiff = -1;
let ans;
for (let i = 0; i < array.length; i++) {
var m = Math.abs(element._view.x - array[i]._view.x);
if (minDiff === -1 || m < minDiff) {
minDiff = m;
ans = array[i];
}
}
return ans;
}
chart._getSortedVisibleDatasetMetas().forEach(function(meta) {
var element = findClosestByX(meta.data, items[0]);
// don't count items that are skipped (null data)
if (element && !element._view.skip) {
elements.push(element);
}
});
return elements;
}
This is basically a modified version of the indexMode function present in the same file, but instead of searching items by their index in the dataset it searches the closest items by their horizontal position on the canvas (see the findClosestByX inner function for the search algorithm)
To make this usable, you also have to add the new mode in the list of exports (in the same file):
module.exports = {
// Helper function for different modes
modes: {
xPosition: xPositionMode,
//Rest of the original code...
}
}
Once this is done, recompile the ChartJS library and use it instead of the original one. Now you can use the new mode like this:
options: {
hover: {
mode: 'xPosition',
intersect: false
}
//other options...
}
Note: all code in this answer refers to the 2.9.4 version of the ChartJS library, it might not work the same for 3.x versions.

Cytoscape Cola.js Layout edge length = text corpus visualisation

I want to set the edge length with the weight in my data.json.
Like in the cytoscape-spread demo the edge length should be longer depending on the weight.
"data" : {
"id" : "1965",
"source" : "108",
"target" : "149",
"shared_name" : "A (interacts with) B",
"shared_interaction" : "interacts with",
"name" : "A (interacts with) B",
"interaction" : "interacts with",
"SUID" : 1965,
"weight" : 342,
"selected" : false
},
"selected" : false
The weighting is the count how often A and B stands together in my text-corpus.
I tried different layouts but don't now how i can change the position, that the highest weight has the shortest distance.
At the moment i try to use the 'cose' Layout and set idealEdgeLength: function( edge ){ return edge.data('weight'); }
var options = {
name: 'cose',
// Called on `layoutready`
ready: function(){},
// Called on `layoutstop`
stop: function(){},
// Whether to animate while running the layout
animate: true,
// Whether to fit the network view after when done
fit: true,
// Padding on fit
padding: 30,
// Constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
boundingBox: undefined,
// Randomize the initial positions of the nodes (true) or use existing positions (false)
randomize: false,
// Ideal edge (non nested) length
idealEdgeLength: function( edge ){ return edge.data('weight'); },
};
cy.layout( options );
And cola.js edgeLength:
name: 'cola',
animate: true, // whether to show the layout as it's running
refresh: 1, // number of ticks per frame; higher is faster but more jerky
maxSimulationTime: 4000, // max length in ms to run the layout
ungrabifyWhileSimulating: false, // so you can't drag nodes during layout
fit: true, // on every layout reposition of nodes, fit the viewport
padding: 0, // padding around the simulation
boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
// layout event callbacks
ready: function(){}, // on layoutready
stop: function(){}, // on layoutstop
// positioning options
randomize: false, // use random node positions at beginning of layout
avoidOverlap: true, // if true, prevents overlap of node bounding boxes
handleDisconnected: true, // if true, avoids disconnected components from overlapping
nodeSpacing: function( node ){ return 10; }, // extra spacing around nodes
flow: undefined, // use DAG/tree flow layout if specified, e.g. { axis: 'y', minSeparation: 30 }
alignment: undefined, // relative alignment constraints on nodes, e.g. function( node ){ return { x: 0, y: 1 } }
// different methods of specifying edge length
// each can be a constant numerical value or a function like `function( edge ){ return 2; }`
edgeLength: function( edge ){var len = parseInt(edge.data('weight')); return len; }, // sets edge length directly in simulation
edgeSymDiffLength: undefined, // symmetric diff edge length in simulation
edgeJaccardLength: undefined, // jaccard edge length in simulation
// iterations of cola algorithm; uses default values on undefined
unconstrIter: undefined, // unconstrained initial layout iterations
userConstIter: undefined, // initial layout iterations with user-specified constraints
allConstIter: undefined, // initial layout iterations with all constraints including non-overlap
// infinite layout options
infinite: false // overrides all other options for a forces-all-the-time mode
If you want nodes to be pulled closer together if the edge weight is high, then the edge length needs to be inversely proportional to the weight, e.g. k / edge.data('weight') for some constant k. You'll have to experiment to find which k value works best for your data.
Take a look at the Cola demo source for an example. It uses this exact approach, and the slider just changes the k value.
http://js.cytoscape.org/demos/2ebdc40f1c2540de6cf0/

Smooth animation for newly added data points with flot

I need to animate a real time graph with flot so that each new data point will transition smooth into the data set etc.
I've made a plunker with the basic flow:
http://plnkr.co/edit/oPahmS?p=preview
But I would like to make it more like highcharts
http://www.highcharts.com/demo/dynamic-update
Does anyone know a plugin or a way to do this with flot?
I don't believe either of the flot animation plugins provide this ability. Instead, it can be done with a little bit of jquery animate magic.
addPointAnimate = function(){
var series = somePlot.getData()[0]; // first series
var lastX = series.data[series.data.length-1][0]; // last x value
var opts = somePlot.getOptions();
opts.xaxes[0].max += 1; // adjust xaxis to make room for new point
somePlot.setupGrid();
$('#placeholder').animate( { 1:1 }, {
duration: 1000,
step: function ( now, fx ) {
series.data.push([lastX+fx.pos, Math.sin(lastX+fx.pos)]); // for each step of animation, push on an intermediate value
somePlot.setData( [series] );
somePlot.draw(); // redraw with intermediate value
}
});
return true;
}
Here's a working fiddle.

How to zoom into x axis for multiple Flot charts that were created

I'm trying to get zoomin to work for the Flot charts created using following code.
var options = {
yaxis: { min: 0 },
xaxis: { mode: "time" },
series:{
lines: { show: true },
points: { show: true }
},
grid: {
hoverable: true,
clickable: false,
mouseActiveRadius: 30,
backgroundColor: { colors: ["#D1D1D1", "#7A7A7A"] }
},
selection:{mode: "x"}
};
var pdata = [];
for (var key in datasets) {
pdata = [];
pdata.push(datasets[key]);
$.plot( $('<div style="width:1200px;height:600px;"></div>').appendTo('#placeholder'),pdata,options);
$('<h5 align="center">'+datasets[key]['label']+'</h5>').appendTo('#placeholder');
$('<br>').appendTo('#placeholder');
$("#placeholder").UseTooltip();
};
Here I'm creating multiple charts in a loop.
How can I add zoomin feature.
Thank you.
Follow-up to Mark's answer: unique IDs are not really a Flot limitation; that's a requirement of the HTML spec. Browsers generally let you get away with breaking this rule, but it's still not a good idea. Mark's answer is good, but here's one that doesn't require an array-search on every event:
$.each(datasets, function(key, dataset) {
var element = $('<div style="width:1200px;height:600px;"></div>')
.appendTo('#placeholder');
var plot = $.plot(element, [dataset], options);
var plotOptions = plot.getOptions();
element.bind('plotselected', function(event, ranges) {
plotOptions.xaxes[0].min = ranges.xaxis.from;
plotOptions.xaxes[0].max = ranges.xaxis.to;
plot.setupGrid();
plot.draw();
});
};
flot generally expects it's place holder div to have a unique id. You would then use this unique id to assign a specific plotselected event to that plot. The way you have your code structured, though, you are appending the real placeholder div to a parent div as you create your plots. I like your approach so we need to work around flot's limitation.
So, in your plot call give your real placeholder div a class name. This will give us something to bind the plotselected event to. Also you need to save a reference to all the plot objects you've created. I'd just use a global array.
myPlots.push(
$.plot( $('<div class="myPlot" style="width:300px;height:100px;"></div>').appendTo('#placeholder'),pdata,options)
);
Where myPlots is the global array and my class is myPlot.
After this, you can set up the plotselected handler on the jquery selector .myPlots. Next for the tricky part, you need to find your plot object reference inside the handler. The easiest way to do this, I found, is to loop your myPlots array and compare their divs to the div the event happens on:
$(".myPlot").bind("plotselected", function (event, ranges) {
for (var i = 0; i < myPlots.length; i++)
{
var aPlot = myPlots[i];
if (aPlot.getPlaceholder()[0] == event.currentTarget) //this is the correct plot
{
var opts = myPlots[i].getOptions();
opts.xaxes[0].min = ranges.xaxis.from;
opts.xaxes[0].max = ranges.xaxis.to;
myPlots[i].setupGrid();
myPlots[i].draw();
}
}
});
You'll see above I'm redrawing the plot a little different than in the flot examples. I prefer this method since you don't have to remember the data, you adjust the min/max options and you redraw.
Here's a fiddle putting this all together.

Create a custom lines in EXTJS Line Chart

I need to add a vertical line and a text on my Line chart near to a specified point on chart (specified by data, not coordinates). I tried to use CompositeSprites, but it doesn't show on screen completely. I'm new to ExtJS drawing.
You should put the logic that adds the vertical line inside of the chart's refresh event listener, that way, if the data changes the line position will be updated to reflect the new data.
Here's an example of how you could do it, assuming you can get a reference to the chart container (e.g. "myPanel"):
var myChart = myPanel.down('chart'),
myChart.on('refresh', function(myChart) {
// First, get a reference to the record that you want to position your
// vertical line at. I used a "findRecord" call below but you can use
// any of the datastore query methods to locate the record based on
// some logic: findBy (returns index #), getAt, getById, query, queryBy
var myRecord = myChart.store.findRecord(/*[someField]*/, /*[someValue]*/),
// a reference to the series (line) on the chart that shows the record
mySeries = myChart.series.first(),
// get the chart point that represents the data
myPoint = Ext.each(mySeries.items, function(point) {
return myRecord.id === point.storeItem.id;
}),
// the horizontal position of the point
xCoord = point.point[0],
// check for any previously drawn vertical line
myLine = myChart.surface.items.findBy(function(item) {
item.id === 'vert'
});
// if there is no previously drawn line add it to the "surface"
if (!myLine) {
myChart.surface.add({
id: 'vert', // an id so that we can find it again later
type: 'rect',
width: 4,
height: myChart.surface.height, // the same height as the chart
fill: 'black',
opacity: 0.5, // some transparency might be good
x: xCoord,
y: 0 // start the line at the top of the chart
});
// if we already had a line just reposition it's x coordinate
} else {
myLine.setAttributes({
translate: {
x: xCoord,
y: 0
}
// I think the chart gets drawn right after the refresh event so
// this can be false, I haven't tested it though
}, false);
}
});
If you are using the MVC pattern your event handler would look a little different (you wouldn't use myChart.on()).

Categories