333 lines
13 KiB
JavaScript
333 lines
13 KiB
JavaScript
|
|
||
|
/*
|
||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||
|
* or more contributor license agreements. See the NOTICE file
|
||
|
* distributed with this work for additional information
|
||
|
* regarding copyright ownership. The ASF licenses this file
|
||
|
* to you under the Apache License, Version 2.0 (the
|
||
|
* "License"); you may not use this file except in compliance
|
||
|
* with the License. You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing,
|
||
|
* software distributed under the License is distributed on an
|
||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
* KIND, either express or implied. See the License for the
|
||
|
* specific language governing permissions and limitations
|
||
|
* under the License.
|
||
|
*/
|
||
|
|
||
|
|
||
|
/**
|
||
|
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||
|
* or more contributor license agreements. See the NOTICE file
|
||
|
* distributed with this work for additional information
|
||
|
* regarding copyright ownership. The ASF licenses this file
|
||
|
* to you under the Apache License, Version 2.0 (the
|
||
|
* "License"); you may not use this file except in compliance
|
||
|
* with the License. You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing,
|
||
|
* software distributed under the License is distributed on an
|
||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
* KIND, either express or implied. See the License for the
|
||
|
* specific language governing permissions and limitations
|
||
|
* under the License.
|
||
|
*/
|
||
|
import { parseSVG, makeViewBoxTransform } from 'zrender/lib/tool/parseSVG.js';
|
||
|
import Group from 'zrender/lib/graphic/Group.js';
|
||
|
import Rect from 'zrender/lib/graphic/shape/Rect.js';
|
||
|
import { assert, createHashMap, each } from 'zrender/lib/core/util.js';
|
||
|
import BoundingRect from 'zrender/lib/core/BoundingRect.js';
|
||
|
import { parseXML } from 'zrender/lib/tool/parseXML.js';
|
||
|
import { GeoSVGRegion } from './Region.js';
|
||
|
/**
|
||
|
* "region available" means that: enable users to set attribute `name="xxx"` on those tags
|
||
|
* to make it be a region.
|
||
|
* 1. region styles and its label styles can be defined in echarts opton:
|
||
|
* ```js
|
||
|
* geo: {
|
||
|
* regions: [{
|
||
|
* name: 'xxx',
|
||
|
* itemStyle: { ... },
|
||
|
* label: { ... }
|
||
|
* }, {
|
||
|
* ...
|
||
|
* },
|
||
|
* ...]
|
||
|
* };
|
||
|
* ```
|
||
|
* 2. name can be duplicated in different SVG tag. All of the tags with the same name share
|
||
|
* a region option. For exampel if there are two <path> representing two lung lobes. They have
|
||
|
* no common parents but both of them need to display label "lung" inside.
|
||
|
*/
|
||
|
var REGION_AVAILABLE_SVG_TAG_MAP = createHashMap(['rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path',
|
||
|
// <text> <tspan> are also enabled because some SVG might paint text itself,
|
||
|
// but still need to trigger events or tooltip.
|
||
|
'text', 'tspan',
|
||
|
// <g> is also enabled because this case: if multiple tags share one name
|
||
|
// and need label displayed, every tags will display the name, which is not
|
||
|
// expected. So we can put them into a <g name="xxx">. Thereby only one label
|
||
|
// displayed and located based on the bounding rect of the <g>.
|
||
|
'g']);
|
||
|
var GeoSVGResource = /** @class */function () {
|
||
|
function GeoSVGResource(mapName, svg) {
|
||
|
this.type = 'geoSVG';
|
||
|
// All used graphics. key: hostKey, value: root
|
||
|
this._usedGraphicMap = createHashMap();
|
||
|
// All unused graphics.
|
||
|
this._freedGraphics = [];
|
||
|
this._mapName = mapName;
|
||
|
// Only perform parse to XML object here, which might be time
|
||
|
// consiming for large SVG.
|
||
|
// Although convert XML to zrender element is also time consiming,
|
||
|
// if we do it here, the clone of zrender elements has to be
|
||
|
// required. So we do it once for each geo instance, util real
|
||
|
// performance issues call for optimizing it.
|
||
|
this._parsedXML = parseXML(svg);
|
||
|
}
|
||
|
GeoSVGResource.prototype.load = function /* nameMap: NameMap */
|
||
|
() {
|
||
|
// In the "load" stage, graphic need to be built to
|
||
|
// get boundingRect for geo coordinate system.
|
||
|
var firstGraphic = this._firstGraphic;
|
||
|
// Create the return data structure only when first graphic created.
|
||
|
// Because they will be used in geo coordinate system update stage,
|
||
|
// and `regions` will be mounted at `geo` coordinate system,
|
||
|
// in which there is no "view" info, so that it should better not to
|
||
|
// make references to graphic elements.
|
||
|
if (!firstGraphic) {
|
||
|
firstGraphic = this._firstGraphic = this._buildGraphic(this._parsedXML);
|
||
|
this._freedGraphics.push(firstGraphic);
|
||
|
this._boundingRect = this._firstGraphic.boundingRect.clone();
|
||
|
// PENDING: `nameMap` will not be supported until some real requirement come.
|
||
|
// if (nameMap) {
|
||
|
// named = applyNameMap(named, nameMap);
|
||
|
// }
|
||
|
var _a = createRegions(firstGraphic.named),
|
||
|
regions = _a.regions,
|
||
|
regionsMap = _a.regionsMap;
|
||
|
this._regions = regions;
|
||
|
this._regionsMap = regionsMap;
|
||
|
}
|
||
|
return {
|
||
|
boundingRect: this._boundingRect,
|
||
|
regions: this._regions,
|
||
|
regionsMap: this._regionsMap
|
||
|
};
|
||
|
};
|
||
|
GeoSVGResource.prototype._buildGraphic = function (svgXML) {
|
||
|
var result;
|
||
|
var rootFromParse;
|
||
|
try {
|
||
|
result = svgXML && parseSVG(svgXML, {
|
||
|
ignoreViewBox: true,
|
||
|
ignoreRootClip: true
|
||
|
}) || {};
|
||
|
rootFromParse = result.root;
|
||
|
assert(rootFromParse != null);
|
||
|
} catch (e) {
|
||
|
throw new Error('Invalid svg format\n' + e.message);
|
||
|
}
|
||
|
// Note: we keep the covenant that the root has no transform. So always add an extra root.
|
||
|
var root = new Group();
|
||
|
root.add(rootFromParse);
|
||
|
root.isGeoSVGGraphicRoot = true;
|
||
|
// [THE_RULE_OF_VIEWPORT_AND_VIEWBOX]
|
||
|
//
|
||
|
// Consider: `<svg width="..." height="..." viewBox="...">`
|
||
|
// - the `width/height` we call it `svgWidth/svgHeight` for short.
|
||
|
// - `(0, 0, svgWidth, svgHeight)` defines the viewport of the SVG, or say,
|
||
|
// "viewport boundingRect", or `boundingRect` for short.
|
||
|
// - `viewBox` defines the transform from the real content ot the viewport.
|
||
|
// `viewBox` has the same unit as the content of SVG.
|
||
|
// If `viewBox` exists, a transform is defined, so the unit of `svgWidth/svgHeight` become
|
||
|
// different from the content of SVG. Otherwise, they are the same.
|
||
|
//
|
||
|
// If both `svgWidth/svgHeight/viewBox` are specified in a SVG file, the transform rule will be:
|
||
|
// 0. `boundingRect` is `(0, 0, svgWidth, svgHeight)`. Set it to Geo['_rect'] (View['_rect']).
|
||
|
// 1. Make a transform from `viewBox` to `boundingRect`.
|
||
|
// Note: only support `preserveAspectRatio 'xMidYMid'` here. That is, this transform will preserve
|
||
|
// the aspect ratio.
|
||
|
// 2. Make a transform from boundingRect to Geo['_viewRect'] (View['_viewRect'])
|
||
|
// (`Geo`/`View` will do this job).
|
||
|
// Note: this transform might not preserve aspect radio, which depending on how users specify
|
||
|
// viewRect in echarts option (e.g., `geo.left/top/width/height` will not preserve aspect ratio,
|
||
|
// but `geo.layoutCenter/layoutSize` will preserve aspect ratio).
|
||
|
//
|
||
|
// If `svgWidth/svgHeight` not specified, we use `viewBox` as the `boundingRect` to make the SVG
|
||
|
// layout look good.
|
||
|
//
|
||
|
// If neither `svgWidth/svgHeight` nor `viewBox` are not specified, we calculate the boundingRect
|
||
|
// of the SVG content and use them to make SVG layout look good.
|
||
|
var svgWidth = result.width;
|
||
|
var svgHeight = result.height;
|
||
|
var viewBoxRect = result.viewBoxRect;
|
||
|
var boundingRect = this._boundingRect;
|
||
|
if (!boundingRect) {
|
||
|
var bRectX = void 0;
|
||
|
var bRectY = void 0;
|
||
|
var bRectWidth = void 0;
|
||
|
var bRectHeight = void 0;
|
||
|
if (svgWidth != null) {
|
||
|
bRectX = 0;
|
||
|
bRectWidth = svgWidth;
|
||
|
} else if (viewBoxRect) {
|
||
|
bRectX = viewBoxRect.x;
|
||
|
bRectWidth = viewBoxRect.width;
|
||
|
}
|
||
|
if (svgHeight != null) {
|
||
|
bRectY = 0;
|
||
|
bRectHeight = svgHeight;
|
||
|
} else if (viewBoxRect) {
|
||
|
bRectY = viewBoxRect.y;
|
||
|
bRectHeight = viewBoxRect.height;
|
||
|
}
|
||
|
// If both viewBox and svgWidth/svgHeight not specified,
|
||
|
// we have to determine how to layout those element to make them look good.
|
||
|
if (bRectX == null || bRectY == null) {
|
||
|
var calculatedBoundingRect = rootFromParse.getBoundingRect();
|
||
|
if (bRectX == null) {
|
||
|
bRectX = calculatedBoundingRect.x;
|
||
|
bRectWidth = calculatedBoundingRect.width;
|
||
|
}
|
||
|
if (bRectY == null) {
|
||
|
bRectY = calculatedBoundingRect.y;
|
||
|
bRectHeight = calculatedBoundingRect.height;
|
||
|
}
|
||
|
}
|
||
|
boundingRect = this._boundingRect = new BoundingRect(bRectX, bRectY, bRectWidth, bRectHeight);
|
||
|
}
|
||
|
if (viewBoxRect) {
|
||
|
var viewBoxTransform = makeViewBoxTransform(viewBoxRect, boundingRect);
|
||
|
// Only support `preserveAspectRatio 'xMidYMid'`
|
||
|
rootFromParse.scaleX = rootFromParse.scaleY = viewBoxTransform.scale;
|
||
|
rootFromParse.x = viewBoxTransform.x;
|
||
|
rootFromParse.y = viewBoxTransform.y;
|
||
|
}
|
||
|
// SVG needs to clip based on `viewBox`. And some SVG files really rely on this feature.
|
||
|
// They do not strictly confine all of the content inside a display rect, but deliberately
|
||
|
// use a `viewBox` to define a displayable rect.
|
||
|
// PENDING:
|
||
|
// The drawback of the `setClipPath` here is: the region label (genereted by echarts) near the
|
||
|
// edge might also be clipped, because region labels are put as `textContent` of the SVG path.
|
||
|
root.setClipPath(new Rect({
|
||
|
shape: boundingRect.plain()
|
||
|
}));
|
||
|
var named = [];
|
||
|
each(result.named, function (namedItem) {
|
||
|
if (REGION_AVAILABLE_SVG_TAG_MAP.get(namedItem.svgNodeTagLower) != null) {
|
||
|
named.push(namedItem);
|
||
|
setSilent(namedItem.el);
|
||
|
}
|
||
|
});
|
||
|
return {
|
||
|
root: root,
|
||
|
boundingRect: boundingRect,
|
||
|
named: named
|
||
|
};
|
||
|
};
|
||
|
/**
|
||
|
* Consider:
|
||
|
* (1) One graphic element can not be shared by different `geoView` running simultaneously.
|
||
|
* Notice, also need to consider multiple echarts instances share a `mapRecord`.
|
||
|
* (2) Converting SVG to graphic elements is time consuming.
|
||
|
* (3) In the current architecture, `load` should be called frequently to get boundingRect,
|
||
|
* and it is called without view info.
|
||
|
* So we maintain graphic elements in this module, and enables `view` to use/return these
|
||
|
* graphics from/to the pool with it's uid.
|
||
|
*/
|
||
|
GeoSVGResource.prototype.useGraphic = function (hostKey /* , nameMap: NameMap */) {
|
||
|
var usedRootMap = this._usedGraphicMap;
|
||
|
var svgGraphic = usedRootMap.get(hostKey);
|
||
|
if (svgGraphic) {
|
||
|
return svgGraphic;
|
||
|
}
|
||
|
svgGraphic = this._freedGraphics.pop()
|
||
|
// use the first boundingRect to avoid duplicated boundingRect calculation.
|
||
|
|| this._buildGraphic(this._parsedXML);
|
||
|
usedRootMap.set(hostKey, svgGraphic);
|
||
|
// PENDING: `nameMap` will not be supported until some real requirement come.
|
||
|
// `nameMap` can only be obtained from echarts option.
|
||
|
// The original `named` must not be modified.
|
||
|
// if (nameMap) {
|
||
|
// svgGraphic = extend({}, svgGraphic);
|
||
|
// svgGraphic.named = applyNameMap(svgGraphic.named, nameMap);
|
||
|
// }
|
||
|
return svgGraphic;
|
||
|
};
|
||
|
GeoSVGResource.prototype.freeGraphic = function (hostKey) {
|
||
|
var usedRootMap = this._usedGraphicMap;
|
||
|
var svgGraphic = usedRootMap.get(hostKey);
|
||
|
if (svgGraphic) {
|
||
|
usedRootMap.removeKey(hostKey);
|
||
|
this._freedGraphics.push(svgGraphic);
|
||
|
}
|
||
|
};
|
||
|
return GeoSVGResource;
|
||
|
}();
|
||
|
export { GeoSVGResource };
|
||
|
function setSilent(el) {
|
||
|
// Only named element has silent: false, other elements should
|
||
|
// act as background and has no user interaction.
|
||
|
el.silent = false;
|
||
|
// text|tspan will be converted to group.
|
||
|
if (el.isGroup) {
|
||
|
el.traverse(function (child) {
|
||
|
child.silent = false;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
function createRegions(named) {
|
||
|
var regions = [];
|
||
|
var regionsMap = createHashMap();
|
||
|
// Create resions only for the first graphic.
|
||
|
each(named, function (namedItem) {
|
||
|
// Region has feature to calculate center for tooltip or other features.
|
||
|
// If there is a <g name="xxx">, the center should be the center of the
|
||
|
// bounding rect of the g.
|
||
|
if (namedItem.namedFrom != null) {
|
||
|
return;
|
||
|
}
|
||
|
var region = new GeoSVGRegion(namedItem.name, namedItem.el);
|
||
|
// PENDING: if `nameMap` supported, this region can not be mounted on
|
||
|
// `this`, but can only be created each time `load()` called.
|
||
|
regions.push(region);
|
||
|
// PENDING: if multiple tag named with the same name, only one will be
|
||
|
// found by `_regionsMap`. `_regionsMap` is used to find a coordinate
|
||
|
// by name. We use `region.getCenter()` as the coordinate.
|
||
|
regionsMap.set(namedItem.name, region);
|
||
|
});
|
||
|
return {
|
||
|
regions: regions,
|
||
|
regionsMap: regionsMap
|
||
|
};
|
||
|
}
|
||
|
// PENDING: `nameMap` will not be supported until some real requirement come.
|
||
|
// /**
|
||
|
// * Use the alias in geoNameMap.
|
||
|
// * The input `named` must not be modified.
|
||
|
// */
|
||
|
// function applyNameMap(
|
||
|
// named: GeoSVGGraphicRecord['named'],
|
||
|
// nameMap: NameMap
|
||
|
// ): GeoSVGGraphicRecord['named'] {
|
||
|
// const result = [] as GeoSVGGraphicRecord['named'];
|
||
|
// for (let i = 0; i < named.length; i++) {
|
||
|
// let regionGraphic = named[i];
|
||
|
// const name = regionGraphic.name;
|
||
|
// if (nameMap && nameMap.hasOwnProperty(name)) {
|
||
|
// regionGraphic = extend({}, regionGraphic);
|
||
|
// regionGraphic.name = name;
|
||
|
// }
|
||
|
// result.push(regionGraphic);
|
||
|
// }
|
||
|
// return result;
|
||
|
// }
|