UNPKG

@progress/kendo-charts

Version:

Kendo UI platform-independent Charts library

470 lines (375 loc) 10.8 kB
import { geometry as g, throttle } from '@progress/kendo-drawing'; import { deepExtend, round, limitValue, hashKey, setDefaultOptions, isFunction } from '../../common'; import { removeChildren } from '../utils'; import { Layer } from './layer'; import TemplateService from '../../services/template-service'; let math = Math, Point = g.Point; function compileTemplate(template) { if (isFunction(template)) { return template; } return TemplateService.compile(template); } function roundPoint(point) { return new Point(round(point.x), round(point.y)); } function renderSize(size) { let newSize = size; if (typeof(size) !== "string") { newSize += "px"; } return newSize; } export class TileLayer extends Layer { constructor(map, options) { super(map, options); if (typeof this.options.subdomains === 'string') { this.options.subdomains = this.options.subdomains.split(''); } let viewType = this._viewType(); this._view = new viewType(this.element, this.options); } destroy() { super.destroy(); this._view.destroy(); this._view = null; } _beforeReset() { let map = this.map; let origin = map.locationToLayer(map.extent().nw).round(); this._view.viewOrigin(origin); } _reset() { super._reset(); this._updateView(); this._view.reset(); } _viewType() { return TileView; } _activate() { super._activate(); if (!this.support.mobileOS) { if (!this._pan) { this._pan = throttle(this._render.bind(this), 100); } this.map.bind('pan', this._pan); } } _deactivate() { super._deactivate(); if (this._pan) { this.map.unbind('pan', this._pan); } } _updateView() { let view = this._view, map = this.map, extent = map.extent(), extentToPoint = { nw: map.locationToLayer(extent.nw).round(), se: map.locationToLayer(extent.se).round() }; view.center(map.locationToLayer(map.center())); view.extent(extentToPoint); view.zoom(map.zoom()); } _resize() { this._render(); } _panEnd(e) { super._panEnd(e); this._render(); } _render() { this._updateView(); this._view.render(); } } setDefaultOptions(TileLayer, { tileSize: 256, subdomains: ['a', 'b', 'c'], urlTemplate: '', zIndex: 1 }); export class TileView { constructor(element, options) { this.element = element; this._initOptions(options); this.pool = new TilePool(); } _initOptions(options) { this.options = deepExtend({}, this.options, options); } center(center) { this._center = center; } extent(extent) { this._extent = extent; } viewOrigin(origin) { this._viewOrigin = origin; } zoom(zoom) { this._zoom = zoom; } pointToTileIndex(point) { return new Point(math.floor(point.x / this.options.tileSize), math.floor(point.y / this.options.tileSize)); } tileCount() { let size = this.size(), firstTileIndex = this.pointToTileIndex(this._extent.nw), nw = this._extent.nw, point = this.indexToPoint(firstTileIndex).translate(-nw.x, -nw.y); return { x: math.ceil((math.abs(point.x) + size.width) / this.options.tileSize), y: math.ceil((math.abs(point.y) + size.height) / this.options.tileSize) }; } size() { let nw = this._extent.nw, se = this._extent.se, diff = se.clone().translate(-nw.x, -nw.y); return { width: diff.x, height: diff.y }; } indexToPoint(index) { let x = index.x, y = index.y; return new Point(x * this.options.tileSize, y * this.options.tileSize); } subdomainText() { let subdomains = this.options.subdomains; return subdomains[this.subdomainIndex++ % subdomains.length]; } destroy() { removeChildren(this.element); this.pool.empty(); } reset() { this.pool.reset(); this.subdomainIndex = 0; this.render(); } render() { let size = this.tileCount(), firstTileIndex = this.pointToTileIndex(this._extent.nw), tile, x, y; for (x = 0; x < size.x; x++) { for (y = 0; y < size.y; y++) { tile = this.createTile({ x: firstTileIndex.x + x, y: firstTileIndex.y + y }); if (!tile.visible) { tile.show(); } } } } createTile(currentIndex) { let options = this.tileOptions(currentIndex); let tile = this.pool.get(this._center, options); if (!tile.element.parentNode) { this.element.append(tile.element); } return tile; } tileOptions(currentIndex) { let index = this.wrapIndex(currentIndex), point = this.indexToPoint(currentIndex), origin = this._viewOrigin, offset = point.clone().translate(-origin.x, -origin.y); return { index: index, currentIndex: currentIndex, point: point, offset: roundPoint(offset), zoom: this._zoom, size: this.options.tileSize, subdomain: this.subdomainText(), urlTemplate: this.options.urlTemplate, errorUrlTemplate: this.options.errorUrlTemplate }; } wrapIndex(index) { let boundary = math.pow(2, this._zoom); return { x: this.wrapValue(index.x, boundary), y: limitValue(index.y, 0, boundary - 1) }; } wrapValue(value, boundary) { let remainder = math.abs(value) % boundary; let wrappedValue = value; if (value >= 0) { wrappedValue = remainder; } else { wrappedValue = boundary - (remainder === 0 ? boundary : remainder); } return wrappedValue; } } export class ImageTile { constructor(id, options) { this.id = id; this.visible = true; this._initOptions(options); this.createElement(); this.show(); } destroy() { const element = this.element; const parentNode = element ? element.parentNode : null; if (element) { if (parentNode) { parentNode.removeChild(element); } this.element = null; } } _initOptions(options) { this.options = deepExtend({}, this.options, options); } createElement() { let el = document.createElement("img"); const size = this.options.size + "px"; el.setAttribute("alt", ""); el.style.position = "absolute"; el.style.display = "block"; el.style.width = el.style.maxWidth = size; el.style.height = el.style.maxHeight = size; this.element = el; // todo // add on error handler // this.element = // $('<img style=\'position: absolute; display: block;\' alt=\'\' />') // .css({ // width: this.options.size, // height: this.options.size // }) // .on('error', proxy(function(e) { // if (this.errorUrl()) { // e.target.setAttribute('src', this.errorUrl()); // } else { // e.target.removeAttribute('src'); // } // }, this)); } show() { let element = this.element; element.style.top = renderSize(this.options.offset.y); element.style.left = renderSize(this.options.offset.x); let url = this.url(); if (url) { element.setAttribute('src', url); } element.style.visibility = 'visible'; this.visible = true; } hide() { this.element.style.visibility = 'hidden'; this.visible = false; } url() { let urlResult = compileTemplate(this.options.urlTemplate); return urlResult(this.urlOptions()); } errorUrl() { let urlResult = compileTemplate(this.options.errorUrlTemplate); return urlResult(this.urlOptions()); } urlOptions() { let options = this.options; return { zoom: options.zoom, subdomain: options.subdomain, z: options.zoom, x: options.index.x, y: options.index.y, s: options.subdomain, quadkey: options.quadkey, q: options.quadkey, culture: options.culture, c: options.culture }; } } setDefaultOptions(ImageTile, { urlTemplate: '', errorUrlTemplate: '' }); export class TilePool { constructor() { this._items = []; } get(center, options) { if (this._items.length >= this.options.maxSize) { this._remove(center); } return this._create(options); } empty() { let items = this._items; for (let i = 0; i < items.length; i++) { items[i].destroy(); } this._items = []; } reset() { let items = this._items; for (let i = 0; i < items.length; i++) { items[i].hide(); } } _create(options) { let items = this._items; let tile; let id = hashKey(options.point.toString() + options.offset.toString() + options.zoom + options.urlTemplate); for (let i = 0; i < items.length; i++) { if (items[i].id === id) { tile = items[i]; break; } } if (tile) { tile.show(); } else { tile = new ImageTile(id, options); this._items.push(tile); } return tile; } _remove(center) { let items = this._items; let maxDist = -1; let index = -1; for (let i = 0; i < items.length; i++) { let dist = items[i].options.point.distanceTo(center); if (dist > maxDist && !items[i].visible) { index = i; maxDist = dist; } } if (index !== -1) { items[index].destroy(); items.splice(index, 1); } } } setDefaultOptions(TilePool, { maxSize: 100 });