提交 152ab8ec 编写于 作者: 1 100pah

fix: [geo]

(1) fix contain point calculation.
(2) remove Geo['_nameCoordMap'], instead, calculate center on demand.
(3) fix that some API missing nameProperty and geoJSON should cached not only by mapName but also by nameProperty.
(4) do not support nameMap in geoSVG until some real requirements come.
(5) svg line polyline use 'region.lineStyle' rather than 'region.itemStyle'
(6) svg text tspan image are enabled to trigger tooltip and user event (but not supported to change state and style yet).
上级 886d0414
......@@ -29,7 +29,7 @@ import ExtensionAPI from '../../core/ExtensionAPI';
import GeoModel, { GeoCommonOptionMixin, GeoItemStyleOption } from '../../coord/geo/GeoModel';
import MapSeries from '../../chart/map/MapSeries';
import GlobalModel from '../../model/Global';
import { Payload, ECElement } from '../../util/types';
import { Payload, ECElement, LineStyleOption } from '../../util/types';
import GeoView from '../geo/GeoView';
import MapView from '../../chart/map/MapView';
import Geo from '../../coord/geo/Geo';
......@@ -38,11 +38,14 @@ import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle';
import { getECData } from '../../util/innerStore';
import { createOrUpdatePatternFromDecal } from '../../util/decal';
import { ViewCoordSysTransformInfoPart } from '../../coord/View';
import { GeoSVGResource } from '../../coord/geo/GeoSVGResource';
import { GeoSVGGraphicRecord, GeoSVGResource } from '../../coord/geo/GeoSVGResource';
import Displayable from 'zrender/src/graphic/Displayable';
import Element, { ElementTextConfig } from 'zrender/src/Element';
import { ElementTextConfig } from 'zrender/src/Element';
import List from '../../data/List';
import { GeoJSONRegion } from '../../coord/geo/Region';
import { RegionGraphic } from '../../coord/geo/geoTypes';
import { ItemStyleProps } from '../../model/mixin/itemStyle';
import { LineStyleProps } from '../../model/mixin/lineStyle';
interface RegionsGroup extends graphic.Group {
......@@ -58,6 +61,11 @@ interface ViewBuildContext {
transformInfoRaw: ViewCoordSysTransformInfoPart;
}
interface GeoStyleableOption {
itemStyle?: GeoItemStyleOption;
lineStyle?: LineStyleOption;
}
function getFixedItemStyle(model: Model<GeoItemStyleOption>) {
const itemStyle = model.getItemStyle();
const areaColor = model.get('areaColor');
......@@ -98,7 +106,7 @@ class MapDraw {
private _svgGroup: graphic.Group;
private _svgRegionElements: Displayable[];
private _svgRegionGraphics: GeoSVGGraphicRecord['regionGraphics'];
constructor(api: ExtensionAPI) {
......@@ -244,10 +252,15 @@ class MapDraw {
});
const centerPt = transformPoint(region.getCenter());
this._resetSingleRegionGraphic(
viewBuildCtx, compoundPath, regionGroup, region.name, centerPt, null
);
const regionGraphic: RegionGraphic = {
name: region.name,
el: compoundPath,
styleOptionKey: 'itemStyle',
stateTrigger: regionGroup,
eventTrigger: regionGroup,
useLabel: true
};
this._resetSingleRegionGraphic(viewBuildCtx, regionGraphic, centerPt, null, false);
regionsGroup.add(regionGroup);
......@@ -268,76 +281,72 @@ class MapDraw {
this._useSVG(mapName);
}
zrUtil.each(this._svgRegionElements, function (el: Displayable) {
zrUtil.each(this._svgRegionGraphics, function (regionGraphic) {
// Note that we also allow different elements have the same name.
// For example, a glyph of a city and the label of the city have
// the same name and their tooltip info can be defined in a single
// region option.
this._resetSingleRegionGraphic(
viewBuildCtx, el, el, el.name, [0, 0], 'inside'
viewBuildCtx, regionGraphic, [0, 0], 'inside',
// We do not know how the SVG like so we'd better not to change z2.
// Otherwise it might bring some unexpected result. For example,
// an area hovered that make some inner city can not be clicked.
true
);
}, this);
}
private _resetSingleRegionGraphic(
viewBuildCtx: ViewBuildContext,
displayable: Displayable,
elForStateChange: Element,
regionName: string,
regionGraphic: RegionGraphic,
labelXY: number[],
labelPosition: ElementTextConfig['position']
labelPosition: ElementTextConfig['position'],
noZ2EmphasisLift: boolean
): void {
const regionName = regionGraphic.name;
const mapOrGeoModel = viewBuildCtx.mapOrGeoModel;
const data = viewBuildCtx.data;
const isVisualEncodedByVisualMap = viewBuildCtx.isVisualEncodedByVisualMap;
const isGeo = viewBuildCtx.isGeo;
const regionModel = mapOrGeoModel.getRegionModel(regionName) || mapOrGeoModel;
// @ts-ignore FIXME:TS fix the "compatible with each other"?
const itemStyleModel = regionModel.getModel('itemStyle');
// @ts-ignore FIXME:TS fix the "compatible with each other"?
const emphasisModel = regionModel.getModel('emphasis');
const emphasisItemStyleModel = emphasisModel.getModel('itemStyle');
// @ts-ignore FIXME:TS fix the "compatible with each other"?
const blurItemStyleModel = regionModel.getModel(['blur', 'itemStyle']);
// @ts-ignore FIXME:TS fix the "compatible with each other"?
const selectItemStyleModel = regionModel.getModel(['select', 'itemStyle']);
// NOTE: DONT use 'style' in visual when drawing map.
// This component is used for drawing underlying map for both geo component and map series.
const itemStyle = getFixedItemStyle(itemStyleModel);
const emphasisItemStyle = getFixedItemStyle(emphasisItemStyleModel);
const blurItemStyle = getFixedItemStyle(blurItemStyleModel);
const selectItemStyle = getFixedItemStyle(selectItemStyleModel);
let dataIdx;
const dataIdx = data ? data.indexOfName(regionName) : null;
const regionModel = mapOrGeoModel.getRegionModel(regionName);
const styles = makeStyleForRegion(regionGraphic.styleOptionKey, regionModel);
// Use the itemStyle in data if has data
if (data) {
dataIdx = data.indexOfName(regionName);
if (styles && styles.styleOptionKey === 'itemStyle' && data) {
// Only visual color of each item will be used. It can be encoded by visualMap
// But visual color of series is used in symbol drawing
//
// Visual color for each series is for the symbol draw
const style = data.getItemVisual(dataIdx, 'style');
const decal = data.getItemVisual(dataIdx, 'decal');
if (isVisualEncodedByVisualMap && style.fill) {
itemStyle.fill = style.fill;
styles.normal.fill = style.fill;
}
if (decal) {
itemStyle.decal = createOrUpdatePatternFromDecal(decal, viewBuildCtx.api);
styles.normal.decal = createOrUpdatePatternFromDecal(decal, viewBuildCtx.api);
}
}
displayable.setStyle(itemStyle);
displayable.style.strokeNoScale = true;
displayable.culling = true;
// PENDING: SVG text, tspan and image can be named but not supporeted
// to be styled by region option yet.
if (styles && regionGraphic.el instanceof graphic.Path) {
regionGraphic.el.setStyle(styles.normal);
regionGraphic.el.style.strokeNoScale = true;
regionGraphic.el.ensureState('emphasis').style = styles.emphasis;
regionGraphic.el.ensureState('select').style = styles.select;
regionGraphic.el.ensureState('blur').style = styles.blur;
}
displayable.ensureState('emphasis').style = emphasisItemStyle;
displayable.ensureState('blur').style = blurItemStyle;
displayable.ensureState('select').style = selectItemStyle;
if (regionGraphic.el instanceof Displayable) {
regionGraphic.el.culling = true;
}
if (noZ2EmphasisLift) {
(regionGraphic.el as ECElement).z2EmphasisLift = 0;
}
let showLabel = false;
for (let i = 0; i < DISPLAY_STATES.length; i++) {
......@@ -357,10 +366,13 @@ class MapDraw {
// In the following cases label will be drawn
// 1. In map series and data value is NaN
// 2. In geo component
// 4. Region has no series legendSymbol, which will be add a showLabel flag in mapSymbolLayout
// 3. Region has no series legendSymbol, which will be add a showLabel flag in mapSymbolLayout
if (
(isGeo || isDataNaN && (showLabel))
|| (itemLayout && itemLayout.showLabel)
regionGraphic.useLabel
&& (
((isGeo || isDataNaN) && showLabel)
|| (itemLayout && itemLayout.showLabel)
)
) {
const query = !isGeo ? dataIdx : regionName;
let labelFetcher;
......@@ -391,21 +403,31 @@ class MapDraw {
} }
);
displayable.setTextContent(textEl);
displayable.setTextConfig({
regionGraphic.el.setTextContent(textEl);
regionGraphic.el.setTextConfig({
local: true,
insideFill: textEl.style.fill,
position: labelPosition
});
(displayable as ECElement).disableLabelAnimation = true;
(regionGraphic.el as ECElement).disableLabelAnimation = true;
}
else {
regionGraphic.el.removeTextContent();
regionGraphic.el.removeTextConfig();
(regionGraphic.el as ECElement).disableLabelAnimation = null;
}
// setItemGraphicEl, setHoverStyle after all polygons and labels
// are added to the rigionGroup
if (data) {
data.setItemGraphicEl(dataIdx, elForStateChange);
// FIXME: when series-map use a SVG map, and there are duplicated name specified
// on different SVG elements, after `data.setItemGraphicEl(...)`:
// (1) all of them will be mounted with `dataIndex`, `seriesIndex`, so that tooltip
// can be triggered only mouse hover. That's correct.
// (2) only the last element will be kept in `data`, so that if trigger tooltip
// by `dispatchAction`, only the last one can be found and triggered. That might be
// not correct. We will fix it in future if anyone demanding that.
data.setItemGraphicEl(dataIdx, regionGraphic.eventTrigger);
}
// series-map will not trigger "geoselectchange" no matter it is
// based on a declared geo component. Becuause series-map will
......@@ -413,9 +435,8 @@ class MapDraw {
// If users call `chart.dispatchAction({type: 'toggleSelect'})`,
// it not easy to also fire event "geoselectchanged".
else {
const regionModel = mapOrGeoModel.getRegionModel(regionName);
// Package custom mouse event for geo component
getECData(displayable).eventData = {
getECData(regionGraphic.eventTrigger).eventData = {
componentType: 'geo',
componentIndex: mapOrGeoModel.componentIndex,
geoIndex: mapOrGeoModel.componentIndex,
......@@ -424,10 +445,25 @@ class MapDraw {
};
}
// @ts-ignore FIXME:TS fix the "compatible with each other"?
elForStateChange.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode');
enableHoverEmphasis(elForStateChange, emphasisModel.get('focus'), emphasisModel.get('blurScope'));
if (!data) {
graphic.setTooltipConfig({
el: regionGraphic.el,
componentModel: mapOrGeoModel,
itemName: regionName,
// @ts-ignore FIXME:TS fix the "compatible with each other"?
itemTooltipOption: regionModel.get('tooltip')
});
}
if (regionGraphic.stateTrigger) {
// @ts-ignore FIXME:TS fix the "compatible with each other"?
regionGraphic.stateTrigger.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode');
// @ts-ignore FIXME:TS fix the "compatible with each other"?
const emphasisModel = regionModel.getModel('emphasis');
enableHoverEmphasis(
regionGraphic.stateTrigger, emphasisModel.get('focus'), emphasisModel.get('blurScope')
);
}
}
remove(): void {
......@@ -447,7 +483,7 @@ class MapDraw {
if (resource && resource.type === 'geoSVG') {
const svgGraphic = (resource as GeoSVGResource).useGraphic(this.uid);
this._svgGroup.add(svgGraphic.root);
this._svgRegionElements = svgGraphic.regionElements;
this._svgRegionGraphics = svgGraphic.regionGraphics;
this._svgMapName = mapName;
}
}
......@@ -461,7 +497,7 @@ class MapDraw {
if (resource && resource.type === 'geoSVG') {
(resource as GeoSVGResource).freeGraphic(this.uid);
}
this._svgRegionElements = null;
this._svgRegionGraphics = null;
this._svgGroup.removeAll();
this._svgMapName = null;
}
......@@ -516,7 +552,7 @@ class MapDraw {
}, this);
controller.setPointerChecker(function (e, x, y) {
return geo.getViewRectAfterRoam().contain(x, y)
return geo.containPoint([x, y])
&& !onIrrelevantElement(e, api, mapOrGeoModel);
});
}
......@@ -563,4 +599,60 @@ function labelTextAfterUpdate(this: graphic.Text) {
m[3] /= scaleY;
}
function makeStyleForRegion(
styleOptionKey: RegionGraphic['styleOptionKey'],
regionModel: Model<
GeoStyleableOption & {
emphasis?: GeoStyleableOption;
select?: GeoStyleableOption;
blur?: GeoStyleableOption;
}
>
): {
styleOptionKey: 'itemStyle';
normal: ItemStyleProps;
emphasis: ItemStyleProps;
select: ItemStyleProps;
blur: ItemStyleProps;
} | {
styleOptionKey: 'lineStyle';
normal: LineStyleProps;
emphasis: LineStyleProps;
select: LineStyleProps;
blur: LineStyleProps;
} {
if (!styleOptionKey) {
return;
}
const normalStyleModel = regionModel.getModel(styleOptionKey);
const emphasisStyleModel = regionModel.getModel(['emphasis', styleOptionKey]);
const blurStyleModel = regionModel.getModel(['blur', styleOptionKey]);
const selectStyleModel = regionModel.getModel(['select', styleOptionKey]);
// NOTE: DONT use 'style' in visual when drawing map.
// This component is used for drawing underlying map for both geo component and map series.
if (styleOptionKey === 'itemStyle') {
return {
styleOptionKey,
normal: getFixedItemStyle(normalStyleModel),
emphasis: getFixedItemStyle(emphasisStyleModel),
select: getFixedItemStyle(selectStyleModel),
blur: getFixedItemStyle(blurStyleModel)
};
}
else if (styleOptionKey === 'lineStyle') {
return {
styleOptionKey,
normal: normalStyleModel.getLineStyle(),
emphasis: emphasisStyleModel.getLineStyle(),
select: selectStyleModel.getLineStyle(),
blur: blurStyleModel.getLineStyle()
};
}
}
export default MapDraw;
// @ts-ignore FIXME:TS fix the "compatible with each other"?
......@@ -50,6 +50,8 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy
/**
* Represents the transform brought by roam/zoom.
* If `View['_viewRect']` applies roam transform,
* we can get the final displayed rect.
*/
private _roamTransformable = new Transformable();
/**
......
......@@ -55,6 +55,7 @@ class Geo extends View {
readonly map: string;
readonly resourceType: GeoResource['type'];
// Only store specified name coord via `addGeoCoord`.
private _nameCoordMap: zrUtil.HashMap<number[]>;
private _regionsMap: zrUtil.HashMap<Region>;
private _invertLongitute: boolean;
......@@ -71,6 +72,7 @@ class Geo extends View {
opt: {
// Specify name alias
nameMap?: NameMap;
nameProperty?: string;
aspectScale?: number;
}
) {
......@@ -78,13 +80,12 @@ class Geo extends View {
this.map = map;
const source = geoSourceManager.load(map, opt.nameMap);
const source = geoSourceManager.load(map, opt.nameMap, opt.nameProperty);
const resource = geoSourceManager.getGeoResource(map);
this.resourceType = resource ? resource.type : null;
const defaultParmas = GEO_DEFAULT_PARAMS[resource.type];
this._nameCoordMap = source.nameCoordMap;
this._regionsMap = source.regionsMap;
this._invertLongitute = defaultParmas.invertLongitute;
this.regions = source.regions;
......@@ -97,16 +98,17 @@ class Geo extends View {
/**
* Whether contain the given [lng, lat] coord.
*/
containCoord(coord: number[]) {
const regions = this.regions;
for (let i = 0; i < regions.length; i++) {
const region = regions[i];
if (region.type === 'geoJSON' && (region as GeoJSONRegion).contain(coord)) {
return true;
}
}
return false;
}
// Never used yet.
// containCoord(coord: number[]) {
// const regions = this.regions;
// for (let i = 0; i < regions.length; i++) {
// const region = regions[i];
// if (region.type === 'geoJSON' && (region as GeoJSONRegion).contain(coord)) {
// return true;
// }
// }
// return false;
// }
protected _transformTo(x: number, y: number, width: number, height: number): void {
let rect = this.getBoundingRect();
......@@ -162,7 +164,9 @@ class Geo extends View {
* Get geoCoord by name
*/
getGeoCoord(name: string): number[] {
return this._nameCoordMap.get(name);
const region = this._regionsMap.get(name);
// calcualte center only on demand.
return this._nameCoordMap.get(name) || (region && region.getCenter());
}
dataToPoint(data: number[], noRoam?: boolean, out?: number[]): number[] {
......
......@@ -30,6 +30,8 @@ import { GeoJSONRegion } from './Region';
import { GeoJSON, GeoJSONCompressed, GeoJSONSourceInput, GeoResource, GeoSpecialAreas, NameMap } from './geoTypes';
const DEFAULT_NAME_PROPERTY = 'name' as const;
export class GeoJSONResource implements GeoResource {
readonly type = 'geoJSON';
......@@ -37,10 +39,10 @@ export class GeoJSONResource implements GeoResource {
private _specialAreas: GeoSpecialAreas;
private _mapName: string;
private _parsed: {
private _parsedMap = createHashMap<{
regions: GeoJSONRegion[];
boundingRect: BoundingRect;
};
}, string>();
constructor(
mapName: string,
......@@ -54,19 +56,24 @@ export class GeoJSONResource implements GeoResource {
this._geoJSON = parseInput(geoJSON);
}
/**
* @param nameMap can be null/undefined
* @param nameProperty can be null/undefined
*/
load(nameMap: NameMap, nameProperty: string) {
let parsed = this._parsed;
nameProperty = nameProperty || DEFAULT_NAME_PROPERTY;
let parsed = this._parsedMap.get(nameProperty);
if (!parsed) {
const rawRegions = this._parseToRegions(nameProperty);
parsed = this._parsed = {
parsed = this._parsedMap.set(nameProperty, {
regions: rawRegions,
boundingRect: calculateBoundingRect(rawRegions)
};
});
}
const regionsMap = createHashMap<GeoJSONRegion>();
const nameCoordMap = createHashMap<ReturnType<GeoJSONRegion['getCenter']>>();
const finalRegions: GeoJSONRegion[] = [];
each(parsed.regions, function (region) {
......@@ -79,14 +86,12 @@ export class GeoJSONResource implements GeoResource {
finalRegions.push(region);
regionsMap.set(regionName, region);
nameCoordMap.set(regionName, region.getCenter());
});
return {
regions: finalRegions,
boundingRect: parsed.boundingRect || new BoundingRect(0, 0, 0, 0),
regionsMap: regionsMap,
nameCoordMap: nameCoordMap
regionsMap: regionsMap
};
}
......
......@@ -61,7 +61,7 @@ interface GeoLabelFormatterDataParams {
export interface RegoinOption extends GeoStateOption, StatesOptionMixin<GeoStateOption> {
name?: string
selected?: boolean
tooltip?: Partial<Pick<CommonTooltipOption<GeoTooltipFormatterParams>, 'show'>>
tooltip?: CommonTooltipOption<GeoTooltipFormatterParams>
}
export interface GeoTooltipFormatterParams {
......@@ -93,6 +93,7 @@ export interface GeoCommonOptionMixin extends RoamOptionMixin {
boundingCoords?: number[][];
nameMap?: NameMap;
nameProperty?: string;
}
export interface GeoOption extends
......@@ -227,15 +228,16 @@ class GeoModel extends ComponentModel<GeoOption> {
optionUpdated(): void {
const option = this.option;
const self = this;
option.regions = geoCreator.getFilledRegions(option.regions, option.map, option.nameMap);
option.regions = geoCreator.getFilledRegions(
option.regions, option.map, option.nameMap, option.nameProperty
);
const selectedMap: Dictionary<boolean> = {};
this._optionModelMap = zrUtil.reduce(option.regions || [], function (optionModelMap, regionOpt) {
this._optionModelMap = zrUtil.reduce(option.regions || [], (optionModelMap, regionOpt) => {
const regionName = regionOpt.name;
if (regionName) {
optionModelMap.set(regionName, new Model(regionOpt, self));
optionModelMap.set(regionName, new Model(regionOpt, this, this.ecModel));
if (regionOpt.selected) {
selectedMap[regionName] = true;
}
......
......@@ -17,22 +17,41 @@
* under the License.
*/
import {parseSVG, makeViewBoxTransform} from 'zrender/src/tool/parseSVG';
import { parseSVG, makeViewBoxTransform, SVGNodeTagLower } from 'zrender/src/tool/parseSVG';
import Group from 'zrender/src/graphic/Group';
import Rect from 'zrender/src/graphic/shape/Rect';
import {assert, createHashMap, HashMap} from 'zrender/src/core/util';
import {assert, createHashMap, HashMap, hasOwn} from 'zrender/src/core/util';
import BoundingRect from 'zrender/src/core/BoundingRect';
import { GeoResource, GeoSVGGraphicRoot, GeoSVGSourceInput, NameMap } from './geoTypes';
import { GeoResource, GeoSVGGraphicRoot, GeoSVGSourceInput, RegionGraphic } from './geoTypes';
import { parseXML } from 'zrender/src/tool/parseXML';
import Displayable from 'zrender/src/graphic/Displayable';
import { GeoSVGRegion } from './Region';
interface GeoSVGGraphicRecord {
export interface GeoSVGGraphicRecord {
root: Group;
boundingRect: BoundingRect;
regionElements: Displayable[];
regionGraphics: RegionGraphic[];
}
const REGION_AVAILABLE_SVG_TAG_MAP = createHashMap<number, SVGNodeTagLower>([
'rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'text', 'tspan', 'path'
]);
const STYLE_OPTION_KEY = createHashMap<'itemStyle' | 'lineStyle', SVGNodeTagLower>({
'rect': 'itemStyle',
'circle': 'itemStyle',
'line': 'lineStyle',
'ellipse': 'itemStyle',
'polygon': 'itemStyle',
'polyline': 'lineStyle',
// 'image': '?', // TODO
// 'text': '?', // TODO
// 'tspan': '?', // TODO
'path': 'itemStyle'
});
const LABEL_HOST_MAP = createHashMap<number, SVGNodeTagLower>([
'rect', 'circle', 'line', 'ellipse', 'polygon', 'polyline', 'path'
]);
export class GeoSVGResource implements GeoResource {
readonly type = 'geoSVG';
......@@ -44,8 +63,6 @@ export class GeoSVGResource implements GeoResource {
private _regions: GeoSVGRegion[] = [];
// Key: region.name
private _regionsMap: HashMap<GeoSVGRegion> = createHashMap<GeoSVGRegion>();
// Key: region.name
private _nameCoordMap: HashMap<number[]> = createHashMap<number[]>();
// All used graphics. key: hostKey, value: root
private _usedGraphicMap: HashMap<GeoSVGGraphicRecord> = createHashMap();
......@@ -67,7 +84,7 @@ export class GeoSVGResource implements GeoResource {
this._parsedXML = parseXML(svg);
}
load(nameMap: NameMap, nameProperty: string) {
load(/* nameMap: NameMap */) {
// In the "load" stage, graphic need to be built to
// get boundingRect for geo coordinate system.
let firstGraphic = this._firstGraphic;
......@@ -84,41 +101,41 @@ export class GeoSVGResource implements GeoResource {
this._boundingRect = this._firstGraphic.boundingRect.clone();
// Create resions only for the first graphic, see coments below.
const regionElements = firstGraphic.regionElements;
for (let i = 0; i < regionElements.length; i++) {
const el = regionElements[i];
// Try use the alias in geoNameMap
let regionName = el.name;
if (nameMap && nameMap.hasOwnProperty(regionName)) {
regionName = nameMap[regionName];
}
const region = new GeoSVGRegion(regionName, el);
const regionGraphics = firstGraphic.regionGraphics;
// PENDING: `nameMap` will not be supported until some real requirement come.
// if (nameMap) {
// regionGraphics = applyNameMap(regionGraphics, nameMap);
// }
// Create resions only for the first graphic.
for (let i = 0; i < regionGraphics.length; i++) {
const regionGraphic = regionGraphics[i];
const region = new GeoSVGRegion(regionGraphic.name, regionGraphic.el);
// PENDING: if `nameMap` supported, this region can not be mounted on
// `this`, but can only be created each time `load()` called.
this._regions.push(region);
this._regionsMap.set(regionName, region);
this._regionsMap.set(regionGraphic.name, region);
}
}
return {
boundingRect: this._boundingRect,
regions: this._regions,
regionsMap: this._regionsMap,
nameCoordMap: this._nameCoordMap
regionsMap: this._regionsMap
};
}
// 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.
useGraphic(hostKey: string): GeoSVGGraphicRecord {
/**
* 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.
*/
useGraphic(hostKey: string /*, nameMap: NameMap */): GeoSVGGraphicRecord {
const usedRootMap = this._usedGraphicMap;
let svgGraphic = usedRootMap.get(hostKey);
......@@ -130,7 +147,17 @@ export class GeoSVGResource implements GeoResource {
// use the first boundingRect to avoid duplicated boundingRect calculation.
|| buildGraphic(this._parsedXML, this._boundingRect);
return usedRootMap.set(hostKey, svgGraphic);
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 `regionGraphics` must not be modified.
// if (nameMap) {
// svgGraphic = extend({}, svgGraphic);
// svgGraphic.regionGraphics = applyNameMap(svgGraphic.regionGraphics, nameMap);
// }
return svgGraphic;
}
freeGraphic(hostKey: string): void {
......@@ -203,9 +230,60 @@ function buildGraphic(
(root as GeoSVGGraphicRoot).isGeoSVGGraphicRoot = true;
return {
root: root,
boundingRect: boundingRect,
regionElements: result.namedElements
};
const regionGraphics = [] as GeoSVGGraphicRecord['regionGraphics'];
const named = result.named;
for (let i = 0; i < named.length; i++) {
const namedItem = named[i];
const svgNodeTagLower = namedItem.svgNodeTagLower;
if (REGION_AVAILABLE_SVG_TAG_MAP.get(svgNodeTagLower) != null) {
const styleOptionKey = STYLE_OPTION_KEY.get(svgNodeTagLower);
const el = namedItem.el;
regionGraphics.push({
name: namedItem.name,
el: el,
styleOptionKey: styleOptionKey,
stateTrigger: styleOptionKey != null ? el : null,
// text/tspan/image do not suport style but support event.
eventTrigger: el,
useLabel: LABEL_HOST_MAP.get(svgNodeTagLower) != null
});
// 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(child => {
child.silent = false;
});
}
}
}
return { root, boundingRect, regionGraphics };
}
// PENDING: `nameMap` will not be supported until some real requirement come.
// /**
// * Use the alias in geoNameMap.
// * The input `regionGraphics` must not be modified.
// */
// function applyNameMap(
// regionGraphics: GeoSVGGraphicRecord['regionGraphics'],
// nameMap: NameMap
// ): GeoSVGGraphicRecord['regionGraphics'] {
// const result = [] as GeoSVGGraphicRecord['regionGraphics'];
// for (let i = 0; i < regionGraphics.length; i++) {
// let regionGraphic = regionGraphics[i];
// const name = regionGraphic.name;
// if (nameMap && nameMap.hasOwnProperty(name)) {
// regionGraphic = extend({}, regionGraphic);
// regionGraphic.name = name;
// }
// result.push(regionGraphic);
// }
// return result;
// }
......@@ -141,6 +141,7 @@ class GeoCreator implements CoordinateSystemCreator {
const geo = new Geo(name + idx, name, {
nameMap: geoModel.get('nameMap'),
nameProperty: geoModel.get('nameProperty'),
aspectScale: geoModel.get('aspectScale')
});
......@@ -186,6 +187,7 @@ class GeoCreator implements CoordinateSystemCreator {
const geo = new Geo(mapType, mapType, {
nameMap: zrUtil.mergeAll(nameMapList),
nameProperty: mapSeries[0].get('nameProperty'),
aspectScale: mapSeries[0].get('aspectScale')
});
......@@ -213,7 +215,10 @@ class GeoCreator implements CoordinateSystemCreator {
* Fill given regions array
*/
getFilledRegions(
originRegionArr: RegoinOption[], mapName: string, nameMap?: NameMap
originRegionArr: RegoinOption[],
mapName: string,
nameMap: NameMap,
nameProperty: string
): RegoinOption[] {
// Not use the original
const regionsArr = (originRegionArr || []).slice();
......@@ -223,7 +228,7 @@ class GeoCreator implements CoordinateSystemCreator {
dataNameMap.set(regionsArr[i].name, regionsArr[i]);
}
const source = geoSourceManager.load(mapName, nameMap);
const source = geoSourceManager.load(mapName, nameMap, nameProperty);
zrUtil.each(source.regions, function (region) {
const name = region.name;
!dataNameMap.get(name) && regionsArr.push({name: name});
......
......@@ -131,7 +131,7 @@ export default {
&& (resource as GeoJSONResource).getMapForUser();
},
load: function (mapName: string, nameMap: NameMap, nameProperty?: string): ReturnType<GeoResource['load']> {
load: function (mapName: string, nameMap: NameMap, nameProperty: string): ReturnType<GeoResource['load']> {
const resource = storage.get(mapName);
if (!resource) {
......
......@@ -19,8 +19,9 @@
import BoundingRect from 'zrender/src/core/BoundingRect';
import { HashMap } from 'zrender/src/core/util';
import { Group } from '../../util/graphic';
import { Group, Path } from '../../util/graphic';
import { Region } from './Region';
import Element from 'zrender/src/Element';
export type GeoSVGSourceInput = 'string' | Document | SVGElement;
......@@ -125,16 +126,35 @@ interface GeoJSONGeometryMultiPolygonCompressed {
export interface GeoResource {
readonly type: 'geoJSON' | 'geoSVG';
load(nameMap: NameMap, nameProperty: string): {
load(
nameMap: NameMap,
nameProperty: string
): {
boundingRect: BoundingRect;
regions: Region[];
// Key: region.name
regionsMap: HashMap<Region>;
// Key: region.name
nameCoordMap: HashMap<number[]>;
};
}
export interface GeoSVGGraphicRoot extends Group {
isGeoSVGGraphicRoot: boolean;
}
export type RegionGraphic = {
// Region name. Can not be null/undefined.
name: string;
// Main el.
el: Element;
// If it specified, use it to trigger state
// style change (emphasis/select/blur)
// Can be null/undefined.
stateTrigger: Element;
// If it specified, use it to trigger event to users
// Can be null/undefined.
eventTrigger: Element;
// Whether to set label on `el.textContent`.
useLabel: boolean;
// Use this key to obtain style config in echarts option.
styleOptionKey: 'itemStyle' | 'lineStyle';
};
......@@ -113,7 +113,7 @@ class OrdinalMeta {
// Consider big data, do not create map until needed.
private _getOrCreateMap(): HashMap<OrdinalNumber> {
return this._map || (
this._map = createHashMap(this.categories)
this._map = createHashMap<OrdinalNumber>(this.categories)
);
}
}
......
......@@ -54,7 +54,7 @@ type LineStyleKeys = 'lineWidth'
| 'lineJoin'
| 'miterLimit';
type LineStyleProps = Pick<PathStyleProps, LineStyleKeys>;
export type LineStyleProps = Pick<PathStyleProps, LineStyleKeys>;
class LineStyleMixin {
......
......@@ -49,20 +49,34 @@ under the License.
<script>
function listenAndPrintSelectChanged(chart) {
function listenAndPrintEvent(chart) {
if (!chart) {
return;
}
const out = {
};
chart.on('geoselectchanged', function (params) {
out.allSelected = params.allSelected;
console.log('geoselectechanged');
out.geoselectechanged = {
allSelected: params.allSelected
};
console.log('geoselectechanged', params);
chart.__testHelper.updateInfo(out, 'event');
});
chart.on('selectchanged', function (params) {
out.selected = params.selected;
console.log('selectechanged');
out.selectechanged = {
selected: params.selected
};
console.log('selectechanged', params);
chart.__testHelper.updateInfo(out, 'event');
});
chart.on('click', function (params) {
out.click = {
componentIndex: params.componentIndex,
componentType: params.componentType,
geoIndex: params.geoIndex,
name: params.name
};
console.log('click', params);
chart.__testHelper.updateInfo(out, 'event');
});
}
......@@ -124,7 +138,7 @@ under the License.
height: 300
});
listenAndPrintSelectChanged(chart);
listenAndPrintEvent(chart);
});
</script>
......@@ -176,7 +190,7 @@ under the License.
height: 300
});
listenAndPrintSelectChanged(chart);
listenAndPrintEvent(chart);
});
</script>
......@@ -247,7 +261,7 @@ under the License.
// recordCanvas: true,
});
listenAndPrintSelectChanged(chart);
listenAndPrintEvent(chart);
});
......@@ -313,7 +327,7 @@ under the License.
// recordCanvas: true,
});
listenAndPrintSelectChanged(chart);
listenAndPrintEvent(chart);
});
......@@ -383,7 +397,7 @@ under the License.
// recordCanvas: true,
});
listenAndPrintSelectChanged(chart);
listenAndPrintEvent(chart);
});
......@@ -416,18 +430,28 @@ under the License.
itemStyle: {
color: null
},
tooltip: {
show: true,
confine: true,
formatter: function (params) {
return [
'This is the introduction:',
'xxxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxxx'
].join('<br>');
}
},
emphasis: {
// itemStyle: {
// borderColor: 'red',
// borderWidth: 5
// },
// areaStyle: {
// borderColor: 'red',
// borderWidth: 5
// },
label: {
textBorderColor: '#fff',
textBorderWidth: 2
show: false
}
},
select: {
......@@ -435,33 +459,55 @@ under the License.
color: '#b50205'
},
label: {
show: false,
textBorderColor: '#fff',
textBorderWidth: 2
show: false
}
},
regions: [{
name: 'Sikeloi',
tooltip: {
formatter: 'Sikeloi',
textStyle: { color: '#555' },
backgroundColor: '#ccc'
}
}, {
name: 'Sikanoi',
tooltip: {
formatter: 'Sikanoi',
textStyle: { color: '#555' },
backgroundColor: '#ccc'
}
}, {
name: 'Elymoi',
tooltip: {
formatter: 'Elymoi',
textStyle: { color: '#555' },
backgroundColor: '#ccc'
}
}],
z: 0
}],
series: {
type: 'map',
selectedMode: 'multiple',
coordinateSystem: 'geo',
geoIndex: 0
}
// series: {
// type: 'map',
// selectedMode: 'multiple',
// coordinateSystem: 'geo',
// geoIndex: 0
// }
};
var chart = testHelper.create(echarts, 'main_geo_svg_regions', {
title: [
'map series on declared geo with svg resource',
'Hover seat: check **tooltip** correct.'
'symbol and label use the same name in SVG.',
'Hover each symbol and text, tooltip should be displayed.',
'Hover the three area, tooltip should be displayed.',
'Click, check **selected**.'
],
option: option,
info: {},
infoKey: 'event',
height: 400
height: 500
});
listenAndPrintSelectChanged(chart);
listenAndPrintEvent(chart);
if (chart) {
chart.on('georoam', function (params) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册