Virtualization when over a Map

In my project I have a Diagram over a Leaflet map. I’m attempting to virtualize and following the instructions in Virtualized Sample with no Layout but the ViewportBoundsChanged only seems to be fired at diagram initialization, and not when the map (and thus the diagram) is moved.

How can I implement virtualization functionality when on top of a map?

Thanks,

K

this.theDiagram.addDiagramListener("ViewportBoundsChanged", (e) => this.onViewportChanged(e));

What happens if you replace the call to Diagram.redraw() with a call to the onViewportChanged function and change onViewportChanged so it doesn’t have to rely on the event to get the diagram reference?

I haven’t tried this yet, but maybe it will accomplish what you’re looking for.

I don’t make a call to redraw at any point in my code currently… on map move end is wired up to translate x,y to lat/lng.

Should I wire up the call to onViewPortChanged(/no e anymore/) from on map move end?

It may be more involved than that actually. We’re looking into it.

Thank you so much for your help.

Relatedly, I’m curious about how you set each individual node’s bounds in the sample:

bounds: new go.Rect((i%sqrt)*100, (i/sqrt)*100, 70, 20)

Should this rectangle size be a constant if the nodes are all constant size?

Correct. In the virtualization sample, all nodes are 70x20, so those are the bounds that get set when loading the model.

but the x,y part of the Rect… can that be a constant 0,0 or how do I determine what that value is?

Thanks

That is a little more complicated. The x,y values at initialization are relative to the mapOrigin. In our Leaflet sample, we coded those values based on where the map would initially be located, so you may need to do something similar.

Also, I can verify that calling onViewportChanged will work to keep the diagram and model in sync as long as your bindings are set up properly.

Great. I can verify that when the map is finished loading, if I call viewport changed it does, indeed virtualize the nodes on the map.

Still confused a bit about x,y but I am getting correct virtualization behavior. Perhaps map origin and x,y are the same?

Thanks again!

K

I noticed that the links version of virtualization depends on the fact that there are nodes they are connecting to. Hide the link if nodes are also hidden. In my case, however, there are no nodes, just “points”. But let’s say I have a series of roads represented by links. Is there any way I can reliably create a bound on them (size and orientation not known until downloaded from server) without having to create invisible end nodes?

Without knowing what you’re trying to accomplish with your app or having any mockups, it’s hard to say. I don’t think it would make sense to have a diagram with just links and no nodes they are connecting.

Thanks jhardy. That’s just it though, its a mapping application with modes of transportation shown as links. I don’t need end nodes because the route is what’s important, so I leave the nodes off to save a bit of memory (quite a bit, in the case of what I’m doing).

In that case it would be most efficient to only use simple Parts, not Nodes and not Links, so that you don’t have the overhead of maintaining relationships between objects. Each Part would have one (or more?) Shape with a Geometry that is what is needed.

Remember that the points in a Geometry are in the Shape’s coordinate system, not the document’s. (That’s different than the points in the route of a Link, which are in document coordinates.)

As an example, take a look at the Freehand Drawing sample: Freehand Drawing Tool

Hi Walter,

Thanks for your reply. I’ve taken a look at the freehand example and am curious how I’d associate a string of lat/longs in place of what looks like SVG coordinates in the example. And since they’re in the space of the Shape I’m not sure how they’d ever translate to real lat longs.

Thanks again,

K

If the Part is a “Position” Panel and consists of one Shape (or more) with Shape.isGeometryPositioned set to true, you can convert a Point in the Geometry to document coordinates by adding the Part.position.

So would I put all coordinates inside a string and assign it to geometryString, and create a conversion function that converts to/from map coordinates just like we’d do for location on nodes?

If you have a sample of what the geometryString would look like for a simple two segment line that would be helpful.

Don’t use strings – it’s always slower to write out numbers and parse a string than it is to just create the Geometry directly.

There are lots of examples of this in samples such as:
Candlestick or Range Charts. Basically, do something like:

var arr = . . .  // an array of numbers
var fig = new go.PathFigure(arr[0], arr[1]);
for (var i = 2; i < arr.length-2; i += 2) {
  fig.add(new go.PathSegment(go.PathSegment.Line, arr[i], arr[i+1]));
}
someShape.geometry = new go.Geometry().add(fig);

But once I figure out how to create the geometry and then make the translation from map to screen coordinates, I feel like I’m back close to the same problem: how do I determine if it should be shown or hidden?

Also, in response to walter’s comment:

If the Part is a “Position” Panel and consists of one Shape (or more) with Shape.isGeometryPositioned set to true, you can convert a Point in the Geometry to document coordinates by adding the Part.position.

I am not sure what you mean when you say convert geometry to doc coordinates by adding the position.

Here’s my relevant logic to assign geometry based on my understanding of typical latlong to point conversions:

Shape section of Part:’
$(go.Shape, new go.Binding('stroke', '', (d) => { if (d.highlighted) { return 'red'; } return d.symbolColor; }), new go.Binding('geometry', '', (d) => this.buildLineGeometry(d)), new go.Binding('figure', 'symbolShape'), { isGeometryPositioned: true, width: 11, strokeWidth: 3, isPanelMain: true, cursor: 'pointer', name: 'shapeMarker', height: 11, margin: 5, }),
Build geometry function:
`
buildLineGeometry(d: IGoJsDiagramLineData): go.Geometry {

let latlongs = d.points;

if (!latlongs) return new go.Geometry();

let point = this.map.latLngToContainerPoint(latlongs[0] as L.LatLngExpression);
let fig = new go.PathFigure(point.x, point.y); //the start of the path, x and y

for (let i = 2; i < latlongs.length - 2; i += 2) {
  let p = this.map.latLngToContainerPoint(latlongs[i] as L.LatLngExpression);

  fig.add(new go.PathSegment(go.PathSegment.Line, p.x, p.y));
}

return new go.Geometry().add(fig);

}
`

Thanks again for your help.

It doesn’t make sense to set or bind both Shape.geometry and Shape.figure and Shape.width and Shape.height at the same time. I suggest that you use different templates: one for showing an arbitrary geometry, and one for showing standard figures. The former would have a binding of Shape.geometry, and the latter would have bindings and/or settings for the other properties.

In my sample code I assumed the Array was just a sequence of numbers; in your case that would mean alternating latitude and longitude values. But your latlongs Array actually has both numbers for each Array element, so your loop should start at 1 and the index should only increment by one.

Should I still include location and position binding?

  $(go.Part, 'Vertical',
    new go.Binding('location', 'latlong', (data) => {
      let point = this.map.latLngToContainerPoint(data);
      return new go.Point(point.x, point.y);
    }),
    new go.Binding('position', 'bounds', function (b) {
      return b.position;
    })