I basically want to create a new path with the canvas globalCompositeOperation set to 'destination-out'. Is this possible? If so, how?
I noticed that Item has a blendMode property: http://paperjs.org/reference/item#blendmode, but trying the different values does not provide the desired effect.
http://jsfiddle.net/D8vMG/12/
In light of your recent comment, you would need to create layers, as described here:
http://paperjs.org/tutorials/project-items/project-hierarchy/#removing-items-and-children
and then you can add your paths to a layer and do something like this:
//add image to project as background
// ... your code here ...
var secondLayer = new Layer();
Whenever you create a new Layer, it becomes the active layer of the project, and then you can draw on top of the second layer all you want.
if you want a simple 'undo' button you can do:
function onMouseDown(event) {
if (window.mode == "drawing") {
myPath = new Path();
myPath.strokeColor = 'black';
}
else if (window.mode == "undo") {
myPath.opacity = '0'; //this makes the last path used completely see through
// or myPath.remove(); // this removes the path.
//if you're intent on something that writes and erases, you could do
}
}
But what you are looking for is something like this:
function onMouseDrag(event) {
if (window.mode == "drawing") {
myPath.add(event.point);
}
else if (window.mode == "erasing") {
myPath.removeSegment(0);
}
}
this erases the segments of the path from beginning to end. For the full functionality, you need something that identifies a path on click, (layer.getChildren() ? then select child). Then using the point on mouse move you need to identify the segment index and remove it from the path using .removeSegment(index).
http://paperjs.org/reference/path#removesegment-index
Well, the simple solution would be to just create a path which is white. http://jsfiddle.net/D8vMG/11/
function onMouseDown(event) {
if (window.mode == "drawing") {
myPath = new Path();
myPath.strokeColor = 'black';
}
else if (window.mode == "erasing") {
myPath = new Path();
myPath.strokeColor = 'white';
myPath.strokeWidth = 10;
}
}
Related
I'm a french student in computering and I have to use Mapbox but since I create a class I'm stuck by this error.When I wasn't in a class it worked perfectly but now it's fully broken.And I saw on some topics it could come from safari but I already tested it on Mozilla and it still broken.
This is my class.
constructor() {
//Temporary array of currentMarkers
let currentMarkers=[];
let type ="";
//Create the map
mapboxgl.accessToken = 'private data';
this.mapbox = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/streets-v11',
center: [-74.5, 40], // starting position
zoom: 9 // starting zoom
});
//Add search bar from a plugin
let geocoder = new MapboxGeocoder({
accessToken: mapboxgl.accessToken,
placeholder: 'Saissisez une adresse', // Placeholder text for the search bar
marker: {
color: 'orange'
},
mapboxgl: mapboxgl
});
this.mapbox.addControl(geocoder);
const mbox = this;
this.mapbox.on("click",function(){
this.getcoordonates();
});
//Allow us to create marker just with a research
geocoder.on('result', function(e) {
//Deleting all current markers
if (currentMarkers!==null) {
for (let i = currentMarkers.length - 1; i >= 0; i--) {
currentMarkers[i].remove();
}
}
//Add the markers who come with the research
this.addMarker(e.result.center[0],e.result.center[1]);
// Delete the last marker who got placed
//currentMarkers[currentMarkers.length - 1].remove();
});
this.addPoint(2.333333 ,48.866667 ,"supervisor");
}
//split the JSON.stringify(e.lngLat.wrap()) to get the coordinates
async mysplit(m){
let z = m.split(',');
let x = z[0].replace("{\"lng\":","");
let g = z[1].replace("\"lat\":","");
let y = g.replace("}","");
await addMarker(x,y);
}
//Add a marker on click after the excution of mysplit() function
async addMarker(x,y) {
// tmp marker
let oneMarker= new mapboxgl.Marker()
.setLngLat([x,y])
.addTo(this.mbox);
currentMarkers.push(oneMarker);
}
// Get the coordinates and send it to split function
getcoordonates(){
mbox.on('click',function(e){
let m = JSON.stringify(e.lngLat.wrap());
this.mbox.mysplit(m);
});
}
addPoint(y,x,type)
{
let color ="";
let t = type;
if(t == "supervisor")
{
color = "grey";
}else if (t =="fieldworker") {
color = "red";
}else if (t == "intervention") {
color = "blue";
}else alert("Nous ne pouvons pas ajouter votre marker\nLatitude : "+x+"\nLongitude :"+y+"\n car "+t+" n'est pas un type reconnu" );
let myMarker = new mapboxgl.Marker({"color": color})
.setLngLat([y,x])
.addTo(this.mbox);
}
}
Thanks for help and have a good day :) ! Sorry if my English isn't that good.
As the first step, the
this.mapbox.on("click", function(e){
this.getcoordonates();
});
was called outside of any method of the Map class. It would most probably belong to the constructor (as discussed above in the question's comments section).
Next, the callback changes the scope of this, so it is not anymore pointing to a Map instance. A common solution to this issue is to store/backup this before, something like:
constructor() {
...
const thisObject = this;
this.mapbox.on("click", function(e) {
thisObject.getcoordonates();
});
}
Update:
The logic of code in its current form tries to add a new click event listener every time the map is clicked. Which is not desired. This getcoordonates() function is not really needed. Instead this should work (never tested it, it is based on your code):
constructor() {
...
const mbox = this;
this.mapbox.on("click", function(e) {
let m = JSON.stringify(e.lngLat.wrap());
mbox.mysplit(m);
});
}
Remarks:
There is no real logic behind JSON-encoding the lngLat object before calling mysplit just to decode it there.
You won't need it here, but the reverse of the JSON.stringify() is JSON.parse(). There is no need to work with it on a string level, like the current mysplit() method does.
Instead, the mysplit() method should be called directly with the e.lngLat object as its argument.
Going further, since there is no "splitting" (decoding) really needed, the mysplit() method isn't really needed either.
In the end, something like this should work:
constructor() {
...
const mbox = this;
this.mapbox.on("click", function(e) {
await mbox.addMarker(e.lngLat.lng, e.lngLat.lat);
});
}
I'm trying to play a sound by looping an array and split an array into each array, and then using switch case to detect what's in the array.
function keeper() {
number2 = get.num;
sNumber = number2.toString();
output = [];
for ( i = 0, len = sNumber.length; i < len; i ++) {
output.push(+sNumber.charAt(i));
console.log(output);
switch (output[i]){
case 0:
console.log('0');
audio0 = new Audio('logo/Q0.wav');
audio0.play();
break;
case 1:
console.log('1');
audio1 = new Audio('logo/Q1.wav');
audio1.play();
break;
case 2:
console.log('2');
audio2 = new Audio('logo/Q2.wav');
audio2.play();
break;
case 3:
console.log('3');
audio3 = new Audio('logo/Q3.wav');
audio3.play();
break;
case 4:
console.log('4');
audio4 = new Audio('logo/Q4.wav');
audio4.play();
break;
case 5:
console.log('5');
audio5 = new Audio('logo/Q5.wav');
audio5.play();
break;
}
}}
The function it works just fine, but apparently the sound thats played out it too quick. is there any solution to fix this?
I'm assuming you want to hear the sounds after each other?
That doesn't work like this.
Lets say the first number in the array is: 0.
So sound 0 gets played.
But, since you loop through the array, and you reach the next number, eg. 2: sound 2 gets played immediately after.
The loop doesn't wait for the first sound the finish before starting the next play().
what you could do is modify the loop to wait for the audio ended event.
for example:
var audio0 = document.getElementById("myAudio");
audio0.onended = function() {
alert("The audio has ended");
};
Try using an audio sprite. I'm sure there's an app or whatever to do certain tasks programmatically but be aware step 1 and 2 are done manually.
Take a group of audio files and either use Audacity or an online service to join them into one file.
Next, get the start times of each clip of the audio file and store them in an array.
The following Demo will take the file and array, generate the HTML layout, create a button for each clip that corresponds to the array parameter. So when a button is clicked it will play only a clip of the audio sprite (the audio file).
The audio sprite in this Demo was not edited very well, I just made it to demonstrate how everything works. The timing relies on the timeupdate event which checks the playing time about every 250ms give or take. So if you want to make a more accurate start and end times, try leaving a gap of 250ms between clips.
Details commented in Demo
Demo
// Store path to audio file in a variable
var xFile = 'https://storage04.dropshots.com/photos7000/photos/1381926/20180318/175955.mp4'
// Store cues of each start time of each clip in an array
var xMap = [0, 1.266, 2.664, 3.409, 4.259,4.682, 5.311, 7.169, 7.777, 9.575, 10.88,11.883,13.64, 15.883, 16.75, 17, 17.58];
/* Register doc to act when the DOM is ready but before the images
|| are fully loaded. When that occurs, call loadAudio()
*/
document.addEventListener('DOMContentLoaded', function(e) {
loadAudio(e, xFile, xMap);
});
/* Pass the Event Object, file, and array through
|| Make a Template Literal of the HTML layout and the hidden
|| <audio> tag. Interpolate the ${file} into the <audio> tag.
|| Insert the TL into the <body> and parse it into HTML.
== Call generateBtn() function...
*/
function loadAudio(e, file, map) {
var template = `
<fieldset class='set'>
<legend>Sound FX Test Panel</legend>
</fieldset>
<audio id='sndFX' hidden>
<source src='${file}' type='audio/wav'>
</audio>`;
document.body.insertAdjacentHTML('beforeend', template);
generateBtn(e, map);
}
/* Pass the Event Object and the array through
|| Reference fieldset.set
|| create a documentFragment in order to speedup appending
|| map() the array...
|| create a <button>
|| Set btn class to .btn
|| Set button.btn data-idx to the corresponding index value of
|| map array.
|| Set button.btn text to its index number.
|| Add button.btn to the documentFragment...
|| return an array of .btn (not used in this demo)
== Call the eventBinder() function...
*/
function generateBtn(e, map) {
var set = document.querySelector('.set');
var frag = document.createDocumentFragment();
map.map(function(mark, index, map) {
var btn = document.createElement('button');
btn.className = 'btn';
btn.dataset.idx = map[index];
btn.textContent = index;
frag.appendChild(btn);
return btn;
});
set.appendChild(frag);
eventBinder(e, set, map);
}
/* Pass EventObject, fieldset.set, and map array through
|| Reference the <audio> tag.
|| Register fieldset.set to the click event
|| if the clicked node (e.target) class is .btn...
|| Determine the start and end time of the audio clip.
== Call playClip() function
*/
function eventBinder(e, set, map) {
var sFX = document.getElementById('sndFX');
set.addEventListener('click', function(e) {
if (e.target.className === 'btn') {
var cue = parseFloat(e.target.textContent);
var start = parseFloat(e.target.dataset.idx);
if (cue !== (map.length - 1)) {
var end = parseFloat(e.target.nextElementSibling.dataset.idx);
} else {
var end = parseFloat(sFX.duration);
}
playClip.call(this, sFX, start, end);
} else {
return false;
}
});
}
/* Pass the reference to the <audio> tag, start and end of clip
|| pause audio
|| Set the currentTime to the start parameter
|| Listen for timeupdate event...
|| should currentTime meet or exceed the end parameter...
|| pause <audio> tag.
*/
function playClip(sFX, start, end) {
sFX.pause();
sFX.currentTime = start;
sFX.play();
sFX.ontimeupdate = function() {
if (sFX.currentTime >= end) {
sFX.pause();
}
}
return false;
}
Try to use a timer:
for (var i = 1; i <= 3; i++) {
(function(index) {
setTimeout(function() { alert(index); }, i * 1000);
})(i);
}
Use the setTimeout fuction like that
Struggling to remove multiple objects from the fabric canvas. Everything seems to be in working order but when the code runs it does not remove the multiple selected objects from the canvas.
service.deleteSelectedObject = function () {
var selectedObject = service.canvas.getActiveObject();
var selectedMultipleObjects = service.canvas.getActiveGroup();
var data = {};
// get object id
// get selected objects from canvas
if (selectedObject) {
data = {
type: 'whiteboard::delete',
id: selectedObject.id
};
delete service.objectMap[selectedObject.id];
service.canvas.remove(selectedObject);
} else {
data = {
type: 'whiteboard::delete',
object: selectedMultipleObjects
};
console.log(selectedMultipleObjects);
selectedMultipleObjects._objects.forEach(function (object, key) {
console.log(object);
service.canvas.remove(object);
});
}
signalService.sendMessage(service.recipient, data);
};
I should point out that all of these console logs do pass what they should. As well as the problem is occuring in the else statement the first part of this code works how it should
please let me know if you need any further information.
Object of active group are indeed on the canvas but removing them from canvas will not remove them from the rendering pipeline if they are in active group.
When fabricjs draws everything, it checks for object on canvas and presence of activeGroup. If activeGroup is present, its objects get rendered later, regardless they are on canvas or not.
So you are effectively removing the objects from the canvas, but untill you clear the selection of the active group, the objects are still there.
Correct procedure is removing the object from the canvas and from the activeGroup.
check the snippet, select all objects with mouse and the press remove all button.
var canvas = new fabric.Canvas('canvas');
var r1 = new fabric.Rect({width:50,height:50});
var r2 = new fabric.Rect({width:50,height:50,top:110, left:110});
var r3 = new fabric.Rect({width:50,height:50,top:60, left:60});
canvas.add(r1,r2,r3);
function removeAll() {
var o = canvas.getActiveGroup();
o._objects.forEach(function (object, key) {
canvas.remove(object);
o.removeWithUpdate(object);
});
canvas.discardActiveGroup();
canvas.renderAll();
}
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js" ></script>
<input type="button" onClick="removeAll()" value="remove"/>
<canvas id="canvas" width=400 height=400 style="height:500px;width:500px;"></canvas>
After version 2 of fabric.js, there is no getActiveGroup. To delete multiple objects, you need to use forEachObject. This example builds on the previous one and can delete single objects or grouped objects.
var canvas = new fabric.Canvas('canvas');
var r1 = new fabric.Rect({width:50,height:50});
var r2 = new fabric.Rect({width:50,height:50,top:110, left:110});
var r3 = new fabric.Rect({width:50,height:50,top:60, left:60});
canvas.add(r1,r2,r3);
function deleteObj(){
var doomedObj = canvas.getActiveObject();
if (doomedObj.type === 'activeSelection') {
// active selection needs a reference to the canvas.
doomedObj.canvas = canvas;
doomedObj.forEachObject(function(obj) {
canvas.remove(obj);
});
}//endif multiple objects
else{
//If single object, then delete it
var activeObject = canvas.getActiveObject();
//How to delete multiple objects?
//if(activeObject !== null && activeObject.type === 'rectangle') {
if(activeObject !== null ) {
canvas.remove(activeObject);
}
}//end else there's a single object
}//end deleteObj
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.6/fabric.min.js" ></script>
<input type="button" onClick="deleteObj()" value="remove"/>
<canvas id="canvas" width=400 height=400 style="height:500px;width:500px;"></canvas>
I am writing a simple widget that simulates a simple 8-bit CPU. For that I am abusing the Ace Editor, as you can see at the center of the image, as my "RAM"-view.
I want to highlight the line that corresponds to the value of the program counter and I am using addMarker() to do so.
However, I can't seem to get rid of that marker once I have set it. _marker is a private member that holds the value of the last marker set. But for some reason removeMarker(_marker) has no effect:
/**
*
*/
setMarker: function(position) {
//if(_marker != null) {
window.cpuRamView.session.removeMarker(_marker);
//}
_marker = new window.Range(position, 0, position, _content[position].length);
window.cpuRamView.session.addMarker(
_marker, "programCounterLocation", "fullLine"
);
}
What am I doing wrong here? :/
add marker returns an id, and removeMarker requires that id, so you can do something like
var Range = require("ace/range").Range // not the window Range!!
var _range
setMarker = function(position) {
if(_range != null) {
window.cpuRamView.session.removeMarker(_range.id);
}
_range = new Range(position, 0, position, _content[position].length);
_range.id = window.cpuRamView.session.addMarker(
_range, "programCounterLocation", "fullLine"
);
}
if(this.marker) {
this.editor.getSession().removeMarker(this.marker);
}
this.marker = this.editor.getSession().addMarker(
new Range(prop('errorLine')(formulaError), prop('errorPosition')(formulaError), prop('errorLine')(formulaError), prop('errorPosition')(formulaError) + 5), style.errorMarker, 'text');
}
set a variable marker to receive the return value, just like this:
marker=editor.session.addMarker(range, "myMarker", "fullLine");
and then remove this marker, like this:
editor.session.removeMarker(marker);
I am trying to get the area measurements of polygons so I can list them in a table to the side of the map, next to the name of the polygon. This is what I have tried with no success:
$("#polygon").on("click", function (){
createPolygon = new L.Draw.Polygon(map, drawControl.options.polygon);
createPolygon.enable();
}
var polygon = new L.featureGroup();
map.on('draw:created', function (e) {
var type = e.layerType,
layer = e.layer;
if (type === 'polygon') {
polygons.addLayer(layer);
}
var seeArea = createPolygon._getMeasurementString();
console.log(seeArea); //Returns null
}
Any help on this would be appreciated!
You can access the geometry utility library provided with Leaflet.
var area = L.GeometryUtil.geodesicArea(layer.getLatLngs());
In your example, you are trying to access a control itself, which is what the variable createPolygon is assigned to. Instead, you want to take the area of the layer that got drawn.
map.on('draw:created', function (e) {
var type = e.layerType,
layer = e.layer;
if (type === 'polygon') {
polygons.addLayer(layer);
var seeArea = L.GeometryUtil.geodesicArea(layer.getLatLngs());
console.log(seeArea);
}
}
Once you verify you are getting the area, you can just assign it to the variables that populate the table next to the map.
Note: area will be in squareMeters by default
I found that none of the above answers worked for calculating the area of non-contiguous polygons. Here's an example polygon where the above functions returned an area of 0:
For anyone who needs to do that, here is the code that worked for me (using the L.GeometryUtil function from Leaflet.draw):
var poly = // Your polygon layer here; may or may not be contiguous
var area = 0;
for (island of poly.getLatLngs()) {
// If the polygon is non-contiguous, access the island
if (island.length < 2) {
island = island[0]
}
// Sum the area within each "island"
area += L.GeometryUtil.geodesicArea(island);
}
L.GeometryUtil.geodesicArea(layer.getLatLngs())[0] should get you the area.
But I ended up using leaflet-geoman-free to do the drawing and use turf.js to get the area.
map.pm.enableDraw('Polygon', {
snappable: true,
snapDistance: 20,
});
map.on('pm:create', e => {
const layer = e.layer
alert(turf.area(layer.toGeoJSON()))
});
add corrections:
var seeArea = L.GeometryUtil.geodesicArea(layer.getLatLngs()[0]);
console.log(seeArea);