I have a list of json objects.
var Planets = [
{name:"maths", percComp: "2", preReq: "english"},
{name:"english", percComp: "20", preReq: "geog"},
{name:"geog", percComp: "57", preReq: "maths"}
];
These objects are planets that i will add to a universe. They are school subjects
I also have a planet class
function Planet(planet, index)
{
this.name = planet.name;
this.percComp = planet.percComp;
this.preReq = planet.preReq;
CreatePlanet(this, index);
this.line = paper.path('');
function RedrawLine(planetFrom, planetTo, strokeWidth)
{
line.attr({
path:"M" + planetFrom.attrs.cx + "," + planetFrom.attrs.cy + "L"+ planetTo.attrs.cx + "," + planetTo.attrs.cy,
"stroke-width": strokeWidth
});
}
function CreatePlanet(planet)
{
var planetName = planet.name;
planetName = paper.circle(200 * index, 100, 80/index);
planetName.attr({
fill:'red',
stroke: '#3b4449',
'stroke-width': 6
});
SetGlow(30, true, 0, 0, 'grey', planetName)
SetHover(planetName);
}
function SetHover(planet)
{
var radius = planet.attrs.r;
planet.mouseover(function(){
this.animate({ r: radius * 1.3}, 150)
}).mouseout(function(){
this.animate({ r: radius}, 150)
})
}
function SetGlow(width, fill, offSetx, offsety, color, planet)
{
planet.glow({
width: 30,
fill: true,
offsetx: 0,
offsety: 0,
color: 'grey'
});
}
}
The code to initiate the program is
var element = document.getElementById('Main-Content')
var paper = new Raphael(element, 1000, 1000);
window.onload = function()
{
var planetsSet = paper.set();
var index = 1;
$(jQuery.parseJSON(JSON.stringify(Planets))).each(function(){
var planet = new Planet(this, index);
planetsSet.push(planet);
index++;
});
for (i=0; i<planetsSet.length; i++)
{
var planetTo = planetsSet[i];
// This is a test to see if RedrawLine works
var planeFrom = planetsFrom[i+1];
planetTo.RedrawLine(planetTo, planeFrom, 30)
var preReq = this.preReq;
}
}
The code populates the screen with 3 planets. I am trying to connect these with a line. The line will connect a planet with its pre requisite that is declared in the json object. So maths has a pre requisite english, and english has a pre requisite geog. I have a function in the Planet class that will draw the line, but I cant access the objects after they have been drawn.
Is this a problem with Raphael or can it be done?
several errors in your approach:
Paper.set is not Array, use Array for such purpse
you messed scopes, pass paper to your object constructor
public methods should be declared at least as this.method not just function method()
keep a public properties of your planet in this.property object or at least not call this.planet as PlanetName.
you forget to use this.line in your redraw method
little code to fill the void:
var planetsSet = [];
working sample
homework:
Remove planetFrom from RedrawLine method parameters, to be possible call this method as
PlanetFrom.RedrawLine(planetTo);
Related
Completely new to JS and jQuery here. The code iterates through each one of the bars and when you press the Reset button, it should shrink the bars down to zero. However, I am trying to get this bar graph animation to stop but once you press the button, it goes through the animation and resets back again to where it was before. Any help would be appreciated!
function barGraph(data) {
//Create graph
var graph = document.createElement("div");
graph.id = "barGraph";
//inserting bar graph before previous 'label' div
const target = document.querySelector('#labels');
target.parentNode.insertBefore(graph, labels);
//Styling for graph
graph.style.position = "relative";
graph.style.marginTop = "20px";
graph.style.height = "500px";
graph.style.backgroundColor = "Gainsboro";
graph.style.borderBottomStyle = "solid";
graph.style.borderBottomWidth = "1px";
graph.style.overflow = "hidden";
//Iterating through each bar
var position = 50;
var width = 75;
for (i = 0; i < data.length; i += 1) {
var spendData = data[i];
var bar = document.createElement("div");
//set a unique id for each of the bars
bar.id = data[i].category;
//Styling for bar
bar.style.position = "absolute";
bar.style.left = position + "px";
bar.style.width = width + "px";
bar.style.backgroundColor = spendData.color;
bar.style.height = (spendData.amount) / 5 + "px";
bar.style.bottom = "0px";
bar.innerHTML = "$" + spendData.amount;
bar.style.fontSize = "11px";
bar.style.color = "Azure";
bar.style.fontWeight = "800";
bar.style.fontFamily = "Arial, Helvetica, sans-serif";
bar.style.textAlign = "center";
bar.style.padding = "1em";
//Appending to the graph
graph.appendChild(bar);
//Set bar width
position += (width * 2);
}
return graph;
}
window.onload = function() {
var data = [{
category: "Food and Dining",
amount: "2005.00",
color: "CadetBlue"
},
{
category: "Auto and Transport",
amount: "1471.31",
color: "CornflowerBlue"
},
{
category: "Shopping",
amount: "892.86",
color: "DarkCyan"
},
{
category: "Bills and Utilities",
amount: "531.60",
color: "DarkSeaGreen"
},
{
category: "Mortgage",
amount: "1646.00",
color: "LightSeaGreen"
},
{
category: "Entertainment",
amount: "179.52",
color: "YellowGreen"
}
];
document.getElementById("resetGraph").addEventListener("click", function() {
for (i = 0; i < data.length; i++) {
var bar = document.getElementById(data[i].category);
if (bar) {
bar.animate({
"height": "0px",
"padding": "0px"
}, 2000);
}
}
});
var graph = barGraph(data);
//document.div.appendChild(graph);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="labels"></div>
<button id="resetGraph">Reset graph</button>
<script src="js/spending-graph.js"></script>
Dom's animate() function will return an AnimationPlaybackEvent object. And we can use onFinish method to reset the height and width of the dom element or can hide it.
Can you please add following lines to your code after bar.animate() function.
.onfinish=function(e){
e.currentTarget.effect.target.style.visibility ="hidden"
};
like below snippet.
bar.animate({
"height": "0px",
"padding": "0px"
}, 2000).onfinish=function(e){
e.currentTarget.effect.target.style.visibility ="hidden"
};
Updated
Interesting question! AFAICT the problem is that you've mixed up jQuery's .animate() with the native Javascript .animate().
Your question is tagged jQuery-animate, you mention it explicitly, and the format of the parameter you are passing to .animate() is exactly what jQuery expects.
But your animate code is running against an HTML DOM element, not a jQuery element:
// bar here is an HTML DOM Element, not a jQuery object:
var bar = document.getElementById(data[i].category);
// That means this will run JS .animate, not jQuery's:
bar.animate( ... );
If you simply change that to run against a jQuery object, it works (almost) as expected:
var bar = $('#' + data[i].category);
bar.animate( ... );
I say almost because there is another problem:
bar.id = data[i].category;
This creates HTML IDs from your plain English categories, which include whitespace, like "Food and Dining". According to the spec:
id's value must not contain whitespace (spaces, tabs etc.).
And at least when trying to use IDs with spaces as jQuery selectors, this matters, and it does not work. So instead let's just use the array index, which is also guaranteed to be unique, with a prefix so we know what we're referring to:
bar.id = 'bar-' + i;
Here's a working snippet with those 2 changes.
Note: the last document.div.appendChild(graph); was throwing an error - maybe this was a debugging attempt, it isn't necessary and I've commented it out here.
function barGraph(data) {
//Create graph
var graph = document.createElement("div");
graph.id = "barGraph";
//inserting bar graph before previous 'label' div
const target = document.querySelector('#labels');
target.parentNode.insertBefore(graph, labels);
//Styling for graph
graph.style.position = "relative";
graph.style.marginTop = "20px";
graph.style.height = "500px";
graph.style.backgroundColor = "Gainsboro";
graph.style.borderBottomStyle = "solid";
graph.style.borderBottomWidth = "1px";
graph.style.overflow = "hidden";
//Iterating through each bar
var position = 50;
var width = 75;
for (i = 0; i < data.length; i += 1) {
var spendData = data[i];
var bar = document.createElement("div");
//set a unique id for each of the bars
// UPDATED - category includes spaces, just use array index
// bar.id = data[i].category;
bar.id = 'bar-' + i;
//Styling for bar
bar.style.position = "absolute";
bar.style.left = position + "px";
bar.style.width = width + "px";
bar.style.backgroundColor = spendData.color;
bar.style.height = (spendData.amount) / 5 + "px";
bar.style.bottom = "0px";
bar.innerHTML = "$" + spendData.amount;
bar.style.fontSize = "11px";
bar.style.color = "Azure";
bar.style.fontWeight = "800";
bar.style.fontFamily = "Arial, Helvetica, sans-serif";
bar.style.textAlign = "center";
bar.style.padding = "1em";
//Appending to the graph
graph.appendChild(bar);
//Set bar width
position += (width * 2);
}
return graph;
}
window.onload = function() {
var data = [{
category: "Food and Dining",
amount: "2005.00",
color: "CadetBlue"
},
{
category: "Auto and Transport",
amount: "1471.31",
color: "CornflowerBlue"
},
{
category: "Shopping",
amount: "892.86",
color: "DarkCyan"
},
{
category: "Bills and Utilities",
amount: "531.60",
color: "DarkSeaGreen"
},
{
category: "Mortgage",
amount: "1646.00",
color: "LightSeaGreen"
},
{
category: "Entertainment",
amount: "179.52",
color: "YellowGreen"
}
];
document.getElementById("resetGraph").addEventListener("click", function() {
for (i = 0; i < data.length; i++) {
// UPDATED: 1. use new format ID without spaces
// UPDATED: 2. use jQuery selector/animation
// UPDATED: 3. use bar.length to test if selector matched anything
var bar = $('#bar-' + i);
if (bar.length) {
bar.animate({
"height": "0px",
"padding": "0px"
}, 2000);
}
}
});
var graph = barGraph(data);
//document.div.appendChild(graph);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="labels"></div>
<button id="resetGraph">Reset graph</button>
So what is happening in your original code? I am not sure. It is the native Javascript .animate() that is running, not jQuery's. The first parameter of the Javascipt .animate() method should be:
Either an array of keyframe objects, or a keyframe object whose properties are arrays of values to iterate over.
Your code is passing an object, not an array, so .animate() will expect the latter, "a keyframe object". The Keyframe Formats spec gives a little more detail and some examples for what "a keyframe object" looks like:
element.animate({
opacity: [ 0, 1 ], // [ from, to ]
color: [ "#fff", "#000" ] // [ from, to ]
}, 2000);
Your object does not match this format though - each attribute is just a string, not an array of start/stop values. The Keyframe docs also describe Implicit to/from keyframes:
In newer browser versions, you are able to set a beginning or end state for an animation only (i.e. a single keyframe), and the browser will infer the other end of the animation if it is able to.
My guess is this is what is happening, somehow? The browser is inferring an implicit end frame of the start values?
I tried to update your code to use the right Keyframe format, so you can use the native .animate() and skip jQuery all together, but it does not work - the bars reset to their original height after the animation, just like in your original code, I don't know why:
document.getElementById("resetGraph").addEventListener("click", function() {
var bar, startHeight;
for (i = 0; i < data.length; i++) {
bar = document.getElementById('bar-' + i);
if (bar) {
startHeight = window.getComputedStyle(bar).height;
bar.animate({
height: [startHeight, '0px'],
padding: ['1em', '0']
}, 2000);
}
}
});
I'm looking to clip the cloud mask function (top of my code) to the AOI. For some reason, my other layers (dNDVI and RGB) work for the AOI, but my cloud mask doesn't apply to it. I've tried putting in the ee.Image.clip function but I couldn't get it to work.
I would love to have some insight as to why it won't apply for this specific layer. Very new to coding!
// --------- Cloud mask: -----------------
var mask_all = function(image) {
var hollstein_mask = cld.hollstein_S2(['shadow', 'cloud', 'cirrus'])(image);
return hollstein_mask;
};
var color = "#98ff00";
var AOI = ee.Geometry.Polygon(
[[[140.88237590701584,-37.896469860299604],
[140.96546001346115,-37.896469860299604],
[140.96546001346115,-37.83819826191272],
[140.88237590701584,-37.83819826191272],
[140.88237590701584,-37.896469860299604]]], null, false);
/* [[[140.5710269570096,-37.669974265519755],
[140.64037815329866,-37.669974265519755],
[140.64037815329866,-37.60037237657578],
[140.5710269570096,-37.60037237657578],
[140.5710269570096,-37.669974265519755] */
//load images for composite
var Sen2mosaic = ee.ImageCollection("COPERNICUS/S2_SR");
ee.Geometry.Polygon(140.88237590701584,-37.896469860299604);
var getQABits = function(image, start, end, newName) {
// Compute the bits we need to extract.
var pattern = 0;
for (var i = start; i <= end; i++) {
pattern += Math.pow(2, i);
}
// Return a single band image of the extracted QA bits, giving the band
// a new name.
return image.select([0], [newName])
.bitwiseAnd(pattern)
.rightShift(start);
};
// A function to mask out cloudy pixels.
var cloud_shadows = function(image) {
// Select the QA band.
var QA = image.select(['B8', 'B11', 'B4']);
// Get the internal_cloud_algorithm_flag bit.
return getQABits(QA, 3,3, 'cloud_shadows').eq(0);
// Return an image masking out cloudy areas.
};
// A function to mask out cloudy pixels.
var clouds = function(image) {
// Select the QA band.
var QA = image.select(['B8', 'B11', 'B4']);
// Get the internal_cloud_algorithm_flag bit.
return getQABits(QA, 5,5, 'Cloud').eq(0);
// Return an image masking out cloudy areas.
};
var maskClouds = function(image) {
var cs = cloud_shadows(image);
var c = clouds(image);
image = image.updateMask(cs);
return image.updateMask(c);
};
// --------- Input: ------------------------------------
var startDate = ee.Date('2018-12-31') //2016-09-15');
var endDate = ee.Date('2021-06-01');
var AOI =
ee.Geometry.Polygon(
[[[140.88237590701584,-37.896469860299604],
[140.96546001346115,-37.896469860299604],
[140.96546001346115,-37.83819826191272],
[140.88237590701584,-37.83819826191272],
[140.88237590701584,-37.896469860299604]]], null, false);
/* [[[140.5710269570096,-37.669974265519755],
[140.64037815329866,-37.669974265519755],
[140.64037815329866,-37.60037237657578],
[140.5710269570096,-37.60037237657578],
[140.5710269570096,-37.669974265519755] */
//Map.addLayer(AOI, {}, 'AOI', true);
Map.centerObject(AOI, 13);
var imageStartDate1 = startDate.advance(-30,"day");
var imageStartDate2 = startDate.advance(30,"day");
var imageEndDate1 = endDate.advance(-30,"day");
var imageEndDate2 = endDate.advance(30,"day");
var imagery = ee.ImageCollection("COPERNICUS/S2_SR");
//S2-SR: COPERNICUS/S2_SR //LANDSAT/LC08/C01/T1_SR
var Sen2 = ee.ImageCollection(imagery
// Filter by dates.
.filterDate(imageStartDate1, imageStartDate2)
// Filter by location.
.filterBounds(AOI));
var Sen2end = ee.ImageCollection(imagery
// Filter by dates.
.filterDate(imageEndDate1, imageEndDate2)
// Filter by location.
.filterBounds(AOI));
var Sen2mosaic = Sen2.mosaic().clip(AOI);
var Sen2mosaicEnd = Sen2end.mosaic().clip(AOI);
//print("Sen2mosaic", Sen2mosaic);
var composite_free = Sen2end.map(maskClouds);
var visParams = {bands:['B8', 'B11', 'B4'], min:0, max:5000};
Map.addLayer(composite_free.first(), visParams, 'cloud mask');
// Create the NDVI and NDWI spectral indices.
var ndvi = Sen2mosaic.normalizedDifference(['B8', 'B4']);
var ndwi = Sen2mosaic.normalizedDifference(['B3', 'B8']);
var ndviEnd = Sen2mosaicEnd.normalizedDifference(['B8', 'B4']);
var ndwiEnd = Sen2mosaicEnd.normalizedDifference(['B3', 'B8']);
// Create some binary images from thresholds on the indices.
// This threshold is designed to detect bare land.
var bare1 = ndvi.lt(0.2).and(ndwi.lt(0.3));
var bare1End = ndviEnd.lt(0.2).and(ndwiEnd.lt(0.3));
// This detects bare land with lower sensitivity. It also detects shadows.
var bare2 = ndvi.lt(0.2).and(ndwi.lt(0.8));
var bare2End = ndviEnd.lt(0.2).and(ndwiEnd.lt(0.8));
// Define visualization parameters for the spectral indices.
var ndviViz = {min: -1, max: 1, palette: ['FF0000', '00FF00']};
var ndwiViz = {min: 0.5, max: 1, palette: ['00FFFF', '0000FF']};
// Mask and mosaic visualization images. The last layer is on top.
var thematic = ee.ImageCollection([
// NDWI > 0.5 is water. Visualize it with a blue palette.
ndwi.updateMask(ndwi.gte(0.5)).visualize(ndwiViz),
// NDVI > 0.2 is vegetation. Visualize it with a green palette.
ndvi.updateMask(ndvi.gte(0.2)).visualize(ndviViz),
// Visualize bare areas with shadow (bare2 but not bare1) as gray.
bare2.updateMask(bare2.and(bare1.not())).visualize({palette: ['AAAAAA']}),
// Visualize the other bare areas as white.
bare1.updateMask(bare1).visualize({palette: ['FFFFFF']}),
]).mosaic();
var thematicEnd = ee.ImageCollection([
ndwiEnd.updateMask(ndwiEnd.gte(0.5)).visualize(ndwiViz),
ndviEnd.updateMask(ndviEnd.gte(0.2)).visualize(ndviViz),
bare2End.updateMask(bare2End.and(bare1.not())).visualize({palette: ['AAAAAA']}),
bare1End.updateMask(bare1End).visualize({palette: ['FFFFFF']}),
]).mosaic();
//Map.addLayer(thematic, {}, 'thematic', false);
//Map.addLayer(thematicEnd, {}, 'thematic end', false);
//Map.addLayer(ndvi, {}, 'NDVI', false);
//Map.addLayer(ndviEnd, {}, 'NDVI end', false);
var Band_R = Sen2mosaic.expression('RED / count', {'RED': Sen2mosaic.select('B4'), count : Sen2.size()});
var Band_G = Sen2mosaic.expression('GREEN / count', {'GREEN': Sen2mosaic.select('B3'), count : Sen2.size()});
var Band_B = Sen2mosaic.expression('BLUE / count', {'BLUE': Sen2mosaic.select('B2'), count : Sen2.size()});
var Band_Rend = Sen2mosaicEnd.expression('RED / count', {'RED': Sen2mosaicEnd.select('B4'), count : Sen2.size()});
var Band_Gend = Sen2mosaicEnd.expression('GREEN / count', {'GREEN': Sen2mosaicEnd.select('B3'), count : Sen2.size()});
var Band_Bend = Sen2mosaicEnd.expression('BLUE / count', {'BLUE': Sen2mosaicEnd.select('B2'), count : Sen2.size()});
var image_RGB = Band_R.addBands(Band_G).addBands(Band_B);
var image_RGBend = Band_Rend.addBands(Band_Gend).addBands(Band_Bend);
var image_viz_params = {
//'bands': ['B5', 'B4', 'B3'],
'min': 2,
'max': 240,
gamma: 1.5,
//'gamma': [0.95, 1.1, 1]
};
Map.addLayer(image_RGB, image_viz_params, 'RGB 12/2018', false);
Map.addLayer(image_RGBend, image_viz_params, 'RGB 12/2020', false);
var dNDVI = ndviEnd.subtract(ndvi);
// Scale product to USGS standards
var dNDVIscaled = dNDVI.multiply(1000);
// Add the difference image to the console on the right
print("Difference Normalized Difference Vegetation Index: ", dNDVI);
//--------------------------- NDVI Product - classified -------------------------------
// Define an SLD style of discrete intervals to apply to the image.
var sld_intervals =
'<RasterSymbolizer>' +
'<ColorMap type="intervals" extended="false" >' +
'<ColorMapEntry color="#ff1b1b" quantity="-250" label="-250" />' +
'<ColorMapEntry color="#ffa81b" quantity="-100" label="-100" />' +
'<ColorMapEntry color="#f5ff1b" quantity="250" label="250" />' +
'<ColorMapEntry color="#1bff2f" quantity="500" label="500" />' +
'<ColorMapEntry color="#099b16" quantity="1000" label="1000" />' +
'</ColorMap>' +
'</RasterSymbolizer>';
Map.addLayer(dNDVIscaled.sldStyle(sld_intervals), {}, 'dNDVI classified');
//==========================================================================================
// ADD A LEGEND
// set position of panel
var legend = ui.Panel({
style: {
position: 'bottom-left',
padding: '8px 15px'
}});
// Create legend title
var legendTitle = ui.Label({
value: 'dNDVI Classes',
style: {fontWeight: 'bold',
fontSize: '18px',
margin: '0 0 4px 0',
padding: '0'
}});
// Add the title to the panel
legend.add(legendTitle);
// Creates and styles 1 row of the legend.
var makeRow = function(color, name) {
// Create the label that is actually the colored box.
var colorBox = ui.Label({
style: {
backgroundColor: '#' + color,
// Use padding to give the box height and width.
padding: '8px',
margin: '0 0 4px 0'
}});
// Create the label filled with the description text.
var description = ui.Label({
value: name,
style: {margin: '0 0 4px 6px'}
});
// return the panel
return ui.Panel({
widgets: [colorBox, description],
layout: ui.Panel.Layout.Flow('horizontal')
})};
// Palette with the colors
var palette =['ff1b1b', 'ffa81b', 'f5ff1b', '1bff2f', '099b16', 'ffffff'];
// name of the legend
var names = ['Forest Loss, High Probability', 'Forest Loss', 'Unchanged', 'Forest Gain, Low', 'Forest Gain, High Probability'];
// Add color and and names
for (var i = 0; i < 5; i++) {
legend.add(makeRow(palette[i], names[i]));
}
// add legend to map (alternatively you can also print the legend to the console)
Map.add(legend);
// Export a cloud-optimized GeoTIFF.
Export.image.toDrive({
image: dNDVIscaled,
description: 'imageToCOGeoTiffExample',
scale: 10,
region: AOI,
fileFormat: 'GeoTIFF',
formatOptions: {
cloudOptimized: true
}
});
https://code.earthengine.google.com/586a1c0df85e74d4df8871618a965f6a
I'm grouping a few elements using snapSVG's group method, pushing them to an array and applying the drag method on the array elements by looping through each element.
Could you please help me in accessing the index postion of the dragged element (grps[i]) in the drag stop handler.
g1 and var g2 are the two gropus.
grps is the array that holds the two groups.
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js"></script>
</head>
JavaScript
var s = Snap(800, 600);
var grps = [];
var objects = [];
var red = s.rect(50, 50, 200, 200).attr({
fill: "red"
});
var green = s.rect(60, 60, 100, 100).attr({
fill: "green"
});
var g1 = s.group(red, green);
grps.push(g1);
var red = s.rect(300, 50, 200, 200).attr({
fill: "red"
});
var green = s.rect(310, 60, 100, 100).attr({
fill: "green"
});
var g2 = s.group(red, green);
grps.push(g1, g2);
var drag_move = function(dx, dy) {
this.attr({
transform: this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx, dy]
});
};
var drag_start = function() {
this.data('origTransform', this.transform().local);
};
var drag_stop = function(i) {
console.log("finished dragging");
console.log(i);
};
for (i = 0; i < grps.length; i++) {
grps[i].drag(drag_move, drag_start, drag_stop);
}
JsBin Link: http://jsbin.com/tonazosicu/10/edit?js
Thanks
You can using Function.prototype.bind() to preset some parameters like below
for (i = 0; i < grps.length; i++) {
grps[i].drag(drag_move, drag_start, drag_stop.bind(null, i));
}
Then on drag_stop you can access them like below.
var drag_stop = function(index, event) {
console.log("finished dragging");
console.log(index);
console.log(event);
};
One can achieve the same thing (in lastest versions of Snap I think) with...
grps.ForEach( function( el, i ) {
el.drag(drag_move, drag_start, drag_stop.bind(null, i))
};
But ultimately you don't need to use i, if you just use 'this' in the handler in most cases, and can simply do....
grps.ForEach( function( el ) {
el.drag(drag_move, drag_start, drag_stop)
};
This question already has answers here:
socket.io - emited objects from server lose their functions and name once received by client
(1 answer)
Socket.io passing javascript object
(2 answers)
Closed 5 years ago.
For an online board game I am making, I have defined a Tile class, whose instances I use to create a "map". The Tile class has several properties, and a method called show which displays the instance on the canvas.
All the scripts are passed on to the clients as static files via script tags in the index.html which is served by node.js. The instantiation of the Tile class worked properly before I introduced the server. Now it produces some very weird and interesting results.
The HexMap class, creates upon instantiation a 2d array of Tiles as follows:
function HexMap (width, height, image) {
this.width = width;
this.height = height;
this.contents = [];
for (let i = 0; i < this.width; i++) {
this.contents[i] = [];
for (let j = 0; j < this.height; j++)
this.contents[i].push(new Tile(i, j, size, image, "SEA"));
}
}
The code for the Tile class is this:
var Tile = function (row, column, side, image, type) {
Vector.call(this, row, column);
this.unit = null;
this.canvas = null;
this.side = side;
this.type = type;
this.startingPoint = new Vector(this.x*Math.sqrt(3)*this.side + (this.y& 1) * this.side*Math.sqrt(3)/2, this.side*1.5*this.y);
this.middlePoint = new Vector(this.startingPoint.x + Math.sqrt(3)*this.side/2, this.startingPoint.y + this.side/2);
this.getOffset = function () {
return {
"SEA" : 0,
"SHORELINE" : 60,
"PLAINS" : 120,
"FOREST_LIGHT" : 180,
"FOREST_HEAVY" : 240,
"MOUNTAINS" : 300,
"SWAMP" : 360,
"MARSH" : 420
}[this.type];
};
this.getVector = function () {
return new Vector(this.x, this.y);
};
this.show = function (context) {
if(!this.canvas){
this.canvas = makeTemplate(side, this.type, image);
}
context.drawImage(this.canvas, this.startingPoint.x - this.getOffset(), this.startingPoint.y);
};
this.getPixelX = function () {
return this.x*Math.sqrt(3)*this.side + (this.y & 1) * this.side;
};
this.getPixelY = function () {
return this.side/2 + this.side*2*this.y;
};
this.setType = function (type){
this.type = type;
};
};
Printing a Tile object in the console would normally display something like this:
Tile {x: 0, y: 0, unit: null, canvas: canvas, side: 15, …}
I tried the same thing using the server this time, and the result is this:
{x: 0, y: 1, unit: null, canvas: null, side: 15, …}
Interestingly enough, the result is indeed an object, but not a Tile object. It has all the properties a Tile object has, but has none of its methods.
The error I end up getting is this:
tile.show is not a function
The map object is created and transferred to the server via socket.io sockets. The following piece of code runs on the client (the guest).
function selectGame(id) {
var hexmap = new HexMap(rowspan, colspan, spritesheet);
hexmap.generateIsland();
socket.emit('game-select', {id: id, hexmap: hexmap});
}
The server then receives the map:
socket.on('game-select', function (data) {
//The 2 sockets corresponding to the players join the same room
io.sockets.connected[data.id].join(games[data.id].identifier);
io.sockets.connected[socket.id].join(games[socket.id].identifier);
//The start-game event is emitted for just the sockets of the same room
io.to(games[socket.id].identifier).emit('start-game', {
map: data.hexmap
});
});
Then both the clients receive it again:
socket.on('start-game', function (data) {
let el = document.getElementById("wrapper");
el.parentNode.removeChild(el);
hexmap = data.map;
contexts = setupCanvas();
displayMap(hexmap, contexts.mapContext);
});
What displayMap does is it iterates through the contents of the map, and displays each Tile using its show method.
I cannot pin down the problem no matter how hard i try... Any suggestion on how to solve this?
I want to create curved text in SVG using Javascript. I have faced a lot of problems, specially the namespace related ones, but now everything works, a path and a circle are successfully shown, but the text is not displayed. When I copy-paste the created svg code in browser inspector and add that to the svg, it works as intended. But I can't make it work using JS. The whole code is as you can see:
<html>
<body onload="onLoad()">
<div id="svgbox"></div>
</body>
<script>
var svg;
var last_shape; // for debug
function qs(sel)
{
return document.querySelector(sel);
}
function SVG(el)
{
this.element = document.createElementNS("http://www.w3.org/2000/svg", "svg");
qs(el).appendChild(this.element);
var props = {
"xmlns": "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink",
"version": "1.1",
"style": "width:100%;height:100%;",
"stroke": "#58b",
"fill":"none",
"stroke-width": "2"
};
for (i in props) {
this.element.setAttribute(i, props[i]);
}
this.create = function(tag,props) {
var o = document.createElementNS("http://www.w3.org/2000/svg", tag);
if(typeof(props)!="undefined") {
for (i in props) {
o.setAttribute(i, props[i]);
}
}
return o;
}
this.add = function(tag,props) {
var o = this.create(tag,props);
this.element.appendChild( o );
return o;
};
this.addTo = function(parent, tag, props) {
var o = document.createElementNS("http://www.w3.org/2000/svg", tag);
if(typeof(props)!="undefined") {
for (i in props) {
o.setAttribute(i, props[i]);
}
}
parent.appendChild(o);
return o;
};
return this;
}
function drawArc(svg, fromX, fromY, toX, toY, controlX, controlY, props)
{
var o = svg.add( "path", {
"d":"M" + fromX + " " + fromY + " Q " +
controlX + " " + controlY + " " +
toX + " " + toY
});
if(typeof(props)!="undefined") {
for (i in props) {
o.setAttribute(i, props[i]);
}
}
last_shape = o;
return o;
}
function drawLabeledArrow(svg, fromX, fromY, toX, toY, title, props)
{
var arc_id = "arc-"+Math.floor(Math.random()*10000000);
var arc = drawArc( svg, fromX, fromY, toX, toY, (fromX+toX)/2, (fromY+toY)/2 - (fromX+toX)/2, {id: arc_id});
var head_base_x = arc.getPointAtLength(arc.getTotalLength() - 4).x;
var head_base_y = arc.getPointAtLength(arc.getTotalLength() - 4).y;
last_shape = svg.add("text");
last_shape = svg.addTo(last_shape, "textPath", {"fill":"#ff0000", "xlink:href":"#"+arc_id});
last_shape.appendChild(document.createTextNode(title));
last_shape = svg.add( "circle", {
cx: head_base_x,
cy: head_base_y,
r: 4,
fill: "#5ad"
});
}
function onLoad()
{
svg = SVG('#svgbox');
drawLabeledArrow(svg, 10,100, 200, 100, "test label");
}
</script>
</html>
I'd appreciate if anyone tells me what's wrong here, and if there is any good and short explanation of all these problems in working with SVG in JS. Thanks.
UPDATE: I modified the code to use setAttributeNS instead, but still no success.
function drawLabeledArrow(svg, fromX, fromY, toX, toY, title, props)
{
var arc_id = "arc-"+Math.floor(Math.random()*10000000);
var arc = drawArc( svg, fromX, fromY, toX, toY, (fromX+toX)/2, (fromY+toY)/2 - (fromX+toX)/2, {id: arc_id});
var head_base_x = arc.getPointAtLength(arc.getTotalLength() - 4).x;
var head_base_y = arc.getPointAtLength(arc.getTotalLength() - 4).y;
last_shape = svg.add("text");
var o = document.createElementNS("http://www.w3.org/2000/svg", "textPath");
o.setAttributeNS("http://www.w3.org/2000/svg", "xlink:href", "#"+arc_id);
o.appendChild(document.createTextNode(title));
last_shape.appendChild( o );
last_shape = svg.add( "circle", {
cx: head_base_x,
cy: head_base_y,
r: 4,
fill: "#5ad"
});
}
The xlink:href attribute cannot be set with setAttribute as that method can only set attributes in the null namespace and xlink:href is in the xlink namespace.
Use setAttributeNS instead and specify the xlink namespace http://www.w3.org/1999/xlink as the first argument.