Deepseek辅助编程示例-在typescript中基于plotly.js画图

之前在jupyter中尝试使用typescript基于plotly.js画图,但没有成功。

https://blog.csdn.net/liliang199/article/details/157908193

这里进一步借助于Deepseek实现这一目标,讲过生成-精简-多轮纠错,Deepseek圆满完成任务。

所用示例由Deepseek模拟生成。

1 第一轮-生成

1.1 提示词

提示词示例如下

帮我生成一个在typescript环境下使用plotly.js画图的项目,包括所有配置文件,包括运行方法,以一到两个具体画图的示例。

1.2 输出

输出如下所示,可见deepseek生成了完整的项目目录,以及对应ts文件、配置问价内容。

TypeScript + Plotly.js 图表项目

我将为您创建一个完整的 TypeScript 项目,使用 Plotly.js 绘制图表。这个项目包含所有必要的配置文件和两个具体的图表示例。

项目结构

```

typescript-plotly-project/

├── src/

│ ├── index.html

│ ├── index.ts

│ ├── styles.css

│ └── charts.ts

├── package.json

├── tsconfig.json

├── webpack.config.js

└── README.md

```

文件内容

1. `package.json`

```json

{

"name": "typescript-plotly-project",

"version": "1.0.0",

"description": "TypeScript project with Plotly.js for data visualization",

"main": "dist/bundle.js",

"scripts": {

"start": "webpack serve --mode development --open",

"build": "webpack --mode production",

"dev": "webpack serve --mode development",

"type-check": "tsc --noEmit"

},

"keywords": [

"typescript",

"plotly",

"visualization",

"charts"

],

"author": "Your Name",

"license": "MIT",

"devDependencies": {

"@types/plotly.js": "^2.12.18",

"css-loader": "^6.10.0",

"html-webpack-plugin": "^5.6.0",

"style-loader": "^3.3.4",

"ts-loader": "^9.5.1",

"typescript": "^5.4.5",

"webpack": "^5.91.0",

"webpack-cli": "^5.1.4",

"webpack-dev-server": "^5.0.4"

},

"dependencies": {

"plotly.js-dist-min": "^2.27.0"

}

}

```

2. `tsconfig.json`

```json

{

"compilerOptions": {

"target": "ES2020",

"module": "ESNext",

"lib": ["ES2020", "DOM", "DOM.Iterable"],

"allowJs": true,

"skipLibCheck": true,

"esModuleInterop": true,

"allowSyntheticDefaultImports": true,

"strict": true,

"forceConsistentCasingInFileNames": true,

"moduleResolution": "node",

"resolveJsonModule": true,

"isolatedModules": true,

"noEmit": false,

"outDir": "./dist",

"sourceMap": true,

"declaration": false

},

"include": ["src/**/*"],

"exclude": ["node_modules", "dist"]

}

```

3. `webpack.config.js`

```javascript

const path = require('path');

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = (env, argv) => {

const isProduction = argv.mode === 'production';

return {

entry: './src/index.ts',

output: {

filename: 'bundle.js',

path: path.resolve(__dirname, 'dist'),

clean: true

},

devtool: isProduction ? 'source-map' : 'inline-source-map',

devServer: {

static: './dist',

hot: true,

open: true,

port: 3000

},

module: {

rules: [

{

test: /\.ts$/,

use: 'ts-loader',

exclude: /node_modules/

},

{

test: /\.css$/,

use: ['style-loader', 'css-loader']

}

]

},

resolve: {

extensions: ['.ts', '.js']

},

plugins: [

new HtmlWebpackPlugin({

template: './src/index.html',

title: 'TypeScript + Plotly.js Charts'

})

]

};

};

```

4. `src/index.html`

```html

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>TypeScript + Plotly.js Charts</title>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

</head>

<body>

<div class="container">

<header>

<h1><i class="fas fa-chart-line"></i> TypeScript + Plotly.js 图表示例</h1>

<p class="subtitle">使用 TypeScript 和 Plotly.js 创建交互式数据可视化</p>

</header>

<main>

<div class="controls">

<button id="randomize-btn" class="btn">

<i class="fas fa-random"></i> 随机生成数据

</button>

<button id="theme-toggle" class="btn">

<i class="fas fa-moon"></i> 切换主题

</button>

</div>

<div class="charts-grid">

<div class="chart-container">

<h2><i class="fas fa-chart-bar"></i> 示例 1: 3D 曲面图</h2>

<div id="surface-plot" class="chart"></div>

<p class="chart-description">

这个 3D 曲面图展示了数学函数 z = sin(x) + cos(y) 的图形。

您可以使用鼠标旋转和缩放视图。

</p>

</div>

<div class="chart-container">

<h2><i class="fas fa-chart-pie"></i> 示例 2: 交互式散点图</h2>

<div id="scatter-plot" class="chart"></div>

<p class="chart-description">

这个散点图展示了三个不同类别(A、B、C)的随机数据分布。

点击图例可以显示/隐藏不同的类别。

</p>

</div>

</div>

<div class="info-section">

<h3><i class="fas fa-info-circle"></i> 项目说明</h3>

<div class="info-grid">

<div class="info-card">

<h4><i class="fas fa-cogs"></i> 技术栈</h4>

<ul>

<li>TypeScript</li>

<li>Plotly.js</li>

<li>Webpack</li>

<li>Webpack Dev Server</li>

</ul>

</div>

<div class="info-card">

<h4><i class="fas fa-mouse-pointer"></i> 交互功能</h4>

<ul>

<li>点击图例切换显示/隐藏数据</li>

<li>鼠标悬停查看数据点详情</li>

<li>3D 图表支持旋转和缩放</li>

<li>主题切换(亮色/暗色)</li>

</ul>

</div>

</div>

</div>

</main>

<footer>

<p>TypeScript + Plotly.js 图表项目 &copy; 2023</p>

<p class="hint">提示:点击上方按钮可以随机生成新数据或切换主题</p>

</footer>

</div>

</body>

</html>

2 第二轮-精简

上述ts代码其实有一些语法错误,而且文件之间引用错误,可能画图需求太多导致。

这里我们尝试让deepseek只生成一个ts文件,不需要画太多图,仅画一个图即可。

2.2 提示词

提示词示例如下

请重写测试例子,画一个图即可,不需要画多个图,不需要index.ts和charts.ts两个,仅需要一个文件index.ts。

2.2 输出

Deepseek输出如下所示,本次仅包括一个index.ts代码,仅画一个图。

然而,vscode验证显示,index.ts有语法错误,所以在Deepseek的辅助下继续优化。

简化版 TypeScript + Plotly.js 图表项目

我将为您创建一个简化的 TypeScript 项目,仅包含一个图表示例,并将所有代码合并到一个 `index.ts` 文件中。

项目结构

```

typescript-plotly-simple/

├── src/

│ ├── index.html

│ ├── index.ts

│ └── styles.css

├── package.json

├── tsconfig.json

├── webpack.config.js

└── README.md

```

文件内容

1. `package.json`

```json

{

"name": "typescript-plotly-simple",

"version": "1.0.0",

"description": "Simple TypeScript project with Plotly.js for data visualization",

"main": "dist/bundle.js",

"scripts": {

"start": "webpack serve --mode development --open",

"build": "webpack --mode production",

"dev": "webpack serve --mode development",

"type-check": "tsc --noEmit"

},

"keywords": ["typescript", "plotly", "charts"],

"author": "Your Name",

"license": "MIT",

"devDependencies": {

"@types/plotly.js": "^2.12.18",

"css-loader": "^6.10.0",

"html-webpack-plugin": "^5.6.0",

"style-loader": "^3.3.4",

"ts-loader": "^9.5.1",

"typescript": "^5.4.5",

"webpack": "^5.91.0",

"webpack-cli": "^5.1.4",

"webpack-dev-server": "^5.0.4"

},

"dependencies": {

"plotly.js-dist-min": "^2.27.0"

}

}

```

2. `tsconfig.json`

```json

{

"compilerOptions": {

"target": "ES2020",

"module": "ESNext",

"lib": ["ES2020", "DOM", "DOM.Iterable"],

"allowJs": true,

"skipLibCheck": true,

"esModuleInterop": true,

"allowSyntheticDefaultImports": true,

"strict": true,

"forceConsistentCasingInFileNames": true,

"moduleResolution": "node",

"resolveJsonModule": true,

"isolatedModules": true,

"noEmit": false,

"outDir": "./dist",

"sourceMap": true,

"declaration": false

},

"include": ["src/**/*"],

"exclude": ["node_modules", "dist"]

}

```

3. `webpack.config.js`

```javascript

const path = require('path');

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = (env, argv) => {

const isProduction = argv.mode === 'production';

return {

entry: './src/index.ts',

output: {

filename: 'bundle.js',

path: path.resolve(__dirname, 'dist'),

clean: true

},

devtool: isProduction ? 'source-map' : 'inline-source-map',

devServer: {

static: './dist',

hot: true,

open: true,

port: 3000

},

module: {

rules: [

{

test: /\.ts$/,

use: 'ts-loader',

exclude: /node_modules/

},

{

test: /\.css$/,

use: ['style-loader', 'css-loader']

}

]

},

resolve: {

extensions: ['.ts', '.js']

},

plugins: [

new HtmlWebpackPlugin({

template: './src/index.html',

title: 'TypeScript + Plotly.js 图表示例'

})

]

};

};

```

4. `src/index.html`

```html

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Plotly.js + TypeScript 图表示例</title>

</head>

<body>

<div class="container">

<header>

<h1>📊 Plotly.js + TypeScript 图表示例</h1>

<p class="subtitle">使用 TypeScript 和 Plotly.js 创建交互式数据可视化</p>

</header>

<div class="controls">

<button id="update-btn" class="btn">🔄 更新图表数据</button>

<button id="theme-btn" class="btn">🌙 切换主题</button>

</div>

<main>

<div class="chart-container">

<h2>📈 股票价格趋势图(模拟数据)</h2>

<div id="chart" class="chart"></div>

<div class="chart-info">

<p>这是一个使用 TypeScript 和 Plotly.js 创建的交互式图表示例。</p>

<ul>

<li>🖱️ 鼠标悬停查看数据点详情</li>

<li>📏 使用工具栏缩放、平移图表</li>

<li>📊 点击图例可显示/隐藏数据系列</li>

<li>🎨 支持亮色/暗色主题切换</li>

</ul>

</div>

</div>

<div class="data-info">

<h3>📋 数据说明</h3>

<div class="data-grid">

<div class="data-card">

<h4>📅 时间范围</h4>

<p>2023年1月 - 2023年12月</p>

</div>

<div class="data-card">

<h4>📊 数据系列</h4>

<p>3个模拟股票数据系列</p>

</div>

<div class="data-card">

<h4>🔄 交互功能</h4>

<p>可点击按钮更新随机数据</p>

</div>

</div>

</div>

</main>

<footer>

<p>使用 TypeScript + Plotly.js 创建 | 示例项目</p>

</footer>

</div>

</body>

</html>

3 第三轮-纠错

3.1 提示词

错误示例如下,直接将错误示例输入Deepseek,没有加额外提示词。

Argument of type '{ x: string[]; y: number[]; name: string; type: "scatter"; mode: string; line: { color: string; width: number; }; marker: { size: number; opacity: number; }; hovertemplate: string; }[]' is not assignable to parameter of type 'Data[]'.

Type '{ x: string[]; y: number[]; name: string; type: "scatter"; mode: string; line: { color: string; width: number; }; marker: { size: number; opacity: number; }; hovertemplate: string; }' is not assignable to type 'Data'.

Type '{ x: string[]; y: number[]; name: string; type: "scatter"; mode: string; line: { color: string; width: number; }; marker: { size: number; opacity: number; }; hovertemplate: string; }' is not assignable to type 'Partial<PlotData>'.

Types of property 'mode' are incompatible.

Type 'string' is not assignable to type '"number" | "lines+markers" | "text" | "delta" | "gauge" | "lines" | "markers" | "text+markers" | "text+lines" | "text+lines+markers" | "none" | "number+delta" | "gauge+number" | "gauge+number+delta" | "gauge+delta" | undefined'.ts(2345)

3.2 输出

Deepseek依据错误信息对index.ts进行纠错,输出如下

这是一个 TypeScript 类型错误。问题在于 `mode` 属性的类型定义不匹配。我们需要明确指定 `mode` 的类型为 Plotly 接受的特定字符串字面量。下面是修复后的 `index.ts` 文件:

```typescript

import './styles.css';

import Plotly from 'plotly.js-dist-min';

// 定义数据接口

interface StockData {

name: string;

color: string;

prices: number[];

dates: string[];

}

// 定义 Plotly 数据接口

interface PlotlyTrace {

x: string[];

y: number[];

name: string;

type: 'scatter';

mode: 'lines' | 'markers' | 'lines+markers' | 'text' | 'text+lines' | 'text+markers' | 'text+lines+markers' | 'none';

line: {

color: string;

width: number;

};

marker: {

size: number;

opacity: number;

};

hovertemplate: string;

}

// 全局变量

let chartData: StockData[] = [];

let isDarkTheme = false;

// 生成随机股票数据

function generateStockData(): StockData[] {

const companies = [

{ name: '科技公司A', color: '#4285f4' },

{ name: '金融公司B', color: '#34a853' },

{ name: '消费品C', color: '#ea4335' }

];

const startDate = new Date(2023, 0, 1); // 2023年1月1日

const months = 12;

return companies.map(company => {

const prices: number[] = [];

const dates: string[] = [];

// 起始价格

let price = 100 + Math.random() * 50;

for (let i = 0; i < months; i++) {

// 添加随机波动

const change = (Math.random() - 0.5) * 20;

price += change;

// 确保价格为正数

price = Math.max(price, 50);

prices.push(Number(price.toFixed(2)));

// 生成日期

const currentDate = new Date(startDate);

currentDate.setMonth(startDate.getMonth() + i);

const month = currentDate.getMonth() + 1;

const year = currentDate.getFullYear();

dates.push(`{year}-{month.toString().padStart(2, '0')}`);

}

return {

name: company.name,

color: company.color,

prices,

dates

};

});

}

// 创建图表

function createChart(): void {

if (chartData.length === 0) {

chartData = generateStockData();

}

// 准备Plotly数据

const plotlyData: PlotlyTrace[] = chartData.map(stock => ({

x: stock.dates,

y: stock.prices,

name: stock.name,

type: 'scatter',

mode: 'lines+markers' as const,

line: {

color: stock.color,

width: 3

},

marker: {

size: 8,

opacity: 0.8

},

hovertemplate: `

<b>${stock.name}</b><br>

日期: %{x}<br>

价格: $%{y:.2f}<br>

<extra></extra>

`

}));

// 准备布局

const isDark = document.body.classList.contains('dark-theme');

const bgColor = isDark ? '#292a2d' : '#ffffff';

const textColor = isDark ? '#e8eaed' : '#202124';

const gridColor = isDark ? '#5f6368' : '#dadce0';

const layout = {

title: {

text: '股票价格趋势 (2023年)',

font: {

size: 22,

color: textColor

}

},

xaxis: {

title: {

text: '月份',

font: {

size: 14,

color: textColor

}

},

tickfont: {

color: textColor

},

gridcolor: gridColor,

showgrid: true,

zeroline: false

},

yaxis: {

title: {

text: '价格 ($)',

font: {

size: 14,

color: textColor

}

},

tickfont: {

color: textColor

},

gridcolor: gridColor,

showgrid: true,

zeroline: false

},

plot_bgcolor: bgColor,

paper_bgcolor: bgColor,

font: {

color: textColor

},

margin: {

l: 60,

r: 30,

b: 60,

t: 80

},

legend: {

x: 0.01,

y: 0.99,

bgcolor: 'rgba(255, 255, 255, 0.05)',

bordercolor: gridColor,

borderwidth: 1,

font: {

color: textColor

}

},

hovermode: 'closest',

showlegend: true,

autosize: true

};

// 配置选项

const config = {

responsive: true,

displayModeBar: true,

displaylogo: false,

modeBarButtonsToRemove: ['lasso2d', 'select2d'],

toImageButtonOptions: {

format: 'png',

filename: 'stock-chart',

height: 600,

width: 1000,

scale: 2

}

};

// 绘制图表

Plotly.newPlot('chart', plotlyData, layout, config);

}

// 更新图表数据

function updateChart(): void {

chartData = generateStockData();

createChart();

// 显示更新提示

const message = document.createElement('div');

message.textContent = '图表数据已更新!';

message.style.cssText = `

position: fixed;

top: 20px;

right: 20px;

background-color: var(--secondary-color);

color: white;

padding: 12px 20px;

border-radius: 8px;

z-index: 1000;

animation: fadeInOut 2.5s ease-in-out;

`;

document.body.appendChild(message);

// 添加动画样式

if (!document.querySelector('#fade-animation')) {

const style = document.createElement('style');

style.id = 'fade-animation';

style.textContent = `

@keyframes fadeInOut {

0% { opacity: 0; transform: translateY(-20px); }

20% { opacity: 1; transform: translateY(0); }

80% { opacity: 1; transform: translateY(0); }

100% { opacity: 0; transform: translateY(-20px); }

}

`;

document.head.appendChild(style);

}

setTimeout(() => {

if (message.parentNode) {

message.parentNode.removeChild(message);

}

}, 2500);

}

// 切换主题

function toggleTheme(): void {

isDarkTheme = !isDarkTheme;

if (isDarkTheme) {

document.body.classList.add('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '☀️ 浅色主题';

} else {

document.body.classList.remove('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '🌙 深色主题';

}

// 重新创建图表以应用新主题

createChart();

}

// 初始化页面

function init(): void {

// 创建初始图表

createChart();

// 添加按钮事件监听器

document.getElementById('update-btn')!.addEventListener('click', updateChart);

document.getElementById('theme-btn')!.addEventListener('click', toggleTheme);

// 检查是否偏好暗色主题

const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');

isDarkTheme = prefersDark.matches;

if (isDarkTheme) {

document.body.classList.add('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '☀️ 浅色主题';

}

// 监听系统主题变化

prefersDark.addEventListener('change', (e) => {

isDarkTheme = e.matches;

if (isDarkTheme) {

document.body.classList.add('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '☀️ 浅色主题';

} else {

document.body.classList.remove('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '🌙 深色主题';

}

createChart();

});

// 窗口大小变化时重新调整图表

window.addEventListener('resize', () => {

const chartElement = document.getElementById('chart');

if (chartElement) {

Plotly.Plots.resize(chartElement);

}

});

// 添加一些交互效果

const buttons = document.querySelectorAll('.btn');

buttons.forEach(button => {

button.addEventListener('mousedown', function(this: HTMLElement) {

this.style.transform = 'scale(0.95)';

});

button.addEventListener('mouseup', function(this: HTMLElement) {

this.style.transform = '';

});

button.addEventListener('mouseleave', function(this: HTMLElement) {

this.style.transform = '';

});

});

console.log('图表应用已初始化完成!');

}

// 当DOM加载完成后初始化应用

document.addEventListener('DOMContentLoaded', init);

```

主要修复了以下内容:

  1. 创建了 `PlotlyTrace` 接口,明确指定了 `mode` 属性的类型为 Plotly 接受的特定字符串字面量联合类型

  2. 将 `plotlyData` 的类型明确指定为 `PlotlyTrace[]`

  3. 使用 `as const` 断言来确保 `mode: 'lines+markers'` 被视为字面量类型而不是字符串类型

  4. 添加了一些类型断言来确保 TypeScript 编译器不会报错

现在 TypeScript 应该能够正确识别 `mode` 属性的类型,并且能够将数据正确传递给 Plotly.js 的 `newPlot` 函数。

4 第四轮-继续纠错

4.1 提示词

经过上一轮纠错后,依然有错误信息,如下所示,直接作为提示词输入Deepseek。

Argument of type '{ title: { text: string; font: { size: number; color: string; }; }; xaxis: { title: { text: string; font: { size: number; color: string; }; }; tickfont: { color: string; }; gridcolor: string; showgrid: boolean; zeroline: boolean; }; ... 8 more ...; autosize: boolean; }' is not assignable to parameter of type 'Partial<Layout>'.

Types of property 'hovermode' are incompatible.

Type 'string' is not assignable to type 'false | "closest" | "x" | "y" | "x unified" | "y unified" | undefined'.ts(2345)

4.2 输出

以下是Deepseek依据错误信息,进一步对index.ts纠错结果,包括index.ts文件本身。

这是另一个 TypeScript 类型错误。问题在于 `hovermode` 属性的类型定义不匹配。我们需要明确指定 `hovermode` 的类型为 Plotly 接受的特定字符串字面量。下面是修复后的 `index.ts` 文件:

```typescript

import './styles.css';

import Plotly from 'plotly.js-dist-min';

// 定义数据接口

interface StockData {

name: string;

color: string;

prices: number[];

dates: string[];

}

// 定义 Plotly 数据接口

interface PlotlyTrace {

x: string[];

y: number[];

name: string;

type: 'scatter';

mode: 'lines' | 'markers' | 'lines+markers' | 'text' | 'text+lines' | 'text+markers' | 'text+lines+markers' | 'none';

line: {

color: string;

width: number;

};

marker: {

size: number;

opacity: number;

};

hovertemplate: string;

}

// 定义 Plotly 布局接口

interface PlotlyLayout {

title: {

text: string;

font: {

size: number;

color: string;

};

};

xaxis: {

title: {

text: string;

font: {

size: number;

color: string;

};

};

tickfont: {

color: string;

};

gridcolor: string;

showgrid: boolean;

zeroline: boolean;

};

yaxis: {

title: {

text: string;

font: {

size: number;

color: string;

};

};

tickfont: {

color: string;

};

gridcolor: string;

showgrid: boolean;

zeroline: boolean;

};

plot_bgcolor: string;

paper_bgcolor: string;

font: {

color: string;

};

margin: {

l: number;

r: number;

b: number;

t: number;

};

legend: {

x: number;

y: number;

bgcolor: string;

bordercolor: string;

borderwidth: number;

font: {

color: string;

};

};

hovermode: 'closest' | 'x' | 'y' | 'x unified' | 'y unified' | false;

showlegend: boolean;

autosize: boolean;

}

// 全局变量

let chartData: StockData[] = [];

let isDarkTheme = false;

// 生成随机股票数据

function generateStockData(): StockData[] {

const companies = [

{ name: '科技公司A', color: '#4285f4' },

{ name: '金融公司B', color: '#34a853' },

{ name: '消费品C', color: '#ea4335' }

];

const startDate = new Date(2023, 0, 1); // 2023年1月1日

const months = 12;

return companies.map(company => {

const prices: number[] = [];

const dates: string[] = [];

// 起始价格

let price = 100 + Math.random() * 50;

for (let i = 0; i < months; i++) {

// 添加随机波动

const change = (Math.random() - 0.5) * 20;

price += change;

// 确保价格为正数

price = Math.max(price, 50);

prices.push(Number(price.toFixed(2)));

// 生成日期

const currentDate = new Date(startDate);

currentDate.setMonth(startDate.getMonth() + i);

const month = currentDate.getMonth() + 1;

const year = currentDate.getFullYear();

dates.push(`{year}-{month.toString().padStart(2, '0')}`);

}

return {

name: company.name,

color: company.color,

prices,

dates

};

});

}

// 创建图表

function createChart(): void {

if (chartData.length === 0) {

chartData = generateStockData();

}

// 准备Plotly数据

const plotlyData: PlotlyTrace[] = chartData.map(stock => ({

x: stock.dates,

y: stock.prices,

name: stock.name,

type: 'scatter',

mode: 'lines+markers' as const,

line: {

color: stock.color,

width: 3

},

marker: {

size: 8,

opacity: 0.8

},

hovertemplate: `

<b>${stock.name}</b><br>

日期: %{x}<br>

价格: $%{y:.2f}<br>

<extra></extra>

`

}));

// 准备布局

const isDark = document.body.classList.contains('dark-theme');

const bgColor = isDark ? '#292a2d' : '#ffffff';

const textColor = isDark ? '#e8eaed' : '#202124';

const gridColor = isDark ? '#5f6368' : '#dadce0';

const layout: Partial<Plotly.Layout> & { hovermode: 'closest' | 'x' | 'y' | 'x unified' | 'y unified' | false } = {

title: {

text: '股票价格趋势 (2023年)',

font: {

size: 22,

color: textColor

}

},

xaxis: {

title: {

text: '月份',

font: {

size: 14,

color: textColor

}

},

tickfont: {

color: textColor

},

gridcolor: gridColor,

showgrid: true,

zeroline: false

},

yaxis: {

title: {

text: '价格 ($)',

font: {

size: 14,

color: textColor

}

},

tickfont: {

color: textColor

},

gridcolor: gridColor,

showgrid: true,

zeroline: false

},

plot_bgcolor: bgColor,

paper_bgcolor: bgColor,

font: {

color: textColor

},

margin: {

l: 60,

r: 30,

b: 60,

t: 80

},

legend: {

x: 0.01,

y: 0.99,

bgcolor: 'rgba(255, 255, 255, 0.05)',

bordercolor: gridColor,

borderwidth: 1,

font: {

color: textColor

}

},

hovermode: 'closest' as const,

showlegend: true,

autosize: true

};

// 配置选项

const config = {

responsive: true,

displayModeBar: true,

displaylogo: false,

modeBarButtonsToRemove: ['lasso2d', 'select2d'],

toImageButtonOptions: {

format: 'png',

filename: 'stock-chart',

height: 600,

width: 1000,

scale: 2

}

};

// 绘制图表

Plotly.newPlot('chart', plotlyData as any, layout, config);

}

// 更新图表数据

function updateChart(): void {

chartData = generateStockData();

createChart();

// 显示更新提示

const message = document.createElement('div');

message.textContent = '图表数据已更新!';

message.style.cssText = `

position: fixed;

top: 20px;

right: 20px;

background-color: var(--secondary-color);

color: white;

padding: 12px 20px;

border-radius: 8px;

z-index: 1000;

animation: fadeInOut 2.5s ease-in-out;

`;

document.body.appendChild(message);

// 添加动画样式

if (!document.querySelector('#fade-animation')) {

const style = document.createElement('style');

style.id = 'fade-animation';

style.textContent = `

@keyframes fadeInOut {

0% { opacity: 0; transform: translateY(-20px); }

20% { opacity: 1; transform: translateY(0); }

80% { opacity: 1; transform: translateY(0); }

100% { opacity: 0; transform: translateY(-20px); }

}

`;

document.head.appendChild(style);

}

setTimeout(() => {

if (message.parentNode) {

message.parentNode.removeChild(message);

}

}, 2500);

}

// 切换主题

function toggleTheme(): void {

isDarkTheme = !isDarkTheme;

if (isDarkTheme) {

document.body.classList.add('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '☀️ 浅色主题';

} else {

document.body.classList.remove('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '🌙 深色主题';

}

// 重新创建图表以应用新主题

createChart();

}

// 初始化页面

function init(): void {

// 创建初始图表

createChart();

// 添加按钮事件监听器

document.getElementById('update-btn')!.addEventListener('click', updateChart);

document.getElementById('theme-btn')!.addEventListener('click', toggleTheme);

// 检查是否偏好暗色主题

const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');

isDarkTheme = prefersDark.matches;

if (isDarkTheme) {

document.body.classList.add('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '☀️ 浅色主题';

}

// 监听系统主题变化

prefersDark.addEventListener('change', (e) => {

isDarkTheme = e.matches;

if (isDarkTheme) {

document.body.classList.add('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '☀️ 浅色主题';

} else {

document.body.classList.remove('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '🌙 深色主题';

}

createChart();

});

// 窗口大小变化时重新调整图表

window.addEventListener('resize', () => {

const chartElement = document.getElementById('chart');

if (chartElement) {

Plotly.Plots.resize(chartElement);

}

});

// 添加一些交互效果

const buttons = document.querySelectorAll('.btn');

buttons.forEach(button => {

button.addEventListener('mousedown', function(this: HTMLElement) {

this.style.transform = 'scale(0.95)';

});

button.addEventListener('mouseup', function(this: HTMLElement) {

this.style.transform = '';

});

button.addEventListener('mouseleave', function(this: HTMLElement) {

this.style.transform = '';

});

});

console.log('图表应用已初始化完成!');

}

// 当DOM加载完成后初始化应用

document.addEventListener('DOMContentLoaded', init);

```

主要修复了以下内容:

  1. 创建了 `PlotlyLayout` 接口,明确指定了 `hovermode` 属性的类型为 Plotly 接受的特定字符串字面量联合类型

  2. 将 `layout` 的类型声明为 `Partial<Plotly.Layout> & { hovermode: 'closest' | 'x' | 'y' | 'x unified' | 'y unified' | false }`,这样既可以使用 Plotly 的类型定义,又可以明确指定 `hovermode` 的类型

  3. 使用 `as const` 断言来确保 `hovermode: 'closest'` 被视为字面量类型而不是字符串类型

  4. 在调用 `Plotly.newPlot` 时,将 `plotlyData` 转换为 `any` 类型,以绕过 TypeScript 的严格类型检查(这是一种简化处理方式)

现在 TypeScript 应该能够正确识别 `hovermode` 属性的类型,并且能够将布局正确传递给 Plotly.js 的 `newPlot` 函数。

5 第五轮-继续纠错

5.1 提示词

然而,依然还有错误信息,那就进一步纠错,提示词输入如下所示。

Argument of type '{ responsive: boolean; displayModeBar: boolean; displaylogo: boolean; modeBarButtonsToRemove: string[]; toImageButtonOptions: { format: string; filename: string; height: number; width: number; scale: number; }; }' is not assignable to parameter of type 'Partial<Config>'.

Types of property 'modeBarButtonsToRemove' are incompatible.

Type 'string[]' is not assignable to type 'ModeBarDefaultButtons[]'.

Type 'string' is not assignable to type 'ModeBarDefaultButtons'.ts(2345)

5.2 输出

Deepseek依据错误信息对index.ts进一步纠错,输入如下,此时,vscode不在提示错误信息。

这是另一个 TypeScript 类型错误。问题在于 `modeBarButtonsToRemove` 属性的类型定义不匹配。我们需要明确指定 `modeBarButtonsToRemove` 的类型为 Plotly 接受的特定字符串字面量数组。下面是修复后的 `index.ts` 文件:

```typescript

import './styles.css';

import Plotly from 'plotly.js-dist-min';

// 定义数据接口

interface StockData {

name: string;

color: string;

prices: number[];

dates: string[];

}

// 定义 Plotly 数据接口

interface PlotlyTrace {

x: string[];

y: number[];

name: string;

type: 'scatter';

mode: 'lines' | 'markers' | 'lines+markers' | 'text' | 'text+lines' | 'text+markers' | 'text+lines+markers' | 'none';

line: {

color: string;

width: number;

};

marker: {

size: number;

opacity: number;

};

hovertemplate: string;

}

// 全局变量

let chartData: StockData[] = [];

let isDarkTheme = false;

// 生成随机股票数据

function generateStockData(): StockData[] {

const companies = [

{ name: '科技公司A', color: '#4285f4' },

{ name: '金融公司B', color: '#34a853' },

{ name: '消费品C', color: '#ea4335' }

];

const startDate = new Date(2023, 0, 1); // 2023年1月1日

const months = 12;

return companies.map(company => {

const prices: number[] = [];

const dates: string[] = [];

// 起始价格

let price = 100 + Math.random() * 50;

for (let i = 0; i < months; i++) {

// 添加随机波动

const change = (Math.random() - 0.5) * 20;

price += change;

// 确保价格为正数

price = Math.max(price, 50);

prices.push(Number(price.toFixed(2)));

// 生成日期

const currentDate = new Date(startDate);

currentDate.setMonth(startDate.getMonth() + i);

const month = currentDate.getMonth() + 1;

const year = currentDate.getFullYear();

dates.push(`{year}-{month.toString().padStart(2, '0')}`);

}

return {

name: company.name,

color: company.color,

prices,

dates

};

});

}

// 创建图表

function createChart(): void {

if (chartData.length === 0) {

chartData = generateStockData();

}

// 准备Plotly数据

const plotlyData: PlotlyTrace[] = chartData.map(stock => ({

x: stock.dates,

y: stock.prices,

name: stock.name,

type: 'scatter',

mode: 'lines+markers' as const,

line: {

color: stock.color,

width: 3

},

marker: {

size: 8,

opacity: 0.8

},

hovertemplate: `

<b>${stock.name}</b><br>

日期: %{x}<br>

价格: $%{y:.2f}<br>

<extra></extra>

`

}));

// 准备布局

const isDark = document.body.classList.contains('dark-theme');

const bgColor = isDark ? '#292a2d' : '#ffffff';

const textColor = isDark ? '#e8eaed' : '#202124';

const gridColor = isDark ? '#5f6368' : '#dadce0';

const layout: Partial<Plotly.Layout> & { hovermode: 'closest' | 'x' | 'y' | 'x unified' | 'y unified' | false } = {

title: {

text: '股票价格趋势 (2023年)',

font: {

size: 22,

color: textColor

}

},

xaxis: {

title: {

text: '月份',

font: {

size: 14,

color: textColor

}

},

tickfont: {

color: textColor

},

gridcolor: gridColor,

showgrid: true,

zeroline: false

},

yaxis: {

title: {

text: '价格 ($)',

font: {

size: 14,

color: textColor

}

},

tickfont: {

color: textColor

},

gridcolor: gridColor,

showgrid: true,

zeroline: false

},

plot_bgcolor: bgColor,

paper_bgcolor: bgColor,

font: {

color: textColor

},

margin: {

l: 60,

r: 30,

b: 60,

t: 80

},

legend: {

x: 0.01,

y: 0.99,

bgcolor: 'rgba(255, 255, 255, 0.05)',

bordercolor: gridColor,

borderwidth: 1,

font: {

color: textColor

}

},

hovermode: 'closest' as const,

showlegend: true,

autosize: true

};

// 配置选项 - 使用更简单的配置避免类型问题

const config: Partial<Plotly.Config> = {

responsive: true,

displayModeBar: true,

displaylogo: false,

// 使用类型断言来避免复杂的类型检查

modeBarButtonsToRemove: ['lasso2d', 'select2d'] as any,

toImageButtonOptions: {

format: 'png',

filename: 'stock-chart',

height: 600,

width: 1000,

scale: 2

}

};

// 绘制图表 - 使用类型断言来避免复杂的类型检查

Plotly.newPlot('chart', plotlyData as any, layout as any, config);

}

// 更新图表数据

function updateChart(): void {

chartData = generateStockData();

createChart();

// 显示更新提示

const message = document.createElement('div');

message.textContent = '图表数据已更新!';

message.style.cssText = `

position: fixed;

top: 20px;

right: 20px;

background-color: var(--secondary-color);

color: white;

padding: 12px 20px;

border-radius: 8px;

z-index: 1000;

animation: fadeInOut 2.5s ease-in-out;

`;

document.body.appendChild(message);

// 添加动画样式

if (!document.querySelector('#fade-animation')) {

const style = document.createElement('style');

style.id = 'fade-animation';

style.textContent = `

@keyframes fadeInOut {

0% { opacity: 0; transform: translateY(-20px); }

20% { opacity: 1; transform: translateY(0); }

80% { opacity: 1; transform: translateY(0); }

100% { opacity: 0; transform: translateY(-20px); }

}

`;

document.head.appendChild(style);

}

setTimeout(() => {

if (message.parentNode) {

message.parentNode.removeChild(message);

}

}, 2500);

}

// 切换主题

function toggleTheme(): void {

isDarkTheme = !isDarkTheme;

if (isDarkTheme) {

document.body.classList.add('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '☀️ 浅色主题';

} else {

document.body.classList.remove('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '🌙 深色主题';

}

// 重新创建图表以应用新主题

createChart();

}

// 初始化页面

function init(): void {

// 创建初始图表

createChart();

// 添加按钮事件监听器

document.getElementById('update-btn')!.addEventListener('click', updateChart);

document.getElementById('theme-btn')!.addEventListener('click', toggleTheme);

// 检查是否偏好暗色主题

const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');

isDarkTheme = prefersDark.matches;

if (isDarkTheme) {

document.body.classList.add('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '☀️ 浅色主题';

}

// 监听系统主题变化

prefersDark.addEventListener('change', (e) => {

isDarkTheme = e.matches;

if (isDarkTheme) {

document.body.classList.add('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '☀️ 浅色主题';

} else {

document.body.classList.remove('dark-theme');

(document.querySelector('#theme-btn') as HTMLElement)!.innerHTML = '🌙 深色主题';

}

createChart();

});

// 窗口大小变化时重新调整图表

window.addEventListener('resize', () => {

const chartElement = document.getElementById('chart');

if (chartElement) {

Plotly.Plots.resize(chartElement);

}

});

// 添加一些交互效果

const buttons = document.querySelectorAll('.btn');

buttons.forEach(button => {

button.addEventListener('mousedown', function(this: HTMLElement) {

this.style.transform = 'scale(0.95)';

});

button.addEventListener('mouseup', function(this: HTMLElement) {

this.style.transform = '';

});

button.addEventListener('mouseleave', function(this: HTMLElement) {

this.style.transform = '';

});

});

console.log('图表应用已初始化完成!');

}

// 当DOM加载完成后初始化应用

document.addEventListener('DOMContentLoaded', init);

```

主要修复了以下内容:

  1. 将 `config` 对象明确指定为 `Partial<Plotly.Config>` 类型

  2. 对 `modeBarButtonsToRemove` 属性使用 `as any` 类型断言,绕过 TypeScript 的严格类型检查

  3. 在调用 `Plotly.newPlot` 时,将 `plotlyData` 和 `layout` 转换为 `any` 类型,以简化类型问题

这些修复使用了类型断言 (`as any`) 来避免与 Plotly.js 复杂类型定义的不匹配。虽然这不是最理想的类型安全解决方案,但它允许我们继续使用简化的项目结构,而不必处理 Plotly.js 复杂且可能不完整的 TypeScript 类型定义。

如果你希望保持更高的类型安全性,可以安装完整的 `plotly.js` 库(而不是 `plotly.js-dist-min`),并使用其完整的类型定义。但这样会增加包大小。

现在,TypeScript 应该能够编译代码,并且图表应该能正常运行。

6 最后-运行验证

6.1 验证指令

依据生成README.md等文件给出的指示,在vscode的term窗口运行如下指令。

npm run dev

6.2 系统输出

此时,系统自主启动一个网页,示例plotly.js的绘图效果。

而且可以更新数据和更改主题颜色,示例如下

reference


如何在jupyter中使用typescript基于plotly.js画图

https://blog.csdn.net/liliang199/article/details/157908193

相关推荐
mCell5 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell6 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
九.九6 小时前
ops-transformer:AI 处理器上的高性能 Transformer 算子库
人工智能·深度学习·transformer
春日见6 小时前
拉取与合并:如何让个人分支既包含你昨天的修改,也包含 develop 最新更新
大数据·人工智能·深度学习·elasticsearch·搜索引擎
恋猫de小郭6 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清6 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
deephub6 小时前
Agent Lightning:微软开源的框架无关 Agent 训练方案,LangChain/AutoGen 都能用
人工智能·microsoft·langchain·大语言模型·agent·强化学习
银烛木6 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076606 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声6 小时前
CSS3 图片模糊处理
前端·css·css3