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/__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/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 b2f6d5f774..0a7040a10f 100644 --- a/packages/vchart-extension/src/charts/sequence-scatter/interface.ts +++ b/packages/vchart-extension/src/charts/sequence-scatter/interface.ts @@ -51,4 +51,18 @@ export interface ISequenceScatterSpec { visible: boolean; style: ITextGraphicAttribute; }; + /** + * 背景数据 + */ + 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 297e0e2f32..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 { @@ -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, @@ -65,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 = { @@ -90,33 +92,3 @@ export class SequenceScatterChartSpecTransformer extends CommonChartSpecTransfor super.transformSpec(spec); } } - -export function processSequenceData(spec: ISequenceScatterSpec) { - const result: any[] = []; - Object.keys(spec.data).forEach(inter => { - 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 - } - ] - } - ] - }); - }); - return result; -} 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; +}