Javascript associative array: pushing an object rewrites other array elements - javascript

I have an Object that describes my document. Each document contains layers, that described in Objects in an array
var example = {
id: 'some_id',
layers: [{
id: 'some_layer_id',
width: 100,
height: 200
}]
};
There are different types of layers, each has it's template:
var layer_templates = [{
type: 'text',
width: 100,
height: 100,
style: 'common'
}];
I create new layers in jQuery event:
jQuery('#button').on('click', function () {
var ldata = layer_templates[0]; // get a template
ldata['id'] = 's-l-' + Math.random().toString(); // create an id
example['layers'].push(ldata); // add new "layer" to document
console.log(example['layers']); // check
});
Every time i add new ldata layer to example['layers'] array, it keeps only initial object with id: 'some_layer_id'and rewrites all newly created objects with the last one.
Please check jsFiddle:Problem in action

The problem is that you are not duplicating your template. You are using and re-using the SAME template among all your layers
var ldata = layer_templates[0];
ldata['id'] = 's-l-' + Math.random().toString(); // create an id
in actuality, ldata is just a pointer that is referencing the template, adding the id value to it only modifies your template so it looks like this now:
var layer_templates = [{
type: 'text',
width: 100,
height: 100,
style: 'common',
id: 's-l-0.08636776800267398'
}];
NEXT time you try to add ANOTHER layer, you do the same thing yet again, which only pulls and changes the template once again... in the end, EVERY layer references the same template and therefore is the exact same data.
To fix this, you need to COPY your template, one way to do this is using JSON:
var ldata = JSON.parse(JSON.stringify(layer_templates[0]));
This method creates an entirely new ldata object that is a copy of the template.

Related

How do I use a variable in an openlayers button?

I've got this button:
function initMap() {
map.addControl(zoomToExtentControl);
};
zoomToExtentControl = new ol.control.ZoomToExtent({
extent: routeExtent,
className: 'custom-zoom-extent',
label: 'Z'
});
routeExtent is defined in globals.js as
var routeExtent = [-1013450.0281739295, 3594671.9021477713, 6578887.117336057, 10110775.689402476];
Elsewhere within initMap a new value for routeExtent is calculated, but the button only zooms to the preset extent defined in globals.js, how do I change this?
If you have access to ZoomToExtent class instance you can change the extent property directly. If don’t have access to it, you can access it through the map controls array and again, change the extent property directly. Here is a tested example:
const zoomToExtentControl = new ZoomToExtent({
extent: [
-1013450.0281739295,
3594671.9021477713,
6578887.117336057,
10110775.689402476
],
label: "Z"
});
map.addControl(zoomToExtentControl);
zoomToExtentControl.extent = [0, 0, 0, 0];
Let me know if this answers your question.

How to insert node into polyline link in gojs, preserving positions of points in links either side of the new node

I asked a question on StackOverflow (Seeking Javascript library for displaying and editing networks of nodes and edges) and was pointed at the gojs splice sample.
This has got me a long way, so thanks for the answer, but I have run into a brick wall trying to get the behaviour I want.
The app I am trying to create is to edit the borders on a map:
node = place where three or borders meet
link = segment of border between two nodes.
Hence, the nodes and links are unlabelled (nodes are just small circles, links are just polylines).
I have attempted to adapt the splice sample (https://gojs.net/extras/splicing.html) appropriately. The key features I need over and above what the sample does are:
choosing exactly where to position the new node on the link between the existing ones
preserving the shape of the polylines.
(The existing sample puts the new node equidistant between the existing ones and uses straight links.)
The user experience I have tried to create is this: first, you select the link, so it gets its usual adornments; then you shift-click on one of the adornments at a point on the polyline and that point becomes the new node.
I have sought to do this by overriding methods of the LinkReshapingTool using the extension mechanism described at https://gojs.net/latest/intro/extensions.html (rather than creating a subclass).
Whatever I have tried, though, I can't get the polylines to stay. By inspecting the diagram data model in the Chrome DevTools debugger after my code has run, it appears that it is correct (i.e. I can see the correct array of points in the links). However, when I then allow execution to continue the links do not display as expected (they are straight), and if I subsequently look at the data model then the multiple points have disappeared and each link just has a start and end.
I have tried various things, without success, for example:
deferring the splicing till after the tool has completed
passing the points into the modified links in different ways (array v list v string)
putting the processing into different overridden methods.
My current code is below. Please excuse crass stylistic faux pas - I am not an experienced JavaScript programmer.
<!DOCTYPE html> <!-- HTML5 document type -->
<!--
Adapted from splicing example from gojs.net
-->
<html>
<head>
<!-- use go-debug.js when developing and go.js when deploying -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gojs/1.8.28/go-debug.js"></script>
</head>
<body>
<div id="myDiagramDiv"
style="width:400px; height:300px; background-color: #DAE4E4;"></div>
<script>
var $ = go.GraphObject.make;
// state variables to remember what to do after reshaping tool aborted
var doSpliceNode = false;
var doSpliceIntoLink = null;
var doSplicePoint = null;
var doSpliceIndex = -1;
// diagram
var myDiagram = $(go.Diagram, "myDiagramDiv",
{
"undoManager.isEnabled": true
});
var tool = myDiagram.toolManager.linkReshapingTool;
// Override doMouseDown on linkreshapingtool. If user clicks on a handle with SHIFT pressed, want to insert
// a new node at that point rather than use the default behaviour to (further) reshape the link, and moreover
// want to retain the points in the link. I.e. turn one of the points in the link into a new node.
// (Existing gojs splicing example does not do this: it just puts a new node at the midpoint of the existing
// link, with no regard to the points along the link.)
tool.doMouseDown = function() {
console.log("mousedown at (" + this.Fp.M + "," + this.Fp.N + ")");
console.log(" on link from " + this.adornedLink.fromNode + " to " + this.adornedLink.toNode);
console.log(" with shift pressed? " + myDiagram.lastInput.shift);
var spliced = false;
if (myDiagram.lastInput.shift)
{
// work out which of the points on the link was clicked
var link = this.adornedLink;
var numpts = link.pointsCount;
var i;
var x = this.Fp.M; // ##TODO - by inspection in debugger this contains the X coord, but what's documented place to get this?
var y = this.Fp.N; // ##TODO - ditto for Y coord
for (i = 1; !spliced && (i < numpts - 1); i++)
{
if ((link.getPoint(i).x == x) && (link.getPoint(i).y == y))
{
console.log(" .. at point " + i);
// Store off what to do. (This used to be done inline - deferred to after as one of the things
// to try to make it work.)
doSpliceNode = true;
doSpliceIntoLink = link;
doSplicePoint = new go.Point(x, y);
doSpliceIndex = i;
spliced = true;
}
}
}
//if (!doSpliceNode)
{
console.log(".. call base class doMouseDown");
go.LinkReshapingTool.prototype.doMouseDown.call(tool);
}
}
// Override doMouseUp as well. If we had decided during mousedown to do the splice, then stop the tool now.
tool.doMouseUp = function()
{
// First call base class
go.LinkReshapingTool.prototype.doMouseUp.call(tool);
if (doSpliceNode)
{
// Doing splice - stop tool
console.log("STOP TOOL");
this.stopTool();
this.doDeactivate();
}
}
// Finally, override doStop to actually do the splice
tool.doStop = function() {
console.log("doStop");
// First call base class
go.LinkReshapingTool.prototype.doStop.call(tool);
if (doSpliceNode)
{
// now splice the node
console.log("splice node");
spliceNewNodeIntoLink2(doSpliceIntoLink, doSplicePoint, doSpliceIndex); // ##TODO make it respect points in existing link before and after
}
// Reset everything
doSpliceNode = false;
doSpliceIntoLink = null;
doSplicePoint = null;
doSpliceIndex = -1;
}
// Debug variable for inspecting later - not functional
var debugLastLink = null;
// Model, node and links for this application. Based heavily on https://gojs.net/temp/splicing.html and adapted as needed.
var myModel = $(go.GraphLinksModel);
myDiagram.nodeTemplate = $(go.Node,
"Auto",
new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "Circle", { width: 6, height: 6, strokeWidth: 0 }));
myDiagram.linkTemplate =
$(go.Link,
{
relinkableFrom: true, relinkableTo: true,
reshapable: true, resegmentable: true,
/* selectionAdornmentTemplate: ## COMMENT OUT - NOT NEEDED
$(go.Adornment,
$(go.Shape, { isPanelMain: true, stroke: "dodgerblue", strokeWidth: 2 }),
$(go.Shape, "PlusLine",
{
isActionable: true, // so that click works in an Adornment
width: 16, height: 16, stroke: "green", strokeWidth: 4, background: "transparent",
segmentOffset: new go.Point(8, 0),
click: function(e, shape) {
alert(e);
var link = shape.part.adornedPart;
var p0 = link.getPoint(0);
var p1 = link.getPoint(link.pointsCount - 1);
var pt = new go.Point((p0.x + p1.x) / 2, (p0.y + p1.y) / 2);
// ##TODO - instead, find the position where the mouse was clicked and place the node there
// ... need to work out which segment of polyline this was in so as to calculate new lines
// ##TODO - handle drag of node so that it just affects segments of lines immediately into it, rather than
// blatting over top of them
// ##TODO - what is object e and its components
spliceNewNodeIntoLink(link, pt);
},
cursor: "pointer"
})
), */
toShortLength: 1
},
new go.Binding("points").makeTwoWay(), // Use the points information from the linkDataArray initializer
$(go.Shape, { strokeWidth: 2 })
);
/* function spliceNewNodeIntoLink(link, pt) { // ## original version no longer called
link.diagram.commit(function(diag) {
var tokey = link.toNode.key;
// add a new node
var newnodedata = { text: "on link", location: go.Point.stringify(pt) };
diag.model.addNodeData(newnodedata);
// and splice it in by changing the existing link to refer to the new node
diag.model.setToKeyForLinkData(link.data, newnodedata.key);
// and by adding a new link from the new node to the original "toNode"
diag.model.addLinkData({ from: newnodedata.key, to: tokey });
// optional: select the new node
diag.select(diag.findNodeForData(newnodedata));
}, "spliced in node on a link");
} */
// Utility function used in one attempt to get this to work. Initializers in nodeDataArray do it via an array of numbers,
// so try that here.
function toArray(nodelist)
{
var returnarray = new Array();
var i;
for (i = 0; i < nodelist.size; i++)
{
var pt = nodelist.elt(i);
returnarray.push(pt.x);
returnarray.push(pt.y);
}
return returnarray;
}
// Function to splice the new node into the link. Parameters are
// - link: the link to splice into
// - pt: the point within the link to turn into a node
// - index: index into existing polyline of that point
function spliceNewNodeIntoLink2(link, pt, index) {
link.diagram.commit(function(diag) {
var oldlinkpointslist = link.points;
var link1pointslist = new go.List(go.Point);
var link2pointslist = new go.List(go.Point);
var i;
// Create new points list, from "from" node to new node to be added
for (i = 0; i <= index; i++)
{
var point = new go.Point(link.getPoint(i).x, link.getPoint(i).y);
link1pointslist.add(point);
}
console.log(link1pointslist);
// .. and from new node to "to" node
for (i = index; i < link.pointsCount; i++)
{
var point = new go.Point(link.getPoint(i).x, link.getPoint(i).y);
link2pointslist.add(point);
}
console.log(link2pointslist);
var tokey = link.toNode.key;
// add a new node
var newnodedata = { text: "on link", location: go.Point.stringify(pt) };
diag.model.addNodeData(newnodedata);
// and splice it in by changing the existing link to refer to the new node
diag.model.setToKeyForLinkData(link.data, newnodedata.key);
// ** NEW CODE
// Code this was based on re-used the existing link, re-purposing it to go from "from" node
// to new node, so do the same, but give it a new points list.
link.points = link1pointslist; // ##TODO find out why this doesn't work
// ... actually it does, but something ditches the points later ...
// so maybe I need to move this code to after the tool has really finished operating
// by saving off the info and calling it in an override of the last tool method that
// gets called (perhaps not - did this and it didn't work)
debugLastLink = link; // ##TEMP
// and by adding a new link from the new node to the original "toNode"
// ** UPDATED to include the second new point list
diag.model.addLinkData({ from: newnodedata.key, to: tokey, points: toArray(link2pointslist) });
// optional: select the new node
diag.select(diag.findNodeForData(newnodedata));
}, "spliced in node on a link");
}
// not called at present
function maySpliceOutNode(node) {
return node.findLinksInto().count === 1 &&
node.findLinksOutOf().count === 1 &&
node.findLinksInto().first() !== node.findLinksOutOf().first();
}
// not called at present
function spliceNodeOutFromLinkChain(node) {
if (maySpliceOutNode(node)) {
node.diagram.commit(function(diag) {
var inlink = node.findLinksInto().first();
var outlink = node.findLinksOutOf().first();
// reconnect the existing incoming link
inlink.toNode = outlink.toNode;
// remove the node and the outgoing link
diag.removeParts([node, outlink], false);
// optional: select the original link
diag.select(inlink);
}, "spliced out node from chain of links");
}
}
// Initialize modeldi
myModel.nodeDataArray = [
{ key: "1" , "location": "30 30" },
{ key: "2" , "location": "130 30" },
{ key: "3" , "location": "30 130" }
];
myModel.linkDataArray = [
{ from: "1", to: "2", "points": [ 30,30, 70,20, 100,40, 130,30 ] },
{ from: "2", to: "3", "points": [ 130,30, 100,80, 70,90, 30,130 ] },
{ from: "3", to: "1", "points": [ 30,130, 20,100, 40,70, 30,30 ] }
];
myDiagram.model = myModel;
</script>
</body>
</html>
Some suggestions:
Call Link.findClosestSegment to find the segment where the user clicked to insert a node.
Don't splice in the new node in an override of Tool.doStop, because that will be called even if the user hit the Escape key to cancel the tool's operation. Do it in either doMouseDown or doMouseUp, depending on the behavior that you want. But doStop is a reasonable time to clean up the tool's state.
I think it should work if you add the new Node and a new Link, connect them together properly, make sure the Node is at the right location, and only then set Link.points explicitly. The TwoWay Binding on Link.points will save the points to the model.
The problem that you are encountering is that when you create a new Node it takes time to be measured and arranged and positioned. Any one of those activities will invalidate the routes of all connected links. And obviously connecting a link with a node will invalidate that link's route. So you have to make sure everything is done in the right order.

Openlayers 2, specifying DOM Id for a Geometry/Feature?

Is there any way I can specify/set the DOM Id of an OpenLayers Geometry Point object i.e. call it "myID" instead of "OpenLayers_Geometry_Point_nnn"?
As far as I can tell, in Openlayers 2.13, creating a OpenLayers.Geometry.Point object does not accept any form of DOM id attribute, and creates its own unique ID through `OpenLayers.Util.createUniqueID. The OpenLayers.Geometry.Point.initialize 'constructor' only accepts X and Y values, not additional info.
I'd really like to use Selenium to verify/manipulate certain objects are on a map, and having predictable DOM Ids seems the be best way to do this.
One "solution" I have implemented is to replace OpenLayers.Util.createUniqueID whilst I am creating the objects I wish to test.
// replace OpenLayer Dom ID generation for this layer
var olPrefix = "MyPrefix";
var olCount = 0;
var old = OpenLayers.Util.createUniqueID;
OpenLayers.Util.createUniqueID = function(prefix) {
if (prefix.search( '^OpenLayers.Geometry') >= 0) {
// special Dom IDs for Geometry nodes only...
olCount++;
return olPrefix + "_" + olCount;
}
// default to using the previous ID generator...
return( old( prefix));
}
var geoJSON = {
"type": "FeatureCollection",
"features": [
{
"type":"Feature",
"geometry": { "type":"Point","coordinates":[ 1.0, 52.0]},
"properties":{ "myProperty":"myValue" }
},
// more features here
]
};
var geoformat = new OpenLayers.Format.GeoJSON();
var features = geoformat.read( geoJSON);
// finished creating your features, so put the "old" ID routine back...
OpenLayers.Util.createUniqueID = old;
Another possibility is to replace OpenLayers.Geometry.initialize in some way

How to zoom into x axis for multiple Flot charts that were created

I'm trying to get zoomin to work for the Flot charts created using following code.
var options = {
yaxis: { min: 0 },
xaxis: { mode: "time" },
series:{
lines: { show: true },
points: { show: true }
},
grid: {
hoverable: true,
clickable: false,
mouseActiveRadius: 30,
backgroundColor: { colors: ["#D1D1D1", "#7A7A7A"] }
},
selection:{mode: "x"}
};
var pdata = [];
for (var key in datasets) {
pdata = [];
pdata.push(datasets[key]);
$.plot( $('<div style="width:1200px;height:600px;"></div>').appendTo('#placeholder'),pdata,options);
$('<h5 align="center">'+datasets[key]['label']+'</h5>').appendTo('#placeholder');
$('<br>').appendTo('#placeholder');
$("#placeholder").UseTooltip();
};
Here I'm creating multiple charts in a loop.
How can I add zoomin feature.
Thank you.
Follow-up to Mark's answer: unique IDs are not really a Flot limitation; that's a requirement of the HTML spec. Browsers generally let you get away with breaking this rule, but it's still not a good idea. Mark's answer is good, but here's one that doesn't require an array-search on every event:
$.each(datasets, function(key, dataset) {
var element = $('<div style="width:1200px;height:600px;"></div>')
.appendTo('#placeholder');
var plot = $.plot(element, [dataset], options);
var plotOptions = plot.getOptions();
element.bind('plotselected', function(event, ranges) {
plotOptions.xaxes[0].min = ranges.xaxis.from;
plotOptions.xaxes[0].max = ranges.xaxis.to;
plot.setupGrid();
plot.draw();
});
};
flot generally expects it's place holder div to have a unique id. You would then use this unique id to assign a specific plotselected event to that plot. The way you have your code structured, though, you are appending the real placeholder div to a parent div as you create your plots. I like your approach so we need to work around flot's limitation.
So, in your plot call give your real placeholder div a class name. This will give us something to bind the plotselected event to. Also you need to save a reference to all the plot objects you've created. I'd just use a global array.
myPlots.push(
$.plot( $('<div class="myPlot" style="width:300px;height:100px;"></div>').appendTo('#placeholder'),pdata,options)
);
Where myPlots is the global array and my class is myPlot.
After this, you can set up the plotselected handler on the jquery selector .myPlots. Next for the tricky part, you need to find your plot object reference inside the handler. The easiest way to do this, I found, is to loop your myPlots array and compare their divs to the div the event happens on:
$(".myPlot").bind("plotselected", function (event, ranges) {
for (var i = 0; i < myPlots.length; i++)
{
var aPlot = myPlots[i];
if (aPlot.getPlaceholder()[0] == event.currentTarget) //this is the correct plot
{
var opts = myPlots[i].getOptions();
opts.xaxes[0].min = ranges.xaxis.from;
opts.xaxes[0].max = ranges.xaxis.to;
myPlots[i].setupGrid();
myPlots[i].draw();
}
}
});
You'll see above I'm redrawing the plot a little different than in the flot examples. I prefer this method since you don't have to remember the data, you adjust the min/max options and you redraw.
Here's a fiddle putting this all together.

Adding data to a highchart chart using an array with IDs

I want to add a series to a highchart scatterplot where I am naming each point in the series. I create a chart in the following way:
var chart; // globally available
makeCharts = function(){
chart = new Highcharts.Chart({
chart: {
renderTo: 'container1',
type: 'scatter'
},
series: [{
name: 'a',
data: [{
'id': 'point1',
'x': 1,
'y': 2
}, {
'id': 'point2',
'x': 2,
'y': 5
}]
}]
});
}
I would like to be able to update the points on the chart using something like:
chart.series[0].setData([{id:['point3', 'point4', 'point5'], y:[0,1,2], x:[1,2,3]}])
but this is not correct. Is it possible to update a chart using this approach where each point has an ID?
EDIT:
Just to clarify, I would like to be able to pass the arrays directly, rather than adding the data point by point using addPoint(). I could loop through an array and use addPoint() doing something like this:
id:['point3', 'point4', 'point5'];
y:[0,1,2];
x:[1,2,3];
for (i=0; i<x.length; i++)
{
chart.series[0].addPoint({
x: x[[i],
y: y[i],
id: id[i]
});
}
However, this is very slow. It's much quicker to add data using the following approach:
chart.series[0].setData([[1,0],[2,1],[3,2]]);
I have found that I can add data like this:
chart.series[0].setData([[1,0, 'point3'],[2,1, 'point4'],[3,2, 'point5']]);
but then the only way that I can access the id when the point is selected, is through this.point.config[2]. With the following approach I am unable to use chart.get('pointID') to identify a point as I did not set the ID. I want to be able to identify the point using just the ID.
Well broadly speaking there are two ways in which you can modify the chart data dynamically
Series.setData() Use this approach when you want to completely replace the existing data with some new data
Series.addPoint() Use this approach when you want to add a subset of the points dynamically. This method is not just for adding one point at a time, if you read the documentation carefully again you will find that this method takes a boolean redraw argument, and the argument detail is as following
redraw: Boolean
Defaults to true. Whether to redraw the chart after
the point is added. When adding more than one point, it is highly
recommended that the redraw option beset to false, and instead
chart.redraw() is explicitly called after the adding of points is
finished.
In your case, since you want to add a few points dynamically, but retaining the existing points, you should go with approach 2. But you need to use it inside a loop, with the redraw being set to false (hence solving the problem of being slow) and then after the loop, call the redraw method explicitly
Code
var id = ['point3', 'point4', 'point5'],
y = [0, 1, 2],
x = [1, 2, 3];
for (var i = 0; i < x.length; i++) {
chart.series[0].addPoint({
x: x[i],
y: y[i],
id: id[i]
},false);
}
chart.redraw();
Adding multiple points dynamically | Highcharts and Highstock # jsFiddle
Try using series.addPoint.
chart.series[0].addPoint({
x: 0,
y: 0,
id: 'anything'
});
But if you need to set data for series, use
chart.series[0].setData([{
x: 0,
y: 0,
id: 'anything'
},{
x: 2,
y: 2,
id: 'another'
}]);
As soon as you can pass your data like this:
chart.series[0].setData([[1,0, 'point3'],[2,1, 'point4'],[3,2, 'point5']]);
(as you stated in question), I can suggest you to use a little hack.
We'll need to add another statement to method applyOptions of Highcharts.Point prototype.
if (typeof options[0] === 'number' && options[2] && typeof options[2] === 'string') this.id = options[2];
Here you can see it in action.

Categories