From bb82f72e58e6c23bb25c114e50f31029ea86fc9f Mon Sep 17 00:00:00 2001 From: "boheng.xie" <3021244050@tju.edu.cn> Date: Sun, 9 Mar 2025 17:30:53 +0800 Subject: [PATCH 1/2] feat: add draw background for sequence-scatter --- .../browser/test-page/sequence-scatter.ts | 15 +++- .../src/charts/sequence-scatter/interface.ts | 6 ++ .../sequence-scatter-transformer.ts | 68 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/packages/vchart-extension/__tests__/runtime/browser/test-page/sequence-scatter.ts b/packages/vchart-extension/__tests__/runtime/browser/test-page/sequence-scatter.ts index b48d1670fe..f25253405b 100644 --- a/packages/vchart-extension/__tests__/runtime/browser/test-page/sequence-scatter.ts +++ b/packages/vchart-extension/__tests__/runtime/browser/test-page/sequence-scatter.ts @@ -2,9 +2,20 @@ import { registerSequenceScatter } from '../../../../src'; import { VChart } from '@visactor/vchart'; import trainingData1 from '../data/sequence-scatter/Training_process1/data.json'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -import trainingData2 from '../data/sequence-scatter/Training_process2/data.json'; +// import trainingData2 from '../data/sequence-scatter/Training_process2/data.json'; +import bgimgData from '../data/sequence-scatter/Training_process1/bgimg_data.json'; const origianlData = trainingData1; // const origianlData = trainingData2; +const bgData = {}; +if (bgimgData) { + // 假设每个帧对应一个300x300的RGB矩阵 + Object.keys(bgimgData).forEach(inter => { + // 确保原始数据中有这一帧 + if (origianlData[inter]) { + bgData[inter] = bgimgData[inter]; + } + }); +} const chartData = {}; Object.keys(origianlData).forEach(inter => { chartData[inter] = []; @@ -21,6 +32,8 @@ const spec = { xField: 'x', yField: 'y', + backgroundColors: bgData, + infoLabel: { visible: true, style: { diff --git a/packages/vchart-extension/src/charts/sequence-scatter/interface.ts b/packages/vchart-extension/src/charts/sequence-scatter/interface.ts index b2f6d5f774..8ccbaab1ed 100644 --- a/packages/vchart-extension/src/charts/sequence-scatter/interface.ts +++ b/packages/vchart-extension/src/charts/sequence-scatter/interface.ts @@ -51,4 +51,10 @@ export interface ISequenceScatterSpec { visible: boolean; style: ITextGraphicAttribute; }; + /** + * 背景数据 + */ + backgroundColors: { + [Iteration: string]: any; + }; } diff --git a/packages/vchart-extension/src/charts/sequence-scatter/sequence-scatter-transformer.ts b/packages/vchart-extension/src/charts/sequence-scatter/sequence-scatter-transformer.ts index 297e0e2f32..9bab2cdb4b 100644 --- a/packages/vchart-extension/src/charts/sequence-scatter/sequence-scatter-transformer.ts +++ b/packages/vchart-extension/src/charts/sequence-scatter/sequence-scatter-transformer.ts @@ -12,6 +12,11 @@ export class SequenceScatterChartSpecTransformer extends CommonChartSpecTransfor spec.type = 'common'; spec.dataKey = DATA_KEY; spec.data = dataSpecs[0].data; + + spec.width = 300; + spec.height = 300; + spec.autoFit = false; + spec.series = [ { type: 'scatter', @@ -49,6 +54,19 @@ export class SequenceScatterChartSpecTransformer extends CommonChartSpecTransfor ]; spec.customMark = [ + // 背景图像 + { + type: 'image', + dataIndex: 2, // 指向背景数据 + style: { + x: 0, + y: 0, + width: 300, + height: 300, + image: (datum: Datum) => datum.imageData, + zIndex: -1 // 确保在散点下方 + } + }, { type: 'text', dataIndex: 1, @@ -93,7 +111,14 @@ export class SequenceScatterChartSpecTransformer extends CommonChartSpecTransfor export function processSequenceData(spec: ISequenceScatterSpec) { const result: any[] = []; + const BACKGROUND_KEY = 'background'; Object.keys(spec.data).forEach(inter => { + let backgroundData = null; + if (spec.backgroundColors && spec.backgroundColors[inter]) { + // 创建背景图像数据 + const imageData = createImageDataFromColorMatrix(spec.backgroundColors[inter]); + backgroundData = { imageData }; + } result.push({ data: [ { @@ -114,9 +139,52 @@ export function processSequenceData(spec: ISequenceScatterSpec) { inter } ] + }, + { + id: BACKGROUND_KEY, + values: backgroundData ? [backgroundData] : [] } ] }); }); return result; } + +// 将RGB三元组数组转换为Canvas可用的imageData +function createImageDataFromColorMatrix(colorMatrix: any[][]): string | null { + // 浏览器环境检查 + if (typeof document === 'undefined') { + return null; // 非浏览器环境下返回null + } + + // 创建Canvas离屏渲染 + const canvas = document.createElement('canvas'); + canvas.width = 300; + canvas.height = 300; + const ctx = canvas.getContext('2d'); + + if (!ctx) { + return null; + } + + // 创建imageData + const imageData = ctx.createImageData(300, 300); + + // 填充像素数据 + for (let y = 0; y < 300; y++) { + for (let x = 0; x < 300; x++) { + const rgb = colorMatrix[y]?.[x] || [0, 0, 0]; + const pixelIndex = (y * 300 + x) * 4; + + // 转换0-1范围到0-255 + imageData.data[pixelIndex] = Math.round(rgb[0] * 255); // R + imageData.data[pixelIndex + 1] = Math.round(rgb[1] * 255); // G + imageData.data[pixelIndex + 2] = Math.round(rgb[2] * 255); // B + imageData.data[pixelIndex + 3] = 255; // A (完全不透明) + } + } + + // 将imageData绘制到canvas然后返回dataURL + ctx.putImageData(imageData, 0, 0); + return canvas.toDataURL('image/png'); +} From efb769d1fc4d0bb6084583c1f52edd7d21d3c1f7 Mon Sep 17 00:00:00 2001 From: "boheng.xie" <3021244050@tju.edu.cn> Date: Fri, 21 Mar 2025 20:47:28 +0800 Subject: [PATCH 2/2] fix: fix some case for add bg func --- VChart | 1 + .../src/charts/sequence-scatter/constant.ts | 2 + .../src/charts/sequence-scatter/interface.ts | 8 ++ .../sequence-scatter-transformer.ts | 104 +----------------- .../src/utils/createImageData.ts | 40 +++++++ .../src/utils/processSequenceData.ts | 37 +++++++ 6 files changed, 92 insertions(+), 100 deletions(-) create mode 160000 VChart create mode 100644 packages/vchart-extension/src/charts/sequence-scatter/constant.ts create mode 100644 packages/vchart-extension/src/utils/createImageData.ts create mode 100644 packages/vchart-extension/src/utils/processSequenceData.ts diff --git a/VChart b/VChart new file mode 160000 index 0000000000..857dcb4ba4 --- /dev/null +++ b/VChart @@ -0,0 +1 @@ +Subproject commit 857dcb4ba43b55e587770d5c2e018e0beea43ff0 diff --git a/packages/vchart-extension/src/charts/sequence-scatter/constant.ts b/packages/vchart-extension/src/charts/sequence-scatter/constant.ts new file mode 100644 index 0000000000..fbc01494b3 --- /dev/null +++ b/packages/vchart-extension/src/charts/sequence-scatter/constant.ts @@ -0,0 +1,2 @@ +export const DATA_KEY = 'dataKey'; +export const BACKGROUND_KEY = 'background'; diff --git a/packages/vchart-extension/src/charts/sequence-scatter/interface.ts b/packages/vchart-extension/src/charts/sequence-scatter/interface.ts index 8ccbaab1ed..0a7040a10f 100644 --- a/packages/vchart-extension/src/charts/sequence-scatter/interface.ts +++ b/packages/vchart-extension/src/charts/sequence-scatter/interface.ts @@ -57,4 +57,12 @@ export interface ISequenceScatterSpec { backgroundColors: { [Iteration: string]: any; }; + /** + * 宽度 + */ + width: number; + /** + * 高度 + */ + height: number; } diff --git a/packages/vchart-extension/src/charts/sequence-scatter/sequence-scatter-transformer.ts b/packages/vchart-extension/src/charts/sequence-scatter/sequence-scatter-transformer.ts index 9bab2cdb4b..189510c26e 100644 --- a/packages/vchart-extension/src/charts/sequence-scatter/sequence-scatter-transformer.ts +++ b/packages/vchart-extension/src/charts/sequence-scatter/sequence-scatter-transformer.ts @@ -1,8 +1,8 @@ import { Datum } from '@visactor/vchart/src/typings'; import type { ISequenceScatterSpec } from './interface'; import { CommonChartSpecTransformer } from '@visactor/vchart'; - -const DATA_KEY = 'dataKey'; +import { processSequenceData } from '../../utils/processSequenceData'; +import { DATA_KEY } from './constant'; export class SequenceScatterChartSpecTransformer extends CommonChartSpecTransformer { transformSpec(spec: any): void { @@ -57,14 +57,14 @@ export class SequenceScatterChartSpecTransformer extends CommonChartSpecTransfor // 背景图像 { type: 'image', - dataIndex: 2, // 指向背景数据 + dataIndex: 2, style: { x: 0, y: 0, width: 300, height: 300, image: (datum: Datum) => datum.imageData, - zIndex: -1 // 确保在散点下方 + zIndex: -1 } }, { @@ -83,22 +83,6 @@ export class SequenceScatterChartSpecTransformer extends CommonChartSpecTransfor ...spec.infoLabel?.style } } - // TODO: draw polygon according to data - // { - // type: 'polygon', - // dataIndex: 1, - // style: { - // points: (datum: Datum) => { - // return [ - // { - // x: , - // y: - // }, - // //.... - // ]; - // }, - // } - // } ]; spec.tooltip = { @@ -108,83 +92,3 @@ export class SequenceScatterChartSpecTransformer extends CommonChartSpecTransfor super.transformSpec(spec); } } - -export function processSequenceData(spec: ISequenceScatterSpec) { - const result: any[] = []; - const BACKGROUND_KEY = 'background'; - Object.keys(spec.data).forEach(inter => { - let backgroundData = null; - if (spec.backgroundColors && spec.backgroundColors[inter]) { - // 创建背景图像数据 - const imageData = createImageDataFromColorMatrix(spec.backgroundColors[inter]); - backgroundData = { imageData }; - } - result.push({ - data: [ - { - id: 'nodes', - values: spec.data[inter].map((d, i) => { - return { ...d, [DATA_KEY]: i }; - }) - }, - // TODO: edges data - // { - // id: 'edges', - // values: [....] - // }, - { - id: 'inter', - values: [ - { - inter - } - ] - }, - { - id: BACKGROUND_KEY, - values: backgroundData ? [backgroundData] : [] - } - ] - }); - }); - return result; -} - -// 将RGB三元组数组转换为Canvas可用的imageData -function createImageDataFromColorMatrix(colorMatrix: any[][]): string | null { - // 浏览器环境检查 - if (typeof document === 'undefined') { - return null; // 非浏览器环境下返回null - } - - // 创建Canvas离屏渲染 - const canvas = document.createElement('canvas'); - canvas.width = 300; - canvas.height = 300; - const ctx = canvas.getContext('2d'); - - if (!ctx) { - return null; - } - - // 创建imageData - const imageData = ctx.createImageData(300, 300); - - // 填充像素数据 - for (let y = 0; y < 300; y++) { - for (let x = 0; x < 300; x++) { - const rgb = colorMatrix[y]?.[x] || [0, 0, 0]; - const pixelIndex = (y * 300 + x) * 4; - - // 转换0-1范围到0-255 - imageData.data[pixelIndex] = Math.round(rgb[0] * 255); // R - imageData.data[pixelIndex + 1] = Math.round(rgb[1] * 255); // G - imageData.data[pixelIndex + 2] = Math.round(rgb[2] * 255); // B - imageData.data[pixelIndex + 3] = 255; // A (完全不透明) - } - } - - // 将imageData绘制到canvas然后返回dataURL - ctx.putImageData(imageData, 0, 0); - return canvas.toDataURL('image/png'); -} diff --git a/packages/vchart-extension/src/utils/createImageData.ts b/packages/vchart-extension/src/utils/createImageData.ts new file mode 100644 index 0000000000..387cbd3aa0 --- /dev/null +++ b/packages/vchart-extension/src/utils/createImageData.ts @@ -0,0 +1,40 @@ +import { ISequenceScatterSpec } from '../charts/sequence-scatter/interface'; + +// 将RGB三元组数组转换为Canvas可用的imageData +export function createImageDataFromColorMatrix(colorMatrix: any[][], spec: ISequenceScatterSpec): string | null { + // 浏览器环境检查 + if (typeof document === 'undefined') { + throw new Error('Canvas rendering requires browser environment with document object'); // 非浏览器环境下返回null + } + + // 创建Canvas离屏渲染 + const canvas = document.createElement('canvas'); + canvas.width = spec.width; + canvas.height = spec.height; + const ctx = canvas.getContext('2d'); + + if (!ctx) { + throw new Error('Failed to get 2D rendering context from canvas'); // Canvas context creation failed + } + + // 创建imageData + const imageData = ctx.createImageData(spec.width, spec.height); + + // 填充像素数据 + for (let y = 0; y < 300; y++) { + for (let x = 0; x < 300; x++) { + const rgb = colorMatrix[y]?.[x] || [0, 0, 0]; + const pixelIndex = (y * 300 + x) * 4; + + // 转换0-1范围到0-255 + imageData.data[pixelIndex] = Math.round(rgb[0] * 255); // R + imageData.data[pixelIndex + 1] = Math.round(rgb[1] * 255); // G + imageData.data[pixelIndex + 2] = Math.round(rgb[2] * 255); // B + imageData.data[pixelIndex + 3] = 255; // A (完全不透明) + } + } + + // 将imageData绘制到canvas然后返回dataURL + ctx.putImageData(imageData, 0, 0); + return canvas.toDataURL('image/png'); +} diff --git a/packages/vchart-extension/src/utils/processSequenceData.ts b/packages/vchart-extension/src/utils/processSequenceData.ts new file mode 100644 index 0000000000..4f4e89f615 --- /dev/null +++ b/packages/vchart-extension/src/utils/processSequenceData.ts @@ -0,0 +1,37 @@ +import { BACKGROUND_KEY, DATA_KEY } from '../charts/sequence-scatter/constant'; +import { ISequenceScatterSpec } from '../charts/sequence-scatter/interface'; +import { createImageDataFromColorMatrix } from './createImageData'; + +export function processSequenceData(spec: ISequenceScatterSpec) { + const result: any[] = []; + Object.keys(spec.data).forEach(inter => { + let backgroundData = null; + if (spec.backgroundColors && spec.backgroundColors[inter]) { + const imageData = createImageDataFromColorMatrix(spec.backgroundColors[inter], spec); + backgroundData = { imageData }; + } + result.push({ + data: [ + { + id: 'nodes', + values: spec.data[inter].map((d, i) => { + return { ...d, [DATA_KEY]: i }; + }) + }, + { + id: 'inter', + values: [ + { + inter + } + ] + }, + { + id: BACKGROUND_KEY, + values: backgroundData ? [backgroundData] : [] + } + ] + }); + }); + return result; +}