In the mxgraph engine, I found the mouse event, but it doesn't reflect the actual map coordinate.
Here is a sample code where evt doesn't fit me.
We need a different solution. We need to get the map coordinate. We can check this by getting negative coordinates (probably).
In general, the task looks like this: the user selects a category of objects, he is shown a modal window with available SVG icons for placement. After the SVG click, the icon is placed in the place where the user clicks. I know that there is a possibility of standard Drag&Drop in mxGraph, but it does not suit us because of the specifics of the project.
import {ref} from "vue";
let dialogVisible = ref(false)
let dialogConfirmed = ref(false)
let selectedItem = ref(false)
let dialogPromise = ref({
resolve: () => {}
reject: () => {}
});
export function useDialog(){
return {
dialogConfirmed,
dialogVisible,
selectedItem,
dialogPromise
}
}
The code below shows the coordinates that we found in mxGraph, but they do not allow us to place the object on click.
this.graph.addListener(MxEvent.CLICK, function(sender, evt)){
console.log('evtOffsetX: ', evt.properties.event.offsetX)
console.log('evtOffsetY: ', evt.properties.event.offsetY)
}
this.graph.getSelectionModel().addListener(MxEvent.CHANGE, function(vertex)){
console.log('vertexCenterX: ', vertex.cells[0].geometry.getCenterX());
console.log('vertexCenterY: ', vertex.cells[0].geometry.getCenterY());
console.log('vertexHeight: ', vertex.cells[0].geometry.height);
console.log('vertexWidth: ', vertex.cells[0].geometry.width);
console.log('vertexX: ', vertex.cells[0].geometry.x);
console.log('vertexY: ', vertex.cells[0].geometry.y);
}
I tried this on mxgraph 4.2.2 and it worked:
this.graph.addListener(mxEvent.CLICK, function(sender, evt) {
console.log("GRAPH CLICK EVENT", sender, evt);
var parent = sender.getDefaultParent();
sender.getModel().beginUpdate();
sender.insertVertex(parent, null, 'TEST', evt.getProperty("event").offsetX, evt.getProperty("event").offsetY, 80, 30);
sender.getModel().endUpdate();
evt.consume();
});
Related
On the project where I work (React, TS), we use the viewer and added the Box Selection extension for it.
The first time you activate it with a button in the toolbar, the extension works, the elements are highlighted. Then you can switch to another mode, for example, the orbit mode. And after that, when you click on the button that activates the "box Selection extension", the extension no longer works. The orbit mode remains working.
At the same time, the button is clicked (console.log() is fired) and the loadExtension('Autodesk.Box Selection') method works.
What could be the problem?
I will give some code snippets
This is the extension code:
export default function RectangleSelectionExtension(
this,
viewer,
options,
) {
window.Autodesk.Viewing.Extension.call(this, viewer, options);
}
RectangleSelectionExtension.prototype = (
Object.create(window.Autodesk.Viewing.Extension.prototype)
);
RectangleSelectionExtension.prototype.constructor = RectangleSelectionExtension;
RectangleSelectionExtension.prototype.load = () => true;
RectangleSelectionExtension.prototype.unload = () => true;
RectangleSelectionExtension.prototype.onToolbarCreated = function onToolbarCreated() {
this.group = this.viewer.toolbar.getControl('allExtensionsToolbar');
if (!this.group) {
this.group = new window.Autodesk.Viewing.UI.ControlGroup('allExtensionsToolbar');
this.viewer.toolbar.addControl(this.group);
}
// Add a new button to the toolbar group
this.button = new window.Autodesk.Viewing.UI.Button('RectangleSelectionExtension');
this.button.onClick = async () => {
const boxSelectionExtension = await this.viewer.loadExtension('Autodesk.BoxSelection');
this.viewer.toolController.activateTool(boxSelectionExtension.boxSelectionTool.getName());
boxSelectionExtension.addToolbarButton(this.viewer);
};
this.button.setToolTip('Select within a rectangle area');
this.button.addClass('RectangleSelectionExtension');
this.group.addControl(this.button);
};
window.Autodesk.Viewing.theExtensionManager.registerExtension('BoxSelection', RectangleSelectionExtension);
Next, in the Viewer component, we import and register the extension:
window.Autodesk.Viewing.theExtensionManager.registerExtension('RectangleSelectionExtension', RectangleSelectionExtension);
And this is how we initialize the viewer:
window.Autodesk.Viewing.Initializer(options, () => {
const container = document.getElementById('forgeViewer');
if (container) {
viewer = new window.Autodesk.Viewing.GuiViewer3D(
container,
{
token,
extensions: [
/* ...some extensions */
'RectangleSelectionExtension',
],
},
);
const startedCode = viewer.start();
if (startedCode > 0) {
return;
}
/* ...some eventListeners */
}
I'm not sure I understand the purpose of your RectangleSelectionExtension. From the code it looks like it just adds a button in the toolbar, and clicking that button repeatedly loads another extension (Autodesk.BoxSelection), repeatedly activates the box selection tool, and repeatedly adds the box selection button to the toolbar. That doesn't seem right.
If you're simply interested in the box selection, you can load it (and include it in the toolbar) like so:
// ...
viewer = new window.Autodesk.Viewing.GuiViewer3D(
container,
{
token,
extensions: [
/* ...some extensions */
'Autodesk.BoxSelection',
]
}
);
// and later ...
const boxSelectionExt = viewer.getExtension('Autodesk.BoxSelection');
boxSelectionExt.addToolbarButton(true); // Add the button to the toolbar
boxSelectionExt.addToolbarButton(false); // Remove the button from the toolbar
// ...
I have set up an app using Electron. For the most part, all of my code is in the index.html file. The app takes some data from the user, and uses it to plot areas on a Leaflet map. The map is plotted onto a <div>
The Leaflet map can be drawn with the taken geolocation, but, when the areas are plotted, the app reloads, causing the data to be lost.
I have rearranged most of the javascript, to find any issues there. I have tried global and local variables.
//Geoloaction for now using dev tools to spoof for test location.
const geo = navigator.geolocation;
let lat;
let lon;
let map;
document.getElementById("geo").addEventListener("click", () => {
geo.getCurrentPosition((position) => {
lat = position.coords.latitude;
lon = position.coords.longitude;
document.getElementById('location').value = (lat + ', ' + lon);
mapIt();
}, error);
});
document.getElementById('submit').addEventListener("click", () => {
let rad = document.getElementById('radius');
let squa = document.getElementById('square');
if (rad.checked) {
radius();
} else if (squa.checked) {
square();
}
});
//Mapping function using Leaflet
function mapIt() {
console.log("RUNNING");
map = L.map('maps').setView([lat, lon], 8);
const attribution = '© OpenStreetMap';
const tileURL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
const tiles = L.tileLayer(tileURL, {
attribution
});
tiles.addTo(map);
}
function error(error) {
console.log(error.POSITION_UNAVAILABLE);
}
At the end of either of these two functions, the app is caused to reload:
function square() {
//Some Calculations...
let bounds = [
[lat,lon],[lat,lon]
];
L.rectangle(bounds, {
color: "#ff7800",
weight: 1
}).addTo(map);
map.fitBounds(bounds)
}
function radius() {
//Some Calculations...
L.circle([lat, lon], {
radius: (km_R * 1000)
}).addTo(map);
}
There should be no need for the app to reload. Everything worked fine, until I added those last two functions. I have debugged, and the functions run until the end. No error messages are ejected.
Is this just an issue with an Electron App's format? Will it go away, after I build and package the App?
Smells like you have an HTML <form> with a <button> or <input type="submit"/>, since you target an element with id "submit".
If that is the case, the HTML specified behaviour is to send the form data to the page indicated by the action attribute. If there no such indication, it sends the data to the current page, effectively reloading it.
See also javascript redirect not working anyway
The easy solution is simply to add event.preventDefault() at the start of your event listener.
Leaflet itself has no mean to reload your page.
For refererence for the case of a button: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button
If your buttons are not for submitting form data to a server, be sure to set their type attribute to button. Otherwise they will try to submit form data and to load the (nonexistent) response, possibly destroying the current state of the document.
I am trying to implement ng2-charts in my Angular 2 project and I was wondering about creating custom onclick events. Meaning, I want to override the current onclick events on the carts to do some custom functions (redirect to a page, have a modal show up, etc).
Is there a simple way to do this? Is it built in at all?
Any insight would be appreciated it
I found this solution at https://github.com/valor-software/ng2-charts/issues/489
public chartClicked(e: any): void {
if (e.active.length > 0) {
const chart = e.active[0]._chart;
const activePoints = chart.getElementAtEvent(e.event);
if ( activePoints.length > 0) {
// get the internal index of slice in pie chart
const clickedElementIndex = activePoints[0]._index;
const label = chart.data.labels[clickedElementIndex];
// get value by index
const value = chart.data.datasets[0].data[clickedElementIndex];
console.log(clickedElementIndex, label, value)
}
}
}
Try to read DOCS
They have pretty good and understandable explanation of use.
There-are built-in 2 event handlers:
Events
chartClick: fires when click on a chart has occurred, returns information regarding active points and labels
chartHover: fires when mousemove (hover) on a chart has occurred, returns information regarding active points and labels
In code it looks like that:
<base-chart class="chart"
[datasets]="lineChartData"
[labels]="lineChartLabels"
[options]="lineChartOptions"
[colors]="lineChartColours"
[legend]="lineChartLegend"
[chartType]="lineChartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></base-chart>
</div>
that chartHovered and chartClicked are your custom functions, which could has another names, and do custom things like showing modal, redirect to url etc.
public chartClicked(e: any): void {
console.log(e);
}
e.active[0]._model and e.active[0]._view contain information about the part of the chart you clicked (i.e. label).
I hope my answer is correct. After much searching for the only solution I found was:
public chartClicked(e:any):void {
if(e.active.length > 0){
var points = [];
var pointSelected = e.active[0]._chart.tooltip._model.caretY;
var legends = e.active[0]._chart.legend.legendItems;
for (var i = 0; i < e.active.length; ++i) {
points.push(e.active[i]._model.y);
}
let position = points.indexOf(pointSelected);
let label = legends[position].text
console.log("Point: "+label)
}}
After checking multiple places, I got it working like this for click event.
HTML:
<div class="chart">
<canvas
baseChart
[data]="pieChartData"
[type]="pieChartType"
[options]="pieChartOptions"
[plugins]="pieChartPlugins"
(chartHover)="chartHovered($event)"
>
</canvas>
</div>
TS:
public chartHovered(e: any): void {
if (e.event.type == "click") {
const clickedIndex = e.active[0]?.index;
console.log("Clicked index=" + clickedIndex);
}
}
Ref
i am using the ARCGIS Javascript API and trying to override the default right click behavior of the vertex points of a shape.
in ESRI's help it does list the onVertexClick event however from here it seems there is no way to determine if this is a right or left click event so i cannot override just the rightclick.
https://developers.arcgis.com/javascript/jsapi/edit.html
I am trying to set the right click behavour to just delete the current node/vertex instead of showing a menu with the option Delete.
EDIT
Here is the current event that exists within the ARCGIS api.
this.eventsList.push(dojo.connect(this._editToolbar, 'onVertexClick', $.proxy(this.addCustomVertexClickEvent, this)));
this event is already in the api however it does not return any way for me to determine left/right click.
your comment "listen for the click event then test the button attribute of the MouseEvent object" would work however i cant actually add a click event to the vertex points directly as these are inside the ARCGIS api code.
For anyone else who is looking for a way to do this without hacking around. You can listen to "contextmenu" (right click) events on the body, set a flag in the "contextmenu" handler to let the application know the current state. Simulate a click event to the "vertex handle" with a "mousedown", "mouseup" combination. In the "vertex-click" handler check for the right click flag set in the "contextmenu" handler
var editToolbar = new Edit(map, options);
var rightClick;
$('body').on('contextmenu', function(e) {
var target = e.target;
if(target.tagName === 'circle') {
// We only care about this event if it targeted a vertex
// which is visualized with an SVG circle element
// Set flag for right click
rightClick = true;
// Simulate click on vertex to allow esri vertex-click
// to fill in the data for us
var mouseDownEvt = new MouseEvent('mousedown', e.originalEvent);
target.dispatchEvent(mouseDownEvt);
var mouseUpEvt = new MouseEvent('mouseup', e.originalEvent);
target.dispatchEvent(mouseUpEvt);
// Since this event will be handled by us lets prevent default
// and stop propagation so the browser context menu doesnt appear
e.preventDefault();
e.stopPropagation();
}
});
editToolbar.on('vertex-click', function(e) {
if(rightClick) {
// Handle the right click on a vertex
rightClick = null;
}
});
after hearing back from ESRI it seems they do not provide this detail in their API so this is not possible yet.
I ended up doing this differently. I wanted to add a UI so the user could enter the XY of the point
// setup to allow editing
this.editToolbar = new EditToolbar(this.map, { allowDeleteVertices: false });
const rcMenuForGraphics = new RightClickVertexContextMenu();
const menu = rcMenuForGraphics.createMenu();
// bind to the map graphics as this is where the vertex editor is
this.map.graphics.on("mouse-over", (evt)=> {
// bind to the graphic underneath the mouse cursor
menu.bindDomNode(evt.graphic.getDojoShape().getNode());
});
this.map.graphics.on("mouse-out", (evt)=> {
menu.unBindDomNode(evt.graphic.getDojoShape().getNode());
});
this.editToolbar.on("vertex-click", (evt2) => {
rcMenuForGraphics.setCurrentTarget(evt2);
// evt2.vertexinfo.graphic.geometry.setX(evt2.vertexinfo.graphic.geometry.x - 1000);
})
// when the graphics layer is clicked start editing
gl.on("click", (evt: any) => {
this.map.setInfoWindowOnClick(false);
// tslint:disable-next-line: no-bitwise
const t: any = EditToolbar.MOVE | EditToolbar.EDIT_VERTICES;
this.editToolbar.deactivate();
this.editToolbar.activate(t, evt.graphic);
})
The code for the menu uses esri's vertex editor to grab the point, change its XY and then manually call the events to refresh the geometry. Only tested with polygon
import Menu = require("dijit/Menu");
import MenuItem = require("dijit/MenuItem");
import Graphic = require("esri/graphic");
import Edit = require("esri/toolbars/edit");
import Point = require("esri/geometry/Point");
class RightClickVertexContextMenu {
private curentTarget: { graphic: Graphic; vertexinfo: any; target: Edit; };
public createMenu() {
const menuForGraphics = new Menu({});
menuForGraphics.addChild(new MenuItem({
label: "Edit",
onClick: () => {
// this is a bit hooky. We grab the verx mover, change the x/y and then call the _moveStopHandler
console.log(this.curentTarget.vertexinfo);
const e: any = this.curentTarget.target;
const mover = e._vertexEditor._findMover(this.curentTarget.vertexinfo.graphic);
const g: Graphic = mover.graphic;
// add in a UI here to allow the user to set the new value. This just shifts the point to the left
g.setGeometry(new Point(mover.point.x - 1000, mover.point.y ))
e._vertexEditor._moveStopHandler(mover, {dx: 15});
this.curentTarget.target.refresh();
}
}));
menuForGraphics.addChild(new MenuItem({
label: "Delete",
onClick: () => {
// call the vertex delete handler
const ct: any = this.curentTarget.target;
ct._vertexEditor._deleteHandler(this.curentTarget.graphic)
}
}));
return menuForGraphics;
}
public setCurrentTarget(evt: { graphic: Graphic; vertexinfo: any; target: Edit; }) {
this.curentTarget = evt;
}
}
export = RightClickVertexContextMenu;
I've been working on a context menu module in ReactJS, and it's got me thinking about how to deal with non-hierarchical components.
The problem I'm running into is that many different items in an application may want to use a context menu. Normally in React, you pass a callback from a parent object to the children that need to communicate with the parent. For example, my first thought was to have an openContextMenu(mousePosition, optionsObject) function passed from my ContextMenu class to all the elements that want to display a context menu on right-click.
But it doesn't make sense for all such elements (or maybe even any) to be children of a context menu! The context menu is not hierarchical with respect to other components of the application. In Angular, I would probably write a ContextMenu service that components required if they wanted access to such a menu.
Is this a situation in which a global event handler should be used? Am I thinking about this all wrong? What's the React way to handle this kind of horizontal interaction between components?
Context menus are special. There should never be more than one context menu open at any time. They're also special because it can be opened from anywhere. Try the demo to get an idea of how this looks when put together.
To solve our global problem, we'll create a mixin which wraps a private event emitter.
var menuEvents = new events.EventEmitter();
var ContextMenuMixin = {
// this.openContextMenu(['foo', 'bar'], (err, choice) => void)
openContextMenu: function(options, callback){
menuEvents.emit('open', {
options: options,
callback: callback
});
},
closeContextMenu: function(){
menuEvents.emit('close');
}
};
Now for the component, we need to do a few things. Here's the initialization part. Just binding to some events, and lightweight mouse tracking.
var mouse = {x: 0, y: 0};
var updateMouse = function(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
};
var ContextMenu = React.createClass({
getInitialState: function(){
return {options: null, callback: null};
},
componentDidMount: function(){
menuEvents.addListener('open', this.handleOpenEvent);
menuEvents.addListener('close', this.closeMenu);
addEventListener('mousemove', updateMouse);
},
These event handlers are very simple. handleOpenEvent just stores the event payload and mouse position in state, which effectively locks in the mouse position until it's opened next. And the counterpart simply resets the state, and calls the callback with an error.
handleOpenEvent: function(payload){
this.setState(_.merge({}, payload, mouse));
},
closeMenu: function(){
if (this.state.callback) {
this.replaceState(this.getInitialState());
this.state.callback(new Error('no selection made'));
}
},
And finally, we render a list of options passed to the event, and we create click handlers for each.
render: function(){
if (!this.state.options) {
return <div />
}
var style = {
left: this.state.x,
top: this.state.y,
position: 'fixed'
};
return (
<div className="react-contextmenu" style={style}>
<ul className="react-contextmenu-options">
{this.state.options.map(function(x, i){
return <li key={i}
onClick={this.makeClickHandler(x)}>
{x}
</li>
}, this)}
</ul>
</div>
);
},
makeClickHandler: function(option){
return function(){
if (this.state.callback) {
this.state.callback(null, option);
this.replaceState(this.getInitialState());
}
}.bind(this);
}