@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
396 lines (318 loc) • 9.73 kB
JavaScript
import {
geometry as g,
drawing as d
} from '@progress/kendo-drawing';
import {
defined,
last,
setDefaultOptions
} from '../../common';
import {
proxy
} from '../utils';
import {
Layer
} from './layer';
import {
Movable
} from '../scroller/draggable';
import {
Location
} from '../location';
const Group = d.Group;
export class ShapeLayer extends Layer {
constructor(map, options) {
super(map, options);
this._pan = proxy(this._pan, this);
this.surface = d.Surface.create(this.element, {
width: map.scrollElement.clientWidth,
height: map.scrollElement.clientHeight
});
this._initRoot();
this.movable = new Movable(this.surface.element);
this._markers = [];
this._click = this._handler('shapeClick');
this.surface.bind('click', this._click);
this._mouseleave = this._handler('shapeMouseLeave');
this.surface.bind('mouseleave', this._mouseleave);
this.surface.bind('mouseenter', this._mouseenter.bind(this));
}
destroy() {
super.destroy();
this.surface.destroy();
}
_reset() {
super._reset();
this._translateSurface();
this._data = this._readData();
if (this._hasData()) {
this._load(this._data);
}
}
_initRoot() {
this._root = new Group();
this.surface.draw(this._root);
}
_beforeReset() {
this.surface.clear();
this._initRoot();
}
_resize() {
this.surface.size(this.map.size());
}
_readData() {
const data = super._readData();
if (data.type === "FeatureCollection") {
return data.features;
}
if (data.type === "GeometryCollection") {
return data.geometries;
}
return data;
}
_load(data) {
this._data = data;
this._clearMarkers();
if (!this._loader) {
this._loader = new GeoJsonLoader(this.map, this.options.style, this);
}
let container = new Group();
for (let i = 0; i < data.length; i++) {
let shape = this._loader.parse(data[i]);
if (shape) {
container.append(shape);
}
}
this._root.clear();
this._root.append(container);
}
shapeCreated(shape) {
let cancelled = false;
// the GeoJSON loader builds "Point" type as a circle
// use the circle shape type as and indicator for rendering a marker
// keep the behavior under a setting as this is supported by kendo jQuery Map
// but we opted out of this in blazor
if (shape instanceof d.Circle && this.map.options.renderPointsAsMarkers) {
cancelled = defined(this._createMarker(shape));
}
if (!cancelled) {
let args = {
layer: this,
shape: shape
};
cancelled = this.map.trigger('shapeCreated', args);
}
return cancelled;
}
featureCreated(e) {
e.layer = this;
this.map.trigger('shapeFeatureCreated', e);
}
_createMarker(shape) {
let marker = this.map.markers.bind({
location: shape.location
}, shape.dataItem);
if (marker) {
this._markers.push(marker);
}
return marker;
}
_clearMarkers() {
for (let i = 0; i < this._markers.length; i++) {
this.map.markers.remove(this._markers[i]);
}
this._markers = [];
}
_pan() {
if (!this._panning) {
this._panning = true;
this.surface.suspendTracking();
}
}
_panEnd(e) {
super._panEnd(e);
this._translateSurface();
this.surface.resumeTracking();
this._panning = false;
}
_translateSurface() {
let map = this.map;
let nw = map.locationToView(map.extent().nw);
if (this.surface.translate) {
this.surface.translate(nw);
this.movable.moveTo({
x: nw.x,
y: nw.y
});
}
}
_eventArgs(e) {
return {
layer: this,
layerIndex: this._layerIndex(),
shape: e.element,
shapeIndex: (this._data || []).indexOf(e.element.dataItem),
originalEvent: e.originalEvent
};
}
_handler(eventName) {
return (e) => {
if (e.element) {
this.map.trigger(eventName, this._eventArgs(e));
}
};
}
_mouseenter(e) {
if (!e.element) {
return;
}
this.map.trigger('shapeMouseEnter', this._eventArgs(e));
const shape = e.element;
const anchor = this._tooltipAnchor(e);
this.map._tooltip.show(anchor, this._tooltipContext(shape));
}
_tooltipContext(shape) {
return {
type: 'shape',
layerIndex: this._layerIndex(),
className: 'k-map-shape-tooltip',
dataItem: shape.dataItem,
location: shape.location
};
}
_tooltipAnchor(e) {
const cursor = this.map.eventOffset(e.originalEvent);
return {
top: cursor.y,
left: cursor.x
};
}
_activate() {
super._activate();
this._panHandler = proxy(this._pan, this);
this.map.bind('pan', this.panHandler);
}
_deactivate() {
super._deactivate();
this.map.unbind('pan', this._panHandler);
}
}
setDefaultOptions(ShapeLayer, {
autoBind: true,
zIndex: 100
});
class GeoJsonLoader {
constructor(locator, defaultStyle, observer) {
this.observer = observer;
this.locator = locator;
this.style = defaultStyle;
}
parse(item) {
let root = new Group();
let unwrap = true;
if (item.type === 'Feature') {
unwrap = false;
this._loadGeometryTo(root, item.geometry, item);
this._featureCreated(root, item);
} else {
this._loadGeometryTo(root, item, item);
}
if (unwrap && root.children.length < 2) {
root = root.children[0];
}
return root;
}
_shapeCreated(shape) {
let cancelled = false;
if (this.observer && this.observer.shapeCreated) {
cancelled = this.observer.shapeCreated(shape);
}
return cancelled;
}
_featureCreated(group, dataItem) {
if (this.observer && this.observer.featureCreated) {
this.observer.featureCreated({
group: group,
dataItem: dataItem,
properties: dataItem.properties
});
}
}
_loadGeometryTo(container, geometry, dataItem) {
let coords = geometry.coordinates;
let i;
let path;
switch (geometry.type) {
case 'LineString':
path = this._loadPolygon(container, [coords], dataItem);
this._setLineFill(path);
break;
case 'MultiLineString':
for (i = 0; i < coords.length; i++) {
path = this._loadPolygon(container, [coords[i]], dataItem);
this._setLineFill(path);
}
break;
case 'Polygon':
this._loadPolygon(container, coords, dataItem);
break;
case 'MultiPolygon':
for (i = 0; i < coords.length; i++) {
this._loadPolygon(container, coords[i], dataItem);
}
break;
case 'Point':
this._loadPoint(container, coords, dataItem);
break;
case 'MultiPoint':
for (i = 0; i < coords.length; i++) {
this._loadPoint(container, coords[i], dataItem);
}
break;
default:
break;
}
}
_setLineFill(path) {
let segments = path.segments;
if (segments.length < 4 || !segments[0].anchor().equals(last(segments).anchor())) {
path.options.fill = null;
}
}
_loadShape(container, shape) {
if (!this._shapeCreated(shape)) {
container.append(shape);
}
return shape;
}
_loadPolygon(container, rings, dataItem) {
let shape = this._buildPolygon(rings);
shape.dataItem = dataItem;
shape.location = this.locator.viewToLocation(shape.bbox().center());
return this._loadShape(container, shape);
}
_buildPolygon(rings) {
let type = rings.length > 1 ? d.MultiPath : d.Path;
let path = new type(this.style);
for (let i = 0; i < rings.length; i++) {
for (let j = 0; j < rings[i].length; j++) {
let point = this.locator.locationToView(Location.fromLngLat(rings[i][j]));
if (j === 0) {
path.moveTo(point.x, point.y);
} else {
path.lineTo(point.x, point.y);
}
}
}
return path;
}
_loadPoint(container, coords, dataItem) {
let location = Location.fromLngLat(coords);
let point = this.locator.locationToView(location);
let circle = new g.Circle(point, 10);
let shape = new d.Circle(circle, this.style);
shape.dataItem = dataItem;
shape.location = location;
return this._loadShape(container, shape);
}
}