Element.ts 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030
  1. import Transformable, {TRANSFORMABLE_PROPS, TransformProp} from './core/Transformable';
  2. import { AnimationEasing } from './animation/easing';
  3. import Animator, {cloneValue} from './animation/Animator';
  4. import { ZRenderType } from './zrender';
  5. import {
  6. Dictionary, ElementEventName, ZRRawEvent, BuiltinTextPosition, AllPropTypes,
  7. TextVerticalAlign, TextAlign, MapToType
  8. } from './core/types';
  9. import Path from './graphic/Path';
  10. import BoundingRect, { RectLike } from './core/BoundingRect';
  11. import Eventful from './core/Eventful';
  12. import ZRText, { DefaultTextStyle } from './graphic/Text';
  13. import { calculateTextPosition, TextPositionCalculationResult, parsePercent } from './contain/text';
  14. import {
  15. guid,
  16. isObject,
  17. keys,
  18. extend,
  19. indexOf,
  20. logError,
  21. mixin,
  22. isArrayLike,
  23. isTypedArray,
  24. isGradientObject,
  25. filter,
  26. reduce
  27. } from './core/util';
  28. import Polyline from './graphic/shape/Polyline';
  29. import Group from './graphic/Group';
  30. import Point from './core/Point';
  31. import { LIGHT_LABEL_COLOR, DARK_LABEL_COLOR } from './config';
  32. import { parse, stringify } from './tool/color';
  33. import { REDRAW_BIT } from './graphic/constants';
  34. export interface ElementAnimateConfig {
  35. duration?: number
  36. delay?: number
  37. easing?: AnimationEasing
  38. during?: (percent: number) => void
  39. // `done` will be called when all of the animations of the target props are
  40. // "done" or "aborted", and at least one "done" happened.
  41. // Common cases: animations declared, but some of them are aborted (e.g., by state change).
  42. // The calling of `animationTo` done rather than aborted if at least one done happened.
  43. done?: Function
  44. // `aborted` will be called when all of the animations of the target props are "aborted".
  45. aborted?: Function
  46. scope?: string
  47. /**
  48. * If force animate
  49. * Prevent stop animation and callback
  50. * immediently when target values are the same as current values.
  51. */
  52. force?: boolean
  53. /**
  54. * If use additive animation.
  55. */
  56. additive?: boolean
  57. /**
  58. * If set to final state before animation started.
  59. * It can be useful if something you want to calcuate depends on the final state of element.
  60. * Like bounding rect for text layouting.
  61. *
  62. * Only available in animateTo
  63. */
  64. setToFinal?: boolean
  65. }
  66. export interface ElementTextConfig {
  67. /**
  68. * Position relative to the element bounding rect
  69. * @default 'inside'
  70. */
  71. position?: BuiltinTextPosition | (number | string)[]
  72. /**
  73. * Rotation of the label.
  74. */
  75. rotation?: number
  76. /**
  77. * Rect that text will be positioned.
  78. * Default to be the rect of element.
  79. */
  80. layoutRect?: RectLike
  81. /**
  82. * Offset of the label.
  83. * The difference of offset and position is that it will be applied
  84. * in the rotation
  85. */
  86. offset?: number[]
  87. /**
  88. * Origin or rotation. Which is relative to the bounding box of the attached element.
  89. * Can be percent value. Relative to the bounding box.
  90. * If specified center. It will be center of the bounding box.
  91. *
  92. * Only available when position and rotation are both set.
  93. */
  94. origin?: (number | string)[] | 'center'
  95. /**
  96. * Distance to the rect
  97. * @default 5
  98. */
  99. distance?: number
  100. /**
  101. * If use local user space. Which will apply host's transform
  102. * @default false
  103. */
  104. local?: boolean
  105. /**
  106. * `insideFill` is a color string or left empty.
  107. * If a `textContent` is "inside", its final `fill` will be picked by this priority:
  108. * `textContent.style.fill` > `textConfig.insideFill` > "auto-calculated-fill"
  109. * In most cases, "auto-calculated-fill" is white.
  110. */
  111. insideFill?: string
  112. /**
  113. * `insideStroke` is a color string or left empty.
  114. * If a `textContent` is "inside", its final `stroke` will be picked by this priority:
  115. * `textContent.style.stroke` > `textConfig.insideStroke` > "auto-calculated-stroke"
  116. *
  117. * The rule of getting "auto-calculated-stroke":
  118. * If (A) the `fill` is specified in style (either in `textContent.style` or `textContent.style.rich`)
  119. * or (B) needed to draw text background (either defined in `textContent.style` or `textContent.style.rich`)
  120. * "auto-calculated-stroke" will be null.
  121. * Otherwise, "auto-calculated-stroke" will be the same as `fill` of this element if possible, or null.
  122. *
  123. * The reason of (A) is not decisive:
  124. * 1. If users specify `fill` in style and still use "auto-calculated-stroke", the effect
  125. * is not good and unexpected in some cases. It not easy and seams uncessary to auto calculate
  126. * a proper `stroke` for the given `fill`, since they can specify `stroke` themselve.
  127. * 2. Backward compat.
  128. */
  129. insideStroke?: string
  130. /**
  131. * `outsideFill` is a color string or left empty.
  132. * If a `textContent` is "inside", its final `fill` will be picked by this priority:
  133. * `textContent.style.fill` > `textConfig.outsideFill` > #000
  134. */
  135. outsideFill?: string
  136. /**
  137. * `outsideStroke` is a color string or left empth.
  138. * If a `textContent` is not "inside", its final `stroke` will be picked by this priority:
  139. * `textContent.style.stroke` > `textConfig.outsideStroke` > "auto-calculated-stroke"
  140. *
  141. * The rule of getting "auto-calculated-stroke":
  142. * If (A) the `fill` is specified in style (either in `textContent.style` or `textContent.style.rich`)
  143. * or (B) needed to draw text background (either defined in `textContent.style` or `textContent.style.rich`)
  144. * "auto-calculated-stroke" will be null.
  145. * Otherwise, "auto-calculated-stroke" will be a neer white color to distinguish "front end"
  146. * label with messy background (like other text label, line or other graphic).
  147. */
  148. outsideStroke?: string
  149. /**
  150. * Tell zrender I can sure this text is inside or not.
  151. * In case position is not using builtin `inside` hints.
  152. */
  153. inside?: boolean
  154. }
  155. export interface ElementTextGuideLineConfig {
  156. /**
  157. * Anchor for text guide line.
  158. * Notice: Won't work
  159. */
  160. anchor?: Point
  161. /**
  162. * If above the target element.
  163. */
  164. showAbove?: boolean
  165. /**
  166. * Candidates of connectors. Used when autoCalculate is true and anchor is not specified.
  167. */
  168. candidates?: ('left' | 'top' | 'right' | 'bottom')[]
  169. }
  170. export interface ElementEvent {
  171. type: ElementEventName,
  172. event: ZRRawEvent,
  173. // target can only be an element that is not silent.
  174. target: Element,
  175. // topTarget can be a silent element.
  176. topTarget: Element,
  177. cancelBubble: boolean,
  178. offsetX: number,
  179. offsetY: number,
  180. gestureEvent: string,
  181. pinchX: number,
  182. pinchY: number,
  183. pinchScale: number,
  184. wheelDelta: number,
  185. zrByTouch: boolean,
  186. which: number,
  187. stop: (this: ElementEvent) => void
  188. }
  189. export type ElementEventCallback<Ctx, Impl> = (
  190. this: CbThis<Ctx, Impl>, e: ElementEvent
  191. ) => boolean | void
  192. type CbThis<Ctx, Impl> = unknown extends Ctx ? Impl : Ctx;
  193. interface ElementEventHandlerProps {
  194. // Events
  195. onclick: ElementEventCallback<unknown, unknown>
  196. ondblclick: ElementEventCallback<unknown, unknown>
  197. onmouseover: ElementEventCallback<unknown, unknown>
  198. onmouseout: ElementEventCallback<unknown, unknown>
  199. onmousemove: ElementEventCallback<unknown, unknown>
  200. onmousewheel: ElementEventCallback<unknown, unknown>
  201. onmousedown: ElementEventCallback<unknown, unknown>
  202. onmouseup: ElementEventCallback<unknown, unknown>
  203. oncontextmenu: ElementEventCallback<unknown, unknown>
  204. ondrag: ElementEventCallback<unknown, unknown>
  205. ondragstart: ElementEventCallback<unknown, unknown>
  206. ondragend: ElementEventCallback<unknown, unknown>
  207. ondragenter: ElementEventCallback<unknown, unknown>
  208. ondragleave: ElementEventCallback<unknown, unknown>
  209. ondragover: ElementEventCallback<unknown, unknown>
  210. ondrop: ElementEventCallback<unknown, unknown>
  211. }
  212. export interface ElementProps extends Partial<ElementEventHandlerProps>, Partial<Pick<Transformable, TransformProp>> {
  213. name?: string
  214. ignore?: boolean
  215. isGroup?: boolean
  216. draggable?: boolean | 'horizontal' | 'vertical'
  217. silent?: boolean
  218. ignoreClip?: boolean
  219. globalScaleRatio?: number
  220. textConfig?: ElementTextConfig
  221. textContent?: ZRText
  222. clipPath?: Path
  223. drift?: Element['drift']
  224. extra?: Dictionary<unknown>
  225. // For echarts animation.
  226. anid?: string
  227. }
  228. // Properties can be used in state.
  229. export const PRESERVED_NORMAL_STATE = '__zr_normal__';
  230. // export const PRESERVED_MERGED_STATE = '__zr_merged__';
  231. const PRIMARY_STATES_KEYS = (TRANSFORMABLE_PROPS as any).concat(['ignore']) as [TransformProp, 'ignore'];
  232. const DEFAULT_ANIMATABLE_MAP = reduce(TRANSFORMABLE_PROPS, (obj, key) => {
  233. obj[key] = true;
  234. return obj;
  235. }, {ignore: false} as Partial<Record<ElementStatePropNames, boolean>>);
  236. export type ElementStatePropNames = (typeof PRIMARY_STATES_KEYS)[number] | 'textConfig';
  237. export type ElementState = Pick<ElementProps, ElementStatePropNames> & ElementCommonState
  238. export type ElementCommonState = {
  239. hoverLayer?: boolean
  240. }
  241. export type ElementCalculateTextPosition = (
  242. out: TextPositionCalculationResult,
  243. style: ElementTextConfig,
  244. rect: RectLike
  245. ) => TextPositionCalculationResult;
  246. let tmpTextPosCalcRes = {} as TextPositionCalculationResult;
  247. let tmpBoundingRect = new BoundingRect(0, 0, 0, 0);
  248. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  249. interface Element<Props extends ElementProps = ElementProps> extends Transformable,
  250. Eventful<{
  251. [key in ElementEventName]: (e: ElementEvent) => void | boolean
  252. } & {
  253. [key in string]: (...args: any) => void | boolean
  254. }>,
  255. ElementEventHandlerProps {
  256. }
  257. class Element<Props extends ElementProps = ElementProps> {
  258. id: number = guid()
  259. /**
  260. * Element type
  261. */
  262. type: string
  263. /**
  264. * Element name
  265. */
  266. name: string
  267. /**
  268. * If ignore drawing and events of the element object
  269. */
  270. ignore: boolean
  271. /**
  272. * Whether to respond to mouse events.
  273. */
  274. silent: boolean
  275. /**
  276. * 是否是 Group
  277. */
  278. isGroup: boolean
  279. /**
  280. * Whether it can be dragged.
  281. */
  282. draggable: boolean | 'horizontal' | 'vertical'
  283. /**
  284. * Whether is it dragging.
  285. */
  286. dragging: boolean
  287. parent: Group
  288. animators: Animator<any>[] = []
  289. /**
  290. * If ignore clip from it's parent or hosts.
  291. * Applied on itself and all it's children.
  292. *
  293. * NOTE: It won't affect the clipPath set on the children.
  294. */
  295. ignoreClip: boolean
  296. /**
  297. * If element is used as a component of other element.
  298. */
  299. __hostTarget: Element
  300. /**
  301. * ZRender instance will be assigned when element is associated with zrender
  302. */
  303. __zr: ZRenderType
  304. /**
  305. * Dirty bits.
  306. * From which painter will determine if this displayable object needs brush.
  307. */
  308. __dirty: number
  309. /**
  310. * If element was painted on the screen
  311. */
  312. __isRendered: boolean;
  313. /**
  314. * If element has been moved to the hover layer.
  315. *
  316. * If so, dirty will only trigger the zrender refresh hover layer
  317. */
  318. __inHover: boolean
  319. /**
  320. * path to clip the elements and its children, if it is a group.
  321. * @see http://www.w3.org/TR/2dcontext/#clipping-region
  322. */
  323. private _clipPath?: Path
  324. /**
  325. * Attached text element.
  326. * `position`, `style.textAlign`, `style.textVerticalAlign`
  327. * of element will be ignored if textContent.position is set
  328. */
  329. private _textContent?: ZRText
  330. /**
  331. * Text guide line.
  332. */
  333. private _textGuide?: Polyline
  334. /**
  335. * Config of textContent. Inlcuding layout, color, ...etc.
  336. */
  337. textConfig?: ElementTextConfig
  338. /**
  339. * Config for guide line calculating.
  340. *
  341. * NOTE: This is just a property signature. READ and WRITE are all done in echarts.
  342. */
  343. textGuideLineConfig?: ElementTextGuideLineConfig
  344. // FOR ECHARTS
  345. /**
  346. * Id for mapping animation
  347. */
  348. anid: string
  349. extra: Dictionary<unknown>
  350. currentStates?: string[] = []
  351. // prevStates is for storager in echarts.
  352. prevStates?: string[]
  353. /**
  354. * Store of element state.
  355. * '__normal__' key is preserved for default properties.
  356. */
  357. states: Dictionary<ElementState> = {}
  358. /**
  359. * Animation config applied on state switching.
  360. */
  361. stateTransition: ElementAnimateConfig
  362. /**
  363. * Proxy function for getting state with given stateName.
  364. * ZRender will first try to get with stateProxy. Then find from states if stateProxy returns nothing
  365. *
  366. * targetStates will be given in useStates
  367. */
  368. stateProxy?: (stateName: string, targetStates?: string[]) => ElementState
  369. protected _normalState: ElementState
  370. // Temporary storage for inside text color configuration.
  371. private _innerTextDefaultStyle: DefaultTextStyle
  372. constructor(props?: Props) {
  373. this._init(props);
  374. }
  375. protected _init(props?: Props) {
  376. // Init default properties
  377. this.attr(props);
  378. }
  379. /**
  380. * Drift element
  381. * @param {number} dx dx on the global space
  382. * @param {number} dy dy on the global space
  383. */
  384. drift(dx: number, dy: number, e?: ElementEvent) {
  385. switch (this.draggable) {
  386. case 'horizontal':
  387. dy = 0;
  388. break;
  389. case 'vertical':
  390. dx = 0;
  391. break;
  392. }
  393. let m = this.transform;
  394. if (!m) {
  395. m = this.transform = [1, 0, 0, 1, 0, 0];
  396. }
  397. m[4] += dx;
  398. m[5] += dy;
  399. this.decomposeTransform();
  400. this.markRedraw();
  401. }
  402. /**
  403. * Hook before update
  404. */
  405. beforeUpdate() {}
  406. /**
  407. * Hook after update
  408. */
  409. afterUpdate() {}
  410. /**
  411. * Update each frame
  412. */
  413. update() {
  414. this.updateTransform();
  415. if (this.__dirty) {
  416. this.updateInnerText();
  417. }
  418. }
  419. updateInnerText(forceUpdate?: boolean) {
  420. // Update textContent
  421. const textEl = this._textContent;
  422. if (textEl && (!textEl.ignore || forceUpdate)) {
  423. if (!this.textConfig) {
  424. this.textConfig = {};
  425. }
  426. const textConfig = this.textConfig;
  427. const isLocal = textConfig.local;
  428. const innerTransformable = textEl.innerTransformable;
  429. let textAlign: TextAlign;
  430. let textVerticalAlign: TextVerticalAlign;
  431. let textStyleChanged = false;
  432. // Apply host's transform.
  433. innerTransformable.parent = isLocal ? this as unknown as Group : null;
  434. let innerOrigin = false;
  435. // Reset x/y/rotation
  436. innerTransformable.copyTransform(textEl);
  437. // Force set attached text's position if `position` is in config.
  438. if (textConfig.position != null) {
  439. let layoutRect = tmpBoundingRect;
  440. if (textConfig.layoutRect) {
  441. layoutRect.copy(textConfig.layoutRect);
  442. }
  443. else {
  444. layoutRect.copy(this.getBoundingRect());
  445. }
  446. if (!isLocal) {
  447. layoutRect.applyTransform(this.transform);
  448. }
  449. if (this.calculateTextPosition) {
  450. this.calculateTextPosition(tmpTextPosCalcRes, textConfig, layoutRect);
  451. }
  452. else {
  453. calculateTextPosition(tmpTextPosCalcRes, textConfig, layoutRect);
  454. }
  455. // TODO Should modify back if textConfig.position is set to null again.
  456. // Or textContent is detached.
  457. innerTransformable.x = tmpTextPosCalcRes.x;
  458. innerTransformable.y = tmpTextPosCalcRes.y;
  459. // User specified align/verticalAlign has higher priority, which is
  460. // useful in the case that attached text is rotated 90 degree.
  461. textAlign = tmpTextPosCalcRes.align;
  462. textVerticalAlign = tmpTextPosCalcRes.verticalAlign;
  463. const textOrigin = textConfig.origin;
  464. if (textOrigin && textConfig.rotation != null) {
  465. let relOriginX;
  466. let relOriginY;
  467. if (textOrigin === 'center') {
  468. relOriginX = layoutRect.width * 0.5;
  469. relOriginY = layoutRect.height * 0.5;
  470. }
  471. else {
  472. relOriginX = parsePercent(textOrigin[0], layoutRect.width);
  473. relOriginY = parsePercent(textOrigin[1], layoutRect.height);
  474. }
  475. innerOrigin = true;
  476. innerTransformable.originX = -innerTransformable.x + relOriginX + (isLocal ? 0 : layoutRect.x);
  477. innerTransformable.originY = -innerTransformable.y + relOriginY + (isLocal ? 0 : layoutRect.y);
  478. }
  479. }
  480. if (textConfig.rotation != null) {
  481. innerTransformable.rotation = textConfig.rotation;
  482. }
  483. // TODO
  484. const textOffset = textConfig.offset;
  485. if (textOffset) {
  486. innerTransformable.x += textOffset[0];
  487. innerTransformable.y += textOffset[1];
  488. // Not change the user set origin.
  489. if (!innerOrigin) {
  490. innerTransformable.originX = -textOffset[0];
  491. innerTransformable.originY = -textOffset[1];
  492. }
  493. }
  494. // Calculate text color
  495. const isInside = textConfig.inside == null // Force to be inside or not.
  496. ? (typeof textConfig.position === 'string' && textConfig.position.indexOf('inside') >= 0)
  497. : textConfig.inside;
  498. const innerTextDefaultStyle = this._innerTextDefaultStyle || (this._innerTextDefaultStyle = {});
  499. let textFill;
  500. let textStroke;
  501. let autoStroke;
  502. if (isInside && this.canBeInsideText()) {
  503. // In most cases `textContent` need this "auto" strategy.
  504. // So by default be 'auto'. Otherwise users need to literally
  505. // set `insideFill: 'auto', insideStroke: 'auto'` each time.
  506. textFill = textConfig.insideFill;
  507. textStroke = textConfig.insideStroke;
  508. if (textFill == null || textFill === 'auto') {
  509. textFill = this.getInsideTextFill();
  510. }
  511. if (textStroke == null || textStroke === 'auto') {
  512. textStroke = this.getInsideTextStroke(textFill);
  513. autoStroke = true;
  514. }
  515. }
  516. else {
  517. textFill = textConfig.outsideFill;
  518. textStroke = textConfig.outsideStroke;
  519. if (textFill == null || textFill === 'auto') {
  520. textFill = this.getOutsideFill();
  521. }
  522. // By default give a stroke to distinguish "front end" label with
  523. // messy background (like other text label, line or other graphic).
  524. // If textContent.style.fill specified, this auto stroke will not be used.
  525. if (textStroke == null || textStroke === 'auto') {
  526. // If some time need to customize the default stroke getter,
  527. // add some kind of override method.
  528. textStroke = this.getOutsideStroke(textFill);
  529. autoStroke = true;
  530. }
  531. }
  532. // Default `textFill` should must have a value to ensure text can be displayed.
  533. textFill = textFill || '#000';
  534. if (textFill !== innerTextDefaultStyle.fill
  535. || textStroke !== innerTextDefaultStyle.stroke
  536. || autoStroke !== innerTextDefaultStyle.autoStroke
  537. || textAlign !== innerTextDefaultStyle.align
  538. || textVerticalAlign !== innerTextDefaultStyle.verticalAlign
  539. ) {
  540. textStyleChanged = true;
  541. innerTextDefaultStyle.fill = textFill;
  542. innerTextDefaultStyle.stroke = textStroke;
  543. innerTextDefaultStyle.autoStroke = autoStroke;
  544. innerTextDefaultStyle.align = textAlign;
  545. innerTextDefaultStyle.verticalAlign = textVerticalAlign;
  546. textEl.setDefaultTextStyle(innerTextDefaultStyle);
  547. }
  548. // Mark textEl to update transform.
  549. // DON'T use markRedraw. It will cause Element itself to dirty again.
  550. textEl.__dirty |= REDRAW_BIT;
  551. if (textStyleChanged) {
  552. // Only mark style dirty if necessary. Update ZRText is costly.
  553. textEl.dirtyStyle(true);
  554. }
  555. }
  556. }
  557. protected canBeInsideText() {
  558. return true;
  559. }
  560. protected getInsideTextFill(): string | undefined {
  561. return '#fff';
  562. }
  563. protected getInsideTextStroke(textFill: string): string | undefined {
  564. return '#000';
  565. }
  566. protected getOutsideFill(): string | undefined {
  567. return this.__zr && this.__zr.isDarkMode() ? LIGHT_LABEL_COLOR : DARK_LABEL_COLOR;
  568. }
  569. protected getOutsideStroke(textFill: string): string {
  570. const backgroundColor = this.__zr && this.__zr.getBackgroundColor();
  571. let colorArr = typeof backgroundColor === 'string' && parse(backgroundColor as string);
  572. if (!colorArr) {
  573. colorArr = [255, 255, 255, 1];
  574. }
  575. // Assume blending on a white / black(dark) background.
  576. const alpha = colorArr[3];
  577. const isDark = this.__zr.isDarkMode();
  578. for (let i = 0; i < 3; i++) {
  579. colorArr[i] = colorArr[i] * alpha + (isDark ? 0 : 255) * (1 - alpha);
  580. }
  581. colorArr[3] = 1;
  582. return stringify(colorArr, 'rgba');
  583. }
  584. traverse<Context>(
  585. cb: (this: Context, el: Element<Props>) => void,
  586. context?: Context
  587. ) {}
  588. protected attrKV(key: string, value: unknown) {
  589. if (key === 'textConfig') {
  590. this.setTextConfig(value as ElementTextConfig);
  591. }
  592. else if (key === 'textContent') {
  593. this.setTextContent(value as ZRText);
  594. }
  595. else if (key === 'clipPath') {
  596. this.setClipPath(value as Path);
  597. }
  598. else if (key === 'extra') {
  599. this.extra = this.extra || {};
  600. extend(this.extra, value);
  601. }
  602. else {
  603. (this as any)[key] = value;
  604. }
  605. }
  606. /**
  607. * Hide the element
  608. */
  609. hide() {
  610. this.ignore = true;
  611. this.markRedraw();
  612. }
  613. /**
  614. * Show the element
  615. */
  616. show() {
  617. this.ignore = false;
  618. this.markRedraw();
  619. }
  620. attr(keyOrObj: Props): this
  621. attr<T extends keyof Props>(keyOrObj: T, value: Props[T]): this
  622. attr(keyOrObj: keyof Props | Props, value?: unknown): this {
  623. if (typeof keyOrObj === 'string') {
  624. this.attrKV(keyOrObj as keyof ElementProps, value as AllPropTypes<ElementProps>);
  625. }
  626. else if (isObject(keyOrObj)) {
  627. let obj = keyOrObj as object;
  628. let keysArr = keys(obj);
  629. for (let i = 0; i < keysArr.length; i++) {
  630. let key = keysArr[i];
  631. this.attrKV(key as keyof ElementProps, keyOrObj[key]);
  632. }
  633. }
  634. this.markRedraw();
  635. return this;
  636. }
  637. // Save current state to normal
  638. saveCurrentToNormalState(toState: ElementState) {
  639. this._innerSaveToNormal(toState);
  640. // If we are switching from normal to other state during animation.
  641. // We need to save final value of animation to the normal state. Not interpolated value.
  642. const normalState = this._normalState;
  643. for (let i = 0; i < this.animators.length; i++) {
  644. const animator = this.animators[i];
  645. const fromStateTransition = animator.__fromStateTransition;
  646. // Ignore animation from state transition(except normal).
  647. // Ignore loop animation.
  648. if (animator.getLoop() || fromStateTransition && fromStateTransition !== PRESERVED_NORMAL_STATE) {
  649. continue;
  650. }
  651. const targetName = animator.targetName;
  652. // Respecting the order of animation if multiple animator is
  653. // animating on the same property(If additive animation is used)
  654. const target = targetName
  655. ? (normalState as any)[targetName] : normalState;
  656. // Only save keys that are changed by the states.
  657. animator.saveTo(target);
  658. }
  659. }
  660. protected _innerSaveToNormal(toState: ElementState) {
  661. let normalState = this._normalState;
  662. if (!normalState) {
  663. // Clear previous stored normal states when switching from normalState to otherState.
  664. normalState = this._normalState = {};
  665. }
  666. if (toState.textConfig && !normalState.textConfig) {
  667. normalState.textConfig = this.textConfig;
  668. }
  669. this._savePrimaryToNormal(toState, normalState, PRIMARY_STATES_KEYS);
  670. }
  671. protected _savePrimaryToNormal(
  672. toState: Dictionary<any>, normalState: Dictionary<any>, primaryKeys: readonly string[]
  673. ) {
  674. for (let i = 0; i < primaryKeys.length; i++) {
  675. let key = primaryKeys[i];
  676. // Only save property that will be changed by toState
  677. // and has not been saved to normalState yet.
  678. if (toState[key] != null && !(key in normalState)) {
  679. (normalState as any)[key] = (this as any)[key];
  680. }
  681. }
  682. }
  683. /**
  684. * If has any state.
  685. */
  686. hasState() {
  687. return this.currentStates.length > 0;
  688. }
  689. /**
  690. * Get state object
  691. */
  692. getState(name: string) {
  693. return this.states[name];
  694. }
  695. /**
  696. * Ensure state exists. If not, will create one and return.
  697. */
  698. ensureState(name: string) {
  699. const states = this.states;
  700. if (!states[name]) {
  701. states[name] = {};
  702. }
  703. return states[name];
  704. }
  705. /**
  706. * Clear all states.
  707. */
  708. clearStates(noAnimation?: boolean) {
  709. this.useState(PRESERVED_NORMAL_STATE, false, noAnimation);
  710. // TODO set _normalState to null?
  711. }
  712. /**
  713. * Use state. State is a collection of properties.
  714. * Will return current state object if state exists and stateName has been changed.
  715. *
  716. * @param stateName State name to be switched to
  717. * @param keepCurrentState If keep current states.
  718. * If not, it will inherit from the normal state.
  719. */
  720. useState(stateName: string, keepCurrentStates?: boolean, noAnimation?: boolean, forceUseHoverLayer?: boolean) {
  721. // Use preserved word __normal__
  722. // TODO: Only restore changed properties when restore to normal???
  723. const toNormalState = stateName === PRESERVED_NORMAL_STATE;
  724. const hasStates = this.hasState();
  725. if (!hasStates && toNormalState) {
  726. // If switched from normal to normal.
  727. return;
  728. }
  729. const currentStates = this.currentStates;
  730. const animationCfg = this.stateTransition;
  731. // No need to change in following cases:
  732. // 1. Keep current states. and already being applied before.
  733. // 2. Don't keep current states. And new state is same with the only one exists state.
  734. if (indexOf(currentStates, stateName) >= 0 && (keepCurrentStates || currentStates.length === 1)) {
  735. return;
  736. }
  737. let state;
  738. if (this.stateProxy && !toNormalState) {
  739. state = this.stateProxy(stateName);
  740. }
  741. if (!state) {
  742. state = (this.states && this.states[stateName]);
  743. }
  744. if (!state && !toNormalState) {
  745. logError(`State ${stateName} not exists.`);
  746. return;
  747. }
  748. if (!toNormalState) {
  749. this.saveCurrentToNormalState(state);
  750. }
  751. const useHoverLayer = !!((state && state.hoverLayer) || forceUseHoverLayer);
  752. if (useHoverLayer) {
  753. // Enter hover layer before states update.
  754. this._toggleHoverLayerFlag(true);
  755. }
  756. this._applyStateObj(
  757. stateName,
  758. state,
  759. this._normalState,
  760. keepCurrentStates,
  761. !noAnimation && !this.__inHover && animationCfg && animationCfg.duration > 0,
  762. animationCfg
  763. );
  764. // Also set text content.
  765. const textContent = this._textContent;
  766. const textGuide = this._textGuide;
  767. if (textContent) {
  768. // Force textContent use hover layer if self is using it.
  769. textContent.useState(stateName, keepCurrentStates, noAnimation, useHoverLayer);
  770. }
  771. if (textGuide) {
  772. textGuide.useState(stateName, keepCurrentStates, noAnimation, useHoverLayer);
  773. }
  774. if (toNormalState) {
  775. // Clear state
  776. this.currentStates = [];
  777. // Reset normal state.
  778. this._normalState = {};
  779. }
  780. else {
  781. if (!keepCurrentStates) {
  782. this.currentStates = [stateName];
  783. }
  784. else {
  785. this.currentStates.push(stateName);
  786. }
  787. }
  788. // Update animating target to the new object after state changed.
  789. this._updateAnimationTargets();
  790. this.markRedraw();
  791. if (!useHoverLayer && this.__inHover) {
  792. // Leave hover layer after states update and markRedraw.
  793. this._toggleHoverLayerFlag(false);
  794. // NOTE: avoid unexpected refresh when moving out from hover layer!!
  795. // Only clear from hover layer.
  796. this.__dirty &= ~REDRAW_BIT;
  797. }
  798. // Return used state.
  799. return state;
  800. }
  801. /**
  802. * Apply multiple states.
  803. * @param states States list.
  804. */
  805. useStates(states: string[], noAnimation?: boolean, forceUseHoverLayer?: boolean) {
  806. if (!states.length) {
  807. this.clearStates();
  808. }
  809. else {
  810. const stateObjects: ElementState[] = [];
  811. const currentStates = this.currentStates;
  812. const len = states.length;
  813. let notChange = len === currentStates.length;
  814. if (notChange) {
  815. for (let i = 0; i < len; i++) {
  816. if (states[i] !== currentStates[i]) {
  817. notChange = false;
  818. break;
  819. }
  820. }
  821. }
  822. if (notChange) {
  823. return;
  824. }
  825. for (let i = 0; i < len; i++) {
  826. const stateName = states[i];
  827. let stateObj: ElementState;
  828. if (this.stateProxy) {
  829. stateObj = this.stateProxy(stateName, states);
  830. }
  831. if (!stateObj) {
  832. stateObj = this.states[stateName];
  833. }
  834. if (stateObj) {
  835. stateObjects.push(stateObj);
  836. }
  837. }
  838. const lastStateObj = stateObjects[len - 1];
  839. const useHoverLayer = !!((lastStateObj && lastStateObj.hoverLayer) || forceUseHoverLayer);
  840. if (useHoverLayer) {
  841. // Enter hover layer before states update.
  842. this._toggleHoverLayerFlag(true);
  843. }
  844. const mergedState = this._mergeStates(stateObjects);
  845. const animationCfg = this.stateTransition;
  846. this.saveCurrentToNormalState(mergedState);
  847. this._applyStateObj(
  848. states.join(','),
  849. mergedState,
  850. this._normalState,
  851. false,
  852. !noAnimation && !this.__inHover && animationCfg && animationCfg.duration > 0,
  853. animationCfg
  854. );
  855. const textContent = this._textContent;
  856. const textGuide = this._textGuide;
  857. if (textContent) {
  858. textContent.useStates(states, noAnimation, useHoverLayer);
  859. }
  860. if (textGuide) {
  861. textGuide.useStates(states, noAnimation, useHoverLayer);
  862. }
  863. this._updateAnimationTargets();
  864. // Create a copy
  865. this.currentStates = states.slice();
  866. this.markRedraw();
  867. if (!useHoverLayer && this.__inHover) {
  868. // Leave hover layer after states update and markRedraw.
  869. this._toggleHoverLayerFlag(false);
  870. // NOTE: avoid unexpected refresh when moving out from hover layer!!
  871. // Only clear from hover layer.
  872. this.__dirty &= ~REDRAW_BIT;
  873. }
  874. }
  875. }
  876. /**
  877. * Return if el.silent or any ancestor element has silent true.
  878. */
  879. isSilent() {
  880. let isSilent = this.silent;
  881. let ancestor = this.parent;
  882. while (!isSilent && ancestor) {
  883. if (ancestor.silent) {
  884. isSilent = true;
  885. break;
  886. }
  887. ancestor = ancestor.parent;
  888. }
  889. return isSilent;
  890. }
  891. /**
  892. * Update animation targets when reference is changed.
  893. */
  894. private _updateAnimationTargets() {
  895. for (let i = 0; i < this.animators.length; i++) {
  896. const animator = this.animators[i];
  897. if (animator.targetName) {
  898. animator.changeTarget((this as any)[animator.targetName]);
  899. }
  900. }
  901. }
  902. /**
  903. * Remove state
  904. * @param state State to remove
  905. */
  906. removeState(state: string) {
  907. const idx = indexOf(this.currentStates, state);
  908. if (idx >= 0) {
  909. const currentStates = this.currentStates.slice();
  910. currentStates.splice(idx, 1);
  911. this.useStates(currentStates);
  912. }
  913. }
  914. /**
  915. * Replace exists state.
  916. * @param oldState
  917. * @param newState
  918. * @param forceAdd If still add when even if replaced target not exists.
  919. */
  920. replaceState(oldState: string, newState: string, forceAdd: boolean) {
  921. const currentStates = this.currentStates.slice();
  922. const idx = indexOf(currentStates, oldState);
  923. const newStateExists = indexOf(currentStates, newState) >= 0;
  924. if (idx >= 0) {
  925. if (!newStateExists) {
  926. // Replace the old with the new one.
  927. currentStates[idx] = newState;
  928. }
  929. else {
  930. // Only remove the old one.
  931. currentStates.splice(idx, 1);
  932. }
  933. }
  934. else if (forceAdd && !newStateExists) {
  935. currentStates.push(newState);
  936. }
  937. this.useStates(currentStates);
  938. }
  939. /**
  940. * Toogle state.
  941. */
  942. toggleState(state: string, enable: boolean) {
  943. if (enable) {
  944. this.useState(state, true);
  945. }
  946. else {
  947. this.removeState(state);
  948. }
  949. }
  950. protected _mergeStates(states: ElementState[]) {
  951. const mergedState: ElementState = {};
  952. let mergedTextConfig: ElementTextConfig;
  953. for (let i = 0; i < states.length; i++) {
  954. const state = states[i];
  955. extend(mergedState, state);
  956. if (state.textConfig) {
  957. mergedTextConfig = mergedTextConfig || {};
  958. extend(mergedTextConfig, state.textConfig);
  959. }
  960. }
  961. if (mergedTextConfig) {
  962. mergedState.textConfig = mergedTextConfig;
  963. }
  964. return mergedState;
  965. }
  966. protected _applyStateObj(
  967. stateName: string,
  968. state: ElementState,
  969. normalState: ElementState,
  970. keepCurrentStates: boolean,
  971. transition: boolean,
  972. animationCfg: ElementAnimateConfig
  973. ) {
  974. const needsRestoreToNormal = !(state && keepCurrentStates);
  975. // TODO: Save current state to normal?
  976. // TODO: Animation
  977. if (state && state.textConfig) {
  978. // Inherit from current state or normal state.
  979. this.textConfig = extend(
  980. {},
  981. keepCurrentStates ? this.textConfig : normalState.textConfig
  982. );
  983. extend(this.textConfig, state.textConfig);
  984. }
  985. else if (needsRestoreToNormal) {
  986. if (normalState.textConfig) { // Only restore if changed and saved.
  987. this.textConfig = normalState.textConfig;
  988. }
  989. }
  990. const transitionTarget: Dictionary<any> = {};
  991. let hasTransition = false;
  992. for (let i = 0; i < PRIMARY_STATES_KEYS.length; i++) {
  993. const key = PRIMARY_STATES_KEYS[i];
  994. const propNeedsTransition = transition && DEFAULT_ANIMATABLE_MAP[key];
  995. if (state && state[key] != null) {
  996. if (propNeedsTransition) {
  997. hasTransition = true;
  998. transitionTarget[key] = state[key];
  999. }
  1000. else {
  1001. // Replace if it exist in target state
  1002. (this as any)[key] = state[key];
  1003. }
  1004. }
  1005. else if (needsRestoreToNormal) {
  1006. if (normalState[key] != null) {
  1007. if (propNeedsTransition) {
  1008. hasTransition = true;
  1009. transitionTarget[key] = normalState[key];
  1010. }
  1011. else {
  1012. // Restore to normal state
  1013. (this as any)[key] = normalState[key];
  1014. }
  1015. }
  1016. }
  1017. }
  1018. if (!transition) {
  1019. // Keep the running animation to the new values after states changed.
  1020. // Not simply stop animation. Or it may have jump effect.
  1021. for (let i = 0; i < this.animators.length; i++) {
  1022. const animator = this.animators[i];
  1023. const targetName = animator.targetName;
  1024. // Ignore loop animation
  1025. if (!animator.getLoop()) {
  1026. animator.__changeFinalValue(targetName
  1027. ? ((state || normalState) as any)[targetName]
  1028. : (state || normalState)
  1029. );
  1030. }
  1031. }
  1032. }
  1033. if (hasTransition) {
  1034. this._transitionState(
  1035. stateName,
  1036. transitionTarget as Props,
  1037. animationCfg
  1038. );
  1039. }
  1040. }
  1041. /**
  1042. * Component is some elements attached on this element for specific purpose.
  1043. * Like clipPath, textContent
  1044. */
  1045. private _attachComponent(componentEl: Element) {
  1046. if (componentEl.__zr && !componentEl.__hostTarget) {
  1047. if (process.env.NODE_ENV !== 'production') {
  1048. throw new Error('Text element has been added to zrender.');
  1049. }
  1050. return;
  1051. }
  1052. if (componentEl === this) {
  1053. if (process.env.NODE_ENV !== 'production') {
  1054. throw new Error('Recursive component attachment.');
  1055. }
  1056. return;
  1057. }
  1058. const zr = this.__zr;
  1059. if (zr) {
  1060. // Needs to add self to zrender. For rerender triggering, or animation.
  1061. componentEl.addSelfToZr(zr);
  1062. }
  1063. componentEl.__zr = zr;
  1064. componentEl.__hostTarget = this as unknown as Element;
  1065. }
  1066. private _detachComponent(componentEl: Element) {
  1067. if (componentEl.__zr) {
  1068. componentEl.removeSelfFromZr(componentEl.__zr);
  1069. }
  1070. componentEl.__zr = null;
  1071. componentEl.__hostTarget = null;
  1072. }
  1073. /**
  1074. * Get clip path
  1075. */
  1076. getClipPath() {
  1077. return this._clipPath;
  1078. }
  1079. /**
  1080. * Set clip path
  1081. *
  1082. * clipPath can't be shared between two elements.
  1083. */
  1084. setClipPath(clipPath: Path) {
  1085. // Remove previous clip path
  1086. if (this._clipPath && this._clipPath !== clipPath) {
  1087. this.removeClipPath();
  1088. }
  1089. this._attachComponent(clipPath);
  1090. this._clipPath = clipPath;
  1091. this.markRedraw();
  1092. }
  1093. /**
  1094. * Remove clip path
  1095. */
  1096. removeClipPath() {
  1097. const clipPath = this._clipPath;
  1098. if (clipPath) {
  1099. this._detachComponent(clipPath);
  1100. this._clipPath = null;
  1101. this.markRedraw();
  1102. }
  1103. }
  1104. /**
  1105. * Get attached text content.
  1106. */
  1107. getTextContent(): ZRText {
  1108. return this._textContent;
  1109. }
  1110. /**
  1111. * Attach text on element
  1112. */
  1113. setTextContent(textEl: ZRText) {
  1114. const previousTextContent = this._textContent;
  1115. if (previousTextContent === textEl) {
  1116. return;
  1117. }
  1118. // Remove previous textContent
  1119. if (previousTextContent && previousTextContent !== textEl) {
  1120. this.removeTextContent();
  1121. }
  1122. if (process.env.NODE_ENV !== 'production') {
  1123. if (textEl.__zr && !textEl.__hostTarget) {
  1124. throw new Error('Text element has been added to zrender.');
  1125. }
  1126. }
  1127. textEl.innerTransformable = new Transformable();
  1128. this._attachComponent(textEl);
  1129. this._textContent = textEl;
  1130. this.markRedraw();
  1131. }
  1132. /**
  1133. * Set layout of attached text. Will merge with the previous.
  1134. */
  1135. setTextConfig(cfg: ElementTextConfig) {
  1136. // TODO hide cfg property?
  1137. if (!this.textConfig) {
  1138. this.textConfig = {};
  1139. }
  1140. extend(this.textConfig, cfg);
  1141. this.markRedraw();
  1142. }
  1143. /**
  1144. * Remove text config
  1145. */
  1146. removeTextConfig() {
  1147. this.textConfig = null;
  1148. this.markRedraw();
  1149. }
  1150. /**
  1151. * Remove attached text element.
  1152. */
  1153. removeTextContent() {
  1154. const textEl = this._textContent;
  1155. if (textEl) {
  1156. textEl.innerTransformable = null;
  1157. this._detachComponent(textEl);
  1158. this._textContent = null;
  1159. this._innerTextDefaultStyle = null;
  1160. this.markRedraw();
  1161. }
  1162. }
  1163. getTextGuideLine(): Polyline {
  1164. return this._textGuide;
  1165. }
  1166. setTextGuideLine(guideLine: Polyline) {
  1167. // Remove previous clip path
  1168. if (this._textGuide && this._textGuide !== guideLine) {
  1169. this.removeTextGuideLine();
  1170. }
  1171. this._attachComponent(guideLine);
  1172. this._textGuide = guideLine;
  1173. this.markRedraw();
  1174. }
  1175. removeTextGuideLine() {
  1176. const textGuide = this._textGuide;
  1177. if (textGuide) {
  1178. this._detachComponent(textGuide);
  1179. this._textGuide = null;
  1180. this.markRedraw();
  1181. }
  1182. }
  1183. /**
  1184. * Mark element needs to be repainted
  1185. */
  1186. markRedraw() {
  1187. this.__dirty |= REDRAW_BIT;
  1188. const zr = this.__zr;
  1189. if (zr) {
  1190. if (this.__inHover) {
  1191. zr.refreshHover();
  1192. }
  1193. else {
  1194. zr.refresh();
  1195. }
  1196. }
  1197. // Used as a clipPath or textContent
  1198. if (this.__hostTarget) {
  1199. this.__hostTarget.markRedraw();
  1200. }
  1201. }
  1202. /**
  1203. * Besides marking elements to be refreshed.
  1204. * It will also invalid all cache and doing recalculate next frame.
  1205. */
  1206. dirty() {
  1207. this.markRedraw();
  1208. }
  1209. private _toggleHoverLayerFlag(inHover: boolean) {
  1210. this.__inHover = inHover;
  1211. const textContent = this._textContent;
  1212. const textGuide = this._textGuide;
  1213. if (textContent) {
  1214. textContent.__inHover = inHover;
  1215. }
  1216. if (textGuide) {
  1217. textGuide.__inHover = inHover;
  1218. }
  1219. }
  1220. /**
  1221. * Add self from zrender instance.
  1222. * Not recursively because it will be invoked when element added to storage.
  1223. */
  1224. addSelfToZr(zr: ZRenderType) {
  1225. if (this.__zr === zr) {
  1226. return;
  1227. }
  1228. this.__zr = zr;
  1229. // 添加动画
  1230. const animators = this.animators;
  1231. if (animators) {
  1232. for (let i = 0; i < animators.length; i++) {
  1233. zr.animation.addAnimator(animators[i]);
  1234. }
  1235. }
  1236. if (this._clipPath) {
  1237. this._clipPath.addSelfToZr(zr);
  1238. }
  1239. if (this._textContent) {
  1240. this._textContent.addSelfToZr(zr);
  1241. }
  1242. if (this._textGuide) {
  1243. this._textGuide.addSelfToZr(zr);
  1244. }
  1245. }
  1246. /**
  1247. * Remove self from zrender instance.
  1248. * Not recursively because it will be invoked when element added to storage.
  1249. */
  1250. removeSelfFromZr(zr: ZRenderType) {
  1251. if (!this.__zr) {
  1252. return;
  1253. }
  1254. this.__zr = null;
  1255. // Remove animation
  1256. const animators = this.animators;
  1257. if (animators) {
  1258. for (let i = 0; i < animators.length; i++) {
  1259. zr.animation.removeAnimator(animators[i]);
  1260. }
  1261. }
  1262. if (this._clipPath) {
  1263. this._clipPath.removeSelfFromZr(zr);
  1264. }
  1265. if (this._textContent) {
  1266. this._textContent.removeSelfFromZr(zr);
  1267. }
  1268. if (this._textGuide) {
  1269. this._textGuide.removeSelfFromZr(zr);
  1270. }
  1271. }
  1272. /**
  1273. * 动画
  1274. *
  1275. * @param path The key to fetch value from object. Mostly style or shape.
  1276. * @param loop Whether to loop animation.
  1277. * @param allowDiscreteAnimation Whether to allow discrete animation
  1278. * @example:
  1279. * el.animate('style', false)
  1280. * .when(1000, {x: 10} )
  1281. * .done(function(){ // Animation done })
  1282. * .start()
  1283. */
  1284. animate(key?: string, loop?: boolean, allowDiscreteAnimation?: boolean) {
  1285. let target = key ? (this as any)[key] : this;
  1286. if (process.env.NODE_ENV !== 'production') {
  1287. if (!target) {
  1288. logError(
  1289. 'Property "'
  1290. + key
  1291. + '" is not existed in element '
  1292. + this.id
  1293. );
  1294. return;
  1295. }
  1296. }
  1297. const animator = new Animator(target, loop, allowDiscreteAnimation);
  1298. key && (animator.targetName = key);
  1299. this.addAnimator(animator, key);
  1300. return animator;
  1301. }
  1302. addAnimator(animator: Animator<any>, key: string): void {
  1303. const zr = this.__zr;
  1304. const el = this;
  1305. animator.during(function () {
  1306. el.updateDuringAnimation(key as string);
  1307. }).done(function () {
  1308. const animators = el.animators;
  1309. // FIXME Animator will not be removed if use `Animator#stop` to stop animation
  1310. const idx = indexOf(animators, animator);
  1311. if (idx >= 0) {
  1312. animators.splice(idx, 1);
  1313. }
  1314. });
  1315. this.animators.push(animator);
  1316. // If animate after added to the zrender
  1317. if (zr) {
  1318. zr.animation.addAnimator(animator);
  1319. }
  1320. // Wake up zrender to start the animation loop.
  1321. zr && zr.wakeUp();
  1322. }
  1323. updateDuringAnimation(key: string) {
  1324. this.markRedraw();
  1325. }
  1326. /**
  1327. * 停止动画
  1328. * @param {boolean} forwardToLast If move to last frame before stop
  1329. */
  1330. stopAnimation(scope?: string, forwardToLast?: boolean) {
  1331. const animators = this.animators;
  1332. const len = animators.length;
  1333. const leftAnimators: Animator<any>[] = [];
  1334. for (let i = 0; i < len; i++) {
  1335. const animator = animators[i];
  1336. if (!scope || scope === animator.scope) {
  1337. animator.stop(forwardToLast);
  1338. }
  1339. else {
  1340. leftAnimators.push(animator);
  1341. }
  1342. }
  1343. this.animators = leftAnimators;
  1344. return this;
  1345. }
  1346. /**
  1347. * @param animationProps A map to specify which property to animate. If not specified, will animate all.
  1348. * @example
  1349. * // Animate position
  1350. * el.animateTo({
  1351. * position: [10, 10]
  1352. * }, { done: () => { // done } })
  1353. *
  1354. * // Animate shape, style and position in 100ms, delayed 100ms, with cubicOut easing
  1355. * el.animateTo({
  1356. * shape: {
  1357. * width: 500
  1358. * },
  1359. * style: {
  1360. * fill: 'red'
  1361. * }
  1362. * position: [10, 10]
  1363. * }, {
  1364. * duration: 100,
  1365. * delay: 100,
  1366. * easing: 'cubicOut',
  1367. * done: () => { // done }
  1368. * })
  1369. */
  1370. animateTo(target: Props, cfg?: ElementAnimateConfig, animationProps?: MapToType<Props, boolean>) {
  1371. animateTo(this, target, cfg, animationProps);
  1372. }
  1373. /**
  1374. * Animate from the target state to current state.
  1375. * The params and the value are the same as `this.animateTo`.
  1376. */
  1377. // Overload definitions
  1378. animateFrom(
  1379. target: Props, cfg: ElementAnimateConfig, animationProps?: MapToType<Props, boolean>
  1380. ) {
  1381. animateTo(this, target, cfg, animationProps, true);
  1382. }
  1383. protected _transitionState(
  1384. stateName: string, target: Props, cfg?: ElementAnimateConfig, animationProps?: MapToType<Props, boolean>
  1385. ) {
  1386. const animators = animateTo(this, target, cfg, animationProps);
  1387. for (let i = 0; i < animators.length; i++) {
  1388. animators[i].__fromStateTransition = stateName;
  1389. }
  1390. }
  1391. /**
  1392. * Interface of getting the minimum bounding box.
  1393. */
  1394. getBoundingRect(): BoundingRect {
  1395. return null;
  1396. }
  1397. getPaintRect(): BoundingRect {
  1398. return null;
  1399. }
  1400. /**
  1401. * The string value of `textPosition` needs to be calculated to a real postion.
  1402. * For example, `'inside'` is calculated to `[rect.width/2, rect.height/2]`
  1403. * by default. See `contain/text.js#calculateTextPosition` for more details.
  1404. * But some coutom shapes like "pin", "flag" have center that is not exactly
  1405. * `[width/2, height/2]`. So we provide this hook to customize the calculation
  1406. * for those shapes. It will be called if the `style.textPosition` is a string.
  1407. * @param {Obejct} [out] Prepared out object. If not provided, this method should
  1408. * be responsible for creating one.
  1409. * @param {module:zrender/graphic/Style} style
  1410. * @param {Object} rect {x, y, width, height}
  1411. * @return {Obejct} out The same as the input out.
  1412. * {
  1413. * x: number. mandatory.
  1414. * y: number. mandatory.
  1415. * align: string. optional. use style.textAlign by default.
  1416. * verticalAlign: string. optional. use style.textVerticalAlign by default.
  1417. * }
  1418. */
  1419. calculateTextPosition: ElementCalculateTextPosition;
  1420. protected static initDefaultProps = (function () {
  1421. const elProto = Element.prototype;
  1422. elProto.type = 'element';
  1423. elProto.name = '';
  1424. elProto.ignore =
  1425. elProto.silent =
  1426. elProto.isGroup =
  1427. elProto.draggable =
  1428. elProto.dragging =
  1429. elProto.ignoreClip =
  1430. elProto.__inHover = false;
  1431. elProto.__dirty = REDRAW_BIT;
  1432. const logs: Dictionary<boolean> = {};
  1433. function logDeprecatedError(key: string, xKey: string, yKey: string) {
  1434. if (!logs[key + xKey + yKey]) {
  1435. console.warn(`DEPRECATED: '${key}' has been deprecated. use '${xKey}', '${yKey}' instead`);
  1436. logs[key + xKey + yKey] = true;
  1437. }
  1438. }
  1439. // Legacy transform properties. position and scale
  1440. function createLegacyProperty(
  1441. key: string,
  1442. privateKey: string,
  1443. xKey: string,
  1444. yKey: string
  1445. ) {
  1446. Object.defineProperty(elProto, key, {
  1447. get() {
  1448. if (process.env.NODE_ENV !== 'production') {
  1449. logDeprecatedError(key, xKey, yKey);
  1450. }
  1451. if (!this[privateKey]) {
  1452. const pos: number[] = this[privateKey] = [];
  1453. enhanceArray(this, pos);
  1454. }
  1455. return this[privateKey];
  1456. },
  1457. set(pos: number[]) {
  1458. if (process.env.NODE_ENV !== 'production') {
  1459. logDeprecatedError(key, xKey, yKey);
  1460. }
  1461. this[xKey] = pos[0];
  1462. this[yKey] = pos[1];
  1463. this[privateKey] = pos;
  1464. enhanceArray(this, pos);
  1465. }
  1466. });
  1467. function enhanceArray(self: any, pos: number[]) {
  1468. Object.defineProperty(pos, 0, {
  1469. get() {
  1470. return self[xKey];
  1471. },
  1472. set(val: number) {
  1473. self[xKey] = val;
  1474. }
  1475. });
  1476. Object.defineProperty(pos, 1, {
  1477. get() {
  1478. return self[yKey];
  1479. },
  1480. set(val: number) {
  1481. self[yKey] = val;
  1482. }
  1483. });
  1484. }
  1485. }
  1486. if (Object.defineProperty
  1487. // Just don't support ie8
  1488. // && (!(env as any).browser.ie || (env as any).browser.version > 8)
  1489. ) {
  1490. createLegacyProperty('position', '_legacyPos', 'x', 'y');
  1491. createLegacyProperty('scale', '_legacyScale', 'scaleX', 'scaleY');
  1492. createLegacyProperty('origin', '_legacyOrigin', 'originX', 'originY');
  1493. }
  1494. })()
  1495. }
  1496. mixin(Element, Eventful);
  1497. mixin(Element, Transformable);
  1498. function animateTo<T>(
  1499. animatable: Element<T>,
  1500. target: Dictionary<any>,
  1501. cfg: ElementAnimateConfig,
  1502. animationProps: Dictionary<any>,
  1503. reverse?: boolean
  1504. ) {
  1505. cfg = cfg || {};
  1506. const animators: Animator<any>[] = [];
  1507. animateToShallow(
  1508. animatable,
  1509. '',
  1510. animatable,
  1511. target,
  1512. cfg,
  1513. animationProps,
  1514. animators,
  1515. reverse
  1516. );
  1517. let finishCount = animators.length;
  1518. let doneHappened = false;
  1519. const cfgDone = cfg.done;
  1520. const cfgAborted = cfg.aborted;
  1521. const doneCb = () => {
  1522. doneHappened = true;
  1523. finishCount--;
  1524. if (finishCount <= 0) {
  1525. doneHappened
  1526. ? (cfgDone && cfgDone())
  1527. : (cfgAborted && cfgAborted());
  1528. }
  1529. };
  1530. const abortedCb = () => {
  1531. finishCount--;
  1532. if (finishCount <= 0) {
  1533. doneHappened
  1534. ? (cfgDone && cfgDone())
  1535. : (cfgAborted && cfgAborted());
  1536. }
  1537. };
  1538. // No animators. This should be checked before animators[i].start(),
  1539. // because 'done' may be executed immediately if no need to animate.
  1540. if (!finishCount) {
  1541. cfgDone && cfgDone();
  1542. }
  1543. // Adding during callback to the first animator
  1544. if (animators.length > 0 && cfg.during) {
  1545. // TODO If there are two animators in animateTo, and the first one is stopped by other animator.
  1546. animators[0].during((target, percent) => {
  1547. cfg.during(percent);
  1548. });
  1549. }
  1550. // Start after all animators created
  1551. // Incase any animator is done immediately when all animation properties are not changed
  1552. for (let i = 0; i < animators.length; i++) {
  1553. const animator = animators[i];
  1554. if (doneCb) {
  1555. animator.done(doneCb);
  1556. }
  1557. if (abortedCb) {
  1558. animator.aborted(abortedCb);
  1559. }
  1560. if (cfg.force) {
  1561. animator.duration(cfg.duration);
  1562. }
  1563. animator.start(cfg.easing);
  1564. }
  1565. return animators;
  1566. }
  1567. function copyArrShallow(source: number[], target: number[], len: number) {
  1568. for (let i = 0; i < len; i++) {
  1569. source[i] = target[i];
  1570. }
  1571. }
  1572. function is2DArray(value: any[]): value is number[][] {
  1573. return isArrayLike(value[0]);
  1574. }
  1575. function copyValue(target: Dictionary<any>, source: Dictionary<any>, key: string) {
  1576. if (isArrayLike(source[key])) {
  1577. if (!isArrayLike(target[key])) {
  1578. target[key] = [];
  1579. }
  1580. if (isTypedArray(source[key])) {
  1581. const len = source[key].length;
  1582. if (target[key].length !== len) {
  1583. target[key] = new (source[key].constructor)(len);
  1584. copyArrShallow(target[key], source[key], len);
  1585. }
  1586. }
  1587. else {
  1588. const sourceArr = source[key] as any[];
  1589. const targetArr = target[key] as any[];
  1590. const len0 = sourceArr.length;
  1591. if (is2DArray(sourceArr)) {
  1592. // NOTE: each item should have same length
  1593. const len1 = sourceArr[0].length;
  1594. for (let i = 0; i < len0; i++) {
  1595. if (!targetArr[i]) {
  1596. targetArr[i] = Array.prototype.slice.call(sourceArr[i]);
  1597. }
  1598. else {
  1599. copyArrShallow(targetArr[i], sourceArr[i], len1);
  1600. }
  1601. }
  1602. }
  1603. else {
  1604. copyArrShallow(targetArr, sourceArr, len0);
  1605. }
  1606. targetArr.length = sourceArr.length;
  1607. }
  1608. }
  1609. else {
  1610. target[key] = source[key];
  1611. }
  1612. }
  1613. function isValueSame(val1: any, val2: any) {
  1614. return val1 === val2
  1615. // Only check 1 dimension array
  1616. || isArrayLike(val1) && isArrayLike(val2) && is1DArraySame(val1, val2);
  1617. }
  1618. function is1DArraySame(arr0: ArrayLike<number>, arr1: ArrayLike<number>) {
  1619. const len = arr0.length;
  1620. if (len !== arr1.length) {
  1621. return false;
  1622. }
  1623. for (let i = 0; i < len; i++) {
  1624. if (arr0[i] !== arr1[i]) {
  1625. return false;
  1626. }
  1627. }
  1628. return true;
  1629. }
  1630. function animateToShallow<T>(
  1631. animatable: Element<T>,
  1632. topKey: string,
  1633. animateObj: Dictionary<any>,
  1634. target: Dictionary<any>,
  1635. cfg: ElementAnimateConfig,
  1636. animationProps: Dictionary<any> | true,
  1637. animators: Animator<any>[],
  1638. reverse: boolean // If `true`, animate from the `target` to current state.
  1639. ) {
  1640. const targetKeys = keys(target);
  1641. const duration = cfg.duration;
  1642. const delay = cfg.delay;
  1643. const additive = cfg.additive;
  1644. const setToFinal = cfg.setToFinal;
  1645. const animateAll = !isObject(animationProps);
  1646. // Find last animator animating same prop.
  1647. const existsAnimators = animatable.animators;
  1648. let animationKeys: string[] = [];
  1649. for (let k = 0; k < targetKeys.length; k++) {
  1650. const innerKey = targetKeys[k] as string;
  1651. const targetVal = target[innerKey];
  1652. if (
  1653. targetVal != null && animateObj[innerKey] != null
  1654. && (animateAll || (animationProps as Dictionary<any>)[innerKey])
  1655. ) {
  1656. if (isObject(targetVal)
  1657. && !isArrayLike(targetVal)
  1658. && !isGradientObject(targetVal)
  1659. ) {
  1660. if (topKey) {
  1661. // logError('Only support 1 depth nest object animation.');
  1662. // Assign directly.
  1663. // TODO richText?
  1664. if (!reverse) {
  1665. animateObj[innerKey] = targetVal;
  1666. animatable.updateDuringAnimation(topKey);
  1667. }
  1668. continue;
  1669. }
  1670. animateToShallow(
  1671. animatable,
  1672. innerKey,
  1673. animateObj[innerKey],
  1674. targetVal,
  1675. cfg,
  1676. animationProps && (animationProps as Dictionary<any>)[innerKey],
  1677. animators,
  1678. reverse
  1679. );
  1680. }
  1681. else {
  1682. animationKeys.push(innerKey);
  1683. }
  1684. }
  1685. else if (!reverse) {
  1686. // Assign target value directly.
  1687. animateObj[innerKey] = targetVal;
  1688. animatable.updateDuringAnimation(topKey);
  1689. // Previous animation will be stopped on the changed keys.
  1690. // So direct assign is also included.
  1691. animationKeys.push(innerKey);
  1692. }
  1693. }
  1694. let keyLen = animationKeys.length;
  1695. // Stop previous animations on the same property.
  1696. if (!additive && keyLen) {
  1697. // Stop exists animation on specific tracks. Only one animator available for each property.
  1698. // TODO Should invoke previous animation callback?
  1699. for (let i = 0; i < existsAnimators.length; i++) {
  1700. const animator = existsAnimators[i];
  1701. if (animator.targetName === topKey) {
  1702. const allAborted = animator.stopTracks(animationKeys);
  1703. if (allAborted) { // This animator can't be used.
  1704. const idx = indexOf(existsAnimators, animator);
  1705. existsAnimators.splice(idx, 1);
  1706. }
  1707. }
  1708. }
  1709. }
  1710. // Ignore values not changed.
  1711. // NOTE: Must filter it after previous animation stopped
  1712. // and make sure the value to compare is using initial frame if animation is not started yet when setToFinal is used.
  1713. if (!cfg.force) {
  1714. animationKeys = filter(animationKeys, key => !isValueSame(target[key], animateObj[key]));
  1715. keyLen = animationKeys.length;
  1716. }
  1717. if (keyLen > 0
  1718. // cfg.force is mainly for keep invoking onframe and ondone callback even if animation is not necessary.
  1719. // So if there is already has animators. There is no need to create another animator if not necessary.
  1720. // Or it will always add one more with empty target.
  1721. || (cfg.force && !animators.length)
  1722. ) {
  1723. let revertedSource: Dictionary<any>;
  1724. let reversedTarget: Dictionary<any>;
  1725. let sourceClone: Dictionary<any>;
  1726. if (reverse) {
  1727. reversedTarget = {};
  1728. if (setToFinal) {
  1729. revertedSource = {};
  1730. }
  1731. for (let i = 0; i < keyLen; i++) {
  1732. const innerKey = animationKeys[i];
  1733. reversedTarget[innerKey] = animateObj[innerKey];
  1734. if (setToFinal) {
  1735. revertedSource[innerKey] = target[innerKey];
  1736. }
  1737. else {
  1738. // The usage of "animateFrom" expects that the element props has been updated dirctly to
  1739. // "final" values outside, and input the "from" values here (i.e., in variable `target` here).
  1740. // So here we assign the "from" values directly to element here (rather that in the next frame)
  1741. // to prevent the "final" values from being read in any other places (like other running
  1742. // animator during callbacks).
  1743. // But if `setToFinal: true` this feature can not be satisfied.
  1744. animateObj[innerKey] = target[innerKey];
  1745. }
  1746. }
  1747. }
  1748. else if (setToFinal) {
  1749. sourceClone = {};
  1750. for (let i = 0; i < keyLen; i++) {
  1751. const innerKey = animationKeys[i];
  1752. // NOTE: Must clone source after the stopTracks. The property may be modified in stopTracks.
  1753. sourceClone[innerKey] = cloneValue(animateObj[innerKey]);
  1754. // Use copy, not change the original reference
  1755. // Copy from target to source.
  1756. copyValue(animateObj, target, innerKey);
  1757. }
  1758. }
  1759. const animator = new Animator(animateObj, false, false, additive ? filter(
  1760. // Use key string instead object reference because ref may be changed.
  1761. existsAnimators, animator => animator.targetName === topKey
  1762. ) : null);
  1763. animator.targetName = topKey;
  1764. if (cfg.scope) {
  1765. animator.scope = cfg.scope;
  1766. }
  1767. if (setToFinal && revertedSource) {
  1768. animator.whenWithKeys(0, revertedSource, animationKeys);
  1769. }
  1770. if (sourceClone) {
  1771. animator.whenWithKeys(0, sourceClone, animationKeys);
  1772. }
  1773. animator.whenWithKeys(
  1774. duration == null ? 500 : duration,
  1775. reverse ? reversedTarget : target,
  1776. animationKeys
  1777. ).delay(delay || 0);
  1778. animatable.addAnimator(animator, topKey);
  1779. animators.push(animator);
  1780. }
  1781. }
  1782. export default Element;