diff --git a/packages/core/src/graph/controller/view.ts b/packages/core/src/graph/controller/view.ts index c5f914b52ac..10002529404 100644 --- a/packages/core/src/graph/controller/view.ts +++ b/packages/core/src/graph/controller/view.ts @@ -501,6 +501,7 @@ export default class ViewController { plugin.positionInit(); } }); + graph.emit('canvas:changesize', { width, height }); } public destroy() { diff --git a/packages/pc/src/index.ts b/packages/pc/src/index.ts index 9b83d922a19..1be6f3d5c8a 100644 --- a/packages/pc/src/index.ts +++ b/packages/pc/src/index.ts @@ -35,6 +35,7 @@ const EdgeFilterLens = Plugin.EdgeFilterLens; const SnapLine = Plugin.SnapLine; const Legend = Plugin.Legend; const Annotation = Plugin.Annotation; +const Ruler = Plugin.Ruler; export * from '@antv/g6-core'; export * from './types'; @@ -62,6 +63,7 @@ export { SnapLine, Legend, Annotation, + Ruler, Arrow, Marker, Shape, @@ -91,6 +93,7 @@ export default { ToolBar: Plugin.ToolBar, Tooltip: Plugin.Tooltip, Legend: Plugin.Legend, + Ruler: Plugin.Ruler, TimeBar, SnapLine, Fisheye, diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index b2763f6aa20..b87fd3eba57 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -12,6 +12,7 @@ import SnapLine from './snapline'; import PluginBase from './base'; import Legend from './legend'; import Annotation from './annotation'; +import Ruler from './ruler'; export { PluginBase, @@ -27,7 +28,8 @@ export { EdgeFilterLens, SnapLine, Legend, - Annotation + Annotation, + Ruler }; const Plugin = { @@ -44,7 +46,8 @@ const Plugin = { EdgeFilterLens, SnapLine, Legend, - Annotation + Annotation, + Ruler }; export default Plugin; diff --git a/packages/plugin/src/ruler/constructor.ts b/packages/plugin/src/ruler/constructor.ts new file mode 100644 index 00000000000..d2208dd8a3c --- /dev/null +++ b/packages/plugin/src/ruler/constructor.ts @@ -0,0 +1,163 @@ +export enum RuleDirection { HORIZONTAL = 'horizontal', VERTICAL = 'vertical' } + +export interface PointConfig { + lineWidth: number; + lineHeight: number; + strokeStyle: CanvasRenderingContext2D['strokeStyle'] + font: CanvasRenderingContext2D['font'] +} + +export interface RulerConfig { + width: number; + height: number; + scale: number; + unitInterval: number; + showTickLabel: boolean; + tickLabelStyle: CanvasRenderingContext2D['strokeStyle'], + background: CanvasRenderingContext2D['fillStyle'], + direction: RuleDirection, + container?: HTMLDivElement, + startNumber: number | string +} + +export default class RulerConstructor { + + private canvas: HTMLCanvasElement = document.createElement('canvas') + + /** canvas 宽度 */ + public width: number = 0 + + /** canvas 高度 */ + private height: number = 0 + + /** 线宽度 */ + private lineWidth: number = 0.5 + + /** 线高度 */ + private lineHeight: number = 10 + + /** 单位区间 */ + private unitInterval: number = 10 + + /** 是否显示刻度值 */ + private showTickLabel: boolean = true + + /** 方向 */ + public direction: RuleDirection = RuleDirection.HORIZONTAL + + /** 当前缩放大小 */ + private scale = 1 + + /** 刻度值的颜色 */ + private tickLabelStyle: CanvasRenderingContext2D['strokeStyle'] = '#333333' + + /** 刻度的颜色 */ + private strokeStyle: CanvasRenderingContext2D['strokeStyle'] = '#b8b7b8' + + /** 文本 */ + private font: CanvasRenderingContext2D['font'] = '10px sans-serif' + + /** canvas背景 */ + private background: CanvasRenderingContext2D['fillStyle'] = '#ffffff' + + /** 从什么数字开始计算刻度的值 */ + private startNumber: number = 0 + + /** 包裹这个ruler canvas的容器是啥 */ + public container?: HTMLElement + + constructor(config) { + this.initConfig(config) + this.init() + } + + public getCanvas() { + return this.canvas + } + + public init() { + this.initBrush() + } + + public initConfig(config) { + Object.keys(config).forEach(key => { + this[key] = config[key] + }) + this.canvas.width = this.width + this.canvas.height = this.height + } + + public changeConfig(config) { + this.initConfig(config) + this.initBrush() + } + + private initBrush() { + const ruleCanvas = this.canvas + const context = ruleCanvas.getContext('2d') + context.strokeStyle = this.strokeStyle + context.font = this.font + context.lineWidth = this.lineWidth + this.drawPointsAndLine() + } + + private drawPointsAndLine() { + const ruleCanvas = this.canvas + let unitInterval = Math.round(this.unitInterval / this.scale) + // fix: 当计算少于1, 就取4位 + if (unitInterval < 1) { + unitInterval = +(this.unitInterval / this.scale).toFixed(4) + } + const showTickLabel = this.showTickLabel + const lineWidth = this.lineWidth + const lineHeight = this.lineHeight + const width = this.width + const height = this.height + const startNumber = this.startNumber + const scaleCount = Math.round((width / this.scale) / unitInterval); + // lineWidth / 2是为了定义笔的起始位置, 防止单数宽度过宽的问题 + const m = lineWidth / 2 + const context = ruleCanvas.getContext('2d') + context.clearRect(0, 0, width, height); + context.beginPath(); + context.fillStyle = this.background; + context.fillRect(0, 0, width, height) + context.fillStyle = this.tickLabelStyle + for (let i = 0; i <= scaleCount; i++) { + const step = Math.round(i * unitInterval) + /* 竖向的时候, 因为旋转的原因, 以横向来想, 从右边开始绘制0, 左边为最大的数字 */ + let pos = this.direction === RuleDirection.HORIZONTAL ? step * this.scale : width - step * this.scale + if (pos < 0) { pos = 0 } + /* 当间隔 * 10 显示文本, 考虑增加配置 */ + if (i % 10 === 0) { + // xPos防止最后一个线不显示 + const x = pos + m + const xPos = x >= width ? width - m : x + context.moveTo(xPos, 0); + if (showTickLabel) { + const text = `${startNumber + step}` + let x = pos + lineWidth + 2 + // 文本不固定, 需要计算占用的大小 + const textWidth = context.measureText(text).width + // 对竖向0的文本,显示在最右边的偏左位置 + if (this.direction === 'vertical' && !step) { + x = width - textWidth - lineWidth - 2 + } else if (this.direction === RuleDirection.HORIZONTAL && (pos + textWidth) >= width) { + // 横向最后一个数字同理 + x = width - textWidth - lineWidth - 2 + } + context.fillText(text, x, 10) + } + context.lineTo(xPos, height - lineWidth); + } else { + context.moveTo(pos + m, height - lineHeight - lineWidth); + // 需要减去底部线的高度 + context.lineTo(pos + m, height - lineWidth); + } + } + // 底部的线, 对于上面的线, 不做绘制, 当画布沾满全屏, 标尺在左上, 非全屏, 在包裹容器外添加 同宽度 左、上 边框即可 + context.moveTo(0, height - m) + context.lineTo(width, height - m) + context.stroke(); + } +} \ No newline at end of file diff --git a/packages/plugin/src/ruler/index.ts b/packages/plugin/src/ruler/index.ts new file mode 100644 index 00000000000..b742932cd5b --- /dev/null +++ b/packages/plugin/src/ruler/index.ts @@ -0,0 +1,473 @@ +import { modifyCSS, createDom } from '@antv/dom-util'; +import { ICanvas } from '@antv/g-base'; +import { IAbstractGraph as IGraph } from '@antv/g6-core'; +import Base from '../base'; +import RulerConstructor, { PointConfig, RulerConfig, RuleDirection } from './constructor'; + +export interface RulerSettings extends PointConfig, RulerConfig { + directions: RuleDirection[] | RuleDirection, // 方向, 默认['horizontal', 'vertical'] + startLen: number // 开始长度 / 距离 + showLock: boolean // 是否显示锁 + lockColor: string // 锁颜色 + lock: boolean // 当前锁住缩放 和 拖放 + visible: boolean // 是否可见 + monitorZoom: boolean // 监听缩放改变刻度 + monitorSize: boolean // 监听大小改变刻度 + monitorViewPort: boolean // 监听视口改变刻度 +} + +interface EventFnObj { + [key: string]: (e: any) => void +} + +export default class Ruler extends Base { + + /** 锁的容器DOM */ + private lockContainer: HTMLElement + + /** 标尺的包裹DOM */ + private rulerWrap: HTMLElement + + /** 所有标尺的实例 */ + public rulerInstances: RulerConstructor[] = [] + + /** 缓存行为的是否执行函数 */ + private shouldBeginFnObj: Object = {} + + /** 锁定会控制哪些行为 */ + private lockBehaviorKey: Array = ['zoom-canvas', 'drag-canvas', 'scroll-canvas'] + + /** 所有绑定的事件函数, 改变this */ + private eventFnObj: EventFnObj = {} + + constructor(config?: RulerSettings) { + super(config); + } + + public getDefaultCfgs(): RulerSettings { + return { + lineWidth: 1, + lineHeight: 10, + startLen: 0, + scale: 1, + width: 0, + height: 25, + unitInterval: 10, + showTickLabel: true, + lock: false, + tickLabelStyle: '#333333', + strokeStyle: '#b8b7b8', + font: '10px sans-serif', + directions: [RuleDirection.HORIZONTAL, RuleDirection.VERTICAL], + showLock: true, + lockColor: '#7F7F7F', + visible: true, + monitorZoom: true, + monitorSize: true, + monitorViewPort: true, + background: '#ffffff', + startNumber: 0, + direction: RuleDirection.HORIZONTAL + }; + } + + public init() { + const graph: IGraph = this.get('graph'); + const graphContainer = graph.get('container'); + const canvas: HTMLCanvasElement = graph.get('canvas').get('el'); + // 高度默认25, 竖向默认取canvas的高度 + const height = this.get('height') || graphContainer.clientHeight + const lineWidth = this.get('lineWidth') + const showLock = this.get('showLock') + const monitorSize = this.get('monitorSize') + const monitorZoom = this.get('monitorZoom') + const monitorViewPort = this.get('monitorViewPort') + + this.set('scale', graph.getZoom()) + + let startLen = this.get('startLen') + let directions = this.get('directions') + + let rulerHeight = height + + this.rulerWrap = createDom( + `
`, + ); + if (directions) { + if (typeof directions === 'string') { + directions = [directions] + } else { + // 当有2个标尺的时候, 需要取最大的值作为尺子的高度, 开始长度也是 + rulerHeight = Math.max(height, startLen) + startLen = rulerHeight + this.set('startLen', startLen) + } + directions.forEach(direction => { + const rulerMaxWidth = this.getRulerMaxWidth(direction) + // 竖向需要增加线的宽度, 跟横向的第一条线对齐 + if (direction === RuleDirection.VERTICAL) { + rulerHeight += lineWidth + } + const container = createDom( + `
`, + ); + + this.rulerWrap.append(container) + const startNumber = this.getStartNumber(direction) + const rulerInstance = new RulerConstructor({ + ...this._cfgs, + width: rulerMaxWidth, + height: rulerHeight, + direction, + container, + startNumber + }) + this.rulerInstances.push(rulerInstance) + const ruleCanvas = rulerInstance.getCanvas() + if (direction === RuleDirection.HORIZONTAL) { + modifyCSS(container, { + left: `${startLen}px`, + top: 0, + width: `${rulerMaxWidth}px`, + }); + } else { + modifyCSS(container, { + // 上面高度增加了, 偏移相应增加 + left: directions.length > 1 ? `${startLen + lineWidth}px` : `${height}px`, + // 跟横向底部线重叠 + top: directions.length > 1 ? `${startLen - lineWidth}px` : `${startLen}px`, + width: `${rulerMaxWidth}px`, + transform: `rotate(${90}deg)`, + 'transform-origin': `0 0` + }); + modifyCSS(ruleCanvas, { + transform: 'rotate(180deg)', + 'transform-origin': `center center`, + }); + } + + container.appendChild(ruleCanvas) + }) + } + + if (startLen && showLock) { + this.createLockDom() + } + this.moveCanvasAndChangeSize() + if (monitorSize) { + this.eventFnObj['canvas:changesize'] = this.bindChangeSizeFn.bind(this) + graph.on('canvas:changesize', this.eventFnObj['canvas:changesize']) + } + + if (monitorZoom) { + this.eventFnObj.wheelzoom = this.bindChangeScaleFn.bind(this) + graph.on('wheelzoom', this.eventFnObj.wheelzoom) + } + + if (monitorViewPort) { + this.eventFnObj.viewportchange = this.bindViewPortFn.bind(this) + graph.on('viewportchange', this.eventFnObj.viewportchange) + } + + this.set('container', this.rulerWrap); + graphContainer.insertBefore(this.rulerWrap, canvas) + } + + private getStartNumber(direction: RuleDirection) { + const graph: IGraph = this.get('graph'); + const scale = this.get('scale') + const canvas: HTMLCanvasElement = graph.get('canvas').get('el'); + const maxWidth = direction === RuleDirection.HORIZONTAL ? canvas.width : canvas.height + const directionKey = direction === RuleDirection.HORIZONTAL ? 'x' : 'y' + const centerLoc = this.get('graph').getViewPortCenterPoint() + const startNumber = centerLoc[directionKey] - (maxWidth / scale) / 2 + return Math.round(startNumber) + } + + private getRulerMaxWidth(direction) { + const graph: IGraph = this.get('graph'); + const graphContainer = graph.get('container'); + const overflow = graphContainer.style.overflow + graphContainer.style.overflow = 'hidden' + const canvas: HTMLCanvasElement = graph.get('canvas').get('el'); + const startLen = this.get('startLen') + const width = graphContainer.clientWidth - startLen + const lineWidth = this.get('lineWidth') + let rulerMaxWidth = canvas.clientWidth < width ? canvas.clientWidth : width + if (direction === RuleDirection.VERTICAL) { + // 增加top的部分 + const vWidth = graphContainer.clientHeight - startLen + lineWidth + rulerMaxWidth = canvas.clientHeight < vWidth ? canvas.clientHeight : vWidth + } + graphContainer.style.overflow = overflow + return rulerMaxWidth + } + + private moveCanvasAndChangeSize(reset = false) { + const graph: IGraph = this.get('graph') + const graphContainer = graph.get('container'); + const gridContainer: HTMLDivElement = graphContainer.querySelector('.g6-grid-container') + const canvas: HTMLCanvasElement = graph.get('canvas').get('el'); + const lineWidth = this.get('lineWidth') + const startLen = this.get('startLen') + const rulerHeight = this.get('height') || graphContainer.clientHeight + const maxStartLen = Math.max(startLen, rulerHeight) + + let left = maxStartLen + lineWidth + let top = maxStartLen + + let width = canvas.width + let height = canvas.height + + /** 兼容只有一个尺子的情况下 */ + if (this.rulerInstances.length && this.rulerInstances.length < 2) { + const direction = this.rulerInstances[0].direction + if (direction === RuleDirection.HORIZONTAL) { + left = 0 + } else { + top = 0 + } + } + + const maxWidth = width + left + const maxHeight = height + top + + if (reset) { + height = maxHeight + width = maxWidth + left = 0 + top = 0 + } else { + // 内容区能放下就不减少 + if (graphContainer.clientHeight <= maxHeight) { + height -= top + } + + if (graphContainer.clientWidth <= maxWidth) { + width -= left + } + } + + modifyCSS(canvas, { + 'margin-left': `${left}px`, + 'margin-top': `${top}px` + }) + modifyCSS(gridContainer, { + 'margin-left': `${left}px`, + 'margin-top': `${top}px` + }) + // 因为有偏移, 所以需要改变canvas的宽度, 以避免startLen的内容给覆盖 + graph.changeSize(width, height) + } + + private createLockDom() { + + const graph: IGraph = this.get('graph'); + const background = this.get('background') + const startLen = this.get('startLen') + let lock = this.get('lock') + const lineWidth = this.get('lineWidth') + const modeController = graph.get('modeController') + const lockFlagArr = [] + this.updateShouldBegin() + + // 初始锁定状态 + this.lockBehaviorKey.forEach(key => { + const behavior = modeController?.currentBehaves?.find(behavior => behavior.type === key); + if (behavior) { + if (behavior.shouldBegin) { + lockFlagArr.push(!behavior.shouldBegin()) + } else { + lockFlagArr.push(false) + } + } + }) + + lock = lockFlagArr.some(flag => flag) + this.set('lock', lock) + + this.lockContainer = createDom( + `
`, + ); + modifyCSS(this.lockContainer, { + // 高度需要减去竖向标尺与横向标尺重叠的线的宽度, 以防止背景重叠线 + height: `${startLen - lineWidth}px`, + width: `${startLen}px`, + left: 0, + top: 0, + position: 'absolute', + overflow: 'hidden', + display: 'flex', + 'align-items': 'center', + 'justify-content': 'center', + cursor: 'pointer', + background, + }) + + const lockColor = this.get('lockColor') + const LOCK_SVG_DOM = createDom(``) + const UNLOCK_SVG_DOM = createDom(``) + + // 3.1px -0.2px 是因为svg图标大小不一致 + modifyCSS(LOCK_SVG_DOM, { + display: !lock ? 'block' : 'none', + transform: `translate(-3.1px, -0.2px)`, + }) + + modifyCSS(UNLOCK_SVG_DOM, { + display: lock ? 'block' : 'none', + transform: `translate(-3.1px, -0.2px)`, + }) + + this.lockContainer.append(LOCK_SVG_DOM, UNLOCK_SVG_DOM) + this.rulerWrap.append(this.lockContainer) + // 缓存函数, 绑定this, 用于销毁能正确解除事件 + this.eventFnObj.lock = this.toggerLock.bind(this) + this.lockContainer.addEventListener('click', this.eventFnObj.lock) + } + + private resetRulerSize() { + const graph: IGraph = this.get('graph'); + const canvas: HTMLDivElement = graph.get('canvas').get('el'); + const height = this.get('height') || canvas.clientHeight + const startLen = this.get('startLen') + const lineWidth = this.get('lineWidth') + const maxHeight = Math.max(height, startLen) + this.rulerInstances.forEach((ruler: RulerConstructor) => { + const rulerMaxWidth = this.getRulerMaxWidth(ruler.direction) + const startNumber = this.getStartNumber(ruler.direction) + ruler.changeConfig({ + width: rulerMaxWidth, + height: ruler.direction === RuleDirection.HORIZONTAL ? maxHeight : maxHeight + lineWidth, + startNumber + }) + modifyCSS(ruler.container, { + width: `${rulerMaxWidth}px`, + }) + }) + } + + public updateShouldBegin() { + const graph: IGraph = this.get('graph') + const modeController = graph.get('modeController') + this.lockBehaviorKey.forEach(key => { + const behavior = modeController?.currentBehaves?.find(behavior => behavior.type === key) + if (behavior) { + if (behavior.shouldBegin) { + this.shouldBeginFnObj[key] = behavior.shouldBegin + } + } + }) + } + + // 改变尺子的配合, 配置改变就会重新绘制尺子, 不管改变什么, 例如改变颜色, 改变缩放 + public changeRulerConfig(config) { + this.rulerInstances.forEach(rulerInstance => { + rulerInstance.changeConfig(config) + }) + } + + private moveAndResetCanvasChangeFn(reset = false) { + const graph: IGraph = this.get('graph') + graph.off('canvas:changesize', this.eventFnObj['canvas:changesize']) + this.moveCanvasAndChangeSize(reset) + graph.on('canvas:changesize', this.eventFnObj['canvas:changesize']) + } + + private bindChangeSizeFn() { + this.moveAndResetCanvasChangeFn() + this.resetRulerSize() + } + + private bindChangeScaleFn() { + const graph: IGraph = this.get('graph') + const zoom = graph.getZoom(); + this.changeScaleSelf(+zoom.toFixed(2)) + } + + private bindViewPortFn() { + this.rulerInstances.forEach(rulerInstance => { + const startNumber = this.getStartNumber(rulerInstance.direction) + rulerInstance.changeConfig({ + startNumber + }) + }) + } + + public toggerLock() { + this.changeLock(!this.get('lock')) + } + + public changeLock(flag: boolean) { + const graph: IGraph = this.get('graph'); + const LOCK_SVG_DOM = this.lockContainer.querySelector('.lock-svg') as HTMLElement + const UNLOCK_SVG_DOM = this.lockContainer.querySelector('.unLock-svg') as HTMLElement + + if (flag) { + // 锁定缩放 + this.lockContainer.title = '解锁' + LOCK_SVG_DOM.style.display = 'none' + UNLOCK_SVG_DOM.style.display = 'block' + } else { + // 解锁缩放 + this.lockContainer.title = '锁定' + LOCK_SVG_DOM.style.display = 'block' + UNLOCK_SVG_DOM.style.display = 'none' + } + + this.lockBehaviorKey.forEach(key => { + graph.updateBehavior(key, { shouldBegin: flag ? () => false : this.shouldBeginFnObj[key] || (() => true) }) + }) + this.set('lock', flag) + } + + public changeVisible(flag: boolean) { + this.rulerWrap.style.display = flag ? 'flex' : 'none' + this.moveAndResetCanvasChangeFn(!flag) + this.set('visible', flag) + } + + public toggerVisible() { + const visible = this.get('visible') + this.changeVisible(!visible) + } + + private changeScaleSelf(scale: number) { + this.set('scale', scale) + this.rulerInstances.forEach(instance => { + const startNumber = this.getStartNumber(instance.direction) + instance.changeConfig({ scale, startNumber }) + }) + } + + public changeScale(scale: number) { + const graph: IGraph = this.get('graph'); + this.changeScaleSelf(scale) + // 主动触发需要同步改变canvas的大小 + graph.zoom(scale) + } + + public getContainer(): HTMLDivElement { + return this.get('container'); + } + + public destroy() { + const graph: IGraph = this.get('graph'); + const graphContainer = graph.get('container'); + this.moveAndResetCanvasChangeFn(true) + Object.keys(this.eventFnObj).forEach(key => { + if (key === 'lock') { + this.lockContainer.removeEventListener('click', this.eventFnObj.lock) + } + graph.off(key, this.eventFnObj[key]) + }) + graphContainer.removeChild(this.rulerWrap as HTMLElement) + this.lockBehaviorKey.forEach(key => { + graph.updateBehavior(key, { shouldBegin: this.shouldBeginFnObj[key] || (() => true) }) + }) + } +} \ No newline at end of file diff --git a/packages/plugin/tests/unit/ruler-spec.ts b/packages/plugin/tests/unit/ruler-spec.ts new file mode 100644 index 00000000000..2619f395e1f --- /dev/null +++ b/packages/plugin/tests/unit/ruler-spec.ts @@ -0,0 +1,48 @@ +import G6 from '@antv/g6'; +import Ruler from '../../src/ruler'; + +const div = document.createElement('div'); +div.id = 'ruler-spec'; +document.body.appendChild(div); + + +describe('ruler', () => { + const ruler = new Ruler(); + const graph = new G6.Graph({ + container: div, + width: 800, + height: 600, + modes: { + default: ['drag-canvas', 'zoom-canvas', 'drag-node'], + }, + plugins: [ruler], + }); + + graph.addItem('node', { x: 100, y: 100 }); + graph.addItem('node', { x: -100, y: -100 }); + + it('ruler', () => { + const container = ruler.getContainer(); + + expect(container).not.toBe(undefined); + expect(container.childNodes.length).toEqual(3); + + ruler.resetRulerSize(800) + expect(ruler.get("width")).toEqual(800); + + ruler.changeVisible(false) + expect(ruler.get("visible")).toEqual(false); + expect(container.style.display).toEqual('none'); + + ruler.changeLockZoom(false) + expect(ruler.get("lockZoom")).toEqual(false); + + }); + it('ruler destroy', () => { + const container = graph.get('container'); + expect(container.childNodes.length).toEqual(2); + + graph.destroy(); + expect(container.childNodes.length).toEqual(0); + }); +}); diff --git a/packages/site/docs/api/Event.en.md b/packages/site/docs/api/Event.en.md index 031d1037126..422764e83d7 100644 --- a/packages/site/docs/api/Event.en.md +++ b/packages/site/docs/api/Event.en.md @@ -139,7 +139,7 @@ Combo inherit all the interaction events of Node. | canvas:touchstart | On touch screen, this event is activated when user begin to touch the canvas | | canvas:touchmove | On touch screen, this event is activated when user is touching the canvas | | canvas:touchend | On touch screen, this event is activated when user finish touching the canvas | - +| canvas:changesize | Triggered when the canvas size changes | ## Timing Events Before and after being called some functions, G6 exports the timing events. These timing events can be listened by the following way: diff --git a/packages/site/docs/api/Event.zh.md b/packages/site/docs/api/Event.zh.md index 8a4bcaa3d4b..3103e0905ab 100644 --- a/packages/site/docs/api/Event.zh.md +++ b/packages/site/docs/api/Event.zh.md @@ -139,6 +139,7 @@ Combo 继承所有 Node 事件。 | canvas:touchstart | 在触控屏上,当画布开始被触碰的时候触发的事件 | | canvas:touchmove | 在触控屏上,当画布开始被触碰过程中触发的事件 | | canvas:touchend | 在触控屏上,当画布开始被触碰结束的时候触发的事件 | +| canvas:changesize | 当画布大小发生变化的时候触发 | ## 时机事件 diff --git a/packages/site/docs/api/Plugins.en.md b/packages/site/docs/api/Plugins.en.md index 113954c44e1..3bd1ce12129 100644 --- a/packages/site/docs/api/Plugins.en.md +++ b/packages/site/docs/api/Plugins.en.md @@ -88,6 +88,61 @@ Grid plugin draws grids on the canvas. Use the code in [Configure to Graph](#configure-to-graph) to instantiate grid plugin with the following configurations. +## Ruler + +The Ruler plugin in G6 can be used to draw rulers on the canvas. It provides various configuration options and methods for customization. + +⚠️ notice: Because the ruler will occupy a certain space, the interior will reduce the width and height of the canvas and offset the size of the ruler, and the grid will also need to offset, if necessary, you need to register the grid first + + + +### Configuration + +| Name | Type | Description | +| ---- | ------ | ---------------------------- | +| directions | ruleDirection[] or ruleDirection | 'horizontal' for horizontal, 'vertical' for vertical, default `['horizontal', 'vertical']`| +| width | number | Default width based on canvas width for horizontal, canvas height for vertical (including `startLen`) | +| height | number | Height of the ruler, default is `25`. Takes the maximum of startLen and height when both are present | +| startLen | number | Starting position of the ruler, default is `25`. Takes the maximum of startLen and height when both are present | +| unitInterval | number | Unit interval | +| showTickLabel | boolean | Whether to show unit interval labels | +| tickLabelStyle | CanvasRenderingContext2D['strokeStyle'] | Color of the unit labels | +| font | CanvasRenderingContext2D['font'] | Font for the text | +| lineWidth | number | Width of the lines | +| lineHeight | number | Height of the lines | +| strokeStyle | CanvasRenderingContext2D['strokeStyle'] | Color of the lines | +| background | CanvasRenderingContext2D['fillStyle'] | The color of the ruler | +| showLock | number | Whether to show the lock icon | +| lockColor | string | Color of the lock icon | +| monitorZoom | boolean | Whether the `wheelzoom` event changes the scale | +| monitorSize | boolean |Whether the `changeSize` event changes the size of the ruler | +| monitorViewPort | boolean | Whether the `viewportchange` event changes the scale | + +### API + +| Name | Description | +| ---- | ---------------------------- | +| changeRulerConfig | Change the configuration of the ruler, such as color, text size, etc. | +| toggerVisible | Toggle visibility of the ruler | +| changeVisible | Change visibility, accepts a boolean value | +| toggerLock | Toggle the lock status. When locked, dragging, moving, and scrolling are disabled. | +| changeLock | Change the lock status, accepts a boolean value. | +| changeScale | Change the scale size. Internally triggers the `graph.zoom` event. | +| updateShouldBegin | Behavioral updates such as scaling, drag and drop need to be called to keep the lock switch up-to-date | + +### Usage + +When the Ruler plug-in is instantiated. + +```javascript +// Instantiate the Ruler plugin +const ruler = new G6.Ruler(); +const graph = new G6.Graph({ + //... other configurations + plugins: [ruler], // Configure the Ruler plugin +}); +``` + ### Configuration | Name | Type | Required | Description | diff --git a/packages/site/docs/api/Plugins.zh.md b/packages/site/docs/api/Plugins.zh.md index 0e5e918c7fb..f6f7fac809c 100644 --- a/packages/site/docs/api/Plugins.zh.md +++ b/packages/site/docs/api/Plugins.zh.md @@ -96,6 +96,60 @@ Grid 插件在画布上绘制了网格。 | ---- | ------ | ---------------------------- | | img | String | grid 图片,base64 格式字符串 | + + +## Ruler + +Ruler 插件在画布上绘制了标尺。通过左上角的锁还可以锁定缩放。 + +⚠️ 注意: 因为尺子会占有占一定空间, 所以内部会对`canvas`减少`宽高`以及`偏移尺子的大小`, 并且`grid`也需要偏移, 如需要, 需要先注册`grid` + +### 配置项 + +| 名称 | 类型 | 描述 | +| ---- | ------ | ---------------------------- | +| directions | ruleDirection[] 或者 ruleDirection | 方向, `horizontal`代表横向, `vertical`代表纵向, 默认`['horizontal', 'vertical']`| +| height | number | 尺子的高度, 默认是`25`, 当存在 startLen 和 height 会取2个最大的值 | +| startLen | number | 尺子的开始位置, 默认`25`, 当存在 startLen 和 height 会取2个最大的值 | +| unitInterval | number | 单位间隔 | +| showTickLabel | boolean | 是否单位间隔的文字 | +| tickLabelStyle | CanvasRenderingContext2D['strokeStyle'] | 单位文字的颜色 | +| font | CanvasRenderingContext2D['font'] | 文字的字体 | +| lineWidth | number | 线的宽度 | +| lineHeight | number | 线的高度 | +| strokeStyle | CanvasRenderingContext2D['strokeStyle'] | 线的颜色 | +| background | CanvasRenderingContext2D['fillStyle'] | 尺子的颜色 | +| showLock | number | 是否显示锁icon | +| lockColor | string | 锁的颜色 | +| monitorZoom | boolean | `wheelzoom`, 事件是否改变刻度 | +| monitorSize | boolean | `changeSize`, 是否改变尺子的大小 | +| monitorViewPort | boolean | `viewportchange`, 事件是否改变刻度 | + +### 方法 + +| 名称 | 描述 | +| ---- | ---------------------------- | +| changeRulerConfig | 改变尺子的配置,例如改变颜色、文本大小等 | +| toggerVisible | 切换显示显示 | +| changeVisible | 改变是否显示, 接受一个布尔值 | +| toggerLock | 切换锁定状态, 锁定状态下, 无法拖拽、移动、滚动 | +| changeLock | 改变锁定状态, 接受一个布尔值 | +| changeScale | 改变缩放大小, 内部会触发`graph.zoom`事件 | +| updateShouldBegin | 缩放、拖拽等行为更新需要调用, 用于锁定切换能保留最新 | + +### 用法 + +实例化 Ruler 插件时。 + +```javascript +// 实例化 Ruler 插件 +const ruler = new G6.Ruler(); +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [ruler], // 配置 Ruler 插件 +}); +``` + ## Minimap Minimap 是用于快速预览和探索图的工具。