Skip to content

Commit

Permalink
feat: add plugin watermark (#5569)
Browse files Browse the repository at this point in the history
* chore: init type define

* fix: badge typo in readme

* feat: plugin watermark

* feat: add watmermark plugin

* test: add tc for watermark, but should skip it

* docs: add demos for watermark

* test: add tc for mock

* chore: fix cr

* test: use jest-canvas-mock make ci works

* chore: update demo

* docs: add case panel

* refactor: render watermark into div

* chore: update
  • Loading branch information
hustcc authored Mar 21, 2024
1 parent 7768227 commit 74c19e9
Show file tree
Hide file tree
Showing 21 changed files with 509 additions and 101 deletions.
2 changes: 1 addition & 1 deletion README.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
![](https://user-images.githubusercontent.com/6113694/45008751-ea465300-b036-11e8-8e2a-166cbb338ce2.png)

[![npm Version](https://img.shields.io/npm/v/@antv/g6.svg@beta)](https://www.npmjs.com/package/@antv/g6)
[![npm Version](https://img.shields.io/npm/v/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6)
[![Build Status](https://github.com/antvis/g6/workflows/build/badge.svg?branch=v5)](https://github.com/antvis/g6/actions)
[![Coverage Status](https://coveralls.io/repos/github/antvis/G6/badge.svg)](https://coveralls.io/github/antvis/G6)
[![npm Download](https://img.shields.io/npm/dm/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
![](https://user-images.githubusercontent.com/6113694/45008751-ea465300-b036-11e8-8e2a-166cbb338ce2.png)

[![npm Version](https://img.shields.io/npm/v/@antv/g6.svg@beta)](https://www.npmjs.com/package/@antv/g6)
[![npm Version](https://img.shields.io/npm/v/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6)
[![Build Status](https://github.com/antvis/g6/workflows/build/badge.svg?branch=v5)](https://github.com/antvis/g6/actions)
[![Coverage Status](https://coveralls.io/repos/github/antvis/G6/badge.svg)](https://coveralls.io/github/antvis/G6)
[![npm Download](https://img.shields.io/npm/dm/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6)
Expand Down
2 changes: 2 additions & 0 deletions packages/g6/__tests__/demo/case/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,7 @@ export * from './layout-radial-prevent-overlap';
export * from './layout-radial-prevent-overlap-unstrict';
export * from './layout-radial-sort';
export * from './plugin-grid-line';
export * from './plugin-watermark';
export * from './plugin-watermark-image';
export * from './theme';
export * from './viewport-fit';
47 changes: 47 additions & 0 deletions packages/g6/__tests__/demo/case/plugin-watermark-image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Graph } from '@/src';
import data from '@@/dataset/cluster.json';
import type { STDTestCase } from '../types';

export const pluginWatermarkImage: STDTestCase = async (context) => {
const graph = new Graph({
...context,
autoResize: true,
data,
layout: { type: 'd3force' },
plugins: [
{
type: 'watermark',
width: 100,
height: 100,
imageURL: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*7svFR6wkPMoAAAAAAAAAAAAADmJ7AQ/original',
},
],
});

await graph.render();

pluginWatermarkImage.form = (panel) => {
const config = {
width: 200,
height: 100,
fontSize: 20,
textFill: 'red',
};
return [
panel
.add(config, 'width', 150, 400, 10)
.name('Width')
.onChange(() => {}),
panel
.add(config, 'height', 100, 200, 10)
.name('Width')
.onChange(() => {}),
panel
.add(config, 'fontSize', 10, 32, 1)
.name('FontSize')
.onChange(() => {}),
];
};

return graph;
};
60 changes: 60 additions & 0 deletions packages/g6/__tests__/demo/case/plugin-watermark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Graph, PluginOptions } from '@/src';
import data from '@@/dataset/cluster.json';
import { isObject } from '@antv/util';
import type { STDTestCase } from '../types';

export const pluginWatermark: STDTestCase = async (context) => {
const graph = new Graph({
...context,
autoResize: true,
data,
layout: { type: 'd3force' },
plugins: [
{
type: 'watermark',
text: 'hello, \na watermark.',
textFontSize: 12,
},
],
});

await graph.render();

function updatePlugin(type: string, config: object) {
return (plugins: PluginOptions) => {
return plugins.map((plugin) => {
if (isObject(plugin) && plugin.type === type) return { ...plugin, ...config };
return plugin;
});
};
}
pluginWatermark.form = (panel) => {
const config = {
width: 200,
height: 100,
textFontSize: 12,
};
return [
panel
.add(config, 'width', 150, 400, 10)
.name('Width')
.onChange((width: number) => {
graph.setPlugins(updatePlugin('watermark', { width }));
}),
panel
.add(config, 'height', 100, 200, 10)
.name('Height')
.onChange((height: number) => {
graph.setPlugins(updatePlugin('watermark', { height }));
}),
panel
.add(config, 'textFontSize', 10, 32, 1)
.name('TextFontSize')
.onChange((textFontSize: number) => {
graph.setPlugins(updatePlugin('watermark', { textFontSize }));
}),
];
};

return graph;
};
1 change: 1 addition & 0 deletions packages/g6/__tests__/setup.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import '@/src/preset';
import 'jest-canvas-mock';
import './utils/to-be-close-to';
import './utils/use-snapshot-matchers';
33 changes: 33 additions & 0 deletions packages/g6/__tests__/unit/plugins/plugin-watermark.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { pluginWatermark, pluginWatermarkImage } from '@@/demo/case';
import { createDemoGraph } from '@@/utils';

describe('plugin watermark', () => {
it('watermark text', async () => {
const graph = await createDemoGraph(pluginWatermark);
const container = graph.getCanvas().getContainer()!;

const el = container.querySelector('.g6-watermark') as HTMLDivElement;

expect(graph.getPlugins()).toEqual([{ type: 'watermark', text: 'hello, \na watermark.', textFontSize: 12 }]);
expect(el.style.backgroundImage).toContain('data:image/png;base64');

await graph.destroy();
expect(container.querySelector('.g6-watermark')).toBeFalsy();
});

it('watermark image', async () => {
const graph = await createDemoGraph(pluginWatermarkImage);
const container = graph.getCanvas().getContainer()!;

expect(graph.getPlugins()).toEqual([
{
type: 'watermark',
width: 100,
height: 100,
imageURL: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*7svFR6wkPMoAAAAAAAAAAAAADmJ7AQ/original',
},
]);
await graph.destroy();
expect(container.querySelector('.g6-watermark')).toBeFalsy();
});
});
15 changes: 14 additions & 1 deletion packages/g6/__tests__/unit/utils/dom.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sizeOf } from '@/src/utils/dom';
import { createPluginContainer, sizeOf } from '@/src/utils/dom';

describe('sizeOf', () => {
it('should return the size of the graph container', () => {
Expand All @@ -13,4 +13,17 @@ describe('sizeOf', () => {
// Assert the result
expect(result).toEqual([500, 300]);
});

it('createPluginContainer', () => {
const el = createPluginContainer('test');
expect(el.style.position).toBe('absolute');
expect(el.style.display).toBe('block');
expect(el.style.top).toBe('0px');
expect(el.style.left).toBe('0px');
expect(el.style.height).toBe('100%');
expect(el.style.width).toBe('100%');
expect(el.style.overflow).toBe('hidden');
expect(el.style.pointerEvents).toBe('none');
expect(el.getAttribute('class')).toBe('g6-test');
});
});
1 change: 1 addition & 0 deletions packages/g6/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@antv/layout-gpu": "^1.1.5",
"@antv/layout-wasm": "^1.4.0",
"@types/xmlserializer": "^0.6.6",
"jest-canvas-mock": "^2.5.1",
"jest-random-mock": "^1.0.0",
"lil-gui": "^0.19.2",
"stats.js": "^0.17.0",
Expand Down
21 changes: 7 additions & 14 deletions packages/g6/src/plugins/grid-line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BaseStyleProps } from '@antv/g';
import { GraphEvent } from '../constants';
import type { RuntimeContext } from '../runtime/types';
import { Point } from '../types';
import { createPluginContainer } from '../utils/dom';
import { ViewportEvent } from '../utils/event';
import { add, mod } from '../utils/vector';
import type { BasePluginOptions } from './base-plugin';
Expand Down Expand Up @@ -35,13 +36,17 @@ export class GridLine extends BasePlugin<GridLineOptions> {
tickStrokeOpacity: 0.5,
};

private $element: HTMLElement = document.createElement('div');
private $element: HTMLElement = createPluginContainer('grid-line');

private offset: Point = [0, 0];

constructor(context: RuntimeContext, options: GridLineOptions) {
super(context, Object.assign({}, GridLine.defaultOptions, options));
this.render();

const $container = this.context.canvas.getContainer()!;
$container.appendChild(this.$element);

this.updateStyle();
this.bindEvents();
}

Expand All @@ -55,22 +60,10 @@ export class GridLine extends BasePlugin<GridLineOptions> {
graph.on(GraphEvent.AFTER_TRANSFORM, this.onTransform);
}

private render() {
const { canvas } = this.context;
const $container = canvas.getContainer();
if (!$container) return;

this.$element.className = 'g6-grid-line';
this.updateStyle();
$container.appendChild(this.$element);
}

private updateStyle() {
const { size, stroke, lineWidth, border, borderLineWidth, borderStroke, borderStyle } = this.options;

Object.assign(this.$element.style, {
width: '100%',
height: '100%',
border: border ? `${borderLineWidth}px ${borderStyle} ${borderStroke}` : 'none',
backgroundImage: `linear-gradient(${stroke} ${lineWidth}px, transparent ${lineWidth}px), linear-gradient(90deg, ${stroke} ${lineWidth}px, transparent ${lineWidth}px)`,
backgroundSize: `${size}px ${size}px`,
Expand Down
2 changes: 2 additions & 0 deletions packages/g6/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { BasePlugin } from './base-plugin';
export { GridLine } from './grid-line';
export { Watermark } from './watermark';

export type { BasePluginOptions } from './base-plugin';
export type { GridLineOptions } from './grid-line';
export type { WatermarkOptions } from './watermark';
98 changes: 98 additions & 0 deletions packages/g6/src/plugins/watermark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { RuntimeContext } from '../runtime/types';
import { createPluginContainer } from '../utils/dom';
import { getImageWatermark, getTextWateramrk } from '../utils/watermark';
import type { BasePluginOptions } from './base-plugin';
import { BasePlugin } from './base-plugin';

export type WatermarkOptions = BasePluginOptions & {
/** 单独一个水印的大小,这个水印最终会用来填充整个大小,所以 repeat 后的间距大小,通过这个 width height 设置 */
width?: number;
height?: number;
/** 透明度 */
opacity?: number;
/** 旋转角度 */
rotate?: number;
/** 图片地址,如果有值,则使用,否则使用文本 */
imageURL?: string;
/** 水印文本 */
text?: string;
/** 文本水印的文本样式 */
textFill: string;
textFontSize: number;
textFontFamily: string;
textFontWeight: string;
textFontVariant: string;
textAlign: CanvasTextAlign;
textBaseline: CanvasTextBaseline;
/** 背景的 CSS 样式 */
backgroundAttachment: string;
backgroundBlendMode: string;
backgroundClip: string;
backgroundColor: string;
backgroundImage: string;
backgroundOrigin: string;
backgroundPosition: string;
backgroundPositionX: string;
backgroundPositionY: string;
backgroundRepeat: string;
backgroundSize: string;
};

/**
* <zh/> 支持使用文本和图片作为水印,实现原理是在 Graph 容器的 div 上加上 background-image 属性,然后就可以通过 css 来控制水印的位置和样式。
* 对于文本,会使用隐藏 canvas 转成图片的方式来实现。
* <en/> Support using text and images as watermarks.
* The principle is to add the background-image property to the div of the Graph container,
* and then you can control the position and style of the watermark through css. For text,
* it will be converted to an image using a hidden canvas.
*/
export class Watermark extends BasePlugin<WatermarkOptions> {
static defaultOptions: Partial<WatermarkOptions> = {
width: 200,
height: 100,
opacity: 0.2,
rotate: Math.PI / 12,
textFill: '#000',
textFontSize: 16,
textAlign: 'center',
textBaseline: 'middle',
backgroundRepeat: 'repeat',
};

private $element: HTMLElement = createPluginContainer('watermark');

constructor(context: RuntimeContext, options: WatermarkOptions) {
super(context, Object.assign({}, Watermark.defaultOptions, options));

const $container = this.context.canvas.getContainer();
$container!.appendChild(this.$element);

this.update(options);
}

public async update(options: Partial<WatermarkOptions>) {
super.update(options);

const { width, height, text, imageURL, ...rest } = this.options;

// Set the background style.
Object.keys(rest).forEach((key) => {
if (key.startsWith('background')) {
// @ts-expect-error ignore
this.$element.style[key] = options[key];
}
});

// Set the background image.
const base64 = imageURL
? await getImageWatermark(width, height, imageURL, rest)
: await getTextWateramrk(width, height, text, rest);
this.$element.style.backgroundImage = `url(${base64})`;
}

public destroy(): void {
super.destroy();
// Remove the background dom.
this.$element.remove();
}
}
3 changes: 2 additions & 1 deletion packages/g6/src/registry/build-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
mindmap,
} from '../layouts';
import { blues, greens, oranges, spectral } from '../palettes';
import { GridLine } from '../plugins';
import { GridLine, Watermark } from '../plugins';
import { dark, light } from '../themes';
import type { ExtensionRegistry } from './types';

Expand Down Expand Up @@ -107,5 +107,6 @@ export const BUILT_IN_EXTENSIONS: ExtensionRegistry = {
},
plugin: {
'grid-line': GridLine,
watermark: Watermark,
},
};
Loading

0 comments on commit 74c19e9

Please sign in to comment.