diff --git a/packages/g-camera-api/package.json b/packages/g-camera-api/package.json index 8de16adf0..ac29c8a7d 100644 --- a/packages/g-camera-api/package.json +++ b/packages/g-camera-api/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-camera-api", - "version": "1.0.6", + "version": "1.0.7", "description": "A simple implementation of Camera API.", "keywords": [ "antv", diff --git a/packages/g-canvas/package.json b/packages/g-canvas/package.json index 8145735e0..0c0b9b0b6 100644 --- a/packages/g-canvas/package.json +++ b/packages/g-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-canvas", - "version": "1.9.5", + "version": "1.9.6", "description": "A renderer implemented by Canvas 2D API", "keywords": [ "antv", @@ -29,12 +29,12 @@ "README.md" ], "dependencies": { - "@antv/g-plugin-canvas-path-generator": "^1.1.17", - "@antv/g-plugin-canvas-picker": "^1.8.14", - "@antv/g-plugin-canvas-renderer": "^1.7.20", - "@antv/g-plugin-dom-interaction": "^1.7.17", - "@antv/g-plugin-html-renderer": "^1.7.17", - "@antv/g-plugin-image-loader": "^1.1.18", + "@antv/g-plugin-canvas-path-generator": "^1.1.18", + "@antv/g-plugin-canvas-picker": "^1.8.15", + "@antv/g-plugin-canvas-renderer": "^1.7.21", + "@antv/g-plugin-dom-interaction": "^1.7.18", + "@antv/g-plugin-html-renderer": "^1.7.18", + "@antv/g-plugin-image-loader": "^1.1.19", "@antv/util": "^3.2.4", "tslib": "^2.3.1" }, diff --git a/packages/g-canvas/src/Canvas2DContextService.ts b/packages/g-canvas/src/Canvas2DContextService.ts index a22442db6..abe53e7c3 100644 --- a/packages/g-canvas/src/Canvas2DContextService.ts +++ b/packages/g-canvas/src/Canvas2DContextService.ts @@ -1,4 +1,5 @@ import type { CanvasLike, DataURLOptions } from '@antv/g-lite'; +import { RenderingContext, RenderReason } from '@antv/g-lite'; import { CanvasConfig, ContextService, @@ -19,10 +20,13 @@ export class Canvas2DContextService implements ContextService= 1 ? Math.ceil(dpr) : 1; - this.dpr = dpr; - this.resize(this.canvasConfig.width, this.canvasConfig.height); } @@ -85,6 +84,13 @@ export class Canvas2DContextService implements ContextService= 1 ? Math.ceil(dpr) : 1; + this.dpr = dpr; + if (this.$canvas) { // set canvas width & height this.$canvas.width = this.dpr * width; @@ -98,6 +104,8 @@ export class Canvas2DContextService implements ContextService { let result = parseColor('linear-gradient(30deg, blue, green 40%, red)') as CSSGradientValue[]; expect(result.length).to.be.eqls(1); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - angle: 30, - hash: 'linear-gradient(30deg, blue, green 40%, red)', - steps: [ - [0, 'blue'], - [0.4, 'green'], - [1, 'red'], - ], - }); + expect((result[0].value as LinearGradient).angle.toString()).to.be.eqls('30deg'); + expect((result[0].value as LinearGradient).steps[0].color.toString()).to.be.eqls('blue'); + expect((result[0].value as LinearGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as LinearGradient).steps[1].color.toString()).to.be.eqls('green'); + expect((result[0].value as LinearGradient).steps[1].offset.toString()).to.be.eqls('40%'); + expect((result[0].value as LinearGradient).steps[2].color.toString()).to.be.eqls('red'); + expect((result[0].value as LinearGradient).steps[2].offset.toString()).to.be.eqls('100%'); // default result = parseColor('linear-gradient(blue, green 40%, red)') as CSSGradientValue[]; expect(result.length).to.be.eqls(1); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - angle: 0, - hash: 'linear-gradient(blue, green 40%, red)', - steps: [ - [0, 'blue'], - [0.4, 'green'], - [1, 'red'], - ], - }); + expect((result[0].value as LinearGradient).angle.toString()).to.be.eqls('0deg'); + expect((result[0].value as LinearGradient).steps[0].color.toString()).to.be.eqls('blue'); + expect((result[0].value as LinearGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as LinearGradient).steps[1].color.toString()).to.be.eqls('green'); + expect((result[0].value as LinearGradient).steps[1].offset.toString()).to.be.eqls('40%'); + expect((result[0].value as LinearGradient).steps[2].color.toString()).to.be.eqls('red'); + expect((result[0].value as LinearGradient).steps[2].offset.toString()).to.be.eqls('100%'); // side or corner result = parseColor('linear-gradient(to right, blue, green 40%, red)') as CSSGradientValue[]; expect(result.length).to.be.eqls(1); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - angle: 0, - hash: 'linear-gradient(to right, blue, green 40%, red)', - steps: [ - [0, 'blue'], - [0.4, 'green'], - [1, 'red'], - ], - }); + expect((result[0].value as LinearGradient).angle.toString()).to.be.eqls('0deg'); + expect((result[0].value as LinearGradient).steps[0].color.toString()).to.be.eqls('blue'); + expect((result[0].value as LinearGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as LinearGradient).steps[1].color.toString()).to.be.eqls('green'); + expect((result[0].value as LinearGradient).steps[1].offset.toString()).to.be.eqls('40%'); + expect((result[0].value as LinearGradient).steps[2].color.toString()).to.be.eqls('red'); + expect((result[0].value as LinearGradient).steps[2].offset.toString()).to.be.eqls('100%'); result = parseColor('linear-gradient(to left, blue, green 40%, red)') as CSSGradientValue[]; expect(result.length).to.be.eqls(1); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - angle: 180, - hash: 'linear-gradient(to left, blue, green 40%, red)', - steps: [ - [0, 'blue'], - [0.4, 'green'], - [1, 'red'], - ], - }); + expect((result[0].value as LinearGradient).angle.toString()).to.be.eqls('180deg'); + expect((result[0].value as LinearGradient).steps[0].color.toString()).to.be.eqls('blue'); + expect((result[0].value as LinearGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as LinearGradient).steps[1].color.toString()).to.be.eqls('green'); + expect((result[0].value as LinearGradient).steps[1].offset.toString()).to.be.eqls('40%'); + expect((result[0].value as LinearGradient).steps[2].color.toString()).to.be.eqls('red'); + expect((result[0].value as LinearGradient).steps[2].offset.toString()).to.be.eqls('100%'); result = parseColor( 'linear-gradient(to right bottom, blue, green 40%, red)', ) as CSSGradientValue[]; expect(result.length).to.be.eqls(1); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - angle: 45, - hash: 'linear-gradient(to right bottom, blue, green 40%, red)', - steps: [ - [0, 'blue'], - [0.4, 'green'], - [1, 'red'], - ], - }); + expect((result[0].value as LinearGradient).angle.toString()).to.be.eqls('45deg'); + expect((result[0].value as LinearGradient).steps[0].color.toString()).to.be.eqls('blue'); + expect((result[0].value as LinearGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as LinearGradient).steps[1].color.toString()).to.be.eqls('green'); + expect((result[0].value as LinearGradient).steps[1].offset.toString()).to.be.eqls('40%'); + expect((result[0].value as LinearGradient).steps[2].color.toString()).to.be.eqls('red'); + expect((result[0].value as LinearGradient).steps[2].offset.toString()).to.be.eqls('100%'); // space color stop result = parseColor('linear-gradient(to right bottom, blue, green, red)') as CSSGradientValue[]; expect(result.length).to.be.eqls(1); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - angle: 45, - hash: 'linear-gradient(to right bottom, blue, green, red)', - steps: [ - [0, 'blue'], - [0.5, 'green'], - [1, 'red'], - ], - }); + expect((result[0].value as LinearGradient).angle.toString()).to.be.eqls('45deg'); + expect((result[0].value as LinearGradient).steps[0].color.toString()).to.be.eqls('blue'); + expect((result[0].value as LinearGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as LinearGradient).steps[1].color.toString()).to.be.eqls('green'); + expect((result[0].value as LinearGradient).steps[1].offset.toString()).to.be.eqls('50%'); + expect((result[0].value as LinearGradient).steps[2].color.toString()).to.be.eqls('red'); + expect((result[0].value as LinearGradient).steps[2].offset.toString()).to.be.eqls('100%'); // multiple gradients result = parseColor( @@ -128,15 +125,21 @@ describe('Property Color', () => { ) as CSSGradientValue[]; expect(result.length).to.be.eqls(2); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - angle: 45, - hash: 'linear-gradient(to right bottom, blue, green, red),linear-gradient(to right bottom, blue, green, red)', - steps: [ - [0, 'blue'], - [0.5, 'green'], - [1, 'red'], - ], - }); + expect((result[0].value as LinearGradient).angle.toString()).to.be.eqls('45deg'); + expect((result[0].value as LinearGradient).steps[0].color.toString()).to.be.eqls('blue'); + expect((result[0].value as LinearGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as LinearGradient).steps[1].color.toString()).to.be.eqls('green'); + expect((result[0].value as LinearGradient).steps[1].offset.toString()).to.be.eqls('50%'); + expect((result[0].value as LinearGradient).steps[2].color.toString()).to.be.eqls('red'); + expect((result[0].value as LinearGradient).steps[2].offset.toString()).to.be.eqls('100%'); + + expect((result[1].value as LinearGradient).angle.toString()).to.be.eqls('45deg'); + expect((result[1].value as LinearGradient).steps[0].color.toString()).to.be.eqls('blue'); + expect((result[1].value as LinearGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[1].value as LinearGradient).steps[1].color.toString()).to.be.eqls('green'); + expect((result[1].value as LinearGradient).steps[1].offset.toString()).to.be.eqls('50%'); + expect((result[1].value as LinearGradient).steps[2].color.toString()).to.be.eqls('red'); + expect((result[1].value as LinearGradient).steps[2].offset.toString()).to.be.eqls('100%'); }); it('should parse CSS radial-gradient() correctly', () => { @@ -145,76 +148,138 @@ describe('Property Color', () => { ) as CSSGradientValue[]; expect(result.length).to.be.eqls(1); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - cx: 0.5, - cy: 0.5, - hash: 'radial-gradient(circle at center, red, blue, green 100%)', - steps: [ - [0, 'red'], - [0.5, 'blue'], - [1, 'green'], - ], - }); + expect((result[0].value as RadialGradient).cx.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).cy.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).steps[0].color.toString()).to.be.eqls('red'); + expect((result[0].value as RadialGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as RadialGradient).steps[1].color.toString()).to.be.eqls('blue'); + expect((result[0].value as RadialGradient).steps[1].offset.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).steps[2].color.toString()).to.be.eqls('green'); + expect((result[0].value as RadialGradient).steps[2].offset.toString()).to.be.eqls('100%'); result = parseColor('radial-gradient(red, blue, green 100%)') as CSSGradientValue[]; expect(result.length).to.be.eqls(1); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - cx: 0.5, - cy: 0.5, - hash: 'radial-gradient(red, blue, green 100%)', - steps: [ - [0, 'red'], - [0.5, 'blue'], - [1, 'green'], - ], - }); + expect((result[0].value as RadialGradient).cx.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).cy.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).steps[0].color.toString()).to.be.eqls('red'); + expect((result[0].value as RadialGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as RadialGradient).steps[1].color.toString()).to.be.eqls('blue'); + expect((result[0].value as RadialGradient).steps[1].offset.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).steps[2].color.toString()).to.be.eqls('green'); + expect((result[0].value as RadialGradient).steps[2].offset.toString()).to.be.eqls('100%'); result = parseColor( 'radial-gradient(circle at 50% 50%, red, blue, green 100%)', ) as CSSGradientValue[]; expect(result.length).to.be.eqls(1); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - cx: 0.5, - cy: 0.5, - hash: 'radial-gradient(circle at 50% 50%, red, blue, green 100%)', - steps: [ - [0, 'red'], - [0.5, 'blue'], - [1, 'green'], - ], - }); + expect((result[0].value as RadialGradient).cx.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).cy.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).steps[0].color.toString()).to.be.eqls('red'); + expect((result[0].value as RadialGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as RadialGradient).steps[1].color.toString()).to.be.eqls('blue'); + expect((result[0].value as RadialGradient).steps[1].offset.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).steps[2].color.toString()).to.be.eqls('green'); + expect((result[0].value as RadialGradient).steps[2].offset.toString()).to.be.eqls('100%'); + + // use `px` + result = parseColor( + 'radial-gradient(circle at 50px 50px, red, blue, green 100%)', + ) as CSSGradientValue[]; + expect(result.length).to.be.eqls(1); + expect(result[0] instanceof CSSGradientValue).to.be.true; + expect((result[0].value as RadialGradient).cx.toString()).to.be.eqls('50px'); + expect((result[0].value as RadialGradient).cy.toString()).to.be.eqls('50px'); + expect((result[0].value as RadialGradient).steps[0].color.toString()).to.be.eqls('red'); + expect((result[0].value as RadialGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as RadialGradient).steps[1].color.toString()).to.be.eqls('blue'); + expect((result[0].value as RadialGradient).steps[1].offset.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).steps[2].color.toString()).to.be.eqls('green'); + expect((result[0].value as RadialGradient).steps[2].offset.toString()).to.be.eqls('100%'); + + // use size in pixel + result = parseColor( + 'radial-gradient(circle 100px at 50px 50px, red, blue, green 100%)', + ) as CSSGradientValue[]; + expect(result.length).to.be.eqls(1); + expect(result[0] instanceof CSSGradientValue).to.be.true; + expect((result[0].value as RadialGradient).size!.toString()).to.be.eqls('100px'); + + // use size keyword + result = parseColor( + 'radial-gradient(circle farthest-corner at 50px 50px, red, blue, green 100%)', + ) as CSSGradientValue[]; + expect(result.length).to.be.eqls(1); + expect(result[0] instanceof CSSGradientValue).to.be.true; + expect((result[0].value as RadialGradient).size!.toString()).to.be.eqls('farthest-corner'); + + // multiple gradients + result = parseColor( + 'radial-gradient(circle at 50% 50%, red, blue, green 100%), radial-gradient(circle at 50% 50%, red, blue, green 100%)', + ) as CSSGradientValue[]; + expect(result.length).to.be.eqls(2); + expect(result[0] instanceof CSSGradientValue).to.be.true; + expect((result[0].value as RadialGradient).cx.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).cy.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).steps[0].color.toString()).to.be.eqls('red'); + expect((result[0].value as RadialGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as RadialGradient).steps[1].color.toString()).to.be.eqls('blue'); + expect((result[0].value as RadialGradient).steps[1].offset.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).steps[2].color.toString()).to.be.eqls('green'); + expect((result[0].value as RadialGradient).steps[2].offset.toString()).to.be.eqls('100%'); + + expect((result[1].value as RadialGradient).cx.toString()).to.be.eqls('50%'); + expect((result[1].value as RadialGradient).cy.toString()).to.be.eqls('50%'); + expect((result[1].value as RadialGradient).steps[0].color.toString()).to.be.eqls('red'); + expect((result[1].value as RadialGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[1].value as RadialGradient).steps[1].color.toString()).to.be.eqls('blue'); + expect((result[1].value as RadialGradient).steps[1].offset.toString()).to.be.eqls('50%'); + expect((result[1].value as RadialGradient).steps[2].color.toString()).to.be.eqls('green'); + expect((result[1].value as RadialGradient).steps[2].offset.toString()).to.be.eqls('100%'); + + // TODO: multiple gradients, use 0 as 0% + // result = parseColor(`radial-gradient(circle at 50% 0%, + // rgba(255,0,0,.5), + // rgba(255,0,0,0) 70.71%), + // radial-gradient(circle at 6.7% 75%, + // rgba(0,0,255,.5), + // rgba(0,0,255,0) 70.71%), + // radial-gradient(circle at 93.3% 75%, + // rgba(0,255,0,.5), + // rgba(0,255,0,0) 70.71%)`) as CSSGradientValue[]; + + // result = parseColor( + // `radial-gradient(circle 480px at 256px 496px, rgb(196, 217, 245) 0%, rgb(50, 80, 176) 50%, rgb(41, 47, 117) 100%)`, + // ); + // console.log(result); }); it('should parse legacy linear gradient color correctly', () => { let result = parseColor('l(0) 0:#ffffff 0.5:#7ec2f3 1:#1890ff') as CSSGradientValue[]; expect(result.length).to.be.eqls(1); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - angle: 0, - hash: 'l(0) 0:#ffffff 0.5:#7ec2f3 1:#1890ff', - steps: [ - [0, '#ffffff'], - [0.5, '#7ec2f3'], - [1, '#1890ff'], - ], - }); + expect((result[0].value as LinearGradient).angle.toString()).to.be.eqls('0deg'); + expect((result[0].value as LinearGradient).steps[0].color.toString()).to.be.eqls('#ffffff'); + expect((result[0].value as LinearGradient).steps[0].offset.toString()).to.be.eqls('0%'); + + expect((result[0].value as LinearGradient).steps[1].color.toString()).to.be.eqls('#7ec2f3'); + expect((result[0].value as LinearGradient).steps[1].offset.toString()).to.be.eqls('50%'); + + expect((result[0].value as LinearGradient).steps[2].color.toString()).to.be.eqls('#1890ff'); + expect((result[0].value as LinearGradient).steps[2].offset.toString()).to.be.eqls('100%'); }); it('should parse legacy radial gradient color correctly', () => { let result = parseColor('r(0.5, 0.5, 0.1) 0:#ffffff 1:#1890ff') as CSSGradientValue[]; expect(result.length).to.be.eqls(1); expect(result[0] instanceof CSSGradientValue).to.be.true; - expect(result[0].value).to.be.eqls({ - cx: 0.5, - cy: 0.5, - hash: 'r(0.5, 0.5, 0.1) 0:#ffffff 1:#1890ff', - steps: [ - [0, '#ffffff'], - [1, '#1890ff'], - ], - }); + expect((result[0].value as RadialGradient).cx.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).cy.toString()).to.be.eqls('50%'); + expect((result[0].value as RadialGradient).steps[0].color.toString()).to.be.eqls('#ffffff'); + expect((result[0].value as RadialGradient).steps[0].offset.toString()).to.be.eqls('0%'); + expect((result[0].value as RadialGradient).steps[1].color.toString()).to.be.eqls('#1890ff'); + expect((result[0].value as RadialGradient).steps[1].offset.toString()).to.be.eqls('100%'); }); it('should merge constant colors correctly', () => { @@ -229,13 +294,12 @@ describe('Property Color', () => { const result = mergeColors( new CSSRGB(255, 0, 0, 1), new CSSGradientValue(GradientType.LinearGradient, { - hash: 'l(0) 0:#ffffff 0.5:#7ec2f3 1:#1890ff', steps: [ [0, '#ffffff'], [0.5, '#7ec2f3'], [1, '#1890ff'], ], - angle: 0, + angle: new CSSUnitValue(0, 'deg'), }), ); diff --git a/packages/g-lite/src/css/parser/gradient.ts b/packages/g-lite/src/css/parser/gradient.ts index edc87ce8c..8d75a2499 100644 --- a/packages/g-lite/src/css/parser/gradient.ts +++ b/packages/g-lite/src/css/parser/gradient.ts @@ -1,8 +1,10 @@ import { isNil, isString, memoize } from '@antv/util'; import type { AngularNode, ColorStop, DirectionalNode, PositionNode } from '../../utils'; import { colorStopToString, parseGradient as parse } from '../../utils'; -import type { RadialGradient } from '../cssom'; +import type { CSSKeywordValue, CSSUnitValue, LinearColorStop, RadialGradient } from '../cssom'; +import { Odeg } from '../cssom'; import { CSSGradientValue, GradientType } from '../cssom'; +import { getOrCreateKeyword, getOrCreateUnitValue } from '../CSSStyleValuePool'; import type { Pattern } from './color'; const regexLG = /^l\s*\(\s*([\d.]+)\s*\)\s*(.*)/i; @@ -26,6 +28,7 @@ function spaceColorStops(colorStops: ColorStop[]) { let previousIndex = 0; let previousOffset = Number(colorStops[0].length.value); for (let i = 1; i < length; i++) { + // support '%' & 'px' const offset = colorStops[i].length?.value; if (!isNil(offset) && !isNil(previousOffset)) { for (let j = 1; j < i - previousIndex; j++) @@ -58,74 +61,83 @@ const SideOrCornerToDegMap: Record = { 'bottom right': 135 - 90, }; -const angleToDeg = memoize((orientation: DirectionalNode | AngularNode) => { - let angle: number; - if (orientation.type === 'angular') { - angle = Number(orientation.value); - } else { - angle = SideOrCornerToDegMap[orientation.value] || 0; - } - return angle; -}); +const angleToDeg: (orientation: DirectionalNode | AngularNode) => CSSUnitValue = memoize( + (orientation: DirectionalNode | AngularNode) => { + let angle: number; + if (orientation.type === 'angular') { + angle = Number(orientation.value); + } else { + angle = SideOrCornerToDegMap[orientation.value] || 0; + } + return getOrCreateUnitValue(angle, 'deg'); + }, +); -const positonToPercentage = memoize((position: PositionNode) => { - let cx = 0.5; - let cy = 0.5; - if (position?.type === 'position') { - const { x, y } = position.value; - if (x?.type === 'position-keyword') { - if (x.value === 'left') { - cx = 0; - } else if (x.value === 'center') { - cx = 0.5; - } else if (x.value === 'right') { - cx = 1; - } else if (x.value === 'top') { - cy = 0; - } else if (x.value === 'bottom') { - cy = 1; +const positonToCSSUnitValue: (position: PositionNode) => { cx: CSSUnitValue; cy: CSSUnitValue } = + memoize((position: PositionNode) => { + let cx = 50; + let cy = 50; + let unitX = '%'; + let unitY = '%'; + if (position?.type === 'position') { + const { x, y } = position.value; + if (x?.type === 'position-keyword') { + if (x.value === 'left') { + cx = 0; + } else if (x.value === 'center') { + cx = 50; + } else if (x.value === 'right') { + cx = 100; + } else if (x.value === 'top') { + cy = 0; + } else if (x.value === 'bottom') { + cy = 100; + } } - } - if (y?.type === 'position-keyword') { - if (y.value === 'left') { - cx = 0; - } else if (y.value === 'center') { - cy = 0.5; - } else if (y.value === 'right') { - cx = 1; - } else if (y.value === 'top') { - cy = 0; - } else if (y.value === 'bottom') { - cy = 1; + if (y?.type === 'position-keyword') { + if (y.value === 'left') { + cx = 0; + } else if (y.value === 'center') { + cy = 50; + } else if (y.value === 'right') { + cx = 100; + } else if (y.value === 'top') { + cy = 0; + } else if (y.value === 'bottom') { + cy = 100; + } } - } - if (x?.type === '%') { - cx = Number(x.value) / 100; - } - if (y?.type === '%') { - cy = Number(x.value) / 100; + if (x?.type === 'px' || x?.type === '%' || x?.type === 'em') { + unitX = x?.type; + cx = Number(x.value); + } + if (y?.type === 'px' || y?.type === '%' || y?.type === 'em') { + unitY = y?.type; + cy = Number(y.value); + } } - } - return { cx, cy }; -}); + return { cx: getOrCreateUnitValue(cx, unitX), cy: getOrCreateUnitValue(cy, unitY) }; + }); export const parseGradient = memoize((colorStr: string) => { if (colorStr.indexOf('linear') > -1 || colorStr.indexOf('radial') > -1) { const ast = parse(colorStr); return ast.map(({ type, orientation, colorStops }) => { spaceColorStops(colorStops); - const steps = colorStops.map<[number, string]>((colorStop) => { + const steps = colorStops.map((colorStop) => { // TODO: only support % for now, should calc percentage of axis length when using px/em - return [Number(colorStop.length.value) / 100, colorStopToString(colorStop)]; + return { + offset: getOrCreateUnitValue(colorStop.length.value, '%'), + color: colorStopToString(colorStop), + }; }); if (type === 'linear-gradient') { return new CSSGradientValue(GradientType.LinearGradient, { - angle: orientation ? angleToDeg(orientation as DirectionalNode | AngularNode) : 0, + angle: orientation ? angleToDeg(orientation as DirectionalNode | AngularNode) : Odeg, steps, - hash: colorStr, }); } else if (type === 'radial-gradient') { if (!orientation) { @@ -137,14 +149,26 @@ export const parseGradient = memoize((colorStr: string) => { ]; } if (orientation[0].type === 'shape' && orientation[0].value === 'circle') { - const { cx, cy } = positonToPercentage(orientation[0].at); + const { cx, cy } = positonToCSSUnitValue(orientation[0].at); + let size: CSSUnitValue | CSSKeywordValue; + if (orientation[0].style) { + const { type, value } = orientation[0].style; + + if (type === 'extent-keyword') { + size = getOrCreateKeyword(value); + } else { + size = getOrCreateUnitValue(value, type); + } + } return new CSSGradientValue(GradientType.RadialGradient, { cx, cy, + size, steps, - hash: colorStr, }); } + // TODO: support ellipse shape + // TODO: repeating-linear-gradient & repeating-radial-gradient // } else if (type === 'repeating-linear-gradient') { // } else if (type === 'repeating-radial-gradient') { } @@ -160,9 +184,11 @@ export const parseGradient = memoize((colorStr: string) => { const steps = arr[2].match(regexColorStop)?.map((stop) => stop.split(':')) || []; return [ new CSSGradientValue(GradientType.LinearGradient, { - angle: parseFloat(arr[1]), - steps: steps.map(([offset, color]) => [Number(offset), color]), - hash: colorStr, + angle: getOrCreateUnitValue(parseFloat(arr[1]), 'deg'), + steps: steps.map(([offset, color]) => ({ + offset: getOrCreateUnitValue(Number(offset) * 100, '%'), + color, + })), }), ]; } @@ -186,10 +212,12 @@ function parseRadialGradient(gradientStr: string): RadialGradient | string | nul if (arr) { const steps = arr[4].match(regexColorStop)?.map((stop) => stop.split(':')) || []; return { - cx: 0.5, - cy: 0.5, - steps: steps.map(([offset, color]) => [Number(offset), color]), - hash: gradientStr, + cx: getOrCreateUnitValue(50, '%'), + cy: getOrCreateUnitValue(50, '%'), + steps: steps.map(([offset, color]) => ({ + offset: getOrCreateUnitValue(Number(offset) * 100, '%'), + color, + })), }; } return null; diff --git a/packages/g-lite/src/dom/Element.ts b/packages/g-lite/src/dom/Element.ts index aa8b620ce..eaf2217e7 100644 --- a/packages/g-lite/src/dom/Element.ts +++ b/packages/g-lite/src/dom/Element.ts @@ -16,6 +16,10 @@ import { MutationEvent } from './MutationEvent'; import { Node } from './Node'; let entityCounter = 0; +export function resetEntityCounter() { + entityCounter = 0; +} + // let this.sceneGraphService: SceneGraphService; const childInsertedEvent = new CustomEvent(ElementEvent.CHILD_INSERTED, { child: null, diff --git a/packages/g-lite/src/plugins/EventPlugin.ts b/packages/g-lite/src/plugins/EventPlugin.ts index 414cd33a2..251321abb 100644 --- a/packages/g-lite/src/plugins/EventPlugin.ts +++ b/packages/g-lite/src/plugins/EventPlugin.ts @@ -1,5 +1,5 @@ import { inject, singleton } from 'mana-syringe'; -import { isUndefined } from '@antv/util'; +import { isNil, isUndefined } from '@antv/util'; import type { FederatedMouseEvent, ICanvas } from '../dom'; import { FederatedPointerEvent } from '../dom/FederatedPointerEvent'; import { FederatedWheelEvent } from '../dom/FederatedWheelEvent'; @@ -146,6 +146,26 @@ export class EventPlugin implements RenderingPlugin { this.setCursor(this.eventService.cursor); }; + private getViewportXY(nativeEvent: PointerEvent | WheelEvent) { + let x: number; + let y: number; + /** + * Should account for CSS Transform applied on container. + * @see https://github.com/antvis/G/issues/1161 + * @see https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent/offsetX + */ + const { offsetX, offsetY, clientX, clientY } = nativeEvent; + if (this.canvasConfig.supportsCSSTransform && !isNil(offsetX) && !isNil(offsetY)) { + x = offsetX; + y = offsetY; + } else { + const point = this.eventService.client2Viewport(new Point(clientX, clientY)); + x = point.x; + y = point.y; + } + return { x, y }; + } + private bootstrapEvent( event: FederatedPointerEvent, nativeEvent: PointerEvent, @@ -167,9 +187,7 @@ export class EventPlugin implements RenderingPlugin { event.twist = nativeEvent.twist; this.transferMouseData(event, nativeEvent); - const { x, y } = this.eventService.client2Viewport( - new Point(nativeEvent.clientX, nativeEvent.clientY), - ); + const { x, y } = this.getViewportXY(nativeEvent); event.viewport.x = x; event.viewport.y = y; const { x: canvasX, y: canvasY } = this.eventService.viewport2Canvas(event.viewport); @@ -201,9 +219,7 @@ export class EventPlugin implements RenderingPlugin { event.deltaY = nativeEvent.deltaY; event.deltaZ = nativeEvent.deltaZ; - const { x, y } = this.eventService.client2Viewport( - new Point(nativeEvent.clientX, nativeEvent.clientY), - ); + const { x, y } = this.getViewportXY(nativeEvent); event.viewport.x = x; event.viewport.y = y; const { x: canvasX, y: canvasY } = this.eventService.viewport2Canvas(event.viewport); diff --git a/packages/g-lite/src/types.ts b/packages/g-lite/src/types.ts index 2ce2b816b..7aee9d37a 100644 --- a/packages/g-lite/src/types.ts +++ b/packages/g-lite/src/types.ts @@ -435,6 +435,11 @@ export interface CanvasConfig { isTouchEvent?: (event: InteractivePointerEvent) => event is TouchEvent; isMouseEvent?: (event: InteractivePointerEvent) => event is MouseEvent; + /** + * Should we account for CSS Transform applied on container? + */ + supportsCSSTransform?: boolean; + /** * 画布宽度 */ diff --git a/packages/g-lite/src/utils/gradient.ts b/packages/g-lite/src/utils/gradient.ts index 9d0b5935e..9200cce54 100644 --- a/packages/g-lite/src/utils/gradient.ts +++ b/packages/g-lite/src/utils/gradient.ts @@ -3,6 +3,10 @@ * @see https://github.com/rafaelcaricio/gradient-parser */ +import { distanceSquareRoot } from '@antv/util'; +import { CSSKeywordValue, CSSUnitValue, UnitType } from '../css'; +import { deg2rad } from './math'; + export interface LinearGradientNode { type: 'linear-gradient'; orientation?: DirectionalNode | AngularNode | undefined; @@ -479,8 +483,8 @@ export const parseGradient = (function () { }; })(); -export function computeLinearGradient(width: number, height: number, angle: number) { - const rad = (angle * Math.PI) / 180; +export function computeLinearGradient(width: number, height: number, angle: CSSUnitValue) { + const rad = deg2rad(angle.value); const rx = 0; const ry = 0; const rcx = rx + width / 2; @@ -496,8 +500,53 @@ export function computeLinearGradient(width: number, height: number, angle: numb return { x1, y1, x2, y2 }; } -export function computeRadialGradient(width: number, height: number, cx: number, cy: number) { - const r = Math.sqrt(width * width + height * height) / 2; +export function computeRadialGradient( + width: number, + height: number, + cx: CSSUnitValue, + cy: CSSUnitValue, + size?: CSSUnitValue | CSSKeywordValue, +) { + // 'px' + let x = cx.value; + let y = cy.value; + + // TODO: 'em' + + // '%' + if (cx.unit === UnitType.kPercentage) { + x = (cx.value / 100) * width; + } + if (cy.unit === UnitType.kPercentage) { + y = (cy.value / 100) * height; + } + + // default to farthest-side + let r = Math.max( + distanceSquareRoot([0, 0], [x, y]), + distanceSquareRoot([0, height], [x, y]), + distanceSquareRoot([width, height], [x, y]), + distanceSquareRoot([width, 0], [x, y]), + ); + if (size) { + if (size instanceof CSSUnitValue) { + r = size.value; + } else if (size instanceof CSSKeywordValue) { + // @see https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Images/Using_CSS_gradients#example_closest-side_for_circles + if (size.value === 'closest-side') { + r = Math.min(x, width - x, y, height - y); + } else if (size.value === 'farthest-side') { + r = Math.max(x, width - x, y, height - y); + } else if (size.value === 'closest-corner') { + r = Math.min( + distanceSquareRoot([0, 0], [x, y]), + distanceSquareRoot([0, height], [x, y]), + distanceSquareRoot([width, height], [x, y]), + distanceSquareRoot([width, 0], [x, y]), + ); + } + } + } - return { x: cx * width, y: cy * height, r }; + return { x, y, r }; } diff --git a/packages/g-lottie-player/README.md b/packages/g-lottie-player/README.md index 6404c27f7..f64440f15 100644 --- a/packages/g-lottie-player/README.md +++ b/packages/g-lottie-player/README.md @@ -2,4 +2,6 @@ Lottie Docs: https://lottiefiles.github.io/lottie-docs/ +Tips for rendering: https://lottiefiles.github.io/lottie-docs/rendering/ + Inspired by https://github.com/pissang/lottie-parser diff --git a/packages/g-lottie-player/package.json b/packages/g-lottie-player/package.json index f052ee753..c1be5ad7b 100644 --- a/packages/g-lottie-player/package.json +++ b/packages/g-lottie-player/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-lottie-player", - "version": "0.0.3", + "version": "0.0.4", "description": "A lottie player for G", "keywords": [ "antv", diff --git a/packages/g-lottie-player/src/LottieAnimation.ts b/packages/g-lottie-player/src/LottieAnimation.ts index 78aee7c0e..ce8009fe2 100644 --- a/packages/g-lottie-player/src/LottieAnimation.ts +++ b/packages/g-lottie-player/src/LottieAnimation.ts @@ -1,17 +1,23 @@ -import type { Canvas, DisplayObject, PointLike, Rectangle } from '@antv/g-lite'; -import { definedProps, Ellipse, Group, Rect, Shape } from '@antv/g-lite'; -import { isNil } from '@antv/util'; -import type { CustomElementOption, KeyframeAnimation } from './parser'; +import type { BaseStyleProps, Canvas, DisplayObject, PointLike, Rectangle } from '@antv/g-lite'; +import { definedProps, Ellipse, Group, Rect, Path, Shape } from '@antv/g-lite'; +import type { PathArray } from '@antv/util'; +import type { + CustomElementOption, + KeyframeAnimation, + KeyframeAnimationKeyframe, + ParseContext, +} from './parser'; export class LottieAnimation { constructor( private width: number, private height: number, private elements: CustomElementOption[], + private context: ParseContext, ) { this.displayObjects = elements.map((element) => this.buildHierachy(element)); - console.log(elements, this.displayObjects); + // console.log(elements, this.displayObjects); } private displayObjects: DisplayObject[]; @@ -24,11 +30,13 @@ export class LottieAnimation { name, anchorX = 0, anchorY = 0, - rotation, - scaleX, - scaleY, - x, - y, + rotation = 0, + scaleX = 1, + scaleY = 1, + x = 0, + y = 0, + // skew = 0, + // skewAxis = 0, children, shape, style, @@ -37,10 +45,26 @@ export class LottieAnimation { let displayObject: DisplayObject; + // @see https://lottiefiles.github.io/lottie-docs/concepts/#transform + const transformStyle: Pick = { + transformOrigin: `${anchorX}px ${anchorY}px`, + transform: `translate(${x - anchorX}px, ${ + y - anchorY + }px) scale(${scaleX}, ${scaleY}) rotate(${rotation}deg)`, + }; + + console.log(transformStyle); + + // TODO: repeater @see https://lottiefiles.github.io/lottie-docs/shapes/#repeater + // @see https://lottiefiles.github.io/lottie-docs/shapes/#shape - // TODO: polystar + // TODO: polystar, convert to Bezier @see https://lottiefiles.github.io/lottie-docs/rendering/#polystar if (type === Shape.GROUP) { - displayObject = new Group(); + displayObject = new Group({ + style: { + ...transformStyle, + }, + }); } else if (type === Shape.ELLIPSE) { displayObject = new Ellipse({ style: { @@ -48,16 +72,44 @@ export class LottieAnimation { cy: shape.cy, rx: shape.rx, ry: shape.ry, + ...transformStyle, }, }); } else if (type === Shape.PATH) { + // @see https://lottiefiles.github.io/lottie-docs/shapes/#path + const { close, v, in: i, out } = shape; + const d: PathArray = [] as unknown as PathArray; + + d.push(['M', v[0][0], v[0][1]]); + + for (let n = 1; n < v.length; n++) { + // @see https://lottiefiles.github.io/lottie-docs/concepts/#bezier + // The nth bezier segment is defined as: + // v[n], v[n]+o[n], v[n+1]+i[n+1], v[n+1] + d.push(['C', out[n - 1][0], out[n - 1][1], i[n][0], i[n][1], v[n][0], v[n][1]]); + } + + if (close) { + d.push(['Z']); + } + displayObject = new Path({ + style: { + d, // use Path Array which can be skipped when parsing + ...transformStyle, + }, + }); } else if (type === Shape.RECT) { + // @see https://lottiefiles.github.io/lottie-docs/shapes/#rectangle + const { x, y, width, height, r } = shape; displayObject = new Rect({ style: { - x: shape.x, - y: shape.y, - width: shape.width, - height: shape.height, + x, + y, + width, + height, + radius: r, + anchor: [0.5, 0.5], + ...transformStyle, }, }); } @@ -67,40 +119,10 @@ export class LottieAnimation { } if (style) { - // { fill, fillOpacity, opacity } + // { fill, fillOpacity, opacity, lineDash, lineDashOffset } displayObject.attr(style); } - /** - * RTS - */ - if (anchorX !== 0 || anchorY !== 0) { - displayObject.setOrigin(anchorX, anchorY); - } - - if (!isNil(rotation) && rotation !== 0) { - // clockwise in degrees - displayObject.setEulerAngles(rotation); - } - - if (!isNil(x) && x !== 0) { - const [, py] = displayObject.getLocalPosition(); - displayObject.setLocalPosition(x, py); - } - if (!isNil(y) && y !== 0) { - const [px] = displayObject.getLocalPosition(); - displayObject.setLocalPosition(px, y); - } - - if (!isNil(scaleX) && scaleX !== 1) { - const [, sy] = displayObject.getLocalScale(); - displayObject.setLocalScale(scaleX, sy); - } - if (!isNil(scaleY) && scaleY !== 1) { - const [sx] = displayObject.getLocalScale(); - displayObject.setLocalScale(sx, scaleY); - } - if (keyframeAnimation) { this.keyframeAnimationMap.set(displayObject, keyframeAnimation); } @@ -117,13 +139,32 @@ export class LottieAnimation { * Returns the animation duration in seconds. */ duration() { - return 0; + return (this.context.endFrame - this.context.startFrame) * this.context.frameTime; } /** * Returns the animation frame rate (frames / second). */ fps() { - return 0; + return this.context.fps; + } + + private isSameKeyframeOptions( + options1: Omit, + options2: Omit, + ) { + return ( + options1.delay === options2.delay && + options1.duration === options2.duration && + options1.easing === options2.easing && + options1.loop === options2.loop + ); + } + + private isSameKeyframes( + keyframe1: KeyframeAnimationKeyframe, + keyframe2: KeyframeAnimationKeyframe, + ) { + return keyframe1.easing === keyframe2.easing && keyframe1.offset === keyframe2.offset; } /** @@ -140,17 +181,65 @@ export class LottieAnimation { parent.forEach((child: DisplayObject) => { const keyframeAnimation = this.keyframeAnimationMap.get(child); if (keyframeAnimation && keyframeAnimation.length) { - keyframeAnimation.map(({ delay = 0, duration, easing, loop, keyframes }, i) => { - const formattedKeyframes = this.formatKeyframes(keyframes, child); + const keyframesOptions: [ + KeyframeAnimationKeyframe[], + Omit, + ][] = []; + + keyframeAnimation.map(({ delay = 0, duration, easing, loop, keyframes }) => { + const formattedKeyframes = keyframes.map((keyframe) => + definedProps(keyframe), + ) as KeyframeAnimationKeyframe[]; const options = definedProps({ delay, duration, easing, - // iterations: !!loop ? Infinity : 1, - iterations: Infinity, - }); - // console.log(formattedKeyframes, options); + iterations: !!loop ? Infinity : 1, + }) as Omit; + + keyframesOptions.push([formattedKeyframes, options]); + }); + const mergedKeyframesOptions = [keyframesOptions[0]]; + // merge [{ offset: 0, cx: 1 }, { offset: 0, cy: 1 }] into { offset: 0, cx: 1, cy: 1 } + for (let i = 1; i < keyframesOptions.length; i++) { + const [currentKeyframes, currentOptions] = keyframesOptions[i]; + // can merge options? + const existedKeyframeOptions = mergedKeyframesOptions.find( + ([keyframes, options]) => + keyframes.length === currentKeyframes.length && + this.isSameKeyframeOptions(currentOptions, options), + ); + + if (existedKeyframeOptions) { + currentKeyframes.forEach((currentKeyframe) => { + const existedKeyframe = existedKeyframeOptions[0].find((keyframe) => + this.isSameKeyframes(currentKeyframe, keyframe), + ); + + if (existedKeyframe) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { offset, easing: e = 'linear', ...rest } = currentKeyframe; + + // merge interpolated properties + Object.assign(existedKeyframe, rest); + } else { + // append if cannot be merged + existedKeyframeOptions[0].push(currentKeyframe); + } + }); + } else { + // cannot be merged since options are different + mergedKeyframesOptions.push(keyframesOptions[i]); + } + } + + // console.log(mergedKeyframesOptions); + + // TODO: return animations + mergedKeyframesOptions.map(([formattedKeyframes, options]) => { + // format interpolated properties, e.g. scaleX -> transform + this.formatKeyframes(formattedKeyframes, child); return child.animate(formattedKeyframes, options); }); } @@ -159,19 +248,21 @@ export class LottieAnimation { } private formatKeyframes(keyframes: Record[], object: DisplayObject) { - keyframes = keyframes.map((keyframe) => definedProps(keyframe)); - keyframes.forEach((keyframe) => { if ('scaleX' in keyframe) { - keyframe.transform = `scaleX(${keyframe.scaleX})`; + keyframe.transform = (keyframe.transform || '') + ` scaleX(${keyframe.scaleX})`; delete keyframe.scaleX; } if ('scaleY' in keyframe) { - keyframe.transform = `scaleY(${keyframe.scaleY})`; + keyframe.transform = (keyframe.transform || '') + ` scaleY(${keyframe.scaleY})`; delete keyframe.scaleY; } // TODO: rotation, skew + if ('rotation' in keyframe) { + keyframe.transform = (keyframe.transform || '') + ` rotate(${keyframe.rotation}deg)`; + delete keyframe.rotation; + } if ('x' in keyframe && object.nodeName === Shape.ELLIPSE) { keyframe.cx = keyframe.x; @@ -183,9 +274,7 @@ export class LottieAnimation { } }); - // merge - - // offset = 1 + // padding offset = 1 if (keyframes[keyframes.length - 1].offset !== 1) { keyframes.push({ ...keyframes[keyframes.length - 1], @@ -218,5 +307,10 @@ export class LottieAnimation { return { width: this.width, height: this.height }; } - version() {} + /** + * Bodymoving version + */ + version() { + return this.context.version; + } } diff --git a/packages/g-lottie-player/src/index.ts b/packages/g-lottie-player/src/index.ts index 0b19f5251..e25326cbd 100644 --- a/packages/g-lottie-player/src/index.ts +++ b/packages/g-lottie-player/src/index.ts @@ -2,7 +2,10 @@ import type * as Lottie from './parser/lottie-type'; import { LottieAnimation } from './LottieAnimation'; import { parse } from './parser'; -export function createAnimation(data: Lottie.Animation, option): LottieAnimation { - const { width, height, elements } = parse(data); - return new LottieAnimation(width, height, elements); +export function createAnimation( + data: Lottie.Animation, + option: { loop: boolean }, +): LottieAnimation { + const { width, height, elements, context } = parse(data, option); + return new LottieAnimation(width, height, elements, context); } diff --git a/packages/g-lottie-player/src/parser/index.ts b/packages/g-lottie-player/src/parser/index.ts index a06b9da16..7930dc3f9 100644 --- a/packages/g-lottie-player/src/parser/index.ts +++ b/packages/g-lottie-player/src/parser/index.ts @@ -1,9 +1,9 @@ -import { definedProps, Shape } from '@antv/g-lite'; +import { definedProps, rad2deg, Shape } from '@antv/g-lite'; import { distanceSquareRoot, isNil } from '@antv/util'; import { completeData } from './complete-data'; import * as Lottie from './lottie-type'; -interface KeyframeAnimationKeyframe { +export interface KeyframeAnimationKeyframe { easing?: string; offset: number; [key: string]: any; @@ -12,7 +12,7 @@ interface KeyframeAnimationKeyframe { export interface KeyframeAnimation { duration?: number; delay?: number; - easing?: number; + easing?: string; loop?: boolean; keyframes: Record[]; } @@ -38,10 +38,12 @@ export interface CustomElementOption { y?: number; } -class ParseContext { +export class ParseContext { + fps: number; frameTime = 1000 / 30; startFrame = 0; endFrame: number; + version: string; assetsMap: Map = new Map(); @@ -88,6 +90,9 @@ function getMultiDimensionValue(val: number | number[], dimIndex?: number) { return val != null ? (typeof val === 'number' ? val : val[dimIndex || 0]) : NaN; } +/** + * @see https://lottiefiles.github.io/lottie-docs/concepts/#easing-handles + */ function getMultiDimensionEasingBezierString( kf: Pick, nextKf: Pick, @@ -95,18 +100,37 @@ function getMultiDimensionEasingBezierString( ) { const bezierEasing: number[] = []; bezierEasing.push( - kf.o?.x ? getMultiDimensionValue(kf.o.x, dimIndex) : 0, - kf.o?.y ? getMultiDimensionValue(kf.o.y, dimIndex) : 0, - nextKf?.o?.x ? getMultiDimensionValue(nextKf.o.x, dimIndex) : 1, - nextKf?.o?.y ? getMultiDimensionValue(nextKf.o.y, dimIndex) : 1, + (kf.o?.x && (getMultiDimensionValue(kf.o.x, dimIndex) || getMultiDimensionValue(kf.o.x, 0))) || + 0, + (kf.o?.y && (getMultiDimensionValue(kf.o.y, dimIndex) || getMultiDimensionValue(kf.o.y, 0))) || + 0, + (kf.i?.x && (getMultiDimensionValue(kf.i.x, dimIndex) || getMultiDimensionValue(kf.i.x, 0))) || + 1, + (kf.i?.y && (getMultiDimensionValue(kf.i.y, dimIndex) || getMultiDimensionValue(kf.i.y, 0))) || + 1, + // nextKf?.o?.x ? getMultiDimensionValue(nextKf.o.x, dimIndex) : 1, + // nextKf?.o?.y ? getMultiDimensionValue(nextKf.o.y, dimIndex) : 1, ); - if (bezierEasing[0] && bezierEasing[1] && bezierEasing[2] !== 1 && bezierEasing[3] !== 1) { + // linear by default + if ( + !( + bezierEasing[0] === 0 && + bezierEasing[1] === 0 && + bezierEasing[2] === 1 && + bezierEasing[3] === 1 + ) + ) { + console.log(`cubic-bezier(${bezierEasing.join(',')})`); + return `cubic-bezier(${bezierEasing.join(',')})`; } return; } +/** + * @see https://lottiefiles.github.io/lottie-docs/concepts/#keyframe + */ function parseKeyframe( kfs: Lottie.OffsetKeyframe[], bezierEasingDimIndex: number, @@ -126,6 +150,9 @@ function parseKeyframe( for (let i = 0; i < kfsLen; i++) { const kf = kfs[i]; const nextKf = kfs[i + 1]; + + // If h is present and it's 1, you don't need i and o, + // as the property will keep the same value until the next keyframe. const isDiscrete = kf.h === 1; const offset = (kf.t + context.layerOffsetTime - context.startFrame) / duration; @@ -260,6 +287,8 @@ function parseTransforms( scaleY: 'scaleY', anchorX: 'anchorX', anchorY: 'anchorY', + skew: 'skew', + skewAxis: 'skewAxis', }, ) { // @see https://lottiefiles.github.io/lottie-docs/concepts/#split-vector @@ -299,16 +328,7 @@ function parseTransforms( context, (val) => val / 100, ); - parseValue( - ks.r, - attrs, - targetProp, - [transformProps.rotation], - animations, - context, - // Rotation in degrees, clockwise - (val) => val, - ); + parseValue(ks.r, attrs, targetProp, [transformProps.rotation], animations, context); parseValue( ks.a, @@ -319,8 +339,9 @@ function parseTransforms( context, ); - // TODO sk: skew, sa: skew axis - // TODO px, py + parseValue(ks.sk, attrs, targetProp, [transformProps.skew], animations, context); + + parseValue(ks.sa, attrs, targetProp, [transformProps.skewAxis], animations, context); } function isGradientFillOrStroke( @@ -344,30 +365,42 @@ function convertColorStops(arr: number[], count: number) { return colorStops; } +function joinColorStops(colorStops: any[]) { + return `${colorStops.map(({ offset, color }) => `${color} ${offset * 100}%`).join(', ')}`; +} + +/** + * TODO: + * * Transition + * * Highlight length & angle in Radial Gradient + * + * @see https://lottiefiles.github.io/lottie-docs/concepts/#gradients + * @see https://lottiefiles.github.io/lottie-docs/shapes/#gradients + */ function parseGradient(shape: Lottie.GradientFillShape | Lottie.GradientStrokeShape) { - // TODO animation const colorArr = shape.g.k.k as number[]; const colorStops = convertColorStops(colorArr, shape.g.p); + // @see https://lottiefiles.github.io/lottie-docs/constants/#gradienttype if (shape.t === Lottie.GradientType.Linear) { - return { - type: 'linear' as const, - colorStops, - x: shape.s.k[0] as number, - y: shape.s.k[1] as number, - x2: shape.e.k[0] as number, - y2: shape.e.k[1] as number, - global: true, - }; + const angle = rad2deg( + Math.atan2( + (shape.e.k[1] as number) - (shape.s.k[1] as number), + (shape.e.k[0] as number) - (shape.s.k[0] as number), + ), + ); + + // @see https://g-next.antv.vision/zh/docs/api/css/css-properties-values-api#linear-gradient + return `linear-gradient(${angle}deg, ${joinColorStops(colorStops)})`; } else if (shape.t === Lottie.GradientType.Radial) { - return { - type: 'radial' as const, - colorStops, - x: shape.s.k[0] as number, - y: shape.s.k[1] as number, - // r: vector.dist(shape.e.k as number[], shape.s.k as number[]), - r: distanceSquareRoot(shape.e.k as [number, number], shape.s.k as [number, number]), - global: true, - }; + // TODO: highlight length & angle (h & a) + // Highlight Length, as a percentage between s and e + // Highlight Angle, relative to the direction from s to e + const size = distanceSquareRoot(shape.e.k as [number, number], shape.s.k as [number, number]); + + // @see https://g-next.antv.vision/zh/docs/api/css/css-properties-values-api#radial-gradient + return `radial-gradient(circle ${size}px at ${shape.s.k[0] as number}px ${ + shape.s.k[1] as number + }px, ${joinColorStops(colorStops)})`; } else { // Invalid gradient return '#000'; @@ -401,6 +434,8 @@ function parseFill( context, (opacity) => opacity / 100, ); + + // TODO: FillRule @see https://lottiefiles.github.io/lottie-docs/constants/#fillrule } function parseStroke( @@ -495,6 +530,7 @@ function parseShapePaths( stroke: 'none', }, }; + // @see https://lottiefiles.github.io/lottie-docs/concepts/#bezier if (isBezier(shape.ks.k)) { attrs.shape = { in: shape.ks.k.i, @@ -628,6 +664,8 @@ function parseShapeLayer(layer: Lottie.ShapeLayer, context: ParseContext) { scaleY: 'repeatScaleY', anchorX: 'repeatAnchorX', anchorY: 'repeatAnchorY', + skew: 'repeatSkew', + skewAxis: 'repeatSkewAxis', }, ); break; @@ -818,24 +856,30 @@ function parseLayers( // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (layer.ty) { case Lottie.LayerType.shape: + // @see https://lottiefiles.github.io/lottie-docs/layers/#shape-layer layerGroup = parseShapeLayer(layer as Lottie.ShapeLayer, context); break; case Lottie.LayerType.null: + // @see https://lottiefiles.github.io/lottie-docs/layers/#null-layer layerGroup = { type: Shape.GROUP, children: [], }; break; case Lottie.LayerType.solid: + // @see https://lottiefiles.github.io/lottie-docs/layers/#solid-color-layer layerGroup = { type: Shape.GROUP, children: [], }; + // Anything you can do with solid layers, you can do better with a shape layer and a rectangle shape + // since none of this layer's own properties can be animated. if ((layer as Lottie.SolidColorLayer).sc) { - layerGroup.children!.push(parseSolidShape(layer as Lottie.SolidColorLayer)); + layerGroup.children.push(parseSolidShape(layer as Lottie.SolidColorLayer)); } break; case Lottie.LayerType.precomp: + // @see https://lottiefiles.github.io/lottie-docs/layers/#precomposition-layer layerGroup = { type: Shape.GROUP, children: parseLayers( @@ -848,6 +892,12 @@ function parseLayers( ), }; break; + case Lottie.LayerType.text: + // TODO: https://lottiefiles.github.io/lottie-docs/layers/#text-layer + break; + case Lottie.LayerType.image: + // TODO: https://lottiefiles.github.io/lottie-docs/layers/#image-layer + break; } if (layerGroup) { @@ -954,9 +1004,11 @@ export function parse( const context = new ParseContext(); opts = opts || {}; - context.frameTime = 1000 / (data.fr || 30); + context.fps = data.fr || 30; + context.frameTime = 1000 / context.fps; context.startFrame = data.ip; context.endFrame = data.op; + context.version = data.v; data.assets?.forEach((asset) => { context.assetsMap.set(asset.id, asset); @@ -989,6 +1041,7 @@ export function parse( width: data.w, height: data.h, elements, + context, each: (cb: (el: CustomElementOption) => void) => { eachElement(elements, cb); diff --git a/packages/g-lottie-player/src/parser/lottie-type.ts b/packages/g-lottie-player/src/parser/lottie-type.ts index 52163b9b8..da6d7ee5f 100644 --- a/packages/g-lottie-player/src/parser/lottie-type.ts +++ b/packages/g-lottie-player/src/parser/lottie-type.ts @@ -161,7 +161,7 @@ export type Layer = { export enum LayerType { precomp, solid, - still, + image, null, shape, text, @@ -174,6 +174,7 @@ export enum LayerType { adjustment, camera, light, + data, } export type Position = { @@ -253,7 +254,7 @@ export type SolidColorLayer = Layer & { }; export type ImageLayer = Layer & { - ty: LayerType.still; + ty: LayerType.image; /** id pointing to the source image defined on 'assets' object. */ refId: string; }; diff --git a/packages/g-math/package.json b/packages/g-math/package.json index 14a595cb0..d81af37d5 100644 --- a/packages/g-math/package.json +++ b/packages/g-math/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-math", - "version": "1.7.17", + "version": "1.7.18", "description": "Geometry util", "keywords": [ "antv", diff --git a/packages/g-mobile-canvas-element/package.json b/packages/g-mobile-canvas-element/package.json index c61d28cf4..d3cda7ce3 100644 --- a/packages/g-mobile-canvas-element/package.json +++ b/packages/g-mobile-canvas-element/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-mobile-canvas-element", - "version": "0.6.17", + "version": "0.6.18", "description": "Create a CanvasLike element from existed context in mobile environment", "keywords": [ "antv", diff --git a/packages/g-mobile-canvas/package.json b/packages/g-mobile-canvas/package.json index 03b9bb9c0..88c6ca2fa 100644 --- a/packages/g-mobile-canvas/package.json +++ b/packages/g-mobile-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-mobile-canvas", - "version": "0.8.14", + "version": "0.8.15", "description": "A renderer implemented with Canvas2D API in mobile environment", "keywords": [ "antv", @@ -27,11 +27,11 @@ "README.md" ], "dependencies": { - "@antv/g-plugin-canvas-path-generator": "^1.1.17", - "@antv/g-plugin-canvas-picker": "^1.8.14", - "@antv/g-plugin-canvas-renderer": "^1.7.20", - "@antv/g-plugin-image-loader": "^1.1.18", - "@antv/g-plugin-mobile-interaction": "^0.7.17", + "@antv/g-plugin-canvas-path-generator": "^1.1.18", + "@antv/g-plugin-canvas-picker": "^1.8.15", + "@antv/g-plugin-canvas-renderer": "^1.7.21", + "@antv/g-plugin-image-loader": "^1.1.19", + "@antv/g-plugin-mobile-interaction": "^0.7.18", "@antv/util": "^3.2.4", "tslib": "^2.3.1" }, diff --git a/packages/g-mobile-svg/package.json b/packages/g-mobile-svg/package.json index 4f1b06de6..b30a10363 100644 --- a/packages/g-mobile-svg/package.json +++ b/packages/g-mobile-svg/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-mobile-svg", - "version": "0.8.14", + "version": "0.8.15", "description": "A renderer implemented by SVG in mobile environment", "keywords": [ "antv", @@ -27,9 +27,9 @@ "README.md" ], "dependencies": { - "@antv/g-plugin-mobile-interaction": "^0.7.17", - "@antv/g-plugin-svg-picker": "^1.7.17", - "@antv/g-plugin-svg-renderer": "^1.8.14", + "@antv/g-plugin-mobile-interaction": "^0.7.18", + "@antv/g-plugin-svg-picker": "^1.7.18", + "@antv/g-plugin-svg-renderer": "^1.8.15", "@antv/util": "^3.2.4", "tslib": "^2.3.1" }, diff --git a/packages/g-mobile-webgl/package.json b/packages/g-mobile-webgl/package.json index acdc0f2d0..f959d1976 100644 --- a/packages/g-mobile-webgl/package.json +++ b/packages/g-mobile-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-mobile-webgl", - "version": "0.7.20", + "version": "0.7.21", "description": "A renderer implemented by WebGL1/2 in mobile environment", "keywords": [ "antv", @@ -27,12 +27,12 @@ "README.md" ], "dependencies": { - "@antv/g-plugin-device-renderer": "^1.7.20", - "@antv/g-plugin-dragndrop": "^1.6.17", - "@antv/g-plugin-html-renderer": "^1.7.17", - "@antv/g-plugin-image-loader": "^1.1.18", - "@antv/g-plugin-mobile-interaction": "^0.7.17", - "@antv/g-plugin-webgl-device": "^1.7.17", + "@antv/g-plugin-device-renderer": "^1.7.21", + "@antv/g-plugin-dragndrop": "^1.6.18", + "@antv/g-plugin-html-renderer": "^1.7.18", + "@antv/g-plugin-image-loader": "^1.1.19", + "@antv/g-plugin-mobile-interaction": "^0.7.18", + "@antv/g-plugin-webgl-device": "^1.7.18", "@antv/util": "^3.2.4" }, "devDependencies": { diff --git a/packages/g-plugin-3d/package.json b/packages/g-plugin-3d/package.json index a9730d9a7..731bfb68b 100644 --- a/packages/g-plugin-3d/package.json +++ b/packages/g-plugin-3d/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-3d", - "version": "1.7.17", + "version": "1.7.18", "description": "Provide 3D extension for G", "keywords": [ "antv", @@ -27,7 +27,7 @@ "README.md" ], "dependencies": { - "@antv/g-shader-components": "^1.7.17", + "@antv/g-shader-components": "^1.7.18", "tslib": "^2.3.1" }, "devDependencies": { diff --git a/packages/g-plugin-a11y/package.json b/packages/g-plugin-a11y/package.json index 5a7ceb3a1..b4d25640c 100644 --- a/packages/g-plugin-a11y/package.json +++ b/packages/g-plugin-a11y/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-a11y", - "version": "0.4.17", + "version": "0.4.18", "description": "A G plugin for accessibility", "keywords": [ "antv", diff --git a/packages/g-plugin-annotation/package.json b/packages/g-plugin-annotation/package.json index 6f2069187..1429fc7c6 100644 --- a/packages/g-plugin-annotation/package.json +++ b/packages/g-plugin-annotation/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-annotation", - "version": "0.2.14", + "version": "0.2.15", "description": "A G plugin for annotation", "keywords": [ "antv", diff --git a/packages/g-plugin-box2d/package.json b/packages/g-plugin-box2d/package.json index 80ae2082d..fdb7b32ac 100644 --- a/packages/g-plugin-box2d/package.json +++ b/packages/g-plugin-box2d/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-box2d", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin for Box2D", "keywords": [ "antv", diff --git a/packages/g-plugin-canvas-path-generator/package.json b/packages/g-plugin-canvas-path-generator/package.json index 6523905c7..ac9121eed 100644 --- a/packages/g-plugin-canvas-path-generator/package.json +++ b/packages/g-plugin-canvas-path-generator/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-canvas-path-generator", - "version": "1.1.17", + "version": "1.1.18", "description": "A G plugin of path generator with Canvas2D API", "keywords": [ "antv", @@ -27,7 +27,7 @@ "README.md" ], "dependencies": { - "@antv/g-math": "^1.7.17", + "@antv/g-math": "^1.7.18", "@antv/util": "^3.2.4", "tslib": "^2.3.1" }, diff --git a/packages/g-plugin-canvas-picker/package.json b/packages/g-plugin-canvas-picker/package.json index b99c2d8e9..0a455bcdc 100644 --- a/packages/g-plugin-canvas-picker/package.json +++ b/packages/g-plugin-canvas-picker/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-canvas-picker", - "version": "1.8.14", + "version": "1.8.15", "description": "A G plugin for picking in canvas", "keywords": [ "antv", @@ -27,7 +27,7 @@ "README.md" ], "dependencies": { - "@antv/g-plugin-canvas-path-generator": "^1.1.17", + "@antv/g-plugin-canvas-path-generator": "^1.1.18", "@antv/util": "^3.2.4", "tslib": "^2.3.1" }, diff --git a/packages/g-plugin-canvas-renderer/package.json b/packages/g-plugin-canvas-renderer/package.json index 217e82a4b..ca97d59e7 100644 --- a/packages/g-plugin-canvas-renderer/package.json +++ b/packages/g-plugin-canvas-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-canvas-renderer", - "version": "1.7.20", + "version": "1.7.21", "description": "A G plugin of renderer implementation with Canvas2D API", "keywords": [ "antv", @@ -27,9 +27,9 @@ "README.md" ], "dependencies": { - "@antv/g-math": "^1.7.17", - "@antv/g-plugin-canvas-path-generator": "^1.1.17", - "@antv/g-plugin-image-loader": "^1.1.18", + "@antv/g-math": "^1.7.18", + "@antv/g-plugin-canvas-path-generator": "^1.1.18", + "@antv/g-plugin-image-loader": "^1.1.19", "@antv/util": "^3.2.4", "tslib": "^2.3.1" }, diff --git a/packages/g-plugin-canvas-renderer/src/CanvasRendererPlugin.ts b/packages/g-plugin-canvas-renderer/src/CanvasRendererPlugin.ts index 889716b76..3e5959d58 100644 --- a/packages/g-plugin-canvas-renderer/src/CanvasRendererPlugin.ts +++ b/packages/g-plugin-canvas-renderer/src/CanvasRendererPlugin.ts @@ -142,8 +142,6 @@ export class CanvasRendererPlugin implements RenderingPlugin { const { width, height } = this.canvasConfig; const context = this.contextService.getContext(); this.clearRect(context, 0, 0, width * dpr, height * dpr); - - mat4.fromScaling(this.dprMatrix, vec3.fromValues(dpr, dpr, 1)); }); renderingService.hooks.destroy.tap(CanvasRendererPlugin.tag, () => { @@ -193,6 +191,8 @@ export class CanvasRendererPlugin implements RenderingPlugin { renderingService.hooks.endFrame.tap(CanvasRendererPlugin.tag, () => { const context = this.contextService.getContext(); // clear & clip dirty rectangle + const dpr = this.contextService.getDPR(); + mat4.fromScaling(this.dprMatrix, vec3.fromValues(dpr, dpr, 1)); mat4.multiply(this.vpMatrix, this.dprMatrix, this.camera.getOrthoMatrix()); if (this.clearFullScreen) { diff --git a/packages/g-plugin-canvas-renderer/src/shapes/styles/Default.ts b/packages/g-plugin-canvas-renderer/src/shapes/styles/Default.ts index f7aba4c8d..b25c1300a 100644 --- a/packages/g-plugin-canvas-renderer/src/shapes/styles/Default.ts +++ b/packages/g-plugin-canvas-renderer/src/shapes/styles/Default.ts @@ -192,7 +192,7 @@ export class DefaultRenderer implements StyleRenderer { color = this.imagePool.getOrCreateGradient( { type: parsedColor.type, - ...(parsedColor.value as LinearGradient | RadialGradient), + ...(parsedColor.value as LinearGradient & RadialGradient), width, height, }, diff --git a/packages/g-plugin-canvaskit-renderer/package.json b/packages/g-plugin-canvaskit-renderer/package.json index 7e9a90870..1c0974a37 100644 --- a/packages/g-plugin-canvaskit-renderer/package.json +++ b/packages/g-plugin-canvaskit-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-canvaskit-renderer", - "version": "1.1.18", + "version": "1.1.19", "description": "A G plugin of renderer implementation with CanvasKit", "keywords": [ "antv", @@ -27,8 +27,8 @@ "README.md" ], "dependencies": { - "@antv/g-math": "^1.7.17", - "@antv/g-plugin-image-loader": "^1.1.18", + "@antv/g-math": "^1.7.18", + "@antv/g-plugin-image-loader": "^1.1.19", "@antv/util": "^3.2.4", "canvaskit-wasm": "^0.34.0", "tslib": "^2.3.1" diff --git a/packages/g-plugin-canvaskit-renderer/src/CanvaskitRendererPlugin.ts b/packages/g-plugin-canvaskit-renderer/src/CanvaskitRendererPlugin.ts index a44a54d66..abcde8ae7 100644 --- a/packages/g-plugin-canvaskit-renderer/src/CanvaskitRendererPlugin.ts +++ b/packages/g-plugin-canvaskit-renderer/src/CanvaskitRendererPlugin.ts @@ -11,6 +11,7 @@ import type { RenderingPlugin, RenderingService, } from '@antv/g-lite'; +import { UnitType } from '@antv/g-lite'; import { CanvasConfig, computeLinearGradient, @@ -329,8 +330,10 @@ export class CanvaskitRendererPlugin implements RenderingPlugin { const { angle, steps } = stroke.value as LinearGradient; const pos: number[] = []; const colors: number[] = []; - steps.forEach(([offset, color]) => { - pos.push(offset); + steps.forEach(({ offset, color }) => { + if (offset.unit === UnitType.kPercentage) { + pos.push(offset.value / 100); + } const c = parseColor(color) as CSSRGB; colors.push( Number(c.alpha) === 0 ? 1 : Number(c.r) / 255, @@ -349,12 +352,14 @@ export class CanvaskitRendererPlugin implements RenderingPlugin { ); return gradient; } else if (stroke.type === GradientType.RadialGradient) { - const { cx, cy, steps } = stroke.value as RadialGradient; - const { x, y, r } = computeRadialGradient(width, height, cx, cy); + const { cx, cy, steps, size } = stroke.value as RadialGradient; + const { x, y, r } = computeRadialGradient(width, height, cx, cy, size); const pos: number[] = []; const colors: Float32Array[] = []; - steps.forEach(([offset, color]) => { - pos.push(Number(offset)); + steps.forEach(({ offset, color }) => { + if (offset.unit === UnitType.kPercentage) { + pos.push(offset.value / 100); + } const c = parseColor(color) as CSSRGB; colors.push( new Float32Array([ @@ -371,7 +376,7 @@ export class CanvaskitRendererPlugin implements RenderingPlugin { r, colors, pos, - CanvasKit.TileMode.Mirror, + CanvasKit.TileMode.Clamp, // mirror for repetition ); return gradient; } diff --git a/packages/g-plugin-control/package.json b/packages/g-plugin-control/package.json index 6faca273e..3d813a576 100644 --- a/packages/g-plugin-control/package.json +++ b/packages/g-plugin-control/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-control", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin for orbit control", "keywords": [ "antv", diff --git a/packages/g-plugin-css-select/package.json b/packages/g-plugin-css-select/package.json index bd212dfa8..2b3d7744b 100644 --- a/packages/g-plugin-css-select/package.json +++ b/packages/g-plugin-css-select/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-css-select", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin for using CSS select syntax in query selector", "keywords": [ "antv", diff --git a/packages/g-plugin-device-renderer/package.json b/packages/g-plugin-device-renderer/package.json index 2c570ef6e..a50dc6b91 100644 --- a/packages/g-plugin-device-renderer/package.json +++ b/packages/g-plugin-device-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-device-renderer", - "version": "1.7.20", + "version": "1.7.21", "description": "A G plugin of renderer implementation with GPUDevice", "keywords": [ "antv", @@ -31,8 +31,8 @@ "README.md" ], "dependencies": { - "@antv/g-plugin-image-loader": "^1.1.18", - "@antv/g-shader-components": "^1.7.17", + "@antv/g-plugin-image-loader": "^1.1.19", + "@antv/g-shader-components": "^1.7.18", "@antv/util": "^3.2.4", "@mapbox/tiny-sdf": "^2.0.4", "@types/offscreencanvas": "^2019.6.4", diff --git a/packages/g-plugin-device-renderer/src/TexturePool.ts b/packages/g-plugin-device-renderer/src/TexturePool.ts index 68173d663..1b21214da 100644 --- a/packages/g-plugin-device-renderer/src/TexturePool.ts +++ b/packages/g-plugin-device-renderer/src/TexturePool.ts @@ -132,7 +132,7 @@ export class TexturePool { const gradient = this.imagePool.getOrCreateGradient( { type: g.type, - ...(g.value as LinearGradient | RadialGradient), + ...(g.value as LinearGradient & RadialGradient), width, height, }, diff --git a/packages/g-plugin-dom-interaction/package.json b/packages/g-plugin-dom-interaction/package.json index 4dcdb4100..fce05e954 100644 --- a/packages/g-plugin-dom-interaction/package.json +++ b/packages/g-plugin-dom-interaction/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-dom-interaction", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin", "keywords": [ "antv", diff --git a/packages/g-plugin-dragndrop/package.json b/packages/g-plugin-dragndrop/package.json index 08352f02f..632a55e11 100644 --- a/packages/g-plugin-dragndrop/package.json +++ b/packages/g-plugin-dragndrop/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-dragndrop", - "version": "1.6.17", + "version": "1.6.18", "description": "A G plugin for Drag n Drop implemented with PointerEvents", "keywords": [ "antv", diff --git a/packages/g-plugin-gpgpu/package.json b/packages/g-plugin-gpgpu/package.json index 3c072ca36..1062b1d01 100644 --- a/packages/g-plugin-gpgpu/package.json +++ b/packages/g-plugin-gpgpu/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-gpgpu", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin for GPGPU based on WebGPU", "keywords": [ "webgpu", diff --git a/packages/g-plugin-html-renderer/package.json b/packages/g-plugin-html-renderer/package.json index 3bc9ac2f7..daf2e3212 100644 --- a/packages/g-plugin-html-renderer/package.json +++ b/packages/g-plugin-html-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-html-renderer", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin for rendering HTML", "keywords": [ "antv", diff --git a/packages/g-plugin-image-loader/package.json b/packages/g-plugin-image-loader/package.json index f9911f1f6..6999be80e 100644 --- a/packages/g-plugin-image-loader/package.json +++ b/packages/g-plugin-image-loader/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-image-loader", - "version": "1.1.18", + "version": "1.1.19", "description": "A G plugin for loading image", "keywords": [ "antv", diff --git a/packages/g-plugin-image-loader/src/ImagePool.ts b/packages/g-plugin-image-loader/src/ImagePool.ts index d5529274f..377ec1c0e 100644 --- a/packages/g-plugin-image-loader/src/ImagePool.ts +++ b/packages/g-plugin-image-loader/src/ImagePool.ts @@ -1,4 +1,5 @@ import type { LinearGradient, Pattern, RadialGradient } from '@antv/g-lite'; +import { UnitType } from '@antv/g-lite'; import { CanvasConfig, computeLinearGradient, @@ -10,7 +11,7 @@ import { } from '@antv/g-lite'; import { isString } from '@antv/util'; -export type GradientParams = (LinearGradient | RadialGradient) & { +export type GradientParams = (LinearGradient & RadialGradient) & { width: number; height: number; type: GradientType; @@ -104,8 +105,7 @@ export class ImagePool { getOrCreateGradient(params: GradientParams, context: CanvasRenderingContext2D) { const key = this.generateGradientKey(params); - // @ts-ignore - const { type, steps, width, height, angle, cx, cy } = params; + const { type, steps, width, height, angle, cx, cy, size } = params; if (this.gradientCache[key]) { return this.gradientCache[key]; @@ -117,14 +117,16 @@ export class ImagePool { // @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createLinearGradient gradient = context.createLinearGradient(x1, y1, x2, y2); } else if (type === GradientType.RadialGradient) { - const { x, y, r } = computeRadialGradient(width, height, cx, cy); + const { x, y, r } = computeRadialGradient(width, height, cx, cy, size); // @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createRadialGradient gradient = context.createRadialGradient(x, y, 0, x, y, r); } if (gradient) { - steps.forEach(([offset, color]) => { - gradient?.addColorStop(offset, color); + steps.forEach(({ offset, color }) => { + if (offset.unit === UnitType.kPercentage) { + gradient?.addColorStop(offset.value / 100, color.toString()); + } }); this.gradientCache[key] = gradient; @@ -134,10 +136,11 @@ export class ImagePool { } private generateGradientKey(params: GradientParams): string { - // @ts-ignore - const { type, width, height, steps, angle, cx, cy } = params; - return `gradient-${type}-${angle || 0}-${cx || 0}-${cy || 0}-${width}-${height}-${steps - .map((step) => step.join('')) + const { type, width, height, steps, angle, cx, cy, size } = params; + return `gradient-${type}-${angle?.toString() || 0}-${cx?.toString() || 0}-${ + cy?.toString() || 0 + }-${size?.toString() || 0}-${width}-${height}-${steps + .map(({ offset, color }) => `${offset}${color}`) .join('-')}`; } diff --git a/packages/g-plugin-matterjs/package.json b/packages/g-plugin-matterjs/package.json index ef15cfd4c..e9012f2a1 100644 --- a/packages/g-plugin-matterjs/package.json +++ b/packages/g-plugin-matterjs/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-matterjs", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin for matter.js physics engine", "keywords": [ "antv", diff --git a/packages/g-plugin-mobile-interaction/package.json b/packages/g-plugin-mobile-interaction/package.json index 24226284e..dcc2cc546 100644 --- a/packages/g-plugin-mobile-interaction/package.json +++ b/packages/g-plugin-mobile-interaction/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-mobile-interaction", - "version": "0.7.17", + "version": "0.7.18", "description": "A G plugin listening events in mobile environment", "keywords": [ "antv", diff --git a/packages/g-plugin-physx/package.json b/packages/g-plugin-physx/package.json index 121865429..27f469182 100644 --- a/packages/g-plugin-physx/package.json +++ b/packages/g-plugin-physx/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-physx", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin for PhysX", "keywords": [ "antv", diff --git a/packages/g-plugin-rough-canvas-renderer/package.json b/packages/g-plugin-rough-canvas-renderer/package.json index 4cd74b76b..2376d8a7d 100644 --- a/packages/g-plugin-rough-canvas-renderer/package.json +++ b/packages/g-plugin-rough-canvas-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-rough-canvas-renderer", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin of renderer implementation with rough.js", "keywords": [ "antv", diff --git a/packages/g-plugin-rough-svg-renderer/package.json b/packages/g-plugin-rough-svg-renderer/package.json index e795233a1..186b90899 100644 --- a/packages/g-plugin-rough-svg-renderer/package.json +++ b/packages/g-plugin-rough-svg-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-rough-svg-renderer", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin of renderer implementation with rough.js", "keywords": [ "antv", diff --git a/packages/g-plugin-svg-picker/package.json b/packages/g-plugin-svg-picker/package.json index 547120d56..d96dd0c75 100644 --- a/packages/g-plugin-svg-picker/package.json +++ b/packages/g-plugin-svg-picker/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-svg-picker", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin for picking in SVG", "keywords": [ "antv", diff --git a/packages/g-plugin-svg-renderer/package.json b/packages/g-plugin-svg-renderer/package.json index 59c20ad7b..ad5d63e67 100644 --- a/packages/g-plugin-svg-renderer/package.json +++ b/packages/g-plugin-svg-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-svg-renderer", - "version": "1.8.14", + "version": "1.8.15", "description": "A G plugin of renderer implementation with SVG", "keywords": [ "antv", diff --git a/packages/g-plugin-svg-renderer/src/shapes/defs/Pattern.ts b/packages/g-plugin-svg-renderer/src/shapes/defs/Pattern.ts index ec756bf01..277143207 100644 --- a/packages/g-plugin-svg-renderer/src/shapes/defs/Pattern.ts +++ b/packages/g-plugin-svg-renderer/src/shapes/defs/Pattern.ts @@ -67,10 +67,12 @@ function generateCacheKey(src: CSSGradientValue | CSSRGB | Pattern, options: any const { type, value } = src; if (type === GradientType.LinearGradient || type === GradientType.RadialGradient) { // @ts-ignore - const { type, width, height, steps, angle, cx, cy } = { ...value, ...options }; - cacheKey = `${type}${width}${height}${angle || 0}${cx || 0}${cy || 0}${steps - .map((step: [number, string]) => step.join('')) - .join('')}`; + const { type, width, height, steps, angle, cx, cy, size } = { ...value, ...options }; + cacheKey = `gradient-${type}-${angle?.toString() || 0}-${cx?.toString() || 0}-${ + cy?.toString() || 0 + }-${size?.toString() || 0}-${width}-${height}-${steps + .map(({ offset, color }) => `${offset}${color}`) + .join('-')}`; } } else if (isPattern(src)) { if (isString(src.image)) { @@ -205,8 +207,9 @@ function createOrUpdateGradient( $existed.setAttribute('gradientUnits', 'userSpaceOnUse'); // add stops let innerHTML = ''; - (parsedColor.value as LinearGradient).steps.forEach(([offset, color]) => { - innerHTML += ``; + (parsedColor.value as LinearGradient).steps.forEach(({ offset, color }) => { + // TODO: support absolute unit like `px` + innerHTML += ``; }); $existed.innerHTML = innerHTML; $existed.id = gradientId; @@ -224,8 +227,8 @@ function createOrUpdateGradient( // $existed.setAttribute('gradientTransform', `rotate(${angle})`); } else { - const { cx, cy } = parsedColor.value as RadialGradient; - const { x, y, r } = computeRadialGradient(width, height, cx, cy); + const { cx, cy, size } = parsedColor.value as RadialGradient; + const { x, y, r } = computeRadialGradient(width, height, cx, cy, size); $existed.setAttribute('cx', `${x}`); $existed.setAttribute('cy', `${y}`); diff --git a/packages/g-plugin-webgl-device/package.json b/packages/g-plugin-webgl-device/package.json index 8baddff9d..02f835d7c 100644 --- a/packages/g-plugin-webgl-device/package.json +++ b/packages/g-plugin-webgl-device/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-webgl-device", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin implements GPUDevice interface with WebGL API", "keywords": [ "antv", diff --git a/packages/g-plugin-webgpu-device/package.json b/packages/g-plugin-webgpu-device/package.json index 0a16a2470..99cb941c7 100644 --- a/packages/g-plugin-webgpu-device/package.json +++ b/packages/g-plugin-webgpu-device/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-webgpu-device", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin implements GPUDevice interface with WebGPU API", "keywords": [ "antv", diff --git a/packages/g-plugin-yoga/package.json b/packages/g-plugin-yoga/package.json index 53d1e0575..932f79ef8 100644 --- a/packages/g-plugin-yoga/package.json +++ b/packages/g-plugin-yoga/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-plugin-yoga", - "version": "1.7.17", + "version": "1.7.18", "description": "A G plugin for Yoga layout engine", "keywords": [ "antv", diff --git a/packages/g-shader-components/package.json b/packages/g-shader-components/package.json index edeccc4a3..59957c292 100644 --- a/packages/g-shader-components/package.json +++ b/packages/g-shader-components/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-shader-components", - "version": "1.7.17", + "version": "1.7.18", "description": "Shader components based on glslify", "main": "scene.both.glsl", "files": [ diff --git a/packages/g-svg/package.json b/packages/g-svg/package.json index 09e1dab70..046c323e1 100644 --- a/packages/g-svg/package.json +++ b/packages/g-svg/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-svg", - "version": "1.8.14", + "version": "1.8.15", "description": "A renderer implemented by SVG", "keywords": [ "antv", @@ -27,9 +27,9 @@ "README.md" ], "dependencies": { - "@antv/g-plugin-dom-interaction": "^1.7.17", - "@antv/g-plugin-svg-picker": "^1.7.17", - "@antv/g-plugin-svg-renderer": "^1.8.14", + "@antv/g-plugin-dom-interaction": "^1.7.18", + "@antv/g-plugin-svg-picker": "^1.7.18", + "@antv/g-plugin-svg-renderer": "^1.8.15", "@antv/util": "^3.2.4", "tslib": "^2.3.1" }, diff --git a/packages/g-svg/src/SVGContextService.ts b/packages/g-svg/src/SVGContextService.ts index 3c9fc101e..e94efd5c3 100644 --- a/packages/g-svg/src/SVGContextService.ts +++ b/packages/g-svg/src/SVGContextService.ts @@ -1,4 +1,5 @@ import type { DataURLOptions } from '@antv/g-lite'; +import { isBrowser } from '@antv/g-lite'; import { CanvasConfig, ContextService, inject, singleton } from '@antv/g-lite'; import { createSVGElement } from '@antv/g-plugin-svg-renderer'; import { isString } from '@antv/util'; @@ -15,7 +16,7 @@ export class SVGContextService implements ContextService { ) {} async init() { - const { container, document: doc } = this.canvasConfig; + const { container, document: doc, devicePixelRatio } = this.canvasConfig; // create container this.$container = isString(container) ? (doc || document).getElementById(container) : container; @@ -32,7 +33,8 @@ export class SVGContextService implements ContextService { this.$namespace = $namespace; } - let dpr = window.devicePixelRatio || 1; + // use user-defined dpr first + let dpr = devicePixelRatio || (isBrowser && window.devicePixelRatio) || 1; dpr = dpr >= 1 ? Math.ceil(dpr) : 1; this.dpr = dpr; } @@ -62,6 +64,7 @@ export class SVGContextService implements ContextService { } resize(width: number, height: number) { + // SVG should ignore DPR if (this.$namespace) { this.$namespace.setAttribute('width', `${width}`); this.$namespace.setAttribute('height', `${height}`); diff --git a/packages/g-web-animations-api/package.json b/packages/g-web-animations-api/package.json index e32cecee3..f39a748ab 100644 --- a/packages/g-web-animations-api/package.json +++ b/packages/g-web-animations-api/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-web-animations-api", - "version": "1.0.6", + "version": "1.0.7", "description": "A simple implementation of Web Animations API.", "keywords": [ "antv", diff --git a/packages/g-web-components/package.json b/packages/g-web-components/package.json index b9b55006b..e6d882619 100644 --- a/packages/g-web-components/package.json +++ b/packages/g-web-components/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-web-components", - "version": "1.7.17", + "version": "1.7.18", "description": "A declarative usage for G implemented with WebComponents", "keywords": [ "antv", diff --git a/packages/g-webgl/package.json b/packages/g-webgl/package.json index 8afefa652..c58ee9f1b 100644 --- a/packages/g-webgl/package.json +++ b/packages/g-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-webgl", - "version": "1.7.20", + "version": "1.7.21", "description": "A renderer implemented by WebGL1/2", "keywords": [ "antv", @@ -27,11 +27,11 @@ "README.md" ], "dependencies": { - "@antv/g-plugin-device-renderer": "^1.7.20", - "@antv/g-plugin-dom-interaction": "^1.7.17", - "@antv/g-plugin-html-renderer": "^1.7.17", - "@antv/g-plugin-image-loader": "^1.1.18", - "@antv/g-plugin-webgl-device": "^1.7.17", + "@antv/g-plugin-device-renderer": "^1.7.21", + "@antv/g-plugin-dom-interaction": "^1.7.18", + "@antv/g-plugin-html-renderer": "^1.7.18", + "@antv/g-plugin-image-loader": "^1.1.19", + "@antv/g-plugin-webgl-device": "^1.7.18", "@antv/util": "^3.2.4" }, "devDependencies": { diff --git a/packages/g-webgl/src/WebGLContextService.ts b/packages/g-webgl/src/WebGLContextService.ts index bfe6e458f..65412484e 100644 --- a/packages/g-webgl/src/WebGLContextService.ts +++ b/packages/g-webgl/src/WebGLContextService.ts @@ -26,7 +26,7 @@ export class WebGLContextService implements ContextService= 1 ? Math.ceil(dpr) : 1; - this.dpr = dpr; + this.resize(this.canvasConfig.width, this.canvasConfig.height); } getDomElement() { @@ -83,6 +80,12 @@ export class WebGLContextService implements ContextService= 1 ? Math.ceil(dpr) : 1; + this.dpr = dpr; + if (this.$canvas) { const dpr = this.getDPR(); diff --git a/packages/g-webgpu/package.json b/packages/g-webgpu/package.json index fb44fa231..f3a4884a7 100644 --- a/packages/g-webgpu/package.json +++ b/packages/g-webgpu/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-webgpu", - "version": "1.7.20", + "version": "1.7.21", "description": "A renderer implemented by WebGPU", "keywords": [ "antv", @@ -27,11 +27,11 @@ "README.md" ], "dependencies": { - "@antv/g-plugin-device-renderer": "^1.7.20", - "@antv/g-plugin-dom-interaction": "^1.7.17", - "@antv/g-plugin-html-renderer": "^1.7.17", - "@antv/g-plugin-image-loader": "^1.1.18", - "@antv/g-plugin-webgpu-device": "^1.7.17", + "@antv/g-plugin-device-renderer": "^1.7.21", + "@antv/g-plugin-dom-interaction": "^1.7.18", + "@antv/g-plugin-html-renderer": "^1.7.18", + "@antv/g-plugin-image-loader": "^1.1.19", + "@antv/g-plugin-webgpu-device": "^1.7.18", "@antv/util": "^3.2.4" }, "devDependencies": { diff --git a/packages/g-webgpu/src/WebGPUContextService.ts b/packages/g-webgpu/src/WebGPUContextService.ts index 3e801b06a..017cbe3a4 100644 --- a/packages/g-webgpu/src/WebGPUContextService.ts +++ b/packages/g-webgpu/src/WebGPUContextService.ts @@ -26,7 +26,7 @@ export class WebGPUContextService implements ContextService { ) {} async init() { - const { container, canvas, devicePixelRatio } = this.canvasConfig; + const { container, canvas } = this.canvasConfig; if (canvas) { this.$canvas = canvas; @@ -51,10 +51,7 @@ export class WebGPUContextService implements ContextService { } } - // use user-defined dpr first - let dpr = devicePixelRatio || (isBrowser && window.devicePixelRatio) || 1; - dpr = dpr >= 1 ? Math.ceil(dpr) : 1; - this.dpr = dpr; + this.resize(this.canvasConfig.width, this.canvasConfig.height); } getDomElement() { @@ -81,6 +78,12 @@ export class WebGPUContextService implements ContextService { } resize(width: number, height: number) { + // use user-defined dpr first + const { devicePixelRatio } = this.canvasConfig; + let dpr = devicePixelRatio || (isBrowser && window.devicePixelRatio) || 1; + dpr = dpr >= 1 ? Math.ceil(dpr) : 1; + this.dpr = dpr; + if (this.$canvas) { const dpr = this.getDPR(); diff --git a/packages/g/package.json b/packages/g/package.json index c460c6f94..40188be2d 100644 --- a/packages/g/package.json +++ b/packages/g/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g", - "version": "5.9.1", + "version": "5.10.0", "description": "A core module for rendering engine implements DOM API.", "keywords": [ "antv", @@ -29,13 +29,13 @@ "README.md" ], "dependencies": { - "@antv/g-camera-api": "^1.0.6", - "@antv/g-compat": "^1.0.6", - "@antv/g-css-layout-api": "^1.0.6", - "@antv/g-css-typed-om-api": "^1.0.6", - "@antv/g-dom-mutation-observer-api": "^1.0.6", - "@antv/g-lite": "^1.0.6", - "@antv/g-web-animations-api": "^1.0.6" + "@antv/g-camera-api": "^1.0.7", + "@antv/g-compat": "^1.0.7", + "@antv/g-css-layout-api": "^1.0.7", + "@antv/g-css-typed-om-api": "^1.0.7", + "@antv/g-dom-mutation-observer-api": "^1.0.7", + "@antv/g-lite": "^1.0.7", + "@antv/g-web-animations-api": "^1.0.7" }, "publishConfig": { "access": "public" diff --git a/packages/g/src/__tests__/dom/element.spec.ts b/packages/g/src/__tests__/dom/element.spec.ts index 664396128..29c9990cf 100644 --- a/packages/g/src/__tests__/dom/element.spec.ts +++ b/packages/g/src/__tests__/dom/element.spec.ts @@ -1,4 +1,4 @@ -import { Element, Node } from '@antv/g'; +import { Element, Node, resetEntityCounter } from '@antv/g'; import chai, { expect } from 'chai'; // @ts-ignore import chaiAlmost from 'chai-almost'; @@ -9,6 +9,20 @@ chai.use(chaiAlmost()); chai.use(sinonChai); describe('DOM Element API', () => { + it('should reset EntityCounter', () => { + resetEntityCounter(); + let group1 = new Element(); + let group2 = new Element(); + expect(group1.entity).to.eqls(0); + expect(group2.entity).to.eqls(1); + + resetEntityCounter(); + group1 = new Element(); + group2 = new Element(); + expect(group1.entity).to.eqls(0); + expect(group2.entity).to.eqls(1); + }); + it('should appendChild with before & after correctly', () => { const group1 = new Element(); const group2 = new Element(); diff --git a/packages/react-g/package.json b/packages/react-g/package.json index 3a0c65caa..cea61ed90 100644 --- a/packages/react-g/package.json +++ b/packages/react-g/package.json @@ -1,6 +1,6 @@ { "name": "@antv/react-g", - "version": "1.8.14", + "version": "1.8.15", "description": "react render for @antv/g", "keywords": [ "react", @@ -28,7 +28,7 @@ "README.md" ], "dependencies": { - "@antv/g": "^5.9.1", + "@antv/g": "^5.10.0", "@antv/util": "^3.2.4", "react-reconciler": "^0.26.2", "scheduler": "^0.20.2", diff --git a/packages/site/docs/api/canvas.en.md b/packages/site/docs/api/canvas.en.md index 7aa3d8bde..e6a724323 100644 --- a/packages/site/docs/api/canvas.en.md +++ b/packages/site/docs/api/canvas.en.md @@ -183,6 +183,19 @@ const canvas = new Canvas({ }); ``` +## supportsCSSTransform + +Optional. 是否支持在容器上应用 CSS Transform 的情况下确保交互事件坐标转换正确。 + +Whether or not CSS Transform is supported on the container to ensure that the interaction event coordinates are transformed correctly. + +In this [example](/en/examples/canvas#supports-css-transform), we have enlarged the container by a factor of 1.1, and with this configuration enabled, mouse movement over the circle changes the mouse style correctly. + +```js +const $wrapper = document.getElementById('container'); +$wrapper.style.transform = 'scale(1.1)'; +``` + ## supportsPointerEvents Optional. Whether PointerEvent is supported or not, the default will use `! !globalThis.PointerEvent`. If `false` is passed, the event listener plugin will not listen for PointerEvent such as `pointerdown`. diff --git a/packages/site/docs/api/canvas.zh.md b/packages/site/docs/api/canvas.zh.md index 46757ad6f..b094fa1eb 100644 --- a/packages/site/docs/api/canvas.zh.md +++ b/packages/site/docs/api/canvas.zh.md @@ -187,6 +187,17 @@ const canvas = new Canvas({ }); ``` +## supportsCSSTransform + +可选。是否支持在容器上应用 CSS Transform 的情况下确保交互事件坐标转换正确。 + +在该 [示例](/zh/examples/canvas#supports-css-transform) 中,我们将容器放大了 1.1 倍,开启该配置项后,鼠标移动到圆上可以正确变化鼠标样式: + +```js +const $wrapper = document.getElementById('container'); +$wrapper.style.transform = 'scale(1.1)'; +``` + ## supportsPointerEvents 可选。是否支持 PointerEvent,默认将使用 `!!globalThis.PointerEvent` 判断。如果传入 `false`,事件监听插件将不会监听例如 `pointerdown` 等 PointerEvent。 diff --git a/packages/site/docs/api/css/css-properties-values-api.en.md b/packages/site/docs/api/css/css-properties-values-api.en.md index 97d9b1d85..06987ec55 100644 --- a/packages/site/docs/api/css/css-properties-values-api.en.md +++ b/packages/site/docs/api/css/css-properties-values-api.en.md @@ -268,9 +268,7 @@ rect.style.fill = `linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70. A radial gradient consists of a gradual transition between two or more colors emanating from the origin. -The usage is exactly like CSS [radial-gradient](https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient/radial-gradient), with the following differences. - -- Shapes are only supported for `circle` but not for `ellipse` +The usage is exactly like CSS [radial-gradient](https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient/radial-gradient). So a gradient centered at the center of the shape, with a radial gradient transitioning from red to blue to green as follows, [example](/en/examples/style#gradient). @@ -280,6 +278,45 @@ rect.style.fill = 'radial-gradient(circle at center, red, blue, green 100%)'; radial gradient +Caution. + +- Shapes are only supported for `circle` but not for `ellipse` +- Support for specifying `circle` radius + + - `'closest-side'` The gradient's ending shape meets the side of the box closest to its center. + - `'farthest-corner'` The default value, the gradient's ending shape is sized so that it exactly meets the farthest corner of the box from its center. + - `'closest-corner'` The gradient's ending shape is sized so that it exactly meets the closest corner of the box from its center. + - `'farthest-side'` Similar to closest-side, except the ending shape is sized to meet the side of the box farthest from its center (or vertical and horizontal sides). + - `` e.g. `'radial-gradient(circle 80px at center, red 100%, blue 100%)'` + +The following figures show the effect of `'closest-side'`, `'farthest-side'` and `80px` respectively. + +radial-gradient-closest-side + +radial-gradient-size-80 + +- Support specifying the position of the center of the circle and positioning it relative to the upper left corner of the enclosing box, e.g. `radial-gradient(circle at 50px 50px, red, blue, green 100%)`. + - `'top'` Top edge midpoint + - `'left'` Left edge midpoint + - `'bottom'` Bottom edge midpoint + - `'right'` Right edge midpoint + - `'center'` Horizontal and vertical centering + - `'top left'` Left-top corner + - `'left top'` Same as `'top left'` + - `'top right'` Right-top corner + - `'bottom left'` Left-bottom corner + - `'bottom right'` Right-bottom corner + - ` ` e.g. `'25% 25%'` and `'50px 50px'` + +The following figures show the effect of `'50px 50px'`, `'top right'` and `'left'` respectively. + +radial-gradient-center-50-50 +radial-gradient-center-top-right +radial-gradient-center-left + +- Like linear gradients, it also supports multiple overlays. + ## \ In this [example](/en/examples/style#pattern) we show the currently supported template padding effects, the sources can include image URLs, `HTMLImageElement` `HTMLCanvasElement` `HTMLVideoElement` etc., and also specify the padding repeat direction. diff --git a/packages/site/docs/api/css/css-properties-values-api.zh.md b/packages/site/docs/api/css/css-properties-values-api.zh.md index 74fa808ed..f37bbcb29 100644 --- a/packages/site/docs/api/css/css-properties-values-api.zh.md +++ b/packages/site/docs/api/css/css-properties-values-api.zh.md @@ -240,6 +240,8 @@ background: linear-gradient(#e66465, #9198e5); rect.style.fill = 'linear-gradient(#e66465, #9198e5)'; ``` +其中渐变色列表 `` 形如:`radial-gradient(cyan 0%, transparent 20%, salmon 40%)`,使用 [\](/zh/docs/api/css/css-properties-values-api#color) 和 [\](/zh/docs/api/css/css-properties-values-api#percentage) 的组合。 + 在该[示例](/zh/examples/style#gradient)中我们展示了目前支持的渐变效果,包括线性和径向渐变、多个渐变叠加等: gradient @@ -272,9 +274,7 @@ rect.style.fill = `linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70. 径向渐变由从原点发出的两种或者多种颜色之间的逐步过渡组成。 -用法完全可以参考 CSS [radial-gradient](https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient/radial-gradient),但有以下区别: - -- 形状仅支持 `circle` 不支持 `ellipse` +用法完全可以参考 CSS [radial-gradient](https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient/radial-gradient)。 因此一个渐变中心位于图形中心,从红过渡到蓝再到绿的径向渐变如下,[示例](/zh/examples/style#gradient): @@ -284,6 +284,45 @@ rect.style.fill = 'radial-gradient(circle at center, red, blue, green 100%)'; radial gradient +注意事项: + +- 形状仅支持 `circle` 不支持 `ellipse` +- 支持指定 `circle` 半径: + + - `'closest-side'` 圆心到包围盒最近边的距离 + - `'farthest-corner'` **默认值**。圆心到包围盒最远角的距离 + - `'closest-corner'` 圆心到包围盒最近角的距离 + - `'farthest-side'` 圆心到包围盒最远边的距离 + - `` 指定长度,例如 `'radial-gradient(circle 80px at center, red 100%, blue 100%)'` + +下图分别展示了 `'closest-side'` `'farthest-side'` 和 `80px` 的效果: + +radial-gradient-closest-side + +radial-gradient-size-80 + +- 支持指定圆心位置,相对包围盒左上角定位,例如 `radial-gradient(circle at 50px 50px, red, blue, green 100%)`: + - `'top'` 上方边缘中点 + - `'left'` 左侧边缘中点 + - `'bottom'` 下方边缘中点 + - `'right'` 右侧边缘中点 + - `'center'` 水平垂直居中 + - `'top left'` 左上角 + - `'left top'` 同 `'top left'` + - `'top right'` 右上角 + - `'bottom left'` 左下角 + - `'bottom right'` 右下角 + - ` ` 指定长度,例如 `'25% 25%'` 或者 `'50px 50px'` + +下图分别展示了 `'50px 50px'`,`'top right'` 和 `'left'` 的效果: + +radial-gradient-center-50-50 +radial-gradient-center-top-right +radial-gradient-center-left + +- 和线性渐变一样,也支持多组叠加 + ### 历史用法 ![](https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*Z5gpQL9ia9kAAAAAAAAAAABkARQnAQ) diff --git a/packages/site/examples/canvas/demo/meta.json b/packages/site/examples/canvas/demo/meta.json index b7770db85..4aa679850 100644 --- a/packages/site/examples/canvas/demo/meta.json +++ b/packages/site/examples/canvas/demo/meta.json @@ -63,6 +63,13 @@ "zh": "通过 API 方式完成拾取", "en": "Use picking API" } + }, + { + "filename": "supports-css-transform.js", + "title": { + "zh": "支持在容器上应用 CSS Transform", + "en": "Support applying CSS Transform on container" + } } ] } diff --git a/packages/site/examples/canvas/demo/resize.tsx b/packages/site/examples/canvas/demo/resize.tsx index 3f9759c78..9b53fadd3 100644 --- a/packages/site/examples/canvas/demo/resize.tsx +++ b/packages/site/examples/canvas/demo/resize.tsx @@ -2,6 +2,7 @@ import { Canvas, CanvasEvent, Circle } from '@antv/g'; import { Renderer as CanvasRenderer } from '@antv/g-canvas'; import { Renderer as SVGRenderer } from '@antv/g-svg'; import { Renderer as WebGLRenderer } from '@antv/g-webgl'; +import { Renderer as CanvaskitRenderer } from '@antv/g-canvaskit'; import * as lil from 'lil-gui'; import React, { useEffect } from 'react'; import ReactDOM from 'react-dom'; @@ -32,6 +33,12 @@ const App = function MultiWorld() { const svgRenderer2 = new SVGRenderer(); const webglRenderer1 = new WebGLRenderer(); const webglRenderer2 = new WebGLRenderer(); + const canvaskitRenderer1 = new CanvaskitRenderer({ + wasmDir: '/', + }); + const canvaskitRenderer2 = new CanvaskitRenderer({ + wasmDir: '/', + }); // create a canvas canvas1 = new Canvas({ @@ -91,25 +98,33 @@ const App = function MultiWorld() { const rendererFolder = gui.addFolder('renderer'); const rendererConfig = { renderer: 'canvas', + devicePixelRatio: window.devicePixelRatio, }; rendererFolder - .add(rendererConfig, 'renderer', ['canvas', 'webgl', 'svg']) + .add(rendererConfig, 'renderer', ['canvas', 'webgl', 'svg', 'canvaskit']) .onChange((renderer) => { canvas1.setRenderer( renderer === 'canvas' ? canvasRenderer1 : renderer === 'webgl' ? webglRenderer1 - : svgRenderer1, + : renderer === 'svg' + ? svgRenderer1 + : canvaskitRenderer1, ); canvas2.setRenderer( renderer === 'canvas' ? canvasRenderer2 : renderer === 'webgl' ? webglRenderer2 - : svgRenderer2, + : renderer === 'svg' + ? svgRenderer2 + : canvaskitRenderer2, ); }); + rendererFolder.add(rendererConfig, 'devicePixelRatio', 0.5, 5).onChange((dpr) => { + canvas1.getConfig().devicePixelRatio = dpr; + }); rendererFolder.open(); }); diff --git a/packages/site/examples/canvas/demo/supports-css-transform.js b/packages/site/examples/canvas/demo/supports-css-transform.js new file mode 100644 index 000000000..fd302dbf7 --- /dev/null +++ b/packages/site/examples/canvas/demo/supports-css-transform.js @@ -0,0 +1,89 @@ +import { Canvas, CanvasEvent, Circle } from '@antv/g'; +import { Renderer as CanvasRenderer } from '@antv/g-canvas'; +import { Renderer as CanvaskitRenderer } from '@antv/g-canvaskit'; +import { Renderer as SVGRenderer } from '@antv/g-svg'; +import { Renderer as WebGLRenderer } from '@antv/g-webgl'; +import { Renderer as WebGPURenderer } from '@antv/g-webgpu'; +import * as lil from 'lil-gui'; +import Stats from 'stats.js'; + +const $wrapper = document.getElementById('container'); +$wrapper.style.transform = 'scale(1.1)'; + +// create a renderer +const canvasRenderer = new CanvasRenderer(); +const svgRenderer = new SVGRenderer(); +const webglRenderer = new WebGLRenderer(); +const webgpuRenderer = new WebGPURenderer(); +const canvaskitRenderer = new CanvaskitRenderer({ + wasmDir: '/', +}); + +// create a canvas +const canvas = new Canvas({ + container: 'container', + width: 600, + height: 500, + renderer: canvasRenderer, + supportsCSSTransform: true, +}); + +// create a circle +const circle = new Circle({ + style: { + cx: 300, + cy: 200, + r: 100, + fill: '#1890FF', + stroke: '#F04864', + lineWidth: 4, + shadowColor: 'black', + shadowBlur: 20, + cursor: 'pointer', + }, +}); + +canvas.addEventListener(CanvasEvent.READY, () => { + // add a circle to canvas + canvas.appendChild(circle); +}); + +// stats +const stats = new Stats(); +stats.showPanel(0); +const $stats = stats.dom; +$stats.style.position = 'absolute'; +$stats.style.left = '0px'; +$stats.style.top = '0px'; +$wrapper.appendChild($stats); +canvas.addEventListener(CanvasEvent.AFTER_RENDER, () => { + if (stats) { + stats.update(); + } +}); + +// GUI +const gui = new lil.GUI({ autoPlace: false }); +$wrapper.appendChild(gui.domElement); +const rendererFolder = gui.addFolder('renderer'); +const rendererConfig = { + renderer: 'canvas', +}; +rendererFolder + .add(rendererConfig, 'renderer', ['canvas', 'svg', 'webgl', 'webgpu', 'canvaskit']) + .onChange((rendererName) => { + let renderer; + if (rendererName === 'canvas') { + renderer = canvasRenderer; + } else if (rendererName === 'svg') { + renderer = svgRenderer; + } else if (rendererName === 'webgl') { + renderer = webglRenderer; + } else if (rendererName === 'webgpu') { + renderer = webgpuRenderer; + } else if (rendererName === 'canvaskit') { + renderer = canvaskitRenderer; + } + canvas.setRenderer(renderer); + }); +rendererFolder.open(); diff --git a/packages/site/examples/ecosystem/demo/d3-geo-polygon.js b/packages/site/examples/ecosystem/demo/d3-geo-polygon.js new file mode 100644 index 000000000..26e38eb46 --- /dev/null +++ b/packages/site/examples/ecosystem/demo/d3-geo-polygon.js @@ -0,0 +1,128 @@ +import { Canvas, CanvasEvent, Polygon } from '@antv/g'; +import { Renderer as CanvasRenderer } from '@antv/g-canvas'; +import { Renderer as CanvaskitRenderer } from '@antv/g-canvaskit'; +import { Renderer as SVGRenderer } from '@antv/g-svg'; +import { Renderer as WebGLRenderer } from '@antv/g-webgl'; +import { Renderer as WebGPURenderer } from '@antv/g-webgpu'; +import * as d3 from 'd3'; +import * as lil from 'lil-gui'; +import Stats from 'stats.js'; + +(async () => { + // Load data. + const data = await fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/a09237e0-58e1-40b5-ac18-d4e0b9156306.json', + ).then((res) => res.json()); + + // Set center of projection in latitude and longitude. + const projection = d3.geoMercator().center([107, 31]); + + // Compute height based on width and feature data. + const width = 900; + const height = (() => { + const [[x0, y0], [x1, y1]] = d3.geoPath(projection.fitWidth(width, data)).bounds(data); + const dy = Math.ceil(y1 - y0); + const l = Math.min(Math.ceil(x1 - x0), dy); + projection.scale((projection.scale() * (l - 1)) / l).precision(0.2); + return dy; + })(); + + // Helper function to map abstract data. + const project = (data) => { + const { features } = data; + return features.flatMap((feature) => { + const { coordinates: C, type } = feature.geometry; + const coordinates = type === 'MultiPolygon' ? C.flatMap((d) => d) : C; + return coordinates.map((coordinate) => { + const P = coordinate.map(projection); + return { + name: feature.properties.name, + x: P.map((d) => d[0]), + y: P.map((d) => d[1]), + }; + }); + }); + }; + + const canvasRenderer = new CanvasRenderer(); + const svgRenderer = new SVGRenderer(); + const webglRenderer = new WebGLRenderer(); + const webgpuRenderer = new WebGPURenderer(); + const canvaskitRenderer = new CanvaskitRenderer({ + wasmDir: '/', + fonts: [ + { + name: 'Roboto', + url: '/Roboto-Regular.ttf', + }, + { + name: 'sans-serif', + url: '/NotoSans-Regular.ttf', + }, + ], + }); + const canvas = new Canvas({ + container: 'container', + width, + height, + renderer: canvasRenderer, + }); + + canvas.addEventListener(CanvasEvent.READY, () => { + project(data).forEach(({ x, y }) => { + const points = x.map((X, i) => [3 * X - 1800, 3 * y[i] - 800]); + console.log(points); + + canvas.appendChild( + new Polygon({ + style: { + stroke: 'black', + strokeWidth: 1, + points, + }, + }), + ); + }); + }); + + // stats + const stats = new Stats(); + stats.showPanel(0); + const $stats = stats.dom; + $stats.style.position = 'absolute'; + $stats.style.left = '0px'; + $stats.style.top = '0px'; + const $wrapper = document.getElementById('container'); + $wrapper.appendChild($stats); + canvas.addEventListener(CanvasEvent.AFTER_RENDER, () => { + if (stats) { + stats.update(); + } + }); + + // GUI + const gui = new lil.GUI({ autoPlace: false }); + $wrapper.appendChild(gui.domElement); + const rendererFolder = gui.addFolder('renderer'); + const rendererConfig = { + renderer: 'canvas', + }; + rendererFolder + .add(rendererConfig, 'renderer', ['canvas', 'svg', 'webgl', 'webgpu', 'canvaskit']) + .onChange((rendererName) => { + let renderer; + if (rendererName === 'canvas') { + renderer = canvasRenderer; + } else if (rendererName === 'svg') { + renderer = svgRenderer; + } else if (rendererName === 'webgl') { + renderer = webglRenderer; + } else if (rendererName === 'webgpu') { + renderer = webgpuRenderer; + } else if (rendererName === 'canvaskit') { + renderer = canvaskitRenderer; + } + canvas.setRenderer(renderer); + }); + rendererFolder.open(); +})(); diff --git a/packages/site/examples/ecosystem/demo/lottie-player.js b/packages/site/examples/ecosystem/demo/lottie-player.js index bb9c36791..48dcb22b8 100644 --- a/packages/site/examples/ecosystem/demo/lottie-player.js +++ b/packages/site/examples/ecosystem/demo/lottie-player.js @@ -196,9 +196,431 @@ const bouncy_ball = { ], }; -const animation = createAnimation(bouncy_ball); +// @see https://lottiefiles.github.io/lottie-docs/shapes/#rectangle +const rect = { + v: '5.5.7', + ip: 0, + op: 180, + nm: 'Animation', + mn: '{8f1618e3-6f83-4531-8f65-07dd4b68ee2e}', + fr: 60, + w: 512, + h: 512, + assets: [], + layers: [ + { + ddd: 0, + ty: 4, + ind: 0, + st: 0, + ip: 0, + op: 180, + nm: 'Layer', + mn: '{85f37d8b-1792-4a4f-82d2-1b3b6d829c07}', + ks: { + a: { + a: 0, + k: [256, 256], + }, + p: { + a: 0, + k: [256, 256], + }, + s: { + a: 0, + k: [100, 100], + }, + r: { + a: 0, + k: 0, + }, + o: { + a: 0, + k: 100, + }, + }, + shapes: [ + { + ty: 'gr', + nm: 'Group', + it: [ + { + ty: 'rc', + nm: 'Rectangle', + p: { + a: 0, + k: [256, 256], + }, + s: { + a: 0, + k: [256, 256], + }, + r: { + a: 0, + k: 0, + }, + }, + { + ty: 'st', + nm: 'Stroke', + mn: '{0930ce27-c8f9-4371-b0cf-111a859abfaf}', + o: { + a: 0, + k: 100, + }, + c: { + a: 0, + k: [1, 0.9803921568627451, 0.2823529411764706], + }, + lc: 2, + lj: 2, + ml: 0, + w: { + a: 0, + k: 30, + }, + }, + { + ty: 'tr', + a: { + a: 0, + k: [249.3134328358209, 254.47164179104476], + }, + p: { + a: 0, + k: [249.3134328358209, 254.47164179104476], + }, + s: { + a: 0, + k: [100, 100], + }, + r: { + a: 0, + k: 0, + }, + o: { + a: 0, + k: 100, + }, + }, + ], + }, + ], + }, + ], + meta: { + g: 'Glaxnimate 0.4.6-26-g7b05e75c', + }, +}; + +// @see https://lottiefiles.github.io/lottie-docs/breakdown/bezier/#beziers-in-lottie +const path = { + v: '5.7.1', + ip: 0, + op: 180, + nm: 'Animation', + mn: '{8f1618e3-6f83-4531-8f65-07dd4b68ee2e}', + fr: 60, + w: 512, + h: 512, + layers: [ + { + ty: 4, + ddd: 0, + nm: 'Layer', + mn: '{85f37d8b-1792-4a4f-82d2-1b3b6d829c07}', + ip: 0, + op: 180, + ind: 0, + st: 0, + sr: 1, + ks: { + a: { + a: 0, + k: [256, 256], + }, + p: { + a: 0, + k: [256, 256], + }, + s: { + a: 0, + k: [100, 100], + }, + r: { + a: 0, + k: 0, + }, + o: { + a: 0, + k: 100, + }, + }, + shapes: [ + { + ty: 'gr', + nm: 'Path', + mn: '{9199543e-3552-4e51-a802-623f2a4a2ca1}', + it: [ + { + ty: 'sh', + ks: { + a: 0, + k: { + c: false, + v: [ + [53, 325], + [429, 147], + [215, 430], + ], + i: [ + [0, 0], + [-147, 186], + [114, 36], + ], + o: [ + [89, -189], + [40, 189], + [0, 0], + ], + }, + }, + }, + { + ty: 'st', + nm: 'Stroke', + mn: '{0930ce27-c8f9-4371-b0cf-111a859abfaf}', + o: { + a: 0, + k: 100, + }, + c: { + a: 0, + k: [1, 0.979995422293431, 0.28000305180437934], + }, + lc: 2, + lj: 2, + ml: 0, + w: { + a: 0, + k: 30, + }, + }, + { + ty: 'tr', + a: { + a: 0, + k: [0, 0], + }, + p: { + a: 0, + k: [0, 0], + }, + s: { + a: 0, + k: [100, 100], + }, + r: { + a: 0, + k: 0, + }, + o: { + a: 0, + k: 100, + }, + }, + ], + }, + ], + }, + ], + meta: { + g: 'Glaxnimate 0.4.6-32-gb62899be', + }, +}; + +const gradient = { + v: '5.7.1', + ip: 0, + op: 180, + nm: 'Animation', + mn: '{8f1618e3-6f83-4531-8f65-07dd4b68ee2e}', + fr: 60, + w: 512, + h: 512, + layers: [ + { + ty: 4, + ddd: 0, + nm: 'Layer', + mn: '{85f37d8b-1792-4a4f-82d2-1b3b6d829c07}', + ip: 0, + op: 180, + ind: 0, + st: 0, + sr: 1, + ks: { + a: { + a: 0, + k: [256, 256], + }, + p: { + a: 0, + k: [256, 256], + }, + s: { + a: 0, + k: [100, 100], + }, + r: { + a: 0, + k: 0, + }, + o: { + a: 0, + k: 100, + }, + }, + shapes: [ + { + ty: 'gf', + nm: 'Gradient Fill', + o: { + a: 0, + k: 100, + }, + r: 1, + s: { + a: 0, + k: [256, 496], + }, + e: { + a: 0, + k: [256, 16], + }, + t: 2, + h: { + a: 0, + k: 0, + }, + a: { + a: 0, + k: 0, + }, + g: { + p: 3, + k: { + a: 0, + k: [ + 0, 0.7686274509803922, 0.8509803921568627, 0.9607843137254902, 0.5, + 0.19600213626306554, 0.31400015259021896, 0.6899977111467155, 1, + 0.16099794003204396, 0.18399328603036547, 0.45900663767452504, 0, 1, 0.5, 1, 1, 1, + ], + }, + }, + }, + // { + // ty: 'gr', + // nm: 'Gradient', + // mn: '{9df3ba96-24a3-412e-abd4-e64e2e76e6df}', + // it: [ + // { + // ty: 'rc', + // nm: 'Rectangle', + // mn: '{20934ad0-1c22-4752-a5b1-be99889ea79a}', + // p: { + // a: 0, + // k: [256, 256], + // }, + // s: { + // a: 0, + // k: [512, 512], + // }, + // r: { + // a: 0, + // k: 0, + // }, + // }, + // { + // ty: 'gf', + // nm: 'Gradient Fill', + // o: { + // a: 0, + // k: 100, + // }, + // r: 1, + // s: { + // a: 0, + // k: [256, 496], + // }, + // e: { + // a: 0, + // k: [256, 16], + // }, + // t: 1, + // h: { + // a: 0, + // k: 0, + // }, + // a: { + // a: 0, + // k: 0, + // }, + // g: { + // p: 3, + // k: { + // a: 0, + // k: [ + // 0, 0.7686274509803922, 0.8509803921568627, 0.9607843137254902, 0.5, + // 0.19600213626306554, 0.31400015259021896, 0.6899977111467155, 1, + // 0.16099794003204396, 0.18399328603036547, 0.45900663767452504, 0, 1, 0.5, 1, 1, + // 1, + // ], + // }, + // }, + // }, + // { + // ty: 'tr', + // a: { + // a: 0, + // k: [257.4805970149254, 255.76119402985074], + // }, + // p: { + // a: 0, + // k: [257.4805970149254, 255.76119402985074], + // }, + // s: { + // a: 0, + // k: [100, 100], + // }, + // r: { + // a: 0, + // k: 0, + // }, + // o: { + // a: 0, + // k: 100, + // }, + // }, + // ], + // }, + ], + }, + ], + meta: { + g: 'Glaxnimate 0.4.6-32-gb62899be', + }, +}; + +const gradientAnimation = createAnimation(gradient); +const rectAnimation = createAnimation(rect); +const pathAnimation = createAnimation(path); +const ballAnimation = createAnimation(bouncy_ball, { loop: true }); canvas.addEventListener(CanvasEvent.READY, () => { - animation.render(canvas); + gradientAnimation.render(canvas); + rectAnimation.render(canvas); + pathAnimation.render(canvas); + ballAnimation.render(canvas); }); // stats diff --git a/packages/site/examples/ecosystem/demo/meta.json b/packages/site/examples/ecosystem/demo/meta.json index 544e45dcf..a3c8b4f94 100644 --- a/packages/site/examples/ecosystem/demo/meta.json +++ b/packages/site/examples/ecosystem/demo/meta.json @@ -66,6 +66,13 @@ }, "screenshot": "https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*cG62RqGKMXsAAAAAAAAAAAAAARQnAQ" }, + { + "filename": "d3-geo-polygon.js", + "title": { + "zh": "D3 Geo Polygon", + "en": "D3 Geo Polygon" + } + }, { "filename": "d3-annotation.js", "title": { diff --git a/packages/site/examples/style/demo/gradient.js b/packages/site/examples/style/demo/gradient.js index b2cd62064..9c37cc07f 100644 --- a/packages/site/examples/style/demo/gradient.js +++ b/packages/site/examples/style/demo/gradient.js @@ -256,6 +256,7 @@ linearGradientFolder.add(linearGradientConfig, 'height', 50, 400).onChange((heig const radialGradientFolder = gui.addFolder('radial gradient'); const radialGradientConfig = { position: 'center', + size: 'farthest-corner', 'green color stop(%)': 100, }; radialGradientFolder @@ -272,12 +273,24 @@ radialGradientFolder 'bottom right', '25% 25%', '50% 50%', + '50px 50px', ]) .onChange((position) => { - rect3.style.fill = `radial-gradient(circle at ${position}, red, blue, green 100%)`; + rect3.style.fill = `radial-gradient(circle ${radialGradientConfig.size} at ${position}, red, blue, green ${radialGradientConfig['green color stop(%)']}%)`; + }); +radialGradientFolder + .add(radialGradientConfig, 'size', [ + 'closest-side', + 'closest-corner', + 'farthest-side', + 'farthest-corner', + '100px', + ]) + .onChange((size) => { + rect3.style.fill = `radial-gradient(circle ${size} at ${radialGradientConfig.position}, red, blue, green ${radialGradientConfig['green color stop(%)']}%)`; }); radialGradientFolder .add(radialGradientConfig, 'green color stop(%)', 0, 100) .onChange((percentage) => { - rect3.style.fill = `radial-gradient(circle at center, red, blue, green ${percentage}%)`; + rect3.style.fill = `radial-gradient(circle ${radialGradientConfig.size} at ${radialGradientConfig.position}, red, blue, green ${percentage}%)`; }); diff --git a/packages/site/package.json b/packages/site/package.json index e650a3b80..8da634230 100644 --- a/packages/site/package.json +++ b/packages/site/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-site", - "version": "1.8.14", + "version": "1.8.15", "private": true, "description": "G sites deployed on gh-pages", "keywords": [