关于chartjs的简单使用,各位大佬有知道什么办法通过js设置图表的宽高嘛

上阶段接到一个新需求,大模型流式输出过程中,要求把部分表格数据用图表展示,很离谱的是,产品叫前端自己从非结构化数据自己抓数据来渲染,然后数据还是流式输出的markdown格式文本,离谱归离谱,也得干啊,事先给他说了,不确定因素很多,可能十个问题只有一个才能触发返回图表格式文本

话不多说,直接贴代码

vue 复制代码
import { Chart, registerables } from 'chart.js';
import ChartDataLabels from 'chartjs-plugin-datalabels';
const renderedMessages = computed(() => {
  return messages.value.map((message,index) => {
    if (message.type === "robot") {
      // 正则表达式匹配表格
      const tableRegex = /(\|.*?\|\n)+/g;
      // 正则表达式匹配注释内容
      // const noteRegex = /\s*\*\*(.*?)\*\*/;
      // 检测是否存在多个表格
      const tableMatches = message.output.match(tableRegex);
      // 提取注释内容
      if (tableMatches && tableMatches.length > 1) {
        console.log("存在多个表格,跳过图表渲染");
        return renderedMarkdown(message.output);
      }
      
      // 检查是否已经绘制过图表且包含表格结构
      if (!message.chartRendered && tableRegex.test(message.output)) {
        try {
          // 提取表格部分
          const tableMatch = message.output.match(tableRegex);
          if (tableMatch) {
            const tableContent = tableMatch.join(''); // 合并所有匹配的表格内容
            // const noteMatch = message.output.match(noteRegex);
            // let noteContent = '';
            // // 把包含"data:"的部分替换成""
            // // const cleanedOutput = message.output.replace(/data:\s*/g, '');
            // if (noteMatch) {
            //   noteContent = noteMatch[1]; // 获取注释内容
            //   console.log("注释内容noteContent",noteContent)
            // }
            // 将表格内容按行分割
            const lines = tableContent.split('\n').map(line => line.trim()).filter(line => line !== '');
            
            // 提取表头
            const headers = lines[0].split('|').map(header => header.trim()).filter(header => header !== '');
            
            // 判断列数是否小于等于三列
            if (headers.length <= 3) {
              const labelIndex = headers.findIndex(header => header.includes('当前月份'));
              const dataIndex = headers.findIndex(header => header.includes('当前水量'));
              const schoolIndex = headers.findIndex(header => header.includes('学校'));
              if (labelIndex < 0 || dataIndex < 0 || schoolIndex < 0) {
                console.log("表头不包含 '当前月份' 或 '当前水量'");
                return renderedMarkdown(message.output); // 返回原始 Markdown 渲染
              }
              
              // 提取数据行
              const dataRows = lines.slice(2).map(row => {
                return row.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
              });
              
              // 提取指定列的数据
              const labels = dataRows.map(row => row[labelIndex]); // 包含"当前月份"的列作为 X 轴标签
              const data = dataRows.map(row => {
                const value = row[dataIndex];
                return value === 'nan%' ? 0 : parseFloat(value); // 处理 'nan%' 的情况
              }); // 包含"当前水量"的列作为 Y 轴数据
              
              // 组装 chartJson 结构
              const chartJson = {
                labels: labels,
                datasets: [
                  {
                    data: data,
                    // order属性用于控制数据集的绘制顺序 值越小,越先绘制
                    // order:2,
                    // barThickness: (context: any) => {
                    //   // 根据数据点的数量动态调整柱子宽度
                    //   const numDataPoints = context.chart.data.labels.length;
                    //   return Math.max(30, 80 / numDataPoints);
                    // }
                  },
                  // {
                  //   type: 'line', // 指定类型为折线图
                  //   data: data,
                  //   order:1,
                  //   borderColor: "rgba(255, 99, 132, 1)", // 折线图的颜色
                  //   // fill: false, // 不填充折线图下方的区域
                  //   // tension: 0.1 // 控制折线的平滑度
                  // }
                ],
              };
              
              console.log("chartJson:", chartJson);
              const canvasElement = renderChart(chartJson, message.uid || index);
              // @ts-ignore
              const canvasHTML = canvasElement.outerHTML;
              
              const renderedMarkdownText = renderedMarkdown(message.output); // 先渲染原有 Markdown
              const replacedText = canvasHTML ? `${renderedMarkdownText}<div>${canvasHTML}</div>` : renderedMarkdownText;
              message.chartRendered = true; // 添加标识符
              // setTimeout 来延迟执行代码,以确保 DOM 元素已经渲染。
              setTimeout(() => {
                const targetSpan = document.querySelector(`[data-id="unique-${message.uid}"]`);
                if (targetSpan) {
                  const table = targetSpan.querySelector('table');
                  if (table) {
                    table.style.display = 'none';
                  }
                }
              }, 50);
              
              return replacedText; // 返回包含图表的内容
            }
          }
        } catch (error) {
          console.error("渲染图表时出错:", error);
          return renderedMarkdown(message.output); // 返回原始 Markdown 渲染
        }
      }
      return renderedMarkdown(message.output); // 保留原有的 Markdown 渲染
    }
    return message.output; // 用户消息直接返回
  });
});

// 渲染图表的函数
const renderChart = async(chartData:any,id: string | number) => {
  const canvasId = `chart-${Date.now()}`;
  const divEle = document.createElement('div');
  divEle.style.minWidth = '340px';
  divEle.style.minHeight = '340px';
  const canvasElement = document.createElement('canvas');
  canvasElement.id = canvasId;
  // canvasElement.width = 400;
  // canvasElement.height = 400;
  // chartjs-plugin-zoom:支持图表的缩放功能
  // 在下一个 tick 中渲染图表
  await nextTick(() => {
    const ctx = canvasElement.getContext('2d');
    if (!ctx) {
      console.error("无法获取 Canvas 上下文");
      return;
    }
    new Chart(ctx, {
      type: 'bar', // 或 'line',根据需要选择
      data: chartData,
      options: {
        aspectRatio: 1,
        responsive: true,
        maintainAspectRatio: false,
        // 柱状图背景色
        backgroundColor:["rgba(54, 162, 235, 0.7)"],
        plugins: {
          // 隐藏图例
          legend: {
            display: false,
          },
          tooltip: {
            enabled: false,
          },
          // 自定义插件来显示数据值
          datalabels: {
            // 用于设置数据标签的锚点位置,即数据标签相对于数据点的相对位置。
            anchor: (context) => {
              // 当前数据集的索引 等于0通常是柱状图
              const datasetIndex = context.datasetIndex;
              return datasetIndex === 0 ? 'end' : 'start';
            },
            // 用于设置数据标签的对齐方式,即数据标签相对于锚点的对齐方向。
            align: (context) => {
              const datasetIndex = context.datasetIndex;
              return datasetIndex === 0 ? 'end' : 'start'; // 柱形图在顶部,折线图在顶部
            },
            // anchor: 'end',
            // align: 'end',
            // 用于自定义标签的格式
            formatter: (value,context:any) => {
              // console.log("context.dataset.type",context.dataset.type);
              // 混合图表 只显示一个数据标签
              // if (context.dataset.type == 'line') {
              //   return "";
              // }
              return value; // 返回要显示的值
            },
          }
        },
        scales: {
          x: {
            ticks: {
              autoSkip: false, // 禁用自动跳过标签
              maxRotation: 90, // 标签旋转角度
              minRotation: 45,
            },
          },
          y: {
            beginAtZero: true,
            title: {
              display: true,
              text: '用水量', // 注意:这种方法通常用于显示Y轴的全局标题,而非顶部单独的文本标签。
              font: {
                size: 16  // 设置字体大小等属性
              }
            },
          },
        },
      },
    });
  });
  // 设置图表大小
  // myChart.canvas.width = 600;
  // myChart.canvas.height = 400;
  // 将 canvas 元素插入到指定的 <span> 元素中
  const targetSpan = document.querySelector(`[data-id="unique-${id}"]`);
  if (targetSpan) {
    divEle.appendChild(canvasElement)
    targetSpan.appendChild(divEle);
  }
  return canvasElement;
};

// 在组件挂载时触发搜索
onMounted(() => {
  // 注册chatjs
  Chart.register(...registerables);
  // 注册显示数据集插件
  Chart.register(ChartDataLabels);
  // renderChart1();
});

实现图表渲染的关键在renderChart函数,因为是大模型多轮对话输出markdown格式文本,前端用v-html处理渲染,不确定性因素很多,不适合直接用html标签创建图表,所以需要通过js创建,其他倒没什么,可以参考官方文档或者网上搜一下

过程中遇到一个非常烦的问题,就是需要适配移动端,用手机打开网页的时候,图表被压缩,因为canvas不能通过css直接设置宽高,其实也可以设置,就是会被压缩画质,然后 canvasElement.width = 400;canvasElement.height = 400;设置宽高居然不起作用,看chartjs官方文档也没找到设置宽高的方法,不知道各位大佬有没有好的办法,没办法最后只能通过给canvas父级元素加个div元素,通过给父元素设置宽高才解决问题

相关推荐
喝拿铁写前端23 分钟前
从列表页到规则引擎:一个组件封装过程中的前端认知进阶
前端·vue.js·架构
龙萌酱1 小时前
力扣每日打卡17 49. 字母异位词分组 (中等)
前端·javascript·算法·leetcode
jingling5551 小时前
前端开发核心知识详解:Vue2、JavaScript 与 CSS
javascript·css·vue.js
AronTing2 小时前
单例模式:确保唯一实例的设计模式
java·javascript·后端
工业互联网专业2 小时前
基于springboot+vue的医院管理系统
java·vue.js·spring boot·毕业设计·源码·课程设计·医院管理系统
H5开发新纪元2 小时前
我是如何用Cursor在10分钟内实现项目管理Mock方案的
前端·vue.js
禾下月2 小时前
vue 使用全屏插件screenfull.js的用法(解决全屏状态不能监听Esc按键)
前端·vue.js
Cutey9162 小时前
Vue 中的高阶函数:提升代码复用与组件抽象的利器
前端·javascript·面试
Bunury3 小时前
element-plus添加暗黑模式
开发语言·前端·javascript