In PaperJS, only regular types seem to be able to be added to a Group. Whenever I try to append the group with a custom object I get the error item._remove is not a function
Whenever I create my own object, for instance:
function textBox(point, width) {
this.box = new Path.Rectangle({
point: point,
size: new Size(width,40),
strokeColor: new Color(0,0,0,0.1),
fillColor: 'white',
strokeWidth: 1
});
this.box.onMouseUp = function(event) {
cursor = this.point + new Point(5,2);
}
}
and try to append this to a group:
var testGroup = new Group();
testGroup.appendTop(new textBox(new Point(0,0), 400));
The error shows up. My question is thus: how do I add custom objects to a group? Surely I can't be expected to either manually create each individual object or otherwise manipulate them all on an individual level without using Group dynamics. It seems I have to, just like every other type in PaperJS, make my object extend Item, but I so far have failed to get it to accept my constructor for it. I'm wondering what is required for it to be accepted.
Indeed, there currently is no built-in mechanism to extend Paper.js classes apart from compiling them along with the library.
So for simple cases like the one that you seem to encounter, I would use a factory function that instantiates my custom object and then interact with it as with any other Paper.js object.
For example, if your custom object is a box with a text in it, you can instantiate a group with a rectangle and a text in it and then just interact with this group.
Here is a sketch demonstrating the solution.
function createMyTextBox(point, content) {
// Create a text item
var text = new PointText({
point: point,
content: content,
fontSize: 36,
fillColor: 'red',
justification: 'center'
});
// Create a rectangle around the text
var rectangle = new Path.Rectangle({
rectangle: text.bounds.scale(1.2),
strokeColor: 'black'
});
// Create a Group that will wrap our items.
var group = new Group([text, rectangle]);
// Return the group
return group;
}
// Create 2 text boxes
var textBox1 = createMyTextBox(view.center, 'text 1');
var textBox2 = createMyTextBox(view.center + [0, 100], 'text 2');
// You can use text boxes like regular items.
textBox2.translate(100, 0);
textBox2.scale(0.8);
Related
I created this codepen that configures an amcharts 5 instance with a map, 2 cities, a line between them, and a bullet (arrow) on the line.
If you hover the arrow on the line, you'll see a tooltip.
arrowSeries.bullets.push(function() {
var arrow = am5.Graphics.new(root, {
fill: am5.color(0x000000),
stroke: am5.color(0x000000),
draw: function (display) {
display.moveTo(0, -5);
display.lineTo(12, 0);
display.lineTo(0, 5);
display.lineTo(0, -5);
},
tooltipText: 'How to access `outbound` from the origin city here?',
});
return am5.Bullet.new(root, {
sprite: arrow
});
});
My question is how can I use data from my input cities in that tooltip?
In this stripped down example, I could just hard-reference my cities array. Of course what I am looking for is a way to pass the origin-destination pair data to the function that creates each instance of the Bullet (arrow). This way as I am iterating a larger set of city pairs to create the lines, each arrow can have a tooltip with data specific to each pair.
I have a featuregroup that adds circlemarkers and paths -- they are added in sequence and I need a way to create a function that will remove the last (2) in the _layers every time it's clicked.
I tried slicing but that doesn't work because it's not really an array.
Any thoughts on how to proceed? I've been searching stackoverflow for awhile and cannot find anything that matches what I'm trying to do.
Let's simply refer to Leaflet documentation:
You can use the methods the layerGroup/featureGroup provides to remove the last two layers from the group.
The getLayers method returns an array of all the layers added to the group.
The eachLayer method iterates over the layers of the group, optionally specifying context of the iterator function.
The removeLayer method removes the layer with the given internal ID from the group.
Leaflet's featureGroup is an extension of the layerGroup so all of these will work on featureGroup as well.
So, say that you have your layers set up like so:
// Layers:
var layers = L.layerGroup().addTo(map);
var marker = L.marker([51.5, -0.09]).addTo(layers);
var circle = L.circle([51.508, -0.11], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 500,
}).addTo(layers);
var polygon = L.polygon([
[51.509, -0.08],
[51.503, -0.06],
[51.51, -0.047],
]).addTo(layers);
You can use those layerGroup methods to remove the last two elements of the array:
// Pass the layerGroup to the function
function removeLastTwo(layerGroup) {
// Use getLayers to get the array
var layerArr = layerGroup.getLayers();
var minusOne = layerArr.length - 1;
var minusTwo = layerArr.length - 2;
// Use eachLayer to iterate the layerGroup
layerGroup.eachLayer((layer) => {
// Grab the index of the layer
var layerIndex = layerArr.indexOf(layer);
// Remove the last two elements of the layerGroup array
if (layerIndex === minusOne || layerIndex === minusTwo) {
layerGroup.removeLayer(layer);
}
});
}
Here is a live example, with this function attached to the click event listener of a button.
I'm currently trying to link the markers on my leaflet map to a portion of text available to the left of the map. What I would like to have happen is when the user clicks on a marker, the site will scroll to that portion of text.
I should also note that my markers are derived from a separate geoJson file.
This is the code I intend to use once each of the markers have their unique IDs to be manipulated:
document.getElementById("A").addEventListener("click", function() {
window.location.href = "#B";
});
So in this case, "A" would be the unique ID given to one of the markers, and #B is the ID of the text I want to scroll to via the href.
*Edit
I should clarify that the primary issue is actually giving ID's to the markers from the geoJson elements I added onto the map.
The relevant code for this is here:
//convert points to circles in layer
function pointToLayer(feature, latlng, attributes){
var attribute = attributes[0];
// styling for the circles which are pink with white borders
var geojsonMarkerOptions = {
fillColor: "#800000",
color: "white",
weight: 1,
opacity: 1,
fillOpacity: 0.5,
};
var layer = L.circleMarker(latlng, geojsonMarkerOptions);
//returns layer with circle markers
return layer;
//function to fill the global attribute array variable at top of page
function processData(data){
//variable to store the first feature in the data
var properties = data.features[0].properties;
//for loop to push each attribute name into array
for (var attribute in properties){
//indexOf will only allow attributes with population values to be pushed
if (attribute.indexOf("Victims") > -1){
attributes.push(attribute);
};
};
return attributes; //returns attributes array to be used in callback
};
//function to retrieve the data from geojson
function getData(map){
//load the data and calls functions
$.getJSON("data/WarCrimes3.geojson");
};
I have quite a few Tile Layers in my map, and they are all organized into different groups (sometimes they are even nested).
I see in API there's a getLayer() method to retrieve the layer a Vector feature belongs to, and a getLayerGroup() to retrieve all groups associated with a Map.
However, I could not find anything on getting the layerGroup a layer is associated with.
Lets'say I have this situation:
var myGroup = new LayerGroup();
var myLayer = new TileLayer();
myGroup.getLayers().insertAt(0, myLayer);
Is there a way to get myGroup from myLayer?
To get the parent group of a layer would need to write your own search function, something like
function searchGroups(group, layer) {
var result;
var layers = group.getLayers().getArray();
for (var i = 0; i < layer.length; i++) {
if (layers[i] === layer) {
result = group;
} else if (layers[i] instanceof LayerGroup) {
result = searchGroup(layers[i], layer)
}
if (result) {
break;
}
}
return result;
}
then call
var myGroup = searchGroups(map.getLayerGroup(), mylayer);
The getLayers() function you linked only works for a select interaction, you cannot determine from a random feature which layer it belongs to (and it could be in more than one) without a similar search of the features in each vector layer source.
I realize this question has already been answered, but alternatively, you also have the properties attribute for your layers, so you could add an array of group names to the layer itself. For example:
let parent_group = "parent_group";
let sub_group = "sub_group";
let group = new LayerGroup({
name: parent_group,
layers: [
new LayerGroup({
name: sub_group,
layers: [
new TileLayer() {
properties: [
parent_group,
sub_group
]
}
]
})
]
})
Then it's just a matter of looking up the layer name and looking up its properties array - probably a bit more cumbersome to setup initially, but it would save having to recursively search through layerGroups.
I want to make a dynamic render of user inputted text using three.js and dat.gui, so far i've made this to render out the text:
var displayGui = function(){
var gui = new dat.GUI();
var parameters = {
message:"sample",
spinVelocity: 0
}
//Adds Text controls
var myText = gui.add(parameters, 'message').name('Text');
myText.onChange(function () {
//adds text
var loader = new THREE.FontLoader();
loader.load('fonts/OpenSansBold.json', function(font) {
console.log(myText);
var textGeo = new THREE.TextGeometry(myText, {
font: font,
size: 200,
height: 50,
curveSegments: 12,
position: 3,
bevelThickness: 2,
bevelSize: 5,
bevelEnabled: true,
});
var textMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000 });
var mesh = new THREE.Mesh(textGeo, textMaterial);
mesh.position.set(100, 100, 100);
scene.add(mesh);
});
});
gui.add(parameters, 'spinVelocity').name('Spin');
gui.open();
};
However, as you can see on here, it just renders out a big red 3D text that says [object Object] , i have suspected that this may be because var myText is an object and not a string, so i tried to String(myText) however, it did not change much and it still did not work.
Is this not working because the text is not a string or is this because three.js is not recognizing the text inputted by the user on the dat.gui interface?
You should not be trying to load the font each time your dat.gui fires. Your code has a horrible performance problem, and it's likely that you would run out of memory after fiddling with the gui for a while.
My understanding is that this code creates a new instance of geometry each time you change the value in the gui, and never disposes of them. You're filling up your gpu with copies of this mesh.
Specific to your question, you're using datgui wrong:
console.log(myText); //logs the intance of a gui object (a JS object with methods and such)
change to:
console.log(parameters.message);
To fix the reloading issue, cache your font
var myFont
loader.load('fonts/OpenSansBold.json', function(font) {
myFont = font
//your gui is not ready until the font comes so, for example you could instantiate it here
gui.add(...).onChange(function(){..})
})
Change line
myText.onChange(function () {
to
myText.onChange(function (value) {
Then value will contain the new value of the input.