How to use vis.js with map - visualization

I am trying to build a network visualization with vis.js and display it on a map (similar to Geo-layout in Gephi). Do you happen to know if something already exists to achieve such goal? (I don't even need to use vis.js if you have suggestions)
So far I tried/thought of using mapbox but I abandoned the idea of a real map because it it turning out to be fairly complex.
I downgraded expectation to doing the following:
done: have an image of a map in the background and have some big nodes that I can position manually by tweaking the x and y position
I still need to do the following:
Make the small nodes automatically fall into place as if it was a forced layout.
The problem is that I need to disable physics if i need to manually set some positions... this of course stops the smaller nodes from being automatically positioned.
any hints on how to achieve this?
Thanks!

I ended doing the following, in options set the layout and physics. (for some reason, improved layout has to be false, it was a lucky guess)
layout: { improvedLayout: false }
physics: { enabled: true}
For the node, set a property called fixed:
fixed: { x: true , y: true }
Here is the son of the node:
{id: 1, value: 2, label: 'xx' , x: 320, y:170 , fixed: { x: true , y: true } }

Related

How to update the scrollWheelZoom in react-leaflet v3?

I want to upgrade from react-leafet v2 to v3 and can't figure out how to update the scrollWheelZoom after initilization so I can control when the map can be zoomed by mouse wheel and when not. In v2, I could simply pass a react state variable to the scrollWheelZoom prop and update the state accordingly. Since this doesn't seem to be working anymore with v3, I tried to change the option directly on the map instance from the initial value false to true:
const map = useMap();
const someEventHandler = () => {
map.options.scrollWheelZoom = true;
};
I can see that the value of map.options.scrollWheelZoom actually changed but the map is still not scrollable. What am I missing here?
I had a look into how this is done in react-leaflet's source code of v2.
So I ended up with the following which works well for me:
const setScrollWheelZoom(scrollWheelZoom: boolean) => {
if (scrollWheelZoom !== map.options.scrollWheelZoom) {
map.options.scrollWheelZoom = scrollWheelZoom;
if (scrollWheelZoom) {
map.scrollWheelZoom.enable();
} else {
map.scrollWheelZoom.disable();
}
}
}
As a side note: I am not sure though, why, in the react-leaflet source code, options.scrollWheelZoom is only set when enabling. For me, this makes the first condition fail when trying to enable it a second time.

How do I render different shapes on each row of a SAPUI5 Gantt chart?

In my application, I have to render Projects, Tasks and Milestones. Projects and Tasks are differently coloured bars, and the Milestone is a Diamond (I'm using BaseRectangle and BaseDiamond respectively).
Since some items in my hierarchy are Projects, Some Tasks and Some Milestones, how can I render differing shapes on each row?
My first thought was to use the common "visible" property, but shapes don't have that, conversely "opacity" makes things invisible, but they still respond to mouse position.
I then tried using an Aggregation factory function, but although my chart renders correctly on first display, it doesn't recalculate the shapes on expanding or collapsing branches.
It seems to me that the factory function should work, but something is breaking in the chart that doesn't throw errors to console.
At the moment in my XML template, I have the following:
rowSettingTemplate has shapes1={path: factory:} and no shapes1 element.
Each of my BaseShapes is in a different fragment which are attached to my TreeTable as dependents.
Example Shape Fragment - Project.fragment.xml
<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m" xmlns:gnt2="sap.gantt.simple">
<gnt2:BaseRectangle id="shapeProject"
shapeId="{plandata>id}" countInBirdEye="true"
time="{plandata>start_date}" endTime="{plandata>end_date}"
resizable="true" selectable="true" draggable= "true" connectable="true"
title="{plandata>text}" showTitle="true"
tooltip=""
fill="#0c1" />
</core:FragmentDefinition>
Factory function:
shapeFactory: function(sId, oContext) {
var parentId = (/(.*)-\d+$/.exec(sId))[1];
var rowSettings = sap.ui.getCore().byId(parentId);
var node: Project.Node = oContext.getProperty();
if (String(node.id) == rowSettings.getProperty("rowId")) {
switch (node.type) {
case "project":
return this.byId('shapeProject').clone(sId);
case "task":
return this.byId('shapeTask').clone(sId);
case "milestone":
return this.byId('shapeMilestone').clone(sId);
default:
return this.byId('shapeErr').clone(sId);
}
} else {
return this.byId('shapeEmpty').clone(sId);
}
}
My empty shape is a BaseGroup - note that SAPUI5 crashes if I return a null from factory, so something has to be returned when I actually want nothing.
I also tried wrapping all my shapes in BaseGroup so that the chart always sees the same control type, but that doesn't work. Note also that if I return a clone of Empty each time without any special logic, then the chart works correctly.
I'm hoping that this is a settings or something to ensure that the aggregation works properly each time. My SAPUI5 version is 1.61.2 — I'll try 1.63.1 when I get some time, but I think that this issue is fairly deep down.
If anybody has any ideas or sample code, it would be greatly appreciated.
I have come up with a workaround for this, that may save somebody several hours. Basically instead of defining the shapes1 aggregation via a factory function, I have used the <shapes1> tag instead. My Shapes1 tag contains a reference to my own custom shape which derives from BaseRectangle. My custom shape can then render whatever SVG it requires based on the bound object context. Now my tree can expand and collapse whilst rendering whatever shapes are required.
My renderer now looks like this:
CustomChartShape.prototype.renderElementRectangle = BaseRectangle.prototype.renderElement;
CustomChartShape.prototype.renderElementDiamond = BaseDiamond.prototype.renderElement;
CustomChartShape.prototype.renderElement = function(oRm, oElement) {
// There is possibilities that x is invalid number.
// for instance wrong timestamp binded to time property
if (this.bHasInvalidPropValue) { return; }
var Node = this.getBindingInfo('endTime').binding.getContext().getProperty();
if (Node.type == "milestone") {
this.renderElementDiamond(oRm, oElement);
} else {
this.renderElementRectangle(oRm, oElement);
}
}
I had to provide a 'getD' function that has a fixed width, and I'll have o go through and rewrite several functions, but I think that this will work for me.

Dragula Copy and removeOnSpill

I'm trying to use the Dragula Drag & Drop library to Clone elements into a target container AND allow the user to remove cloned elements from the Target Container by drag & dropping them outside of the target container (spilling).
Using the examples provided I have this:
dragula([$('left-copy-1tomany'), $('right-copy-1tomany')], {
copy: function (el, source) {
return source === $('left-copy-1tomany');
},
accepts: function (el, target) {
return target !== $('left-copy-1tomany');
}
});
dragula([$('right-copy-1tomany')], { removeOnSpill: true });
Which does not work - it seems that 'removeOnSpill' simply doesn't work if the container accepts a copy.
Does anybody know what I am not doing, doing wrong or if there is a work-around?
TIA!
I came here after looking for a while for a solution to a similar issue using the ng2-dragula for angular2.
dragulaService.setOptions('wallet-bag', {
removeOnSpill: (el: Element, source: Element): boolean => {
return source.id === 'wallet';
},
copySortSource: false,
copy: (el: Element, source: Element): boolean => {
return source.id !== 'wallet';
},
accepts: (el: Element, target: Element, source: Element, sibling: Element): boolean => {
return !el.contains(target) && target.id === 'wallet';
}
});
I've got 4 divs that can all drag into one which has the id of wallet
They are all part of the wallet-bag
using this code, they can all copy into the wallet, not copy between each other, and you can remove them from the wallet using the spill but not from the others.
I'm posting my solution as it may also help someone.
Ok, so the general answer I came trough is that:
you can have 'removeOnSpill' working - even with 'copy' option set to true - , only if you set the 'copy' option applying ONLY when the 'source' container IS NOT the one you are trying to remove elements from.
In my case I had 3 containers from which I can drag in another one called 'to_drop_to'.
Those container have all id starting with 'drag'.
So I set:
var containers = [document.querySelector('#drag1'),
document.querySelector('#drag2'),
document.querySelector('#drag3'),
document.querySelector('#to_drop_to')];
dragula(containers, {
accepts: function (el, target, source, sibling) {
return $(target).attr('id')=="gadget_drop"; // elements can be dropped only in 'to_drop_to' container
},
copy: function(el,source){
return $(source).attr('id').match('drag'); //elements are copied only if they are not already copied ones. That enables the 'removeOnSpill' to work
},
removeOnSpill: true
}
and this worked for me.
Hope it helps.
From the dragula documentation
options.removeOnSpill
By default, spilling an element outside of any containers will move
the element back to the drop position previewed by the feedback
shadow. Setting removeOnSpill to true will ensure elements dropped
outside of any approved containers are removed from the DOM. Note that
remove events won't fire if copy is set to true.

React Animations with VelocityJS

I'm having a really hard time getting animations to work in React. Perhaps there is something I'm fundamentally missing.
I'm doing this in coffeescript -- I hope you don't mind.
I've created a very simple UI. Theres a div with a title in it. When you click the title, the title is changed, and I want to animate a fade in/out transition using VelocityJS.
ReactTransitionGroup = React.createFactory(React.addons.CSSTransitionGroup)
{div} = React.DOM
TitleClass = React.createClass
displayName: "Title"
render: ->
(div {onClick:#props.changeTitle}, #props.title)
componentWillEnter: (done) ->
node = #getDOMNode()
console.log "willEnter"
Velocity(node, 'transition.fadeIn', {complete: done})
componentWillLeave: (done) ->
node = #getDOMNode()
console.log "willLeave"
Velocity(node, 'transition.fadeOut', {complete: done})
Title = React.createFactory(TitleClass)
MainClass = React.createClass
displayName: "Main"
getInitialState: ->
title: 'Main'
changeTitle: ->
if #state.title is 'Home'
#setState {title: 'Main'}
else
#setState {title: 'Home'}
render: ->
(div {style:{width: '100%', fontSize:'25px', textAlign:'center', marginTop:'20px'}},
(ReactTransitionGroup {transitionName: 'fade'},
(Title {changeTitle:#changeTitle, title:#state.title})
)
)
Main = React.createFactory(MainClass)
React.render(Main({}), document.body)
So thats it. Pretty self explanatory. This ReactTransitionGroup is still quite a mystery to me. It is my understanding that any of its children should get calls to componentWillEnter and componentWillLeave but that doesn't end up happening. According to the docs it seems that I should see the console.log "willEnter" but I don't.
I've been hung up on this for hours. Any ideas?
There are two problems I can see in your code above.
The first one is that you are using React.addons.CSSTransitionGroup instead of React.addons.TransitionGroup.
The second problem is that you are expecting componentWillEnter to get triggered on mount when in fact componentWillAppear is the method being triggered.
CSSTransitionGroup watches for actual uses of the CSS transition property written onto the elements. I'm not sure exactly how, but it sounds like Velocity isn't doing its work in a way that CSSTransitionGroup is twigging to. You may have to call componentWillEnter and componentWillLeave manually. In this situation, it doesn't sound like that'll be hard.
EDIT: oops, I missed that you don't have key attributes on your child components in the group. From what I can tell, componentWillEnter and componentWillLeave should get called, irrespective of anything else, if you get keys on those kids.
The solution ends up being to use React.addons.TransitionGroup. The CSSTransitionGroup is just a wrapper that does CSS stuff.
The other issue is that to get the animation callbacks, the children must have a key!
ReactTransitionGroup = React.createFactory(React.addons.TransitionGroup)
# {clip}
(ReactTransitionGroup {transitionName: 'fade'},
(Title {changeTitle:#changeTitle, title:#state.title, key:'title'})
)

Set bounds on a window that has an id

My app has a settings window, I want this window to be unique so that a user cannot display two settings windows at the same time. The problem is that I also want the window to be centered compared to the main window. I use this code (in the JS of the main window):
chrome.app.window.create("settings.html",
{
alwaysOnTop: true,
bounds: {
left: Math.round((window.screenX + (($(window).width() - 498) / 2))), // Perfect left position.
top: Math.round((window.screenY + (($(window).height() - 664) / 2))), // Perfect top position.
width: 498,
height: 664
},
frame : "none",
id: "settings",
resizable: false
}
);
The problem is that if an id is specified and a window with a matching id has been shown before, the remembered bounds will be used but I would prefer to set the bounds myself at every creation with the bounds option. Thus if the user moves the window and closes it the position would still be "perfect" at the next creation. Is there a solution to have this behavior?
Thanks for your help.
I'm afraid you can't override this.
You can reposition the window immediately on creation in the callback, using outerBounds.setPosition:
chrome.app.window.create(
"settings.html",
{/*...*/},
function(win) {
win.outerBounds.setPosition(
/*left, top*/
);
}
);
By the way, bounds is deprecated, you should switch to using innerBounds/outerBounds.
Alternatively, you can try to implement your own ID system. Don't use an ID, and check if the window you need is open (identifying it somehow) before creating it.