How to get web element (id) from the element position in D3.js force graph - web

I am working with the D3.js force graph but I am not able to find out the element id from the element position (which I know).
I am using Leap motion. I need to simulate a mouse event (a click, a move, a drag, etc.) without a mouse. And, if I am right, in order to be able to do this, I need to find out what is the the element id from the coordinates x and y (these coordinates I know from the Leap motion pointer). So from what you wrote above, I need to find out the ('.node’).
Here is what I already tried but it did not work:
Is it possible to use non-mouse, non-touch events to interact with a D3.js graph? If so, what is the most efficient way to go about it?
So I used this function (see below), but I need to know the element id to make it work correctly:
//graph.simulate(document.getElementById("r_1"), 'dblclick', {pointerX: posX, pointerY: posY});
//here id r_1 is hardcoded, but I need to find out id from x and y coordinates.
this.simulate = function (element, eventName) {
function extend(destination, source) {
for (var property in source)
destination[property] = source[property];
return destination;
}
var eventMatchers = {
'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
'MouseEvents': /^(?:click|dblclick|mouse(?:down|up|over|move|out))$/
};
var defaultOptions = {
pointerX: 0,
pointerY: 0,
button: 0,
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
bubbles: true,
cancelable: true
};
var options = extend(defaultOptions, arguments[2] || {});
var oEvent, eventType = null;
for (var name in eventMatchers) {
if (eventMatchers[name].test(eventName)) {
eventType = name;
break;
}
}
if (!eventType)
throw new SyntaxError('Only HTMLEvents and MouseEvents interfaces are supported');
if (document.createEvent) {
oEvent = document.createEvent(eventType);
if (eventType == 'HTMLEvents') {
oEvent.initEvent(eventName, options.bubbles, options.cancelable);
}
else {
oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView,
options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
}
element.dispatchEvent(oEvent);
}
else {
options.clientX = options.pointerX;
options.clientY = options.pointerY;
var evt = document.createEventObject();
oEvent = extend(evt, options);
element.fireEvent('on' + eventName, oEvent);
}
return element;
}
Many thanks for your help and ideas.

If you want access to the element, it's implicit in D3's iterators via this.
d3.selectAll('.node').each(function(d) {
console.log(this); // Logs the element attached to d.
});
If you really need access to the id, you can get it with selection.attr():
d3.selectAll('.node').each(function() {
console.log(d3.select(this).attr('id')); // Logs the id attribute.
});
You don't have to use each. Any of the iterators, such as attr or style, etc., have 'this' as the bound element:
d3.selectAll('.node').style('opacity', function(d) {
console.log(this);// Logs the element attached to d.
});
If you want the x and y coordinates of a node, it's part of the data:
d3.selectAll('.node').each(function(d) {
console.log(d.x, d.y); // Logs the x and y position of the datum.
});
If you really need the node attributes themselves, you can use the attr accessor.
d3.selectAll('.node').each(function(d) {
// Logs the cx and cy attributes of a node.
console.log(d3.select(this).attr('cx'), d3.select(this).attr('cy'));
});
EDIT: It looks like you need an element reference, but the only thing you know about the node in context is its position. One solution is to search through all nodes for a node with matching coordinates.
// Brute force search of all nodes.
function search(root, x, y) {
var found;
function recurse(node) {
if (node.x === x && node.y === y)
found = node;
!found && node.children && node.children.forEach(function(child) {
recurse(child);
});
}
recurse(root);
return found;
}
However this only gives you the node object, not the element itself. You will likely need to store the element references on the nodes:
// Give each node a reference to its dom element.
d3.selectAll('.node').each(function(d) {
d.element = this;
});
With that in place, you should be able to access the element and get its id.
var id, node = search(root, x, y);
if (node) {
id = node.element.getAttribute('id');
}
The brute-force search is fine for a small number of nodes, but if you're pushing a large number of nodes you might want to use D3's quadtree (example) to speed up the search.

Use d3.select('#yourElementId')
For more info check this out: https://github.com/mbostock/d3/wiki/Selections

Related

PhaserJS: After Rotation of camera dragging a Sprite gives strange coords

Basically the problem is that after you rotate the camera, the points that are given as arguments in the callback for dragging are not what I expected. I'm guessing I have to Rotate the given points also but I just couldn't.
Can Someone explain what is going on, is this some kind of bug or what should I do in order the sprite to follow the mouse cursor?
Easiest way to explain the problem is to reproduce it:
1) Go to Phaser Example Runner
2) Copy- Paste this code:
var config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
scene: {
preload: preload,
create: create
}
};
var game = new Phaser.Game(config);
function preload ()
{
this.load.image('eye', 'assets/pics/lance-overdose-loader-eye.png');
}
function create ()
{
var image = this.add.sprite(200, 300, 'eye').setInteractive();
this.cameras.main.setRotation(Math.PI);
image.on('pointerover', function () {
this.setTint(0x00ff00);
});
image.on('pointerout', function () {
this.clearTint();
});
this.input.setDraggable(image);
this.input.on('dragstart', function (pointer, gameObject) {
gameObject.setTint(0xff0000);
});
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
console.log(`x: ${dragX}, y: ${dragY}`);
gameObject.x = dragX;
gameObject.y = dragY;
});
this.input.on('dragend', function (pointer, gameObject) {
gameObject.clearTint();
});
}
3) Open the console, drag around the Eye and look at what coordinates are given.
4) If you remove line 24 (the rotation of the camera) Everything works as expected.
(The example is taken from Phaser 3 Official examples and a bit changed for the bug)
According to Phaser's API Documentation on the setRotation() method, the rotation given in radians applies to everything rendered by the camera. Unfortunately, your pointer isn't rendered by the camera so it doesn't get the same rotated coordinates. Not sure if this is a bug with the library or just a poorly documented exception, but I believe there is a workaround.
Create 2 variables to hold an initial position and a final position:
var image = this.add.sprite(200, 300, 'eye').setInteractive();
var initial = [];
var final = [];
Populate the initial position in your .on('dragstart') method:
this.input.on('dragstart', function (pointer, gameObject) {
initial = [
gameObject.x,
gameObject.y,
pointer.x,
pointer.y
];
gameObject.setTint(0xff0000);
});
Then, populate the final variable in your .on('drag') method:
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
final = [
gameObject.x, // not necessary but keeping for variable shape consistency
gameObject.y, // not necessary but keeping for variable shape consistency
pointer.x,
pointer.y
];
gameObject.x = initial[0] + (initial[2] - final[2]);
gameObject.y = initial[1] + (initial[3] - final[3]);
});
All we're doing here is keeping track of the change in pointer position and mimicking that change in our gameObject.

Bing Maps SpatialMath Module Intersection is not accurate with Multiple pins with same coordinates

I figured an issue, while i have thousands of pins over the map, i am using drawing tool to draw shapes free hand and then executing the Intersection on "drawingEnded" event, While i could see the intersection should return more than it actually returns,
Am i missing something ? For Example, If there are around 500 pins under the new area drawn, Intersection method only returns 100 or few more,
My Spider Cluster Configuration:
` Microsoft.Maps.loadModule(['SpiderClusterManager'], function () {
spiderManager = new SpiderClusterManager(map, pinssame, {
//clusteredPinCallback: function (cluster) {
// //Customize clustered pushpin.
// cluster.setOptions({
// color: 'red',
// icon:'https://www.bingmapsportal.com/Content/images/poi_custom.png'
// });
//},
pinSelected: function (pin, cluster) {
if (cluster) {
showInfobox(cluster.getLocation(), pin);
} else {
showInfobox(pin.getLocation(), pin);
}
},
pinUnselected: function () {
hideInfobox();
},
gridSize: 80
});
});
`
Intersection Function Code which gets triggered after "drawingEnded" event:
` function findIntersectingData(searchArea) {
//Ensure that the search area is a valid polygon, should have 4 Locations in it's ring as it automatically closes.
if (searchArea && searchArea.getLocations().length >= 4) {
//Get all the pushpins from the pinLayer.
//var pins = spiderManager._data;
//Using spatial math find all pushpins that intersect with the drawn search area.
//The returned data is a copy of the intersecting data and not a reference to the original shapes,
//so making edits to them will not cause any updates on the map.
var intersectingPins = Microsoft.Maps.SpatialMath.Geometry.intersection(pins, searchArea);
//The data returned by the intersection function can be null, a single shape, or an array of shapes.
if (intersectingPins) {
//For ease of usem wrap individudal shapes in an array.
if (intersectingPins && !(intersectingPins instanceof Array)) {
intersectingPins = [intersectingPins];
}
var selectedPins = [];
//Loop through and map the intersecting pushpins back to their original pushpins by comparing their coordinates.
for (var j = 0; j < intersectingPins.length; j++) {
for (var i = 0; i < pins.length; i++) {
if (Microsoft.Maps.Location.areEqual(pins[i].getLocation(), intersectingPins[j].getLocation())) {
selectedPins.push(pins[i]);
break;
}
}
}
//Return the pushpins that were selected.
console.log(selectedPins);
return selectedPins;
}
}
return null;
}
`
The function is not returning accurate pin data,
Am i missing something here ?
Any Help Appreciated,
Thanks & Regards,
Shohil Sethia
UPDATE :
Just figured, It is an assumption ,I have multiple pins with same coordinates over the layer, Is this the reason that it returns only pins which intersects with different coordinates over the map ?,
Thanks & Regards,
Shohil Sethia
The method returns objects that represent the intersection, not the exact copies of input shapes. So yes, if multiple pushpins with the same coordinates are within the area, only one pushpin of that coordinates will be in the result, since that alone is good enough as a representation.
You can try the sample below, only one pushpin is returned:
// Creates a polygon of current map bounds
var polygon = new Microsoft.Maps.SpatialMath.locationRectToPolygon(map.getBounds());
// Creates a bunch of the pushpins of the same coordinates(map center)
var pushpin1 = new Microsoft.Maps.Pushpin(map.getCenter());
var pushpin2 = new Microsoft.Maps.Pushpin(map.getCenter());
var pushpin3 = new Microsoft.Maps.Pushpin(map.getCenter());
var pushpin4 = new Microsoft.Maps.Pushpin(map.getCenter());
var pushpin5 = new Microsoft.Maps.Pushpin(map.getCenter());
// Adds the shapes to map for some visualization
map.entities.push([polygon, pushpin1, pushpin2, pushpin3, pushpin4, pushpin5]);
// Only one pushpin is returned as result
var intersectingPin = Microsoft.Maps.SpatialMath.Geometry.intersection([pushpin1, pushpin2, pushpin3, pushpin4, pushpin5], polygon);
Have you checked if the number of results adds up when taking duplicate pins into account?
I got a solution, Since Intersection API ignore multiple pushPins with same coordinates, Therefore there is another API named as contains which takes two parameters which are the shape and the pushpin, and it returns whether it is contained in that shape or not in a boolean form. So true if pushpin is in that shape, and false in the other way.
function findIntersectingData(searchArea) {
//Ensure that the search area is a valid polygon, should have 4 Locations in it's ring as it automatically closes.
if (searchArea && searchArea.getLocations().length >= 4) {
var selectedPins = [];
for (var i = 0; i < pins.length; i++) {
if (Microsoft.Maps.SpatialMath.Geometry.contains(searchArea, pins[i])) {
selectedPins.push(pins[i]);
}
}
//Return the pushpins that were selected.
console.log(selectedPins);
//return updatePrescriberTerr(selectedPins);
return selectedPins;
}
return null;
}
Therefore in the above function the we can loop it from the pushPins array and form the intersection set accordingly based on the boolean values.
Hope it helps to those with similar scenario !
Regards,
Shohil Sethia

Revit API SetPointElementReference(PointOnPlane) - wrong workplane

I try to create a family instance using CreateAdaptiveComponentInstance and try to host it on a reference point, that's also a control point of a CurveByPoints (spline).
The family links properly to the reference point, but the rotation of the reference point's workplane is totally ignored.
Try this standalone example. Move the reference point P2 -> The cross section at P1 will not rotate.
Now, rebuild and change the >> if(true) << to 'false'. Now you see what I want. But as soon as you move the point P2, the link between P2's coordinates and the family is broken.
CurveByPoints spl = null;
ReferencePointArray pts = null;
// create ref points
var p1 = doc.FamilyCreate.NewReferencePoint(new XYZ( 0, 0, 0)); p1.Name = "P1";
var p2 = doc.FamilyCreate.NewReferencePoint(new XYZ(10,10, 0)); p2.Name = "P2";
var p3 = doc.FamilyCreate.NewReferencePoint(new XYZ(30,20, 0)); p3.Name = "P3";
pts = new ReferencePointArray();
pts.Append(p1); pts.Append(p2); pts.Append(p3);
// create a spline
spl = doc.FamilyCreate.NewCurveByPoints(pts);
spl.Visible = true;
spl.IsReferenceLine = false; // MOdelliinie
// change points to adaptive points
foreach(ReferencePoint p in pts)
{
AdaptiveComponentFamilyUtils.MakeAdaptivePoint(doc, p.Id, AdaptivePointType.PlacementPoint);
p.CoordinatePlaneVisibility = CoordinatePlaneVisibility.Always;
p.ShowNormalReferencePlaneOnly = true;
}
// find an adaptive family to place at the points
FamilySymbol fam_sym = null;
var filter = new FilteredElementCollector(doc);
ICollection<Element> col = filter.OfClass(typeof(FamilySymbol)).ToElements();
if(col!=null)
{
foreach(FamilySymbol ele in col)
{
if(ele == null || !AdaptiveComponentInstanceUtils.IsAdaptiveFamilySymbol(ele) ) {continue;}
if(fam_sym == null)
{
fam_sym=ele;
}
if(ele.Name == "profil_adapt_offset_einfach2") // use a special one instead of the first matching
{
fam_sym = ele as FamilySymbol;
break;
}
}
}
// create family instances
if(fam_sym != null)
{
if(true) // this is waht I want. Try "false" to see what I expect
{
foreach (ReferencePoint p in pts)
{
var inst = AdaptiveComponentInstanceUtils.CreateAdaptiveComponentInstance(doc, fam_sym);
var placements = AdaptiveComponentInstanceUtils.GetInstancePlacementPointElementRefIds(inst);
ReferencePoint fam_pt = doc.GetElement(placements.FirstOrDefault()) as ReferencePoint;
var pl = Plane.CreateByNormalAndOrigin(new XYZ(1,0,0), p.Position);
// #### I THINK HERE IS MY PROBLEM ####
// "plane" just points to the reference POINT,
// and not the XZ-PLANE of the reference point.
Reference plane = p.GetCoordinatePlaneReferenceYZ();
PointOnPlane pop = doc.Application.Create.NewPointOnPlane(plane, UV.Zero, UV.BasisU, 0.0);
fam_pt.SetPointElementReference(pop);
}
}
else
{
// create family instances and place along the path
// -> looks good until you move a reference point
double ltot=0.0;
for(var i=0; i<pts.Size-1; ++i)
{
ltot += pts.get_Item(i).Position.DistanceTo(pts.get_Item(i+1).Position);
}
double lfromstart=0;
for(var i=0; i<pts.Size; ++i)
{
if(i>0)
{
lfromstart += pts.get_Item(i).Position.DistanceTo(pts.get_Item(i-1).Position);
}
var inst = AdaptiveComponentInstanceUtils.CreateAdaptiveComponentInstance(doc, fam_sym);
var placements = AdaptiveComponentInstanceUtils.GetInstancePlacementPointElementRefIds(inst);
var location = new PointLocationOnCurve(PointOnCurveMeasurementType.NormalizedCurveParameter, lfromstart / ltot, PointOnCurveMeasureFrom.Beginning);
PointOnEdge po = doc.Application.Create.NewPointOnEdge(spl.GeometryCurve.Reference, location);
// attach first adaptive point to ref point
var firstPoint = doc.GetElement(placements.FirstOrDefault()) as ReferencePoint;
firstPoint.SetPointElementReference(po);
}
}
}
I'm using Revit 2018.2, here.
People might also search for: GetCoordinatePlaneReferenceXZ, GetCoordinatePlaneReferenceXY.
[edit1]
NewReferencePoint() does not create SketchPlanes
When I manually move a generated reference point -> Now the SketchPlane for this ReferencePoint is generated. But how create that with the API?
[edi2]
- I found that e.g. manually changing the ReferencePoint.CoordinatePlaneVisibility=true will create the SketchPlane I need. But I can't do that in code:
var sel = new List<ElementId>();
foreach (ReferencePoint p in pts)
{
sel.Add(p.Id);
// make plane visible
p.CoordinatePlaneVisibility = CoordinatePlaneVisibility.Always;
// shake point
ElementTransformUtils.MoveElement(doc, p.Id, XYZ.BasisX);
ElementTransformUtils.MoveElement(doc, p.Id, XYZ.BasisX.Negate());
}
ui_doc.Selection.SetElementIds(sel);
doc.Regenerate();
ui_doc.RefreshActiveView();
Will it help if you use the NewReferencePoint overload taking a Transform argument, cf. NewReferencePoint Method (Transform)? That might define the required plane for you and associate it with the point.
The development team replied to your detailed description and test project provided in the attached ZIP file:
The developer seems to be in need of some API we haven’t exposed. PointElement’s API was written before RIDL, so the entire user interface is not exposed to the API. They need an API to get the reference plane that is normal to the the curve that it is driving. This plane is not the same as what is exposed by API PointElement.GetCoordinatePlaneReferenceYZ() (one among the three planes exposed to API).
As per SketchPlanes not being created (in the macro seen in the linked zip file), yes that is correct and matches the UI. SketchPlanes are elements and are different from Autodesk.Revit.DB.Reference. We don’t create them until we have to. And they get garbage collected too. At the end of description, I see some code that should work (assuming they wrapped it in a transaction). Normally selection of the point alone will trigger creation of the SketchPlane from the UI. That aside, I would still not recommend putting the point on SketchPlane for what they want to do.
While, what they want does not exist in API, it still does not quite mean there is no workaround for what they want to achieve. If the Selection or Reference API exposes selectable/visible references on an element, Autodesk.Revit.DB.Reference, then one can iterate over these and host the point. This is generic Element/Reference API.
I hope this clarifies.

FLOT crosshair working, but 'legends' needs to be re-initialized

All,
I am trying to have the crosshair track the three waveforms.
The crosshair does show up, and the tracking does not (oops ... see EDIT) take place. Please note - as specified in the picture - that the legend for the horizontal bar is simply a DIV styled to look like a legend from FLOT.
The CodePen can be found here
As the program is executed, the 'legend' becomes null, and the strings do not update ... the code is:
legends = $(".legendLabel");
legends.each(function () {
// fix the widths so they don't jump around
$(this).css('width', $(this).width());
});
I ended up having to put this code in the updateLegend() function. The question is 'why'? Am I sitting on a time bomb? :-) :-)
On a side note, thank you again to Raidri for getting me started earlier in the day.
-- EDIT -- The tracking DOES take place (I had managed to fix it in the middle of posting the question). The only doubt I have is about having to re-initialize variable 'legend' in function updateLegend()
-- EDIT 2 -- The code works in Chrome and Firefox, but fails in IE: 'Object doesn't support property or method "assign"'. I found the solution here
The code below will fix the problem ...
if (typeof Object.assign != 'function') {
Object.assign = function(target) {
'use strict';
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
target = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
if (source != null) {
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
}
return target;
};
}
$.each(data1, function (idx, item) {
Object.assign(item, {
stack: true,
lines: {show: false, steps: false },
bars: {show: true, horizontal:true, width: 1}
});
});

How to snap a polyline to a road with walking travelmode in Google Maps?

II want an user to draw a route with polylines on GoogleMaps. I've found a way to snap a polyline ( example in the link, code in below ). The code works with the directions service to draw a polyline on a road, the only problem is this only works with G_TRAVELMODE_DRIVING but I want to use G_TRAVELMODE_WALKING and when you use WALKING you have to supply the map and a div in the constructor of the directions object. When I do that it automatically removes the last line and only displays the current line. I've tried several things like supplying the map as null, or leaving out the map in the constructor.. I've also tried to give a second map in the constructor, get the polylines from the directions service and display them on the right map. But nothing works! I'd appreciate it if someone could help me!
http://econym.org.uk/gmap/example_snappath.htm
if (GBrowserIsCompatible()) {
var map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(53.7877, -2.9832),13)
map.addControl(new GLargeMapControl());
map.addControl(new GMapTypeControl());
var dirn = new GDirections();
var firstpoint = true;
var gmarkers = [];
var gpolys = [];
var dist = 0;
GEvent.addListener(map, "click", function(overlay,point) {
// == When the user clicks on a the map, get directiobns from that point to itself ==
if (!overlay) {
if (firstpoint) {
dirn.loadFromWaypoints([point.toUrlValue(6),point.toUrlValue(6)],{getPolyline:true});
} else {
dirn.loadFromWaypoints([gmarkers[gmarkers.length-1].getPoint(),point.toUrlValue(6)],{getPolyline:true});
}
}
});
// == when the load event completes, plot the point on the street ==
GEvent.addListener(dirn,"load", function() {
// snap to last vertex in the polyline
var n = dirn.getPolyline().getVertexCount();
var p=dirn.getPolyline().getVertex(n-1);
var marker=new GMarker(p);
map.addOverlay(marker);
// store the details
gmarkers.push(marker);
if (!firstpoint) {
map.addOverlay(dirn.getPolyline());
gpolys.push(dirn.getPolyline());
dist += dirn.getPolyline().Distance();
document.getElementById("distance").innerHTML="Path length: "+(dist/1000).toFixed(2)+" km. "+(dist/1609.344).toFixed(2)+" miles.";
}
firstpoint = false;
});
GEvent.addListener(dirn,"error", function() {
GLog.write("Failed: "+dirn.getStatus().code);
});
}
else {
alert("Sorry, the Google Maps API is not compatible with this browser");
}
I know it's an old post, but since no answer came forward and I've recently been working on this sort of code (and got it working), try swapping the addListener with this.
GEvent.addListener(map, "click", function(overlay,point) {
// == When the user clicks on a the map, get directiobns from that point to itself ==
if (!overlay) {
if (firstpoint) {
dirn1.loadFromWaypoints([point.toUrlValue(6),point.toUrlValue(6)],{ getPolyline:true,travelMode:G_TRAVEL_MODE_WALKING});
} else {
dirn1.loadFromWaypoints([gmarkers[gmarkers.length-1].getPoint(),point.toUrlValue(6)],{ getPolyline:true,travelMode:G_TRAVEL_MODE_WALKING});
}
}
});

Resources