怎么在HTML5中利用Canvas繪制一個(gè)K線圖

本篇文章為大家展示了怎么在HTML5中利用Canvas繪制一個(gè)K線圖,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過(guò)這篇文章的詳細(xì)介紹希望你能有所收獲。

站在用戶的角度思考問(wèn)題,與客戶深入溝通,找到貢井網(wǎng)站設(shè)計(jì)與貢井網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:成都網(wǎng)站制作、做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、域名申請(qǐng)、網(wǎng)站空間、企業(yè)郵箱。業(yè)務(wù)覆蓋貢井地區(qū)。

SVG 是一種使用 XML 描述 2D 圖形的語(yǔ)言。 Canvas 通過(guò) JavaScript 來(lái)繪制 2D 圖形。 Canvas 是逐像素進(jìn)行渲染的。

怎么在HTML5中利用Canvas繪制一個(gè)K線圖 '

經(jīng)過(guò)上面的比較不難發(fā)現(xiàn), SVG更適用于偏靜態(tài),渲染頻率不高的場(chǎng)景,所以這種要實(shí)現(xiàn)實(shí)時(shí)報(bào)價(jià)更新繪制的情況只能選擇 canvas。

2. 實(shí)現(xiàn)哪些需求

歷史報(bào)價(jià)實(shí)時(shí)報(bào)價(jià)繪制圖表

支持 拖拽查看歷史時(shí)間段的報(bào)價(jià)圖表

支持鼠標(biāo) 滾輪和觸摸板 雙指操作放大或縮小圖表

支持鼠標(biāo)指針 移動(dòng)查看鼠標(biāo)位置報(bào)價(jià)

3. 代碼實(shí)現(xiàn)過(guò)程

1. 準(zhǔn)備工作

/**
 * K-line - K線圖渲染函數(shù)
 * Date: 2019.12.18  Author: isnan
 */
const BLOCK_MARGIN = 2; //方塊水平間距
const START_PRICE_INDEX = 'open_price'; //開(kāi)始價(jià)格在數(shù)據(jù)組中的位置
const END_PRICE_INDEX = 'close'; //結(jié)束價(jià)格在數(shù)據(jù)組中的位置
const MIN_PRICE_INDEX = 'low'; //最小價(jià)格在數(shù)據(jù)組中的位置
const MAX_PRICE_INDEX = 'high'; //最大價(jià)格在數(shù)據(jù)組中的位置
const TIME_INDEX = 'time'; //時(shí)間在數(shù)據(jù)組中的位置
const LINE_WIDTH = 1; //1px 寬度 (中間線、x軸等)
const BOTTOM_SPACE = 40; //底部空間
const TOP_SPACE = 20; //頂部空間
const RIGHT_SPACE = 60; //右側(cè)空間
let _addEventListener, _removeEventListener, prefix = ''; //addEventListener 瀏覽器兼容
function RenderKLine (id, /*Optional*/options) {
  if (!id) return;
  options = options || {};
  this.id = id;   //canvas box id
  // detect event model
  if (window.addEventListener) {
    _addEventListener = "addEventListener";
    _removeEventListener = "removeEventListener";
  } else {
    _addEventListener = "attachEvent";
    _removeEventListener = "detachEvent"
    prefix = "on";
  }
  // options params
  this.sharpness = options.sharpness;  // 清晰度 (正整數(shù) 太大可能會(huì)卡頓,取決于電腦配置 建議在2~5區(qū)間)
  this.blockWidth = options.blockWidth; // 方塊的寬度 (最小為3,最大49 為了防止中間線出現(xiàn)位置偏差 設(shè)定為奇數(shù),若為偶數(shù)則向下減1)
  this.buyColor = options.buyColor || '#F05452';  // color 漲
  this.sellColor = options.sellColor || '#25C875';  // color 跌
  this.fontColor = options.fontColor || '#666666';  //文字顏色
  this.lineColor = options.lineColor || '#DDDDDD';  //參考線顏色
  this.digitsPoint = options.digitsPoint || 2; //報(bào)價(jià)的digits (有幾位小數(shù))
  this.horizontalCells = options.horizontalCells || 5; //水平方向切割多少格子 (中間虛線數(shù) = 5 - 1)
  this.crossLineStatus = options.crossLineStatus || true; //鼠標(biāo)移動(dòng)十字線顯示狀態(tài)

  //basic params
  this.totalWidth = 0;  //總寬度
  this.movingRange = 0; //橫向移動(dòng)的距離 取正數(shù)值,使用時(shí)再加負(fù)號(hào)
  this.minPrice = 9999999;
  this.maxPrice = 0; //繪制的所有數(shù)據(jù)中 最小/最大數(shù)據(jù) 用來(lái)繪制y軸
  this.diffPrice = 0;  //最大報(bào)價(jià)與最小報(bào)價(jià)的差值
  this.perPricePixel = 0; //每一個(gè)單位報(bào)價(jià)占用多少像素
  this.centerSpace = 0; //x軸到頂部的距離 繪圖區(qū)域
  this.xDateSpace = 6;  //x軸上的時(shí)間繪制間隔多少組
  this.fromSpaceNum = 0;  //x軸上的時(shí)間繪制從第 (fromSpaceNum%xDateSpace) 組數(shù)據(jù)開(kāi)始 
  this.dataArr = [];  //數(shù)據(jù)
  this.lastDataTimestamp = undefined; //歷史報(bào)價(jià)中第一個(gè)時(shí)間戳, 用來(lái)和實(shí)時(shí)報(bào)價(jià)做比較畫(huà)圖
  this.buyColorRGB = {r: 0, g: 0, b: 0};
  this.sellColorRGB = {r: 0, g: 0, b: 0};
  
  this.processParams();
  this.init();
}

定義了一些常量和變量,生成一個(gè) 構(gòu)造函數(shù),接收兩個(gè)參數(shù),一個(gè)是id,canvas會(huì)在插入到這個(gè)id的盒子內(nèi),第二個(gè)參數(shù)是一些配置項(xiàng),可選。

/**
 *    sharpness {number} 清晰度
 *    buyColor {string} color - 漲
 *    sellColor {string} color - 跌
 *    fontColor {string} 文字顏色
 *    lineColor {string} 參考線顏色
 *    blockWidth {number} 方塊的寬度
 *    digitsPoint {number} 報(bào)價(jià)有幾位小數(shù)
 *    horizontalCells {number} 水平方向切割幾個(gè)格子
 *    crossLineStatus {boolean} 鼠標(biāo)移動(dòng)十字線顯示狀態(tài)
 */

2. init方法和canvas畫(huà)布的翻轉(zhuǎn)

RenderKLine.prototype.init = function () {
  let cBox = document.getElementById(this.id);
  // 創(chuàng)建canvas并獲得canvas上下文
  this.canvas = document.createElement("canvas");
  if (this.canvas && this.canvas.getContext) {
    this.ctx = this.canvas.getContext("2d");
  }

  this.canvas.innerHTML = '您的當(dāng)前瀏覽器不支持HTML5 canvas';
  cBox.appendChild(this.canvas);
  this.actualWidth = cBox.clientWidth;
  this.actualHeight = cBox.clientHeight;
  
  this.enlargeCanvas();
}
// 因?yàn)槔L制區(qū)域超出canvas區(qū)域,此方法也用來(lái)代替clearRect 清空畫(huà)布的作用
RenderKLine.prototype.enlargeCanvas = function () {
  this.canvas.width = this.actualWidth * this.sharpness;
  this.canvas.height = this.actualHeight * this.sharpness;
  this.canvas.style.height = this.canvas.height / this.sharpness + 'px';
  this.canvas.style.width = this.canvas.width / this.sharpness + 'px';
  this.centerSpace = this.canvas.height - (BOTTOM_SPACE + TOP_SPACE) * this.sharpness;
  // 將canvas原點(diǎn)坐標(biāo)轉(zhuǎn)換到右上角
  this.transformOrigin();
  // base settings
  this.ctx.lineWidth = LINE_WIDTH*this.sharpness;
  this.ctx.font = `${12*this.sharpness}px Arial`;
  // 還原之前滾動(dòng)的距離
  this.ctx.translate(-this.movingRange * this.sharpness, 0);
  // console.log(this.movingRange);
}

init方法初始化了一個(gè)canvas,enlargeCanvas是一個(gè)替代clearRect的方法,其中需要注意的是 transformOrigin這個(gè)方法,因?yàn)檎5腸anvas原點(diǎn)坐標(biāo)在坐上角,但是我們需要繪制的圖像是從右側(cè)開(kāi)始繪制的,所以我這里為了方便繪圖,把整個(gè)canvas做了一次轉(zhuǎn)換,原點(diǎn)坐標(biāo)轉(zhuǎn)到了右上角位置。

// 切換坐標(biāo)系走向 (原點(diǎn)在左上角 or 右上角)
RenderKLine.prototype.transformOrigin = function () {
  this.ctx.translate(this.canvas.width, 0);
  this.ctx.scale(-1, 1);
}

這里有一點(diǎn)需要注意的是,雖然翻轉(zhuǎn)過(guò)來(lái)繪制一些矩形,直線沒(méi)什么問(wèn)題,但是繪制文本是不行的,繪制文本需要還原回去,不然文字就是翻轉(zhuǎn)過(guò)來(lái)的狀態(tài)。如下圖所示:

怎么在HTML5中利用Canvas繪制一個(gè)K線圖 

3. 移動(dòng)、拖拽、滾輪事件

//監(jiān)聽(tīng)鼠標(biāo)移動(dòng)
RenderKLine.prototype.addMouseMove = function () {
  this.canvas[_addEventListener](prefix+"mousemove", mosueMoveEvent);
  this.canvas[_addEventListener](prefix+"mouseleave", e => {
    this.event = undefined;
    this.enlargeCanvas();
    this.updateData();
  });
  const _this = this;
  function mosueMoveEvent (e) {
    if (!_this.dataArr.length) return;
    _this.event = e || event;
    _this.enlargeCanvas();
    _this.updateData();
  }
}

//拖拽事件
RenderKLine.prototype.addMouseDrag = function () {
  let pageX, moveX = 0;
  this.canvas[_addEventListener](prefix+'mousedown', e => {
    e = e || event;
    pageX = e.pageX;
    this.canvas[_addEventListener](prefix+'mousemove', dragMouseMoveEvent);
  });
  this.canvas[_addEventListener](prefix+'mouseup', e => {
    this.canvas[_removeEventListener](prefix+'mousemove', dragMouseMoveEvent);
  });
  this.canvas[_addEventListener](prefix+'mouseleave', e => {
    this.canvas[_removeEventListener](prefix+'mousemove', dragMouseMoveEvent);
  });
  
  const _this = this;
  function dragMouseMoveEvent (e) {
    if (!_this.dataArr.length) return;
    e = e || event;
    moveX = e.pageX - pageX;
    pageX = e.pageX;
    _this.translateKLine(moveX);
    // console.log(moveX);
  }
}

//Mac雙指行為 & 鼠標(biāo)滾輪
RenderKLine.prototype.addMouseWheel = function () {
  addWheelListener(this.canvas, wheelEvent);
  const _this = this;
  function wheelEvent (e) {
      if (Math.abs(e.deltaX) !== 0 && Math.abs(e.deltaY) !== 0) return; //沒(méi)有固定方向,忽略
      if (e.deltaX < 0) return _this.translateKLine(parseInt(-e.deltaX)); //向右
      if (e.deltaX > 0) return _this.translateKLine(parseInt(-e.deltaX)); //向左
      if (e.ctrlKey) {
        if (e.deltaY > 0) return _this.scaleKLine(-1); //向內(nèi)
        if (e.deltaY < 0) return _this.scaleKLine(1); //向外
      } else {
        if (e.deltaY > 0) return _this.scaleKLine(1); //向上
        if (e.deltaY < 0) return _this.scaleKLine(-1); //向下
      }
  }
}

滾輪事件上一篇已經(jīng)說(shuō)過(guò)了,這里就是對(duì)不同情況做相應(yīng)的處理;

鼠標(biāo)移動(dòng)事件把event更新到 this上,然后調(diào)用 updateData方法,繪制圖像即可。會(huì)調(diào)用下面方法畫(huà)出十字線。

function drawCrossLine () {
  if (!this.crossLineStatus || !this.event) return;
  let cRect = this.canvas.getBoundingClientRect();
  //layerX 有兼容性問(wèn)題,使用clientX
  let x = this.canvas.width - (this.event.clientX - cRect.left - this.movingRange) * this.sharpness;
  let y = (this.event.clientY - cRect.top) * this.sharpness;
  // 在報(bào)價(jià)范圍內(nèi)畫(huà)線
  if (y < TOP_SPACE*this.sharpness || y > this.canvas.height - BOTTOM_SPACE * this.sharpness) return;
  this.drawDash(this.movingRange * this.sharpness, y, this.canvas.width+this.movingRange * this.sharpness, y, '#999999');
  this.drawDash(x, TOP_SPACE*this.sharpness, x, this.canvas.height - BOTTOM_SPACE*this.sharpness, '#999999');
  //報(bào)價(jià)
  this.ctx.save();
  this.ctx.translate(this.movingRange * this.sharpness, 0);
  // 填充文字時(shí)需要把canvas的轉(zhuǎn)換還原回來(lái),防止文字翻轉(zhuǎn)變形
  let str = (this.maxPrice - (y - TOP_SPACE * this.sharpness) / this.perPricePixel).toFixed(this.digitsPoint);
  this.transformOrigin();
  this.ctx.translate(this.canvas.width - RIGHT_SPACE * this.sharpness, 0);
  this.drawRect(-3*this.sharpness, y-10*this.sharpness, this.ctx.measureText(str).width+6*this.sharpness, 20*this.sharpness, "#ccc");
  this.drawText(str, 0, y, RIGHT_SPACE * this.sharpness)
  this.ctx.restore();
}

拖拽事件pageX的移動(dòng)距離傳遞給 translateKLine方法來(lái)實(shí)現(xiàn)橫向滾動(dòng)查看。

/**
 * 縮放圖表 
 * @param {int} scaleTimes 縮放倍數(shù)
 *  正數(shù)為放大,負(fù)數(shù)為縮小,數(shù)值*2 代表蠟燭圖width的變化度
 *  eg:  2 >> this.blockWidth + 2*2  
 *      -3 >> this.blockWidth - 3*2
 * 為了保證縮放的效果,
 * 應(yīng)該以當(dāng)前可視區(qū)域的中心為基準(zhǔn)縮放
 * 所以縮放前后兩邊的長(zhǎng)度在總長(zhǎng)度中所占比例應(yīng)該一樣
 * 公式:(oldRange+0.5*canvasWidth)/oldTotalLen = (newRange+0.5*canvasWidth)/newTotalLen
 * diffRange = newRange - oldRange
 *           = (oldRange*newTotalLen + 0.5*canvasWidth*newTotalLen - 0.5*canvasWidth*oldTotalLen)/oldTotalLen - oldRange
 */
RenderKLine.prototype.scaleKLine = function (scaleTimes) {
  if (!this.dataArr.length) return;
  let oldTotalLen = this.totalWidth;
  this.blockWidth += scaleTimes*2;
  this.processParams();
  this.computeTotalWidth();
  let newRange = (this.movingRange*this.sharpness*this.totalWidth+this.canvas.width/2*this.totalWidth-this.canvas.width/2*oldTotalLen)/oldTotalLen/this.sharpness;
  let diffRange = newRange - this.movingRange;
  // console.log(newRange, this.movingRange, diffRange);
  this.translateKLine(diffRange);
}
// 移動(dòng)圖表
RenderKLine.prototype.translateKLine = function (range) {
  if (!this.dataArr.length) return;
  this.movingRange += parseInt(range);
  let maxMovingRange =  (this.totalWidth - this.canvas.width) / this.sharpness + this.blockWidth;
  if (this.totalWidth <= this.canvas.width || this.movingRange <= 0) {
    this.movingRange = 0;
  } else if (this.movingRange >= maxMovingRange) {
    this.movingRange = maxMovingRange;
  }
  this.enlargeCanvas();
  this.updateData();
}

4. 核心方法 updateData

所有的繪制過(guò)程都是在這個(gè)方法中完成的,這樣無(wú)論想要什么操作,都可以通過(guò)此方法重繪canvas來(lái)實(shí)現(xiàn),需要做的只是改變?cè)蜕系囊恍傩远眩热缦胍笥乙苿?dòng),只需要把 this.movingRange設(shè)置好,再調(diào)用 updateData就完成了。

RenderKLine.prototype.updateData = function (isUpdateHistory) {
  if (!this.dataArr.length) return;
  if (isUpdateHistory) {
    this.fromSpaceNum = 0;
  }
  // console.log(data);
  this.computeTotalWidth();
  this.computeSpaceY();
  this.ctx.save();
  // 把原點(diǎn)坐標(biāo)向下方移動(dòng) TOP_SPACE 的距離,開(kāi)始繪制水平線
  this.ctx.translate(0, TOP_SPACE * this.sharpness);
  this.drawHorizontalLine();
  // 把原點(diǎn)坐標(biāo)再向左邊移動(dòng) RIGHT_SPACE 的距離,開(kāi)始繪制垂直線和蠟燭圖
  this.ctx.translate(RIGHT_SPACE * this.sharpness, 0);
  // 開(kāi)始繪制蠟燭圖
  let item, col;
  let lineWidth = LINE_WIDTH * this.sharpness,
      margin = blockMargin = BLOCK_MARGIN*this.sharpness,
      blockWidth = this.blockWidth*this.sharpness;//乘上清晰度系數(shù)后的間距、塊寬度
  let blockHeight, lineHeight, blockYPoint, lineYPoint; //單一方塊、單一中間線的高度、y坐標(biāo)點(diǎn)
  let realTime, realTimeYPoint; //實(shí)時(shí)(最后)報(bào)價(jià)及y坐標(biāo)點(diǎn)
  for (let i=0; i<this.dataArr.length; i++) {
    item = this.dataArr[i];
    if (item[START_PRICE_INDEX] > item[END_PRICE_INDEX]) {
      //跌了 sell
      col = this.sellColor;
      blockHeight = (item[START_PRICE_INDEX] - item[END_PRICE_INDEX])*this.perPricePixel;
      blockYPoint = (this.maxPrice - item[START_PRICE_INDEX])*this.perPricePixel;
    } else {
      //漲了 buy
      col = this.buyColor;
      blockHeight = (item[END_PRICE_INDEX] - item[START_PRICE_INDEX])*this.perPricePixel;
      blockYPoint = (this.maxPrice - item[END_PRICE_INDEX])*this.perPricePixel;
    }
    lineHeight = (item[MAX_PRICE_INDEX] - item[MIN_PRICE_INDEX])*this.perPricePixel;
    lineYPoint = (this.maxPrice - item[MAX_PRICE_INDEX])*this.perPricePixel;
    // if (i === 0) console.log(lineHeight, blockHeight, lineYPoint, blockYPoint);
    lineHeight = lineHeight > 2*this.sharpness ? lineHeight : 2*this.sharpness;
    blockHeight = blockHeight > 2*this.sharpness ? blockHeight : 2*this.sharpness;
    if (i === 0) {
      realTime = item[END_PRICE_INDEX];
      realTimeYPoint = blockYPoint + (item[START_PRICE_INDEX] > item[END_PRICE_INDEX] ? blockHeight : 0)
    };
    // 繪制垂直方向的參考線、以及x軸的日期時(shí)間
    if (i%this.xDateSpace === (this.fromSpaceNum%this.xDateSpace)) {
      this.drawDash(margin+(blockWidth-1*this.sharpness)/2, 0, margin+(blockWidth-1*this.sharpness)/2, this.centerSpace);
      this.ctx.save();
      // 填充文字時(shí)需要把canvas的轉(zhuǎn)換還原回來(lái),防止文字翻轉(zhuǎn)變形
      this.transformOrigin();
      // 翻轉(zhuǎn)后將原點(diǎn)移回翻轉(zhuǎn)前的位置
      this.ctx.translate(this.canvas.width, 0);
      this.drawText(processXDate(item[TIME_INDEX], this.dataType), -(margin+(blockWidth-1*this.sharpness)/2), this.centerSpace + 12*this.sharpness, undefined, 'center', 'top');
      
      this.ctx.restore();
    }
    this.drawRect(margin+(blockWidth-1*this.sharpness)/2, lineYPoint, lineWidth, lineHeight, col);
    this.drawRect(margin, blockYPoint, blockWidth, blockHeight, col);
    margin = margin+blockWidth+blockMargin;
  }
  //繪制實(shí)時(shí)報(bào)價(jià)線、價(jià)格
  this.drawLine((this.movingRange-RIGHT_SPACE) * this.sharpness, realTimeYPoint, (this.movingRange-RIGHT_SPACE) * this.sharpness + this.canvas.width, realTimeYPoint, '#cccccc');
  this.ctx.save();
  this.ctx.translate(-RIGHT_SPACE * this.sharpness, 0);
  this.transformOrigin();
  this.drawRect((17-this.movingRange) * this.sharpness, realTimeYPoint - 10 * this.sharpness, this.ctx.measureText(realTime).width+6*this.sharpness, 20*this.sharpness, "#ccc");
  this.drawText(realTime, (20-this.movingRange) * this.sharpness, realTimeYPoint);
  this.ctx.restore();
  //最后繪制y軸上報(bào)價(jià),放在最上層
  this.ctx.translate(-RIGHT_SPACE * this.sharpness, 0);
  this.drawYPrice();
  this.ctx.restore();
  drawCrossLine.call(this);
}

這個(gè)方法不難,只是繪制時(shí)為了方便計(jì)算位置,需要經(jīng)常變換原點(diǎn)坐標(biāo),不要搞錯(cuò)了就好。

還需要注意的是 sharpness這個(gè)變量,代表清晰度,整個(gè)canvas的寬高是在原有的基礎(chǔ)上乘上了這個(gè)系數(shù)得到的,所以,計(jì)算時(shí)需要特別注意帶上這個(gè)系數(shù)。

5. 更新歷史&實(shí)時(shí)報(bào)價(jià)方法

// 實(shí)時(shí)報(bào)價(jià)
RenderKLine.prototype.updateRealTimeQuote = function (quote) {
  if (!quote) return;
  pushQuoteInData.call(this, quote);
}
/**
 * 歷史報(bào)價(jià)
 * @param {Array} data 數(shù)據(jù)
 * @param {int}   type 報(bào)價(jià)類型  默認(rèn) 60(1小時(shí))
 *    (1, 5, 15, 30, 60, 240, 1440, 10080, 43200)
      (1分鐘 5分鐘 15分鐘 30分鐘 1小時(shí) 4小時(shí) 日 周 月)
 */
RenderKLine.prototype.updateHistoryQuote = function (data, type = 60) {
  if (!data instanceof Array || !data.length) return;
  this.dataArr = data;
  this.dataType = type;
  this.updateData(true);
}

6. 調(diào)用demo

<div id="myCanvasBox" style="width: 1000px; height: 500px;"></div>

<script>
    let data = [
      {
        "time": 1576648800, 
        "open_price": "1476.94", 
        "high": "1477.44", 
        "low": "1476.76", 
        "close": "1476.96"
      }, 
      //...
    ];
    let options = {
      sharpness: 3,
      blockWidth: 11,
      horizontalCells: 10
    };
    let kLine = new RenderKLine("myCanvasBox", options);
    //更新歷史報(bào)價(jià)
    kLine.updateHistoryQuote(data);
    //模擬實(shí)時(shí)報(bào)價(jià)
    let realTime = `{
      "time": 1575858840, 
      "open_price": "1476.96", 
      "high": "1482.12", 
      "low": "1470.96", 
      "close": "1476.96"
    }`;
    setInterval(() => {
      let realTimeCopy = JSON.parse(realTime);
      realTimeCopy.time = parseInt(new Date().getTime()/1000);
      realTimeCopy.close = (1476.96 - (Math.random() * 4 - 2)).toFixed(2);
      kLine.updateRealTimeQuote(realTimeCopy);
     }, parseInt(Math.random() * 1000 + 500))
</script>

7. 效果圖

怎么在HTML5中利用Canvas繪制一個(gè)K線圖 

上述內(nèi)容就是怎么在HTML5中利用Canvas繪制一個(gè)K線圖,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

文章名稱:怎么在HTML5中利用Canvas繪制一個(gè)K線圖
路徑分享:http://muchs.cn/article34/gdcppe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開(kāi)發(fā)、標(biāo)簽優(yōu)化、小程序開(kāi)發(fā)、服務(wù)器托管、網(wǎng)頁(yè)設(shè)計(jì)公司、虛擬主機(jī)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

手機(jī)網(wǎng)站建設(shè)