基于SVG的价格数据线条图表绘制逻辑求助
问题描述
我设置了5条间距为30px的水平线,第一条对应价格最大值,最后一条对应价格最小值。现有一个包含13个不同数值的价格数组,希望根据每个价格在对应水平位置绘制绿色线条(制作简易图表)。按照@Mina推荐的方案实现后无法正常运行,相关代码如下:
原代码
HTML
<svg id="main" xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="220"></svg>
JavaScript
var prices = []; prices.push(28990, 30240, 30890, 32162, 31000, 31820, 31770, 30080, 28340, 28620, 28640, 27930, 28850); var minPrice = Math.floor(Math.min(...prices)/1000)*1000; var maxPrice = Math.ceil(Math.max(...prices)/1000)*1000; function addText(x, y, t) { var text = document.createElementNS('http://www.w3.org/2000/svg','text'); text.setAttribute('x', x); text.setAttribute('y', y); text.setAttribute('fill', 'black'); var textNode = document.createTextNode(t); text.appendChild(textNode); document.getElementById('main').appendChild(text); } function addLine(id, x1, y1, x2, y2, sc,sw) { const line = document.createElementNS('http://www.w3.org/2000/svg','line'); line.setAttribute('id', id); line.setAttribute('x1', x1); line.setAttribute('y1', y1); line.setAttribute('x2', x2); line.setAttribute('y2', y2); line.setAttribute('stroke', sc); line.setAttribute('stroke-width', sw); document.getElementById("main").appendChild(line); } function addHorizontalLinesPrice() { var m = maxPrice; addText(20, 55, m); addText(20, 85, m = m-1500); addText(20, 115, m = m-1500); addText(20, 145, m = m-1500); addText(20, 175, m = m-1500); } addHorizontalLinesPrice(); function addHorizontalLines() { var y = 50.5; for (var i = 0; i < 5; i++) { addLine("horizontalLine"+(i+1), 90, y, window.innerWidth-20, y, "black", 0.5); y = y + 30; } } addHorizontalLines(); var totalHeight = 100; function getYPosition(price) { var scale = (price - minPrice) / (maxPrice - minPrice); return totalHeight - (scale * totalHeight); } prices.forEach(price => { var x = 20; var yPos = getYPosition(price); addLine("", x, yPos, x+200, yPos, "green", 3); x = x + 100; });
问题分析及修正方案
原代码存在3个关键错误导致图表无法正常显示:
- Y坐标映射基准错误:水平线的Y范围是
50.5到170.5,总高度为120px而非100px;且SVG Y轴从上到下递增,价格越高对应Y值应越小,原计算逻辑完全错位。 - X值未循环递增:
forEach循环中每次重置x=20,导致所有绿线重叠。 - 硬编码刻度维护性差:手动写死刻度数值,若价格范围变化需逐一修改,易出错。
修正后的完整代码
HTML
<svg id="main" xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="220"></svg>
JavaScript
var prices = [28990, 30240, 30890, 32162, 31000, 31820, 31770, 30080, 28340, 28620, 28640, 27930, 28850]; // 计算价格上下限(取1000的倍数) var minPrice = Math.floor(Math.min(...prices)/1000)*1000; var maxPrice = Math.ceil(Math.max(...prices)/1000)*1000; // 图表基础配置 const baseY = 50.5; // 第一条水平线的Y坐标 const lineGap = 30; // 水平线间距 const totalLines = 5; // 水平线总数 const priceRange = maxPrice - minPrice; const priceGap = priceRange / (totalLines - 1); // 每条线对应的价格差 function addText(x, y, t) { var text = document.createElementNS('http://www.w3.org/2000/svg','text'); text.setAttribute('x', x); text.setAttribute('y', y); text.setAttribute('fill', 'black'); text.setAttribute('font-size', '14px'); var textNode = document.createTextNode(t); text.appendChild(textNode); document.getElementById('main').appendChild(text); } function addLine(id, x1, y1, x2, y2, sc, sw) { const line = document.createElementNS('http://www.w3.org/2000/svg','line'); line.setAttribute('id', id); line.setAttribute('x1', x1); line.setAttribute('y1', y1); line.setAttribute('x2', x2); line.setAttribute('y2', y2); line.setAttribute('stroke', sc); line.setAttribute('stroke-width', sw); document.getElementById("main").appendChild(line); } // 绘制价格刻度文本 function addHorizontalLinesPrice() { for (let i = 0; i < totalLines; i++) { const currentPrice = maxPrice - i * priceGap; const y = baseY + i * lineGap + 5; // 文本在水平线下方5px处 addText(20, y, currentPrice); } } // 绘制水平线 function addHorizontalLines() { for (let i = 0; i < totalLines; i++) { const y = baseY + i * lineGap; addLine(`horizontalLine${i+1}`, 90, y, window.innerWidth - 20, y, "black", 0.5); } } // 计算价格对应的Y坐标 function getYPosition(price) { const ratio = (maxPrice - price) / priceRange; return baseY + ratio * (totalLines - 1) * lineGap; } // 绘制每个价格对应的绿色线条 function drawPriceLines() { let x = 100; // 起始X坐标(避开左侧刻度) const lineLength = 80; // 单条绿线长度 prices.forEach(price => { const yPos = getYPosition(price); addLine(`priceLine${price}`, x, yPos, x + lineLength, yPos, "green", 3); x += 50; // 绿线之间的间距 }); } // 执行所有绘制逻辑 addHorizontalLinesPrice(); addHorizontalLines(); drawPriceLines();
修正说明
- 调整Y坐标计算逻辑,确保价格最大值对应第一条水平线,最小值对应最后一条水平线,完全匹配视觉预期。
- 将X值移到循环外部,每条绿线依次排列,不再重叠。
- 用循环替代硬编码绘制刻度和水平线,价格范围变化时无需手动修改数值,提升维护性。
- 增加字体大小、线长等可配置参数,方便快速调整图表样式。
内容的提问来源于stack exchange,提问作者JCss
相关产品推荐
相关产品推荐

