diff --git a/packages/g6-extension-3d/__tests__/demos/index.ts b/packages/g6-extension-3d/__tests__/demos/index.ts index 9c1c971ad00..211cb3449ab 100644 --- a/packages/g6-extension-3d/__tests__/demos/index.ts +++ b/packages/g6-extension-3d/__tests__/demos/index.ts @@ -4,6 +4,7 @@ export * from './behavior-roll-canvas'; export * from './behavior-zoom-canvas'; export * from './layer-top'; export * from './layout-d3-force-3d'; +export { massiveElements } from './massive-elements'; export * from './position'; export * from './shapes'; export * from './solar-system'; diff --git a/packages/g6-extension-3d/__tests__/demos/massive-elements.ts b/packages/g6-extension-3d/__tests__/demos/massive-elements.ts new file mode 100644 index 00000000000..54ed3408177 --- /dev/null +++ b/packages/g6-extension-3d/__tests__/demos/massive-elements.ts @@ -0,0 +1,63 @@ +import { CameraSetting, ExtensionCategory, Graph, register } from '@antv/g6'; +import { Light, Line3D, ObserveCanvas3D, Sphere, ZoomCanvas3D, renderer } from '../../src'; + +export const massiveElements: TestCase = async (context) => { + register(ExtensionCategory.PLUGIN, '3d-light', Light); + register(ExtensionCategory.NODE, 'sphere', Sphere); + register(ExtensionCategory.EDGE, 'line3d', Line3D); + register(ExtensionCategory.PLUGIN, 'camera-setting', CameraSetting); + register(ExtensionCategory.BEHAVIOR, 'zoom-canvas-3d', ZoomCanvas3D); + register(ExtensionCategory.BEHAVIOR, 'observe-canvas-3d', ObserveCanvas3D); + + const data = await fetch('https://assets.antv.antgroup.com/g6/eva-3d-data.json').then((res) => res.json()); + + const graph = new Graph({ + ...context, + animation: false, + renderer, + data, + node: { + type: 'sphere', + style: { + materialType: 'phong', + size: 50, + x: (d) => d.data!.x, + y: (d) => d.data!.y, + z: (d) => d.data!.z, + }, + palette: { + color: 'tableau', + type: 'group', + field: 'cluster', + }, + }, + edge: { + type: 'line3d', + }, + behaviors: ['observe-canvas-3d', 'zoom-canvas-3d'], + plugins: [ + { + type: 'camera-setting', + projectionMode: 'orthographic', + near: 1, + far: 10000, + fov: 45, + aspect: 1, + }, + { + type: '3d-light', + directional: { + direction: [0, 0, 1], + }, + }, + ], + }); + + console.time('time'); + + await graph.draw(); + + console.timeEnd('time'); + + return graph; +}; diff --git a/packages/g6-extension-3d/__tests__/demos/solar-system.ts b/packages/g6-extension-3d/__tests__/demos/solar-system.ts index 413af7b44bd..4120c8711e0 100644 --- a/packages/g6-extension-3d/__tests__/demos/solar-system.ts +++ b/packages/g6-extension-3d/__tests__/demos/solar-system.ts @@ -10,7 +10,6 @@ export const solarSystem: TestCase = async (context) => { const graph = new Graph({ ...context, renderer, - background: 'black', data: { nodes: [ { @@ -68,6 +67,10 @@ export const solarSystem: TestCase = async (context) => { direction: [0, 0, 1], }, }, + { + type: 'background', + background: 'black', + }, ], }); diff --git a/packages/g6-extension-3d/__tests__/unit/utils/geometry.spec.ts b/packages/g6-extension-3d/__tests__/unit/utils/geometry.spec.ts new file mode 100644 index 00000000000..c67e22e7539 --- /dev/null +++ b/packages/g6-extension-3d/__tests__/unit/utils/geometry.spec.ts @@ -0,0 +1,14 @@ +import { CubeGeometry } from '@antv/g-plugin-3d'; +import { createGeometry } from '../../../src/utils/geometry'; + +describe('geometry', () => { + it('createGeometry', () => { + const device: any = {}; + const geometry1 = createGeometry('cube', device, CubeGeometry, { width: 1, height: 1, depth: 1 }); + const geometry2 = createGeometry('cube', device, CubeGeometry, { depth: 1, height: 1, width: 1 }); + const geometry3 = createGeometry('cube', device, CubeGeometry, { width: 2, height: 2, depth: 2 }); + + expect(geometry1).toBe(geometry2); + expect(geometry1).not.toBe(geometry3); + }); +}); diff --git a/packages/g6-extension-3d/src/elements/capsule.ts b/packages/g6-extension-3d/src/elements/capsule.ts index 367ad9cacea..36f99801023 100644 --- a/packages/g6-extension-3d/src/elements/capsule.ts +++ b/packages/g6-extension-3d/src/elements/capsule.ts @@ -2,6 +2,7 @@ import type { DisplayObjectConfig } from '@antv/g'; import type { CapsuleGeometryProps, ProceduralGeometry as GGeometry } from '@antv/g-plugin-3d'; import { CapsuleGeometry } from '@antv/g-plugin-3d'; import { deepMix } from '@antv/util'; +import { createGeometry } from '../utils/geometry'; import type { BaseNode3DStyleProps } from './base-node-3d'; import { BaseNode3D } from './base-node-3d'; @@ -22,6 +23,6 @@ export class Capsule extends BaseNode3D { protected getGeometry(attributes: Required): GGeometry | undefined { const size = this.getSize(); const { radius = size[0] / 2, height = size[1], heightSegments, sides } = attributes; - return new CapsuleGeometry(this.device, { radius, height, heightSegments, sides }); + return createGeometry('capsule', this.device, CapsuleGeometry, { radius, height, heightSegments, sides }); } } diff --git a/packages/g6-extension-3d/src/elements/cone.ts b/packages/g6-extension-3d/src/elements/cone.ts index acd822dffc4..8132266c9df 100644 --- a/packages/g6-extension-3d/src/elements/cone.ts +++ b/packages/g6-extension-3d/src/elements/cone.ts @@ -2,6 +2,7 @@ import type { DisplayObjectConfig } from '@antv/g'; import type { ConeGeometryProps, ProceduralGeometry as GGeometry } from '@antv/g-plugin-3d'; import { ConeGeometry } from '@antv/g-plugin-3d'; import { deepMix } from '@antv/util'; +import { createGeometry } from '../utils/geometry'; import type { BaseNode3DStyleProps } from './base-node-3d'; import { BaseNode3D } from './base-node-3d'; @@ -28,6 +29,12 @@ export class Cone extends BaseNode3D { heightSegments, capSegments, } = attributes; - return new ConeGeometry(this.device, { baseRadius, peakRadius, height, heightSegments, capSegments }); + return createGeometry('cone', this.device, ConeGeometry, { + baseRadius, + peakRadius, + height, + heightSegments, + capSegments, + }); } } diff --git a/packages/g6-extension-3d/src/elements/cube.ts b/packages/g6-extension-3d/src/elements/cube.ts index 8b1627e7c71..8863f70c94d 100644 --- a/packages/g6-extension-3d/src/elements/cube.ts +++ b/packages/g6-extension-3d/src/elements/cube.ts @@ -2,6 +2,7 @@ import type { DisplayObjectConfig } from '@antv/g'; import type { CubeGeometryProps, ProceduralGeometry as GGeometry } from '@antv/g-plugin-3d'; import { CubeGeometry } from '@antv/g-plugin-3d'; import { deepMix } from '@antv/util'; +import { createGeometry } from '../utils/geometry'; import type { BaseNode3DStyleProps } from './base-node-3d'; import { BaseNode3D } from './base-node-3d'; @@ -28,6 +29,13 @@ export class Cube extends BaseNode3D { heightSegments, depthSegments, } = attributes; - return new CubeGeometry(this.device, { width, height, depth, widthSegments, heightSegments, depthSegments }); + return createGeometry('cube', this.device, CubeGeometry, { + width, + height, + depth, + widthSegments, + heightSegments, + depthSegments, + }); } } diff --git a/packages/g6-extension-3d/src/elements/cylinder.ts b/packages/g6-extension-3d/src/elements/cylinder.ts index 2249d6f8cb6..6cdaa4d9ac3 100644 --- a/packages/g6-extension-3d/src/elements/cylinder.ts +++ b/packages/g6-extension-3d/src/elements/cylinder.ts @@ -2,6 +2,7 @@ import type { DisplayObjectConfig } from '@antv/g'; import type { CylinderGeometryProps, ProceduralGeometry as GGeometry } from '@antv/g-plugin-3d'; import { CylinderGeometry } from '@antv/g-plugin-3d'; import { deepMix } from '@antv/util'; +import { createGeometry } from '../utils/geometry'; import type { BaseNode3DStyleProps } from './base-node-3d'; import { BaseNode3D } from './base-node-3d'; @@ -22,6 +23,6 @@ export class Cylinder extends BaseNode3D { protected getGeometry(attributes: Required): GGeometry | undefined { const size = this.getSize(); const { radius = size[0] / 2, height = size[1], heightSegments, capSegments } = attributes; - return new CylinderGeometry(this.device, { radius, height, heightSegments, capSegments }); + return createGeometry('cylinder', this.device, CylinderGeometry, { radius, height, heightSegments, capSegments }); } } diff --git a/packages/g6-extension-3d/src/elements/plane.ts b/packages/g6-extension-3d/src/elements/plane.ts index 23718acad9b..869014366eb 100644 --- a/packages/g6-extension-3d/src/elements/plane.ts +++ b/packages/g6-extension-3d/src/elements/plane.ts @@ -2,6 +2,7 @@ import type { DisplayObjectConfig } from '@antv/g'; import type { ProceduralGeometry as GGeometry, PlaneGeometryProps } from '@antv/g-plugin-3d'; import { CullMode, PlaneGeometry } from '@antv/g-plugin-3d'; import { deepMix } from '@antv/util'; +import { createGeometry } from '../utils/geometry'; import type { BaseNode3DStyleProps } from './base-node-3d'; import { BaseNode3D } from './base-node-3d'; @@ -19,6 +20,6 @@ export class Plane extends BaseNode3D { protected getGeometry(attributes: Required): GGeometry | undefined { const size = this.getSize(); const { width = size[0], depth = size[1], widthSegments, depthSegments } = attributes; - return new PlaneGeometry(this.device, { width, depth, widthSegments, depthSegments }); + return createGeometry('plane', this.device, PlaneGeometry, { width, depth, widthSegments, depthSegments }); } } diff --git a/packages/g6-extension-3d/src/elements/sphere.ts b/packages/g6-extension-3d/src/elements/sphere.ts index c86b668d720..83483523b9b 100644 --- a/packages/g6-extension-3d/src/elements/sphere.ts +++ b/packages/g6-extension-3d/src/elements/sphere.ts @@ -2,6 +2,7 @@ import type { DisplayObjectConfig } from '@antv/g'; import type { ProceduralGeometry as GGeometry, SphereGeometryProps } from '@antv/g-plugin-3d'; import { SphereGeometry } from '@antv/g-plugin-3d'; import { deepMix } from '@antv/util'; +import { createGeometry } from '../utils/geometry'; import type { BaseNode3DStyleProps } from './base-node-3d'; import { BaseNode3D } from './base-node-3d'; @@ -22,6 +23,6 @@ export class Sphere extends BaseNode3D { protected getGeometry(attributes: Required): GGeometry | undefined { const size = this.getSize(); const { radius = size[0] / 2, latitudeBands, longitudeBands } = attributes; - return new SphereGeometry(this.device, { radius, latitudeBands, longitudeBands }); + return createGeometry('sphere', this.device, SphereGeometry, { radius, latitudeBands, longitudeBands }); } } diff --git a/packages/g6-extension-3d/src/elements/torus.ts b/packages/g6-extension-3d/src/elements/torus.ts index 672313d5dbb..fed5ddbd530 100644 --- a/packages/g6-extension-3d/src/elements/torus.ts +++ b/packages/g6-extension-3d/src/elements/torus.ts @@ -2,6 +2,7 @@ import type { DisplayObjectConfig } from '@antv/g'; import type { ProceduralGeometry as GGeometry, TorusGeometryProps } from '@antv/g-plugin-3d'; import { TorusGeometry } from '@antv/g-plugin-3d'; import { deepMix } from '@antv/util'; +import { createGeometry } from '../utils/geometry'; import type { BaseNode3DStyleProps } from './base-node-3d'; import { BaseNode3D } from './base-node-3d'; @@ -22,6 +23,6 @@ export class Torus extends BaseNode3D { protected getGeometry(attributes: Required): GGeometry | undefined { const size = this.getSize(); const { tubeRadius = size[0] / 2, ringRadius = size[1] / 2, segments, sides } = attributes; - return new TorusGeometry(this.device, { tubeRadius, ringRadius, segments, sides }); + return createGeometry('torus', this.device, TorusGeometry, { tubeRadius, ringRadius, segments, sides }); } } diff --git a/packages/g6-extension-3d/src/utils/geometry.ts b/packages/g6-extension-3d/src/utils/geometry.ts new file mode 100644 index 00000000000..f6b60720956 --- /dev/null +++ b/packages/g6-extension-3d/src/utils/geometry.ts @@ -0,0 +1,39 @@ +import type { Device } from '@antv/g-device-api'; +import type { ProceduralGeometry } from '@antv/g-plugin-3d'; + +const GEOMETRY_CACHE = new Map(); + +/** + * 创建几何体 + * + * Create geometry + * @param type - 几何体类型 geometry type + * @param device - 设备对象 device object + * @param Ctor - 几何体构造函数 geometry constructor + * @param style - 几何体样式 geometry style + * @returns 几何体对象 geometry object + */ +export function createGeometry>( + type: string, + device: Device, + Ctor: new (...args: any[]) => T, + style: Record, +) { + const cacheKey = + type + + '|' + + Object.entries(style) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([k, v]) => `${k}:${v}`) + .join(','); + + if (GEOMETRY_CACHE.has(cacheKey)) { + return GEOMETRY_CACHE.get(cacheKey) as T; + } + + const geometry = new Ctor(device, style); + + GEOMETRY_CACHE.set(cacheKey, geometry); + + return geometry; +} diff --git a/packages/g6/__tests__/demos/index.ts b/packages/g6/__tests__/demos/index.ts index e9f0ef6473f..c756889a244 100644 --- a/packages/g6/__tests__/demos/index.ts +++ b/packages/g6/__tests__/demos/index.ts @@ -93,6 +93,7 @@ export { layoutRadialConfigurationTranslate } from './layout-radial-configuratio export { layoutRadialPreventOverlap } from './layout-radial-prevent-overlap'; export { layoutRadialPreventOverlapUnstrict } from './layout-radial-prevent-overlap-unstrict'; export { layoutRadialSort } from './layout-radial-sort'; +export { perf20000Elements } from './perf-20000-elements'; export { perfFCP } from './perf-fcp'; export { pluginBackground } from './plugin-background'; export { pluginBubbleSets } from './plugin-bubble-sets'; diff --git a/packages/g6/__tests__/demos/perf-20000-elements.ts b/packages/g6/__tests__/demos/perf-20000-elements.ts new file mode 100644 index 00000000000..2ba26b4612c --- /dev/null +++ b/packages/g6/__tests__/demos/perf-20000-elements.ts @@ -0,0 +1,86 @@ +import type { GraphOptions } from '@/src'; +import { Graph } from '@/src'; + +export const perf20000Elements: TestCase = async (context) => { + const data = await fetch('https://assets.antv.antgroup.com/g6/20000.json').then((res) => res.json()); + + const graph = new Graph({ + ...context, + animation: false, + data, + node: { + style: { + size: 8, + }, + palette: { + type: 'group', + field: 'cluster', + }, + }, + behaviors: ['zoom-canvas', 'drag-canvas', 'drag-element'], + autoFit: 'view', + plugins: [{ type: 'background', background: '#fff' }], + }); + + console.time('time'); + await graph.draw(); + console.timeEnd('time'); + + perf20000Elements.form = (gui) => { + const themes: Record = { + '🌞 Light': { + theme: 'light', + node: { + palette: { + type: 'group', + field: 'cluster', + }, + }, + plugins: [{ type: 'background', background: '#fff' }], + }, + '🌚 Dark': { + theme: 'dark', + node: { + palette: { + type: 'group', + field: 'cluster', + }, + }, + plugins: [{ type: 'background', background: '#000' }], + }, + '🌎 Blue': { + theme: 'light', + node: { + palette: { + type: 'group', + field: 'cluster', + color: 'blues', + invert: true, + }, + }, + plugins: [{ type: 'background', background: '#f3faff' }], + }, + '🌕 Yellow': { + background: '#fcf9f1', + theme: 'light', + node: { + palette: { + type: 'group', + field: 'cluster', + color: ['#ffe7ba', '#ffd591', '#ffc069', '#ffa940', '#fa8c16', '#d46b08', '#ad4e00', '#873800', '#612500'], + }, + }, + plugins: [{ type: 'background', background: '#fcf9f1' }], + }, + }; + + return [ + gui.add({ theme: '🌞 Light' }, 'theme', Object.keys(themes)).onChange((theme: string) => { + graph.setOptions(themes[theme]); + graph.draw(); + }), + ]; + }; + + return graph; +}; diff --git a/packages/g6/__tests__/demos/perf-fcp.ts b/packages/g6/__tests__/demos/perf-fcp.ts index 9881f2067b2..39faa9902ae 100644 --- a/packages/g6/__tests__/demos/perf-fcp.ts +++ b/packages/g6/__tests__/demos/perf-fcp.ts @@ -7,6 +7,7 @@ export const perfFCP: TestCase = async (context) => { const graph = new Graph({ ...context, + animation: false, data, node: { type: 'circle', // 👈🏻 Node shape type. diff --git a/packages/g6/src/elements/edges/base-edge.ts b/packages/g6/src/elements/edges/base-edge.ts index 2bedee07f42..044e5827261 100644 --- a/packages/g6/src/elements/edges/base-edge.ts +++ b/packages/g6/src/elements/edges/base-edge.ts @@ -8,7 +8,7 @@ import type { } from '@antv/g'; import { Image, Path } from '@antv/g'; import type { PathArray } from '@antv/util'; -import { isEmpty, isFunction } from '@antv/util'; +import { isFunction } from '@antv/util'; import type { BaseElementStyleProps, EdgeArrowStyleProps, @@ -272,7 +272,7 @@ export abstract class BaseEdge extends BaseElement { } protected getLabelStyle(attributes: ParsedBaseEdgeStyleProps): false | LabelStyleProps { - if (attributes.label === false || isEmpty(attributes.labelText)) return false; + if (attributes.label === false || !attributes.labelText) return false; const labelStyle = subStyleProps>(this.getGraphicStyle(attributes), 'label'); const { placement, offsetX, offsetY, autoRotate, maxWidth, ...restStyle } = labelStyle; @@ -299,7 +299,7 @@ export abstract class BaseEdge extends BaseElement { if (enable) { const arrowStyle = this.getArrowStyle(attributes, isStart); - const Ctor = !isEmpty(arrowStyle.src) ? Image : Path; + const Ctor = arrowStyle.src ? Image : Path; const [marker, markerOffset, arrowOffset] = isStart ? (['markerStart', 'markerStartOffset', 'startArrowOffset'] as const) : (['markerEnd', 'markerEndOffset', 'endArrowOffset'] as const); diff --git a/packages/g6/src/elements/nodes/base-node.ts b/packages/g6/src/elements/nodes/base-node.ts index 601be3b472c..08ff48905c1 100644 --- a/packages/g6/src/elements/nodes/base-node.ts +++ b/packages/g6/src/elements/nodes/base-node.ts @@ -1,6 +1,5 @@ import type { BaseStyleProps, DisplayObject, DisplayObjectConfig, Group } from '@antv/g'; import { Circle as GCircle } from '@antv/g'; -import { isEmpty } from '@antv/util'; import type { CategoricalPalette } from '../../palettes/types'; import type { NodeData } from '../../spec'; import type { @@ -234,7 +233,7 @@ export abstract class BaseNode): false | LabelStyleProps { - if (attributes.label === false || isEmpty(attributes.labelText)) return false; + if (attributes.label === false || !attributes.labelText) return false; const { placement, maxWidth, ...labelStyle } = subStyleProps>( this.getGraphicStyle(attributes), @@ -260,7 +259,7 @@ export abstract class BaseNode): false | IconStyleProps { - if (attributes.icon === false || isEmpty(attributes.iconText || attributes.iconSrc)) return false; + if (attributes.icon === false || (!attributes.iconText && !attributes.iconSrc)) return false; const iconStyle = subStyleProps(this.getGraphicStyle(attributes), 'icon'); const keyShape = this.getKey(); @@ -276,7 +275,7 @@ export abstract class BaseNode { badgesShapeStyle[key] = false; }); - if (attributes.badge === false || isEmpty(attributes.badges)) return badgesShapeStyle; + if (attributes.badge === false || !attributes.badges?.length) return badgesShapeStyle; const { badges: badgeOptions = [], badgePalette, opacity = 1, ...restAttributes } = attributes; const colors = getPaletteColors(badgePalette); @@ -309,7 +308,7 @@ export abstract class BaseNode(this.getGraphicStyle(attributes), 'port'); const { ports: portOptions = [] } = attributes; diff --git a/packages/g6/src/plugins/background/index.ts b/packages/g6/src/plugins/background/index.ts index 3132c2294a6..c74235e52c1 100644 --- a/packages/g6/src/plugins/background/index.ts +++ b/packages/g6/src/plugins/background/index.ts @@ -24,7 +24,6 @@ export class Background extends BasePlugin { static defaultOptions: Partial = { transition: 'background 0.5s', backgroundSize: 'cover', - opacity: '0.35', }; private $element: HTMLElement = createPluginContainer('background'); @@ -33,7 +32,7 @@ export class Background extends BasePlugin { super(context, Object.assign({}, Background.defaultOptions, options)); const $container = this.context.canvas.getContainer(); - $container!.appendChild(this.$element); + $container!.prepend(this.$element); this.update(options); } diff --git a/packages/g6/src/runtime/element.ts b/packages/g6/src/runtime/element.ts index fd8a031a3d1..175457720d6 100644 --- a/packages/g6/src/runtime/element.ts +++ b/packages/g6/src/runtime/element.ts @@ -2,7 +2,7 @@ /* eslint-disable jsdoc/require-param */ import type { BaseStyleProps } from '@antv/g'; import { Group } from '@antv/g'; -import { groupBy, isEmpty, isString } from '@antv/util'; +import { groupBy } from '@antv/util'; import { AnimationType, COMBO_KEY, ChangeType, GraphEvent } from '../constants'; import { ELEMENT_TYPES } from '../constants/element'; import { getExtension } from '../registry'; @@ -80,9 +80,11 @@ export class ElementController { const userDefinedType = options[elementType]?.type || datum.type; if (!userDefinedType) { - return { node: 'circle', edge: 'line', combo: 'circle' }[elementType]; + if (elementType === 'edge') return 'line'; + // node / combo + else return 'circle'; } - if (isString(userDefinedType)) return userDefinedType; + if (typeof userDefinedType === 'string') return userDefinedType; return userDefinedType(datum as any); } @@ -112,7 +114,7 @@ export class ElementController { parsePalette(this.getTheme(elementType)?.palette), parsePalette(options[elementType]?.palette), ); - if (!isEmpty(palette) && palette?.field) { + if (palette?.field) { Object.assign(this.paletteStyle, assignColorByPalette(elementData, palette)); } }); @@ -453,7 +455,6 @@ export class ElementController { if (exactStage === 'show') updateStyle(element, { visibility: 'visible' }); } }, - afterAnimate: () => {}, after: () => { if (stage === 'collapse') updateStyle(element, style); if (exactStage === 'hide') updateStyle(element, { visibility: getCachedStyle(element, 'visibility') }); diff --git a/packages/g6/src/runtime/graph.ts b/packages/g6/src/runtime/graph.ts index 83c60b955d8..57fe90d1639 100644 --- a/packages/g6/src/runtime/graph.ts +++ b/packages/g6/src/runtime/graph.ts @@ -872,6 +872,7 @@ export class Graph extends EventEmitter { public async draw(): Promise { await this.prepare(); await this.context.element!.draw()?.finished; + await this.autoFit(); } /** diff --git a/packages/g6/src/utils/palette.ts b/packages/g6/src/utils/palette.ts index 52d0f7e1bd3..18b6c550395 100644 --- a/packages/g6/src/utils/palette.ts +++ b/packages/g6/src/utils/palette.ts @@ -1,4 +1,4 @@ -import { groupBy, isFunction, isNumber, isString } from '@antv/util'; +import { groupBy } from '@antv/util'; import type { CategoricalPalette } from '../palettes/types'; import { getExtension } from '../registry'; import type { PaletteOptions, STDPaletteOptions } from '../spec/element/palette'; @@ -54,21 +54,29 @@ export function assignColorByPalette(data: ElementData, palette?: STDPaletteOpti const { type, color: colorPalette, field, invert } = palette; const assignColor = (args: [ID, number][]): Record => { - const palette = isString(colorPalette) ? getExtension('palette', colorPalette) : colorPalette; + const palette = typeof colorPalette === 'string' ? getExtension('palette', colorPalette) : colorPalette; - if (isFunction(palette)) { + if (typeof palette === 'function') { // assign by continuous - return Object.fromEntries(args.map(([groupKey, value]) => [groupKey, palette(invert ? 1 - value : value)])); + const result: Record = {}; + args.forEach(([id, value]) => { + result[id] = palette(invert ? 1 - value : value); + }); + return result; } else if (Array.isArray(palette)) { // assign by discrete const colors = invert ? [...palette].reverse() : palette; - return Object.fromEntries(args.map(([id, index]) => [id, colors[index % palette.length]])); + const result: Record = {}; + args.forEach(([id, index]) => { + result[id] = colors[index % palette.length]; + }); + return result; } return {}; }; const parseField = (field: STDPaletteOptions['field'], datum: ElementDatum) => - isString(field) ? datum.data?.[field] : field?.(datum); + typeof field === 'string' ? datum.data?.[field] : field?.(datum); if (type === 'group') { const groupData = groupBy(data, (datum) => { @@ -91,7 +99,7 @@ export function assignColorByPalette(data: ElementData, palette?: STDPaletteOpti const [min, max] = data.reduce( ([min, max], datum) => { const value = parseField(field, datum); - if (!isNumber(value)) throw new Error(`Palette field ${field} is not a number`); + if (typeof value !== 'number') throw new Error(`Palette field ${field} is not a number`); return [Math.min(min, value), Math.max(max, value)]; }, [Infinity, -Infinity], @@ -112,7 +120,7 @@ export function assignColorByPalette(data: ElementData, palette?: STDPaletteOpti * @returns 色板上具体颜色 | Specific color on the palette */ export function getPaletteColors(colorPalette?: string | CategoricalPalette): CategoricalPalette | undefined { - const palette = isString(colorPalette) ? getExtension('palette', colorPalette) : colorPalette; - if (isFunction(palette)) return undefined; + const palette = typeof colorPalette === 'string' ? getExtension('palette', colorPalette) : colorPalette; + if (typeof palette === 'function') return undefined; return palette; } diff --git a/packages/g6/src/utils/prefix.ts b/packages/g6/src/utils/prefix.ts index 033e69899e2..f3d72c0caa9 100644 --- a/packages/g6/src/utils/prefix.ts +++ b/packages/g6/src/utils/prefix.ts @@ -1,4 +1,4 @@ -import { isString, lowerFirst, upperFirst } from '@antv/util'; +import { lowerFirst, upperFirst } from '@antv/util'; import type { ReplacePrefix } from '../types'; /** @@ -106,14 +106,15 @@ export function subObject(obj: Record, prefix: string): Record 子样式前缀 | sub style prefix * @returns 排除子样式后的样式 | style without sub style */ -export function omitStyleProps(style: object, prefix: string | string[]) { - const prefixArray = isString(prefix) ? [prefix] : prefix; - return Object.entries(style).reduce((acc, [key, value]) => { +export function omitStyleProps>(style: Record, prefix: string | string[]) { + const prefixArray = typeof prefix === 'string' ? [prefix] : prefix; + const omitStyle: Record = {}; + Object.keys(style).forEach((key) => { if (!prefixArray.find((p) => key.startsWith(p))) { - acc[key as keyof T] = value; + omitStyle[key] = style[key]; } - return acc; - }, {} as T); + }); + return omitStyle as T; } /** diff --git a/packages/g6/src/utils/shape.ts b/packages/g6/src/utils/shape.ts index 713fd781f48..86ffc997427 100644 --- a/packages/g6/src/utils/shape.ts +++ b/packages/g6/src/utils/shape.ts @@ -12,7 +12,7 @@ export function getDescendantShapes(shape: T) { // 遍历所有子元素,并将子元素的子元素加入到数组中 const traverse = (shape: DisplayObject) => { - if (shape.children && shape.children.length) { + if (shape?.children.length) { (shape.children as DisplayObject[]).forEach((child) => { succeeds.push(child); traverse(child); diff --git a/packages/g6/src/utils/size.ts b/packages/g6/src/utils/size.ts index 92401c5600b..68896b54ea8 100644 --- a/packages/g6/src/utils/size.ts +++ b/packages/g6/src/utils/size.ts @@ -1,4 +1,3 @@ -import { isNumber } from '@antv/util'; import type { STDSize, Size } from '../types'; /** @@ -7,7 +6,7 @@ import type { STDSize, Size } from '../types'; * @returns 标准尺寸格式 | standard size format */ export function parseSize(size: Size = 0): STDSize { - if (isNumber(size)) return Array(3).fill(size) as STDSize; + if (typeof size === 'number') return [size, size, size] as STDSize; const [x, y = x, z = x] = size; return [x, y, z]; } diff --git a/packages/g6/src/utils/style.ts b/packages/g6/src/utils/style.ts index e5d6a8cd287..d16cd0edd20 100644 --- a/packages/g6/src/utils/style.ts +++ b/packages/g6/src/utils/style.ts @@ -42,15 +42,14 @@ export function zIndexOf(datum: ElementDatum) { * 合并图形配置项 * * Merge graphic configuration - * @param options - 图形配置项数组 | graphic configuration array + * @param opt1 - 配置项1 | configuration 1 + * @param opt2 - 配置项2 | configuration 2 * @returns 合并后的配置项 | merged configuration */ -export function mergeOptions(...options: DisplayObjectConfig[]): DisplayObjectConfig { - const option = { style: {} }; - for (const opt of options) { - const { style, ..._ } = opt; - Object.assign(option.style, style); - Object.assign(option, _); - } - return option; +export function mergeOptions(opt1: DisplayObjectConfig, opt2: DisplayObjectConfig): DisplayObjectConfig { + const s1 = opt1?.style || {}; + const s2 = opt2?.style || {}; + return Object.assign({}, opt1, opt2, { + style: Object.assign({}, s1, s2), + }); }