初学者快速入门:使用JavaScript简单实现条形图,饼图,折线图,玫瑰图,散点图

目录

数据结构设计

工具函数

图表实现细节

1. 条形图(Bar Chart)

2. 饼图(Pie Chart)

3. 玫瑰图(Rose Chart)

4. 折线图(Line Chart)

5. 散点图(Scatter Chart)

响应式处理

样式设计


整体架构

代码使用纯 JavaScript 和 Canvas API 实现了五种基本图表,采用模块化设计:

  • 图表数据统一存储在data对象中
  • 使用ChartUtils工具类封装通用方法
  • 每种图表有独立的绘制函数
  • 添加了响应式处理,支持窗口大小变化时重绘

数据结构设计

const data = {
    labels: ['一月', '二月', '三月', '四月', '五月', '六月'],
    values: [65, 59, 80, 81, 56, 55],
    colors: ['#3B82F6', '#6366F1', '#8B5CF6', '#A855F7', '#C026D3', '#DB2777'],
    scatterData: Array.from({length: 20}, () => ({
        x: Math.random() * 300,
        y: Math.random() * 200,
        r: Math.random() * 10 + 5
    }))
};
  • labelsvalues用于大多数图表
  • colors定义了图表的配色方案
  • scatterData为散点图生成随机数据点

工具函数

const ChartUtils = {
    getColor: (index) => data.colors[index % data.colors.length],
    drawLabel: (ctx, text, x, y, align = 'center') => {
        ctx.save();
        ctx.font = '12px Inter';
        ctx.textAlign = align;
        ctx.textBaseline = 'middle';
        ctx.fillText(text, x, y);
        ctx.restore();
    }
};

  • getColor:循环使用预设颜色
  • drawLabel:封装文本绘制逻辑,保存 / 恢复上下文状态避免样式冲突

图表实现细节

1. 条形图(Bar Chart)
function drawBarChart() {
    // 计算边距和坐标系
    const margin = {top: 20, right: 20, bottom: 40, left: 40};
    const width = barChart.width - margin.left - margin.right;
    const height = barChart.height - margin.top - margin.bottom;
    const barWidth = width / data.labels.length * 0.7;
    const maxValue = Math.max(...data.values) * 1.1;
    
    // 绘制坐标轴
    barCtx.beginPath();
    barCtx.moveTo(margin.left, margin.top);
    barCtx.lineTo(margin.left, margin.top + height);
    barCtx.lineTo(margin.left + width, margin.top + height);
    barCtx.strokeStyle = '#ccc';
    barCtx.stroke();
    
    // 绘制条形和标签
    data.values.forEach((value, index) => {
        // 计算条形位置和高度
        const barHeight = (value / maxValue) * height;
        const x = margin.left + (width / data.labels.length) * index + (width / data.labels.length - barWidth) / 2;
        const y = margin.top + height - barHeight;
        
        // 绘制条形
        barCtx.fillStyle = ChartUtils.getColor(index);
        barCtx.fillRect(x, y, barWidth, barHeight);
        
        // 绘制标签
        ChartUtils.drawLabel(barCtx, value, x + barWidth / 2, y - 10);
        ChartUtils.drawLabel(barCtx, data.labels[index], x + barWidth / 2, margin.top + height + 20);
    });
}
  • 关键计算:根据最大值缩放高度,使用边距留出标签空间
  • 坐标轴绘制:使用 Canvas 路径绘制简单的 XY 轴
  • 数据可视化:通过矩形高度表示数值大小

代码效果展示:

2. 饼图(Pie Chart)
function drawPieChart() {
    // 计算中心点和半径
    const centerX = pieChart.width / 2;
    const centerY = pieChart.height / 2;
    const radius = Math.min(centerX, centerY) * 0.7;
    const total = data.values.reduce((a, b) => a + b, 0);
    let startAngle = 0;
    
    // 绘制扇形
    data.values.forEach((value, index) => {
        const sliceAngle = (value / total) * Math.PI * 2;
        const endAngle = startAngle + sliceAngle;
        
        // 绘制扇形
        pieCtx.beginPath();
        pieCtx.moveTo(centerX, centerY);
        pieCtx.arc(centerX, centerY, radius, startAngle, endAngle);
        pieCtx.closePath();
        pieCtx.fillStyle = ChartUtils.getColor(index);
        pieCtx.fill();
        
        // 绘制标签和连接线
        const labelAngle = startAngle + sliceAngle / 2;
        const labelX = centerX + Math.cos(labelAngle) * (radius * 1.2);
        const labelY = centerY + Math.sin(labelAngle) * (radius * 1.2);
        
        // 绘制连接线
        pieCtx.beginPath();
        pieCtx.moveTo(centerX + Math.cos(labelAngle) * radius, centerY + Math.sin(labelAngle) * radius);
        pieCtx.lineTo(labelX, labelY);
        pieCtx.strokeStyle = '#ccc';
        pieCtx.stroke();
        
        // 绘制标签文本
        ChartUtils.drawLabel(
            pieCtx,
            `${data.labels[index]} (${((value / total) * 100).toFixed(1)}%)`,
            labelX + (labelAngle > Math.PI/2 && labelAngle < Math.PI*3/2 ? -10 : 10),
            labelY,
            labelAngle > Math.PI/2 && labelAngle < Math.PI*3/2 ? 'right' : 'left'
        );
        
        startAngle = endAngle;
    });
}
  • 关键计算:使用弧度计算扇形角度(表示完整圆)
  • 标签优化:根据扇形位置智能调整标签对齐方式
  • 百分比计算:自动计算并显示每个扇形占总体的百分比

代码效果展示:

3. 玫瑰图(Rose Chart)
function drawRoseChart() {
    const centerX = roseChart.width / 2;
    const centerY = roseChart.height / 2;
    const maxRadius = Math.min(centerX, centerY) * 0.7;
    const maxValue = Math.max(...data.values);
    const angleStep = (Math.PI * 2) / data.labels.length;
    
    // 绘制扇形
    data.values.forEach((value, index) => {
        const angle = index * angleStep;
        const radius = (value / maxValue) * maxRadius;
        
        // 绘制扇形
        roseCtx.beginPath();
        roseCtx.moveTo(centerX, centerY);
        roseCtx.arc(centerX, centerY, radius, angle, angle + angleStep);
        roseCtx.closePath();
        roseCtx.fillStyle = ChartUtils.getColor(index);
        roseCtx.fill();
        roseCtx.strokeStyle = '#fff';
        roseCtx.lineWidth = 1;
        roseCtx.stroke();
        
        // 绘制标签
        const labelAngle = angle + angleStep / 2;
        const labelX = centerX + Math.cos(labelAngle) * (radius + 20);
        const labelY = centerY + Math.sin(labelAngle) * (radius + 20);
        
        ChartUtils.drawLabel(pieCtx, data.labels[index], labelX, labelY);
    });
}
  • 与饼图的区别:玫瑰图使用半径而非角度来表示数值大小
  • 角度计算:平均分配每个类别对应的角度
  • 视觉效果:通过不同半径的扇形形成花瓣状分布

代码效果展示:

4. 折线图(Line Chart)
function drawLineChart() {
    const margin = {top: 20, right: 20, bottom: 40, left: 40};
    const width = lineChart.width - margin.left - margin.right;
    const height = lineChart.height - margin.top - margin.bottom;
    const maxValue = Math.max(...data.values) * 1.1;
    
    // 绘制坐标轴
    lineCtx.beginPath();
    lineCtx.moveTo(margin.left, margin.top);
    lineCtx.lineTo(margin.left, margin.top + height);
    lineCtx.lineTo(margin.left + width, margin.top + height);
    lineCtx.strokeStyle = '#ccc';
    lineCtx.stroke();
    
    // 计算点的位置
    const points = data.values.map((value, index) => ({
        x: margin.left + (width / (data.labels.length - 1)) * index,
        y: margin.top + height - (value / maxValue) * height,
        value
    }));
    
    // 绘制折线
    lineCtx.beginPath();
    lineCtx.moveTo(points[0].x, points[0].y);
    points.forEach(point => {
        lineCtx.lineTo(point.x, point.y);
    });
    lineCtx.strokeStyle = '#3B82F6';
    lineCtx.lineWidth = 2;
    lineCtx.stroke();
    
    // 绘制数据点和标签
    points.forEach(point => {
        lineCtx.beginPath();
        lineCtx.arc(point.x, point.y, 5, 0, Math.PI * 2);
        lineCtx.fillStyle = '#3B82F6';
        lineCtx.fill();
        lineCtx.strokeStyle = '#fff';
        lineCtx.lineWidth = 1;
        lineCtx.stroke();
        
        // 绘制值标签和类别标签
        ChartUtils.drawLabel(lineCtx, point.value, point.x, point.y - 15);
        ChartUtils.drawLabel(lineCtx, data.labels[points.indexOf(point)], point.x, margin.top + height + 20);
    });
}
  • 点位置计算:根据数据值和最大值得出 Y 坐标,根据索引计算 X 坐标
  • 折线绘制:使用 Canvas 路径连接所有点
  • 视觉增强:在数据点周围添加白色边框提高可读性

代码效果展示:

5. 散点图(Scatter Chart)
function drawScatterChart() {
    const margin = {top: 20, right: 20, bottom: 20, left: 20};
    const width = scatterChart.width - margin.left - margin.right;
    const height = scatterChart.height - margin.top - margin.bottom;
    
    // 绘制坐标轴
    scatterCtx.beginPath();
    scatterCtx.moveTo(margin.left, margin.top);
    scatterCtx.lineTo(margin.left, margin.top + height);
    scatterCtx.lineTo(margin.left + width, margin.top + height);
    scatterCtx.strokeStyle = '#ccc';
    scatterCtx.stroke();
    
    // 绘制散点
    data.scatterData.forEach((point, index) => {
        scatterCtx.beginPath();
        scatterCtx.arc(
            margin.left + point.x,
            margin.top + height - point.y,
            point.r,
            0,
            Math.PI * 2
        );
        scatterCtx.fillStyle = ChartUtils.getColor(index);
        scatterCtx.fill();
        scatterCtx.strokeStyle = '#fff';
        scatterCtx.lineWidth = 1;
        scatterCtx.stroke();
    });
}
  • 数据结构:使用包含 x、y 坐标和半径的对象数组
  • 坐标映射:直接使用数据中的坐标值,减去边距进行偏移
  • 视觉变化:随机大小的点增加视觉丰富度

代码效果展示:

响应式处理

window.addEventListener('resize', () => {
    // 重置每个Canvas的尺寸并重绘
    barChart.width = barChart.offsetWidth;
    barChart.height = barChart.offsetHeight;
    drawBarChart();
    
    // 其他图表类似...
});
  • 监听窗口大小变化事件
  • 重置 Canvas 尺寸(重要:重置尺寸会清除画布内容)
  • 重新调用绘制函数

样式设计

  • 使用 Tailwind CSS 定义图表容器样式
  • 采用现代蓝色系配色方案
  • 添加悬停效果增强交互感
  • 合理使用阴影和圆角提升视觉层次

这个图表库实现了基本的数据可视化功能,通过模块化设计和清晰的代码结构,便于后续扩展更多图表类型或添加交互功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值