Skip to content

Commit

Permalink
fix: add createCanvas to G2Context so that heatmap can use it on serv…
Browse files Browse the repository at this point in the history
…er-side rendering
  • Loading branch information
xiaoiver committed May 19, 2023
1 parent ad5dc56 commit 0499d11
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 17 deletions.
Binary file modified __tests__/integration/snapshots/static/HeatmapHeatmapBasic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion __tests__/integration/utils/createNodeGCanvas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createCanvas } from 'canvas';
import { createCanvas, Image } from 'canvas';
import { Canvas } from '@antv/g';
import { Renderer } from '@antv/g-canvas';
import { Plugin as DragAndDropPlugin } from '@antv/g-plugin-dragndrop';
Expand All @@ -22,5 +22,9 @@ export function createNodeGCanvas(width: number, height: number): Canvas {
canvas: nodeCanvas as any,
renderer,
offscreenCanvas: offscreenNodeCanvas as any,
createImage: () => {
const image = new Image();
return image as any;
},
});
}
6 changes: 6 additions & 0 deletions __tests__/integration/utils/renderSpec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Canvas } from '@antv/g';
import { createCanvas } from 'canvas';
import { G2Context, G2Spec, render } from '../../../src';
import { renderToMountedElement } from '../../utils/renderToMountedElement';
import { createNodeGCanvas } from './createNodeGCanvas';
Expand All @@ -14,6 +15,11 @@ export async function renderSpec(
const renderFunction = mounted ? renderToMountedElement : render;
const options = preprocess({ ...raw, width, height });
context.canvas = gCanvas;
context.createCanvas = () => {
// The width attribute defaults to 300, and the height attribute defaults to 150.
// @see https://stackoverflow.com/a/12019582
return createCanvas(300, 150) as unknown as HTMLCanvasElement;
};
await new Promise<Canvas>((resolve) =>
// @ts-ignore
renderFunction({ theme: 'classic', ...options }, context, resolve),
Expand Down
18 changes: 13 additions & 5 deletions src/runtime/plot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,12 @@ export async function plot<T extends G2ViewTree>(
.attr('id', (view) => view.key)
.call(applyTranslate)
.each(function (view) {
plotView(view, select(this), transitions, library);
plotView(view, select(this), transitions, library, context);
enterContainer.set(view, this);
}),
(update) =>
update.call(applyTranslate).each(function (view) {
plotView(view, select(this), transitions, library);
plotView(view, select(this), transitions, library, context);
updateContainer.set(view, this);
}),
(exit) =>
Expand Down Expand Up @@ -305,7 +305,7 @@ function createUpdateView(
return async (newOptions) => {
const transitions = [];
const [newView, newChildren] = await initializeView(newOptions, library);
plotView(newView, selection, transitions, library);
plotView(newView, selection, transitions, library, context);
updateTooltip(selection, newOptions, newView, library, context);
for (const child of newChildren) {
plot(child, selection, library, context);
Expand Down Expand Up @@ -645,6 +645,7 @@ async function plotView(
selection: Selection,
transitions: GAnimation[],
library: G2Library,
context: G2Context,
): Promise<void> {
const { components, theme, layout, markState, coordinate, key, style, clip } =
view;
Expand Down Expand Up @@ -783,7 +784,13 @@ async function plotView(
const { data } = state;
const { key, class: cls, type } = mark;
const viewNode = selection.select(`#${key}`);
const shapeFunction = createMarkShapeFunction(mark, state, view, library);
const shapeFunction = createMarkShapeFunction(
mark,
state,
view,
library,
context,
);
const enterFunction = createEnterFunction(mark, state, view, library);
const updateFunction = createUpdateFunction(mark, state, view, library);
const exitFunction = createExitFunction(mark, state, view, library);
Expand Down Expand Up @@ -1199,6 +1206,7 @@ function createMarkShapeFunction(
state: G2MarkState,
view: G2ViewDescriptor,
library: G2Library,
context: G2Context,
): (
data: Record<string, any>,
index: number,
Expand Down Expand Up @@ -1236,7 +1244,7 @@ function createMarkShapeFunction(
...visualStyle,
type: shapeName(mark, shape),
});
return shapeFunction(points, value, coordinate, theme, point2d);
return shapeFunction(points, value, coordinate, theme, point2d, context);
};
}

Expand Down
3 changes: 2 additions & 1 deletion src/runtime/types/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { DataComponent } from './data';
import { Encode, EncodeComponent } from './encode';
import { Mark, MarkComponent } from './mark';
import { G2ViewTree, G2Library, G2Mark } from './options';
import { G2ViewTree, G2Library, G2Mark, G2Context } from './options';
import { Transform, TransformComponent } from './transform';

export type G2ComponentNamespaces =
Expand Down Expand Up @@ -145,6 +145,7 @@ export type Shape = (
coordinate: Coordinate,
theme: G2Theme,
point2d?: Vector2[][],
context?: G2Context,
) => DisplayObject;
export type ShapeProps = {
defaultMarker?: string;
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ export type G2Context = {
group?: DisplayObject;
animations?: GAnimation[];
views?: G2ViewDescriptor[];
/**
* Tell G2 how to create a canvas-like element, some marks will use it later such as wordcloud & heatmap.
* Use `document.createElement('canvas')` instead if not provided.
*/
createCanvas?: () => HTMLCanvasElement;
};

export type G2View = {
Expand Down
17 changes: 13 additions & 4 deletions src/shape/heatmap/heatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import { max as d3max, min as d3min } from 'd3-array';
import { Image as GImage } from '@antv/g';
import { applyStyle, getShapeTheme } from '../utils';
import { select } from '../../utils/selection';
import { ShapeComponent as SC, Vector2 } from '../../runtime';
import { ShapeComponent as SC } from '../../runtime';
import { HeatmapRenderer } from './renderer';
import type { HeatmapRendererOptions } from './renderer/types';

export type HeatmapOptions = HeatmapRendererOptions;

export const Heatmap: SC<HeatmapOptions> = (options) => {
const { ...style } = options;
return (points: number[][], value, coordinate, theme) => {
const { mark, shape, defaultShape, color, transform } = value;
return (points: number[][], value, coordinate, theme, _, context) => {
const { mark, shape, defaultShape, transform } = value;
const {
defaultColor,
fill = defaultColor,
Expand All @@ -29,7 +29,16 @@ export const Heatmap: SC<HeatmapOptions> = (options) => {
const min = d3min(points, (p) => p[2]);
const max = d3max(points, (p) => p[2]);

const ctx = HeatmapRenderer(width, height, min, max, data, { ...style });
const { createCanvas } = context;
const ctx = HeatmapRenderer(
width,
height,
min,
max,
data,
{ ...style },
createCanvas,
);

return select(new GImage())
.call(applyStyle, shapeTheme)
Expand Down
24 changes: 18 additions & 6 deletions src/shape/heatmap/renderer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ import { HeatmapRendererData, HeatmapRendererOptions } from './types';
* @param blurFactor
* @returns
*/
function getPointTemplate(radius: number, blurFactor: number) {
const tplCanvas = document.createElement('canvas');
function getPointTemplate(
radius: number,
blurFactor: number,
createCanvas?: () => HTMLCanvasElement,
) {
const tplCanvas = createCanvas
? createCanvas()
: document.createElement('canvas');
const tplCtx = tplCanvas.getContext('2d');
const x = radius;
const y = radius;
Expand Down Expand Up @@ -67,6 +73,7 @@ function drawAlpha(
max: number,
data: HeatmapRendererData[],
options: HeatmapRendererOptions,
createCanvas?: () => HTMLCanvasElement,
) {
const { blur } = options;
let len = data.length;
Expand All @@ -78,7 +85,7 @@ function drawAlpha(
const rectY = y - radius;

// TODO: cache for performance.
const tpl = getPointTemplate(radius, blur);
const tpl = getPointTemplate(radius, blur, createCanvas);
// Value from minimum / value range, => [0, 1].
const templateAlpha = (value - min) / (max - min);
// Small values are not visible because globalAlpha < .01 cannot be read from imageData.
Expand Down Expand Up @@ -139,6 +146,7 @@ export function HeatmapRenderer(
max: number,
data: HeatmapRendererData[],
options: HeatmapRendererOptions,
createCanvas?: () => HTMLCanvasElement,
) {
const opts = {
blur: 0.5,
Expand All @@ -154,15 +162,19 @@ export function HeatmapRenderer(
minOpacity: (options.opacity || 0) * 255,
};

const canvas = document.createElement('canvas');
const shadowCanvas = document.createElement('canvas');
const canvas = createCanvas
? createCanvas()
: document.createElement('canvas');
const shadowCanvas = createCanvas
? createCanvas()
: document.createElement('canvas');

const ctx = canvas.getContext('2d');
const shadowCtx = shadowCanvas.getContext('2d');

const palette = getColorPalette(opts.gradient);

drawAlpha(shadowCtx, min, max, data, opts);
drawAlpha(shadowCtx, min, max, data, opts, createCanvas);
const img = colorize(shadowCtx, width, height, palette, opts);

ctx.putImageData(img, 0, 0);
Expand Down

0 comments on commit 0499d11

Please sign in to comment.