depth-chart-hb

(function (global, factory) { // 检查模块系统并初始化depthChart if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof module !== 'undefined' && module.exports) { module.exports = factory(); } else { global.depthChart = factory(); } })(this, function () { // 判断是否为数组 function isArrayLike(obj, limit) { if (Array.isArray(obj)) { return obj; } } // 判断是否为可迭代对象 function isIterable(obj, limit) { if (Symbol.iterator in Object(obj) || "[object Arguments]" === Object.prototype.toString.call(obj)) { var result = []; var done = false; var error = false; var errorMsg = void 0; try { for (var iterator, step = obj[Symbol.iterator](); !(done = (iterator = step.next()).done) && (result.push(iterator.value), !limit || result.length !== limit); done = true); } catch (err) { error = true; errorMsg = err; } finally { try { if (!done && step.return != null) { step.return(); } } finally { if (error) { throw errorMsg; } } } return result; } } // 获取字符串 function getString(value, limit) { return isArrayLike(value, limit) || isIterable(value, limit); } // 深度图主函数 function depthChart(element, options) { var prototype, originalGetContext; // 修改canvas的getContext方法,调整canvas的分辨率 (prototype = HTMLCanvasElement.prototype).getContext = (originalGetContext = prototype.getContext, function (contextType) { var context, pixelRatio, backingStoreRatio; context = originalGetContext.call(this, contextType); if (contextType === "2d") { backingStoreRatio = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; pixelRatio = (window.devicePixelRatio || 1) / backingStoreRatio; if (pixelRatio > 1) { this.style.height = this.height + "px"; this.style.width = this.width + "px"; this.width *= pixelRatio; this.height *= pixelRatio; } } return context; }); // 主题设置 var themes = { "hb-night": { bidsLineColor: options && options.bidsLineColor || "rgba(0, 141, 115, 1)", asksLineColor: options && options.asksLineColor || "rgba(236, 49, 75, 1)", bidsFillColor: options && options.bidsFillColor || "rgba(0, 141, 115, .2)", asksFillColor: options && options.asksFillColor || "rgba(236, 49, 75, .2)", axisColor: options && options.axisColor || "rgba(97, 104, 138, .3)", color: options && options.color || "rgba(81, 128, 159, .8)", bgColor: options && options.bgColor || "rgba(23, 54, 72, .95)", dotColor: "rgba(0 , 126, 224, 1)", tipColor: options && options.tipColor || "#C7CCE6", tipShadow: 4 }, "hb-day": { bidsLineColor: options && options.bidsLineColor || "rgba(3, 192, 135, 0)", asksLineColor: options && options.asksLineColor || "rgba(231, 109, 66, 0)", bidsFillColor: options && options.bidsFillColor || "rgba(3, 192, 135, .1)", asksFillColor: options && options.asksFillColor || "rgba(231, 109, 66, .1)", axisColor: options && options.axisColor || "rgba(180, 188, 227, .3)", color: options && options.color || "#232A4A", bgColor: options && options.bgColor || "#ffffff", dotColor: "rgba(0 , 126, 224, 1)", tipColor: options && options.tipColor || "#9CA9B5", tipShadow: 4 } }; // 配置参数 var config = { theme: options && options.theme || "hb-night", ruleHeight: options && options.ruleHeight || 30, ruleWidth: options && options.ruleWidth || 54, priceFix: options && options.priceFix || 2, amountFix: options && options.amountFix || 0, paddingTop: options && options.paddingTop || 15, noAmountTick: options && options.noAmountTick || 500, lang: options && options.lang || "en-us", langMap: options && options.langMap }; // 初始化主题 function initializeTheme(theme) { var selectedTheme = theme || config.theme; Object.keys(themes["hb-day"]).forEach(function (key) { config[key] = themes[selectedTheme][key]; }); } initializeTheme(); // 语言映射 var label, hoveredX, hoveredY, canvasElement = Object.assign({ "zh-cn": { "委托价": "委托价", "累计": "累计" }, "en-us": { "委托价": "Price", "累计": "Sum" } }, config.langMap || {}); // 数据和画布元素 var askData = [], bidData = [], maxValue = 0, targetElement = typeof element === "string" ? document.querySelector("#" + element.replace("#", "")) : element || document.querySelector("#chart"), mainCanvas = document.createElement("canvas"), containerWidth = targetElement.offsetWidth, containerHeight = targetElement.offsetHeight, overlayCanvas = document.createElement("canvas"), overlayVisible = true, mainContext = mainCanvas.getContext("2d"), overlayContext = overlayCanvas.getContext("2d"), devicePixelRatio = (context = mainContext, (window.devicePixelRatio || 1) / (context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1)), chartWidth = containerWidth - config.ruleWidth, chartHeight = containerHeight - config.ruleHeight, padding = ~~(containerHeight * config.paddingTop / 100), offsetY = padding * devicePixelRatio, availableHeight = chartHeight - padding, availableHeightScaled = availableHeight * devicePixelRatio, chartWidthScaled = chartWidth * devicePixelRatio, chartHeightScaled = chartHeight * devicePixelRatio; // 绘制单个点 function drawPoint(x, y) { if (x = x || hoveredX) { if (hoveredX = x, clearCanvas(overlayContext), overlayVisible = false, x > chartWidthScaled - devicePixelRatio) { overlayVisible = true; } else { for (var imageData = mainContext.getImageData(x, 0, 1, chartHeightScaled - devicePixelRatio), i = 0; i < imageData.height; i++) { var red = imageData.data[4 * i * imageData.width], green = imageData.data[4 * i * imageData.width + 1]; if (red || green) return drawTooltip(x, i, x > chartWidthScaled / 2 ? "asks" : "bids"), void (overlayVisible = true); } overlayVisible = true; } } } // 获取字符串的小数部分长度 function getDecimalLength(value) { var parts = (value + "").split("."); return getString(parts, 1)[0].length; } // 初始化画布尺寸 mainCanvas.width = overlayCanvas.width = containerWidth * devicePixelRatio; mainCanvas.height = overlayCanvas.height = containerHeight * devicePixelRatio; mainCanvas.style.position = overlayCanvas.style.position = "absolute"; mainCanvas.style.width = overlayCanvas.style.width = containerWidth + "px"; mainCanvas.style.height = overlayCanvas.style.height = containerHeight + "px"; targetElement.style.position = "relative"; targetElement.appendChild(mainCanvas); targetElement.appendChild(overlayCanvas); // 大数转换 var largeNumberFormat = { 1e3: ["K", "M", "B"], 1e4: ["万", "亿", "兆"] }; // 绘制标记点 function drawMarker(x, y, type) { config.dotColor = "bids" === type ? config.bidsLineColor : config.asksLineColor; overlayContext.beginPath(); overlayContext.arc(x, y, 10 * devicePixelRatio, 0, 2 * Math.PI); overlayContext.closePath(); overlayContext.fillStyle = config.dotColor.replace("1)", ".3)"); overlayContext.fill(); overlayContext.beginPath(); overlayContext.arc(x, y, 5 * devicePixelRatio, 0, 2 * Math.PI); overlayContext.closePath(); overlayContext.fillStyle = config.dotColor; overlayContext.fill(); function drawDashedLine(x, y, type) { overlayContext.beginPath(); overlayContext.strokeStyle = config[type + "LineColor"]; overlayContext.lineWidth = ~~(2 * devicePixelRatio); overlayContext.setLineDash([5 * devicePixelRatio, 5 * devicePixelRatio]); overlayContext.moveTo(x, Math.min(y + 10 * devicePixelRatio, chartHeightScaled)); overlayContext.lineTo(x, chartHeightScaled); overlayContext.stroke(); overlayContext.closePath(); } drawDashedLine(x, y, type); } // 绘制提示框 function drawTooltip(x, y, type) { drawMarker(x, y, type); CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius) { var r = Math.min(width, height); if (radius > r / 2) radius = r / 2; this.beginPath(); this.moveTo(x + radius, y); this.arcTo(x + width, y, x + width, y + height, radius); this.arcTo(x + width, y + height, x, y + height, radius); this.arcTo(x, y + height, x, y, radius); this.arcTo(x, y, x + width, y, radius); this.closePath(); return this; }; var boxWidth = (6 * (maxValue ? Math.max(maxValue.toFixed(config.amountFix).toString().length - 9, 0) : 0) + 140) * devicePixelRatio; var boxHeight = 90 * devicePixelRatio; var boxPadding = 18 * devicePixelRatio; overlayContext.shadowBlur = config.tipShadow * devicePixelRatio; overlayContext.shadowOffsetY = config.tipShadow * devicePixelRatio; var boxX = chartWidthScaled - x > boxWidth ? x + 4 : x - boxWidth - 2; var boxY = y - boxHeight - boxPadding > overlayContext.shadowBlur ? y - boxHeight - boxPadding : y + boxPadding; var textY = y - boxHeight - boxPadding > overlayContext.shadowBlur ? y - boxHeight - boxPadding : y + boxPadding; var pointerY = y - boxHeight - boxPadding > overlayContext.shadowBlur ? y - boxPadding : y + boxHeight + boxPadding; var scale = getScale(); var price = scale.pTick * x + scale.pMin; var amount = getAmount(price, type); overlayContext.beginPath(); overlayContext.moveTo(adjustX(x), adjustY(textY)); overlayContext.lineTo(adjustX(x), adjustY(pointerY)); overlayContext.closePath(); overlayContext.lineWidth = ~~(4 * devicePixelRatio); overlayContext.strokeStyle = "bids" === type ? config.bidsLineColor : config.asksLineColor; overlayContext.stroke(); overlayContext.fillStyle = config.bgColor; overlayContext.roundRect(boxX, boxY, boxWidth, boxHeight, 3 * devicePixelRatio); overlayContext.fill(); overlayContext.shadowBlur = 0; overlayContext.shadowOffsetY = 0; overlayContext.fillStyle = config.tipColor; overlayContext.font = 12 * devicePixelRatio + "px Arial"; overlayContext.fillText(canvasElement[config.lang]["委托价"], boxX + 16 * devicePixelRatio, boxY + 20 * devicePixelRatio); overlayContext.fillText(price.toFixed(config.priceFix), boxX + 16 * devicePixelRatio, boxY + 36 * devicePixelRatio); overlayContext.fillText(canvasElement[config.lang]["累计"], boxX + 16 * devicePixelRatio, boxY + 60 * devicePixelRatio); overlayContext.fillText(amount.toFixed(config.amountFix), boxX + 16 * devicePixelRatio, boxY + 76 * devicePixelRatio); } // 绘制坐标轴 function drawAxis() { mainContext.strokeStyle = config.axisColor; mainContext.lineWidth = ~~(1.5 * devicePixelRatio); mainContext.beginPath(); mainContext.moveTo(adjustX(0), adjustY(chartHeightScaled)); mainContext.lineTo(adjustX(chartWidthScaled), adjustY(chartHeightScaled)); mainContext.lineTo(adjustX(chartWidthScaled), adjustY(0)); mainContext.stroke(); mainContext.closePath(); } // 绘制买单曲线 function drawBids() { mainContext.strokeStyle = config.bidsLineColor; mainContext.lineWidth = ~~(2 * devicePixelRatio); mainContext.beginPath(); var sortedBids = bidData.sort(function (a, b) { return a[0] - b[0]; }); var stepX = chartWidthScaled / sortedBids.length / 2; for (var i = 0; i < sortedBids.length; i++) { if (i === 0) { mainContext.moveTo(adjustX(i * stepX), adjustY(getYCoordinate(sortedBids[i][1]))); } mainContext.lineTo(adjustX(i * stepX), adjustY(getYCoordinate(sortedBids[i][1]))); if (i === sortedBids.length - 1) { mainContext.lineTo(adjustX(i * stepX), adjustY(chartHeightScaled - devicePixelRatio)); } } mainContext.stroke(); mainContext.lineTo(adjustX(0), adjustY(chartHeightScaled)); mainContext.lineTo(adjustX(0), adjustY(0)); mainContext.closePath(); mainContext.fillStyle = config.bidsFillColor; mainContext.fill(); } // 绘制卖单曲线 function drawAsks() { mainContext.strokeStyle = config.asksLineColor; mainContext.beginPath(); var sortedAsks = askData.sort(function (a, b) { return a[0] - b[0]; }); var stepX = chartWidthScaled / sortedAsks.length / 2; var midX = chartWidthScaled / 2 + 2 * devicePixelRatio; for (var i = 0; i < sortedAsks.length; i++) { if (i === 0) { mainContext.lineTo(adjustX(midX), adjustY(chartHeightScaled - devicePixelRatio)); } mainContext.lineTo(adjustX(i * stepX + midX), adjustY(getYCoordinate(sortedAsks[i][1]))); if (i === sortedAsks.length - 1) { mainContext.lineTo(adjustX(chartWidthScaled), adjustY(getYCoordinate(sortedAsks[i][1]))); } } mainContext.stroke(); mainContext.lineTo(adjustX(chartWidthScaled), adjustY(chartHeightScaled - devicePixelRatio)); mainContext.lineTo(adjustX(midX), adjustY(chartHeightScaled - devicePixelRatio)); mainContext.closePath(); mainContext.fillStyle = config.asksFillColor; mainContext.fill(); } // 绘制网格和标签 function drawGrid() { var step = 32 * devicePixelRatio; var numStepsY = 1 + ~~(chartHeight / 100); var stepX = (chartWidthScaled - 2 * step) / (~~(chartWidth / 100) - 1); var stepY = (chartHeightScaled - 2 * (16 * devicePixelRatio)) / numStepsY; var xTicks = []; var yTicks = []; var xLabels = []; var yLabels = []; var yTotal = 0; var yLabelsTemp = []; var yLabelsFixed = []; var scale = getScale(); for (var x = step; x < chartWidthScaled; x += stepX) { xTicks.push(x); xLabels.push(scale.pMin + x * scale.pTick); } for (var y = chartHeightScaled - devicePixelRatio; y > 0; y -= stepY) { yTicks.push(y); yLabels.push((chartHeightScaled - devicePixelRatio - y) * scale.aTick); } yLabels.forEach(function (val, i) { yTotal += val; yLabelsTemp.push(config.noAmountTick * i); yLabelsFixed.push(i); }); if (maxValue < 5 && yTotal !== 0) { yLabels = yLabelsFixed; maxValue = (availableHeightScaled - stepY * numStepsY - 1) / stepY + 5; } if (yTotal === 0) { yLabels = yLabelsTemp; } yLabels[0] = 0; drawTicks(xTicks, xLabels, "x"); drawTicks(yTicks, yLabels, "y"); } // 绘制坐标轴标签 function drawTicks(ticks, labels, axis) { mainContext.lineWidth = ~~(1.5 * devicePixelRatio); mainContext.strokeStyle = config.axisColor; mainContext.font = 12 * devicePixelRatio + "px Arial"; mainContext.fillStyle = config.color; mainContext.textAlign = axis === "x" ? "center" : "left"; var offset = devicePixelRatio; if (axis === "x") { ticks.forEach(function (tick, i) { mainContext.beginPath(); mainContext.lineTo(adjustX(tick), adjustY(chartHeightScaled + offset)); mainContext.lineTo(adjustX(tick), adjustY((chartHeight + 4) * devicePixelRatio + offset)); mainContext.stroke(); mainContext.closePath(); mainContext.fillText(labels[i].toFixed(config.priceFix), adjustX(tick), adjustY((chartHeight + 20) * devicePixelRatio + offset)); }); } else { ticks.forEach(function (tick, i) { var label = labels[i] <= 1e3 ? labels[i].toFixed(0) : formatLargeNumber(labels[i], 2, 1e3); mainContext.beginPath(); mainContext.lineTo(adjustX(chartWidthScaled + offset), adjustY(tick + offset)); mainContext.lineTo(adjustX((chartWidth + 4) * devicePixelRatio + offset), adjustY(tick + offset)); mainContext.stroke(); mainContext.fillText(label, adjustX((chartWidth + 8) * devicePixelRatio + offset), adjustY(tick + 4 * devicePixelRatio)); mainContext.closePath(); }); } } // 获取适当的像素位置 function adjustX(value) { return .5 + ~~value; } // 获取Y轴位置 function adjustY(value) { if (value === 0) { return chartHeightScaled - devicePixelRatio; } var yPosition = availableHeightScaled - availableHeightScaled * value / maxValue + offsetY; return yPosition - chartHeightScaled < ~~(mainContext.lineWidth * devicePixelRatio) ? yPosition - ~~(mainContext.lineWidth * devicePixelRatio) : yPosition; } // 清空画布 function clearCanvas(context) { context.clearRect(0, 0, containerWidth * devicePixelRatio, containerHeight * devicePixelRatio); } // 获取价格和数量刻度 function getScale() { var minPrice = bidData[0] && bidData[0][0] || 0; var maxPrice = askData[askData.length - 1] && askData[askData.length - 1][0] || 0; return { pMin: 1 * minPrice, pMax: 1 * maxPrice, pTick: (maxPrice - minPrice) / chartWidthScaled, aTick: maxValue / availableHeightScaled }; } // 更新图表 function updateChart() { clearCanvas(mainContext); drawAxis(); drawGrid(); drawBids(); drawAsks(); } // 添加事件监听 overlayCanvas.addEventListener("mousemove", function (event) { var coordinates = getMouseCoordinates(event); if (overlayVisible) { drawPoint(coordinates.x, coordinates.y); } }, false); overlayCanvas.addEventListener("mouseout", function (event) { setTimeout(function () { return clearCanvas(overlayContext); }, 500); hoveredX = null; }, false); // 获取鼠标坐标 function getMouseCoordinates(event) { var rect = mainCanvas.getBoundingClientRect(); return { x: (event.clientX - rect.left) * devicePixelRatio, y: (event.clientY - rect.top) * devicePixelRatio }; } // 获取交易量 function getAmount(price, type) { if (type === "bids") { var bidDistances = bidData.map(function (item) { return Math.abs(item[0] - price); }); var closestIndex = bidDistances.indexOf(Math.min.apply(null, bidDistances)); if (closestIndex > -1) { label = bidData[closestIndex][1]; } return label; } var askDistances = askData.map(function (item) { return Math.abs(item[0] - price); }); var closestIndex = askDistances.indexOf(Math.min.apply(null, askDistances)); if (closestIndex > -1) { label = askData[closestIndex][1]; } return label; } // 格式化大数 function formatLargeNumber(value, precision, base) { var symbols = largeNumberFormat[base]; var formattedValue = symbols.reduce(function (result, symbol) { if (result[0] > base) { result[0] /= base; result[1] = symbol; } return result; }, [value, ""]); var decimalPlaces = precision; if (getDecimalLength(formattedValue[0]) === getDecimalLength(base) - 1) { decimalPlaces -= 1; } formattedValue[0] = formattedValue[0].toFixed(decimalPlaces); return formattedValue.join(""); } return { update: updateChart, putData: function (data) { clearCanvas(mainContext); clearCanvas(overlayContext); processChartData(data); drawAxis(); drawGrid(); drawBids(); drawAsks(); drawPoint(); }, forceUpdate: function () { containerWidth = targetElement.offsetWidth; containerHeight = targetElement.offsetHeight; mainCanvas.width = overlayCanvas.width = containerWidth * devicePixelRatio; mainCanvas.height = overlayCanvas.height = containerHeight * devicePixelRatio; mainCanvas.style.width = overlayCanvas.style.width = containerWidth + "px"; mainCanvas.style.height = overlayCanvas.style.height = containerHeight + "px"; chartWidth = containerWidth - config.ruleWidth; chartHeight = containerHeight - config.ruleHeight; padding = ~~(containerHeight * config.paddingTop / 100); offsetY = padding * devicePixelRatio; availableHeight = chartHeight - padding; availableHeightScaled = availableHeight * devicePixelRatio; chartWidthScaled = chartWidth * devicePixelRatio; chartHeightScaled = chartHeight * devicePixelRatio; overlayVisible = true; clearCanvas(overlayContext); updateChart(); }, initTheme: initializeTheme, reload: function (newConfig) { Object.assign(config, newConfig); } }; } return depthChart; });

公开 最后更新: 2024-07-03 01:34:54 AM