/**
* Copyright (c) 2018-present clchart Contributors.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
/** @module DrawUtils */
/**
* get image data
* @export
* @param {Object} context canvas's context
* @param {Number} xx
* @param {Number} yy
* @param {Number} ww
* @param {Number} hh
* @return {Object}
*/
export function _getImageData (context, xx, yy, ww, hh) {
if (context.getImageData) {
return context.getImageData(xx, yy, ww, hh)
}
return undefined
}
/**
* put image data
* @export
* @param {Object} context canvas's context
* @param {Object} img
* @param {Number} xx
* @param {Number} yy
*/
export function _putImageData (context, img, xx, yy) {
if (context.putImageData) {
context.putImageData(img, xx, yy)
}
}
/**
* set line width
* @export
* @param {Object} context canvas's context
* @param {Number} l line width
*/
export function _setLineWidth (context, l) {
context.lineWidth = l
}
/**
* get line width
* @export
* @param {Object} context canvas's context
* @return {Number} line width
*/
export function _getLineWidth (context) {
return context.lineWidth
}
// 画竖线
/**
* draw vertical line
* @export
* @param {Object} context canvas's context
* @param {Number} xx
* @param {Number} yy1
* @param {Number} yy2
*/
export function _drawVline (context, xx, yy1, yy2) {
context.moveTo(xx, yy1)
context.lineTo(xx, yy2)
}
// 画横线
/**
* draw horizontal line
* @export
* @param {Object} context canvas's context
* @param {Number} xx1
* @param {Number} xx2
* @param {Number} yy
*/
export function _drawHline (context, xx1, xx2, yy) {
context.moveTo(xx1, yy)
context.lineTo(xx2, yy)
}
// 画斜线
/**
* draw line
* @export
* @param {Object} context canvas's context
* @param {Number} xx1
* @param {Number} yy1
* @param {Number} xx2
* @param {Number} yy2
*/
export function _drawline (context, xx1, yy1, xx2, yy2) {
context.moveTo(xx1, yy1)
context.lineTo(xx2, yy2)
}
/**
* move to position
* @export
* @param {Object} context canvas's context
* @param {Number} xx
* @param {Number} yy
*/
export function _drawmoveTo (context, xx, yy) {
context.moveTo(xx, yy)
}
/**
* draw line to
* @export
* @param {Object} context canvas's context
* @param {Number} xx
* @param {Number} yy
*/
export function _drawlineTo (context, xx, yy) {
context.lineTo(xx, yy)
}
// 画空心长方形
/**
* draw empty rect
* @export
* @param {Object} context canvas's context
* @param {Number} xx
* @param {Number} yy
* @param {Number} ww
* @param {Number} hh
*/
export function _drawRect (context, xx, yy, ww, hh) {
context.strokeRect(xx, yy, ww, hh) // 这里的宽度是指不算xx的起始点的宽度,所以你写宽5,实际出来图形是6个像素,
}
// 画实心长方形
/**
* draw fill rect
* @export
* @param {Object} context canvas's context
* @param {Number} xx
* @param {Number} yy
* @param {Number} ww
* @param {Number} hh
* @param {String} fillclr
*/
export function _fillRect (context, xx, yy, ww, hh, fillclr) {
context.fillStyle = fillclr || context.fillStyle
context.fillRect(xx, yy, ww, hh)
}
/**
* clear rect
* @export
* @param {Object} context canvas's context
* @param {Number} xx
* @param {Number} yy
* @param {Number} ww
* @param {Number} hh
*/
export function _clearRect (context, xx, yy, ww, hh) {
context.clearRect(xx, yy, ww, hh)
}
// 画实心长方形
/**
* fill by color
* @export
* @param {Object} context canvas's context
* @param {String} fillclr
*/
export function _fill (context, fillclr) {
context.fillStyle = fillclr
context.fill()
}
// 开始画线
/**
* start draw line
* @export
* @param {Object} context canvas's context
* @param {String} clr
*/
export function _drawBegin (context, clr) {
context.beginPath()
context.strokeStyle = clr || context.strokeStyle
}
// 结束画线
/**
* end draw
* @export
* @param {Object} context canvas's context
*/
export function _drawEnd (context) {
context.stroke()
}
/**
* get beveling
* @private
* @param {Number} x
* @param {Number} y
* @return {Number}
*/
function __getBeveling (x, y) {
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
}
/**
* draw dash line
* @export
* @param {Object} context canvas's context
* @param {Number} x1
* @param {Number} y1
* @param {Number} x2
* @param {Number} y2
* @param {Number} dashLen
*/
export function _drawDashLine (context, x1, y1, x2, y2, dashLen) {
dashLen = dashLen === undefined ? 5 : dashLen
// 得到斜边的总长度
const beveling = __getBeveling(x2 - x1, y2 - y1)
// 计算有多少个线段
const num = Math.floor(beveling / dashLen)
for (let i = 0; i < num; i++) {
context[i % 2 === 0 ? 'moveTo' : 'lineTo'](x1 + (x2 - x1) / num * i, y1 + (y2 - y1) / num * i)
}
// context.stroke();
}
// 以下显示文字
/**
* set font size
* @export
* @param {Object} context canvas's context
* @param {String} font
* @param {Number} pixel
*/
export function _setFontSize (context, font, pixel) {
context.font = pixel + 'px ' + font
}
/**
* draw text
* @export
* @param {Object} context canvas's context
* @param {Number} xx
* @param {Number} yy
* @param {String} txt
* @param {String} font
* @param {Number} pixel
* @param {String} clr
* @param {Object} pos
*/
export function _drawTxt (context, xx, yy, txt, font, pixel, clr, pos) {
_setFontSize(context, font, pixel)
context.fillStyle = clr || context.fillStyle
context.textBaseline = pos ? pos.y || 'top' : 'top' // top(默认);middle bottom
context.textAlign = pos ? pos.x || 'start' : 'start' // start(默认);center end
// 需要将txt转为string类型,不然gcanvas会报错
context.fillText(txt.toString(), xx, yy)
}
/**
* get text width from charmap by fontsize
* @param {Object} charMap
* @param {String} txt
* @param {Number} fontSize
* @return {Number}
*/
function getTxtWith (charMap, txt, fontSize) {
const scale = fontSize / 12
let allWidth = 0
for (let i = 0; i < txt.length; i++) {
const element = txt[i].toString()
if (charMap && charMap[element]) {
allWidth += charMap[element].width
} else {
allWidth += 12
}
}
return allWidth * scale
}
/**
* get text width
* @export
* @param {Object} context canvas's context
* @param {String} txt
* @param {String} font
* @param {Number} pixel
* @return {Number} string width
*/
export function _getTxtWidth (context, txt, font, pixel) {
_setFontSize(context, font, pixel)
let width
if (context.measureText) {
try {
width = context.measureText(txt).width
} catch (error) {
width = getTxtWith(context.charMap, txt, pixel)
}
// 简单的计算尺寸返回,这样子计算存在误差,特别是存在中英文的时候
} else {
width = getTxtWith(context.charMap, txt, pixel)
}
return width
}
// export function _getTxtWidth (context, txt, font, pixel) {
// _setFontSize(context, font, pixel)
// let width
// if (context.measureText) {
// try {
// width = context.measureText(txt).width
// } catch (error) {
// // 简单的计算尺寸返回,这样子计算存在误差,特别是存在中英文的时候
// width = pixel * txt.length
// }
// }
// return width
// }
// 获取文字显示的最适合的Rect
/**
* get text bound rect
* @param {Object} context canvas's context
* @param {String} txt
* @param {Object} config
* @return {Object}
*/
function __getTxtRect (context, txt, config) {
const spaceX = config.spaceX || 2
const spaceY = config.spaceY || 2
const len = _getTxtWidth(context, txt, config.font, config.pixel)
return { width: len + 2 * spaceX, height: config.pixel + spaceY * 2 }
}
// 把文本画在矩形内
/**
* draw text rect
* @export
* @param {Object} context canvas's context
* @param {Number} xx
* @param {Number} yy
* @param {String} txt
* @param {Object} config
*/
export function _drawTxtRect (context, xx, yy, txt, config) {
const spaceX = config.spaceX || 2
const spaceY = config.spaceY || 2
const tr = __getTxtRect(context, txt, config)
let xxx, yyy
yyy = yy // top
xxx = xx // start
if (config.y === 'middle') yyy = yy - Math.floor(tr.height / 2) // middle
if (config.x === 'end') xxx = xx - tr.width
if (config.x === 'center') xxx = xx - Math.floor(tr.width / 2)
_fillRect(context, xxx, yyy, tr.width, tr.height, config.bakclr)
_drawBegin(context, config.clr)
_drawRect(context, xxx, yyy, tr.width, tr.height)
xxx = xx
yyy = yy
if (config.x === 'start') xxx = xx + spaceX // ||config.x==='center'
if (config.x === 'end') xxx = xx - spaceX
if (config.y === 'top') yyy = yy - spaceY
_drawTxt(context, xxx, yyy, txt, config.font, config.pixel, config.clr, {
x: config.x,
y: config.y
})
_drawEnd(context)
}
// 画空心圆
/**
* draw circle
* @export
* @param {Object} context canvas's context
* @param {Number} x
* @param {Number} y
* @param {Number} r
*/
export function _drawCircle (context, x, y, r) {
context.moveTo(x + r, y)
context.arc(x, y, r, 0, Math.PI * 2, true)
}
// 画实心圆
/**
* draw circle center point
* @param {Object} context canvas's context
* @param {Number} x
* @param {Number} y
* @param {Number} r
* @param {String} clr
*/
function _drawCircleAndFilled (context, x, y, r, clr) {
context.moveTo(x + r, y)
context.arc(x, y, r, 0, Math.PI * 2, true)
context.fillStyle = clr
context.fill()
}
// 画一根独立的线,不影响后面的画线颜色
/**
* draw a alone lien
* @export
* @param {Object} context canvas's context
* @param {Number} xx
* @param {Number} yy
* @param {Number} xx1
* @param {Number} yy1
* @param {String} clr
*/
export function _drawLineAlone (context, xx, yy, xx1, yy1, clr) {
const oldclr = context.strokeStyle
_drawBegin(context, clr)
context.moveTo(xx, yy)
context.lineTo(xx1, yy1)
_drawEnd(context)
context.strokeStyle = oldclr
}
// 画一个椭圆
/**
* draw a ellipse
* @export
* @param {Object} context canvas's context
* @param {Number} x
* @param {Number} y
* @param {Number} a
* @param {Number} b
* @param {Number} h
*/
export function _BezierEllipse (context, x, y, a, b, h) {
const k = 0.5522848
const ox = a * k // 水平控制点偏移量
const oy = b * k // 垂直控制点偏移量
context.beginPath()
// 从椭圆的左端点开始顺时针绘制四条三次贝塞尔曲线
if (!h) {
context.moveTo(x - a, y)
context.bezierCurveTo(x - a, y - oy, x - ox, y - b, x, y - b)
context.bezierCurveTo(x + ox, y - b, x + a, y - oy, x + a, y)
context.bezierCurveTo(x + a, y + oy, x + ox, y + b, x, y + b)
context.bezierCurveTo(x - ox, y + b, x - a, y + oy, x - a, y)
context.closePath()
} else {
context.moveTo(x, y - b)
context.bezierCurveTo(x + ox, y - b, x + a, y - oy, x + a, y)
context.bezierCurveTo(x + a, y + oy, x + ox, y + b, x, y + b)
}
context.stroke()
}
// 画一个LOGO
/**
* draw logo
* @export
* @param {any} context
* @param {any} xx
* @param {any} yy
* @param {any} size
*/
export function _drawLogo (context, xx, yy, size) {
context.beginPath()
const lw = size
context.lineWidth = lw
context.strokeStyle = '#efefef'
context.moveTo(xx - 0.5 * lw, yy) // 创建开始点
context.lineTo(xx + 5.5 * lw, yy) // 创建水平线
context.moveTo(xx, yy) // 创建开始点
context.lineTo(xx, yy + 13 * lw) // 创建水平线
context.moveTo(xx - 3 * lw, yy + 13 * lw) // 创建开始点
context.lineTo(xx + 5.5 * lw, yy + 13 * lw) // 创建水平线
context.moveTo(xx + 10 * lw, yy + 3.5 * lw)
context.lineTo(xx + 13.5 * lw, yy + 3.5 * lw) // 创建水平线
context.moveTo(xx + 10 * lw, yy + 9.5 * lw)
context.lineTo(xx + 13.5 * lw, yy + 9.5 * lw) // 创建水平线
context.stroke()
_BezierEllipse(context, xx + 5.5 * lw, yy + 6.5 * lw, 5 * lw, 6.5 * lw, true)
_BezierEllipse(context, xx + 9 * lw, yy + 6.5 * lw, 5 * lw, 6.5 * lw)
context.fillStyle = '#000'
context.fillRect(xx + 8.5 * lw, yy + 4 * lw, 6 * lw, 5 * lw)
}
// 以下函数只能调用上面的函数,不能直接画图
// data {o,h,l,c}
/**
* draw sign plot
* @export
* @param {Object} context canvas's context
* @param {Number} x
* @param {Number} y
* @param {Object} Arc1
* @param {Object} Arc2
*/
export function _drawSignPlot (context, x, y, Arc1, Arc2) {
if (Arc1 !== undefined && Arc1.r > 0) {
_drawBegin(context, Arc1.clr)
_drawmoveTo(context, x - Arc1.r, y)
_drawlineTo(context, x + Arc1.r, y)
_drawlineTo(context, x, y + 2 * Arc1.r)
_drawlineTo(context, x - Arc1.r, y)
_fill(context, Arc1.clr)
_drawCircleAndFilled(context, x, y, Arc1.r, Arc1.clr)
_drawEnd(context)
}
if (Arc2 !== undefined && Arc2.r > 0) {
_drawBegin(context, Arc2.clr)
_drawCircleAndFilled(context, x, y, Arc2.r, Arc2.clr)
_drawEnd(context)
}
}
/**
* draw sign circle
* @export
* @param {Object} context canvas's context
* @param {Number} x
* @param {Number} y
* @param {Object} Arc1
* @param {Object} Arc2
* @param {Object} Arc3
*/
export function _drawSignCircle (context, x, y, Arc1, Arc2, Arc3) {
if (Arc1 !== undefined && Arc1.r > 0) {
_drawBegin(context, Arc1.clr)
_drawCircleAndFilled(context, x, y, Arc1.r, Arc1.clr)
_drawEnd(context)
}
if (Arc2 !== undefined && Arc2.r > 0) {
_drawBegin(context, Arc2.clr)
_drawCircleAndFilled(context, x, y, Arc2.r, Arc2.clr)
_drawEnd(context)
}
if (Arc3 !== undefined && Arc3.r > 0) {
_drawBegin(context, Arc3.clr)
_drawCircleAndFilled(context, x, y, Arc3.r, Arc3.clr)
_drawEnd(context)
}
}
/**
* draw sing horizontal line
* @export
* @param {Object} context canvas's context
* @param {Object} config
* @param {Object} item
*/
export function _drawSignHLine (context, config, item) {
_drawBegin(context, config.clr)
_drawDashLine(context, config.xx, config.yy, config.right - config.pixel / 2, config.yy, 7)
_drawEnd(context)
const spaceX = config.spaceX || config.linew * 2
const spaceY = config.spaceY || config.linew
config.width = config.right - config.xx
for (let i = 0; i < item.length; i++) {
if (item[i].display === 'false') continue
let xx = config.xx + config.width * item[i].set / 100
if (item[i].txt === 'arc') {
if ((xx + item[i].maxR) > config.right) xx = config.right - item[i].maxR
_drawSignCircle(context, xx, config.yy,
{ r: item[i].maxR, clr: config.clr },
{ r: item[i].minR, clr: config.bakclr }
)
} else {
const tr = __getTxtRect(context, item[i].txt, {
font: config.font, pixel: config.pixel, spaceX, spaceY
})
if ((xx + tr.width) > config.right) xx = config.right - tr.width
let yy = config.yy
if (config.top && yy < config.top + tr.height / 2) {
yy = config.top + tr.height / 2
}
if (config.bottom && yy > config.bottom - tr.height / 2) {
yy = config.bottom - tr.height / 2
}
_drawTxtRect(context, xx, yy, item[i].txt, {
font: config.font,
pixel: config.pixel,
clr: config.clr,
bakclr: config.bakclr,
x: 'start',
y: config.y,
spaceX,
spaceY
})
}
}
}
/**
* draw sign vertical line
* @export
* @param {Object} context canvas's context
* @param {Object} config
* @param {Object} item
*/
export function _drawSignVLine (context, config, item) {
_drawBegin(context, config.clr)
_drawDashLine(context, config.xx, config.yy, config.xx, config.bottom - config.pixel / 2, 7)
_drawEnd(context)
const spaceX = config.spaceX || config.linew * 2
const spaceY = config.spaceY || config.linew
config.height = config.bottom - config.yy
for (let i = 0; i < item.length; i++) {
if (item[i].display === 'false') continue
let yy = config.yy + config.height * item[i].set / 100
if (item[i].txt === 'arc') {
if ((yy + item[i].maxR) > config.bottom) yy = config.bottom - item[i].maxR
_drawSignCircle(context, config.xx, yy,
{ r: item[i].maxR, clr: config.clr },
{ r: item[i].minR, clr: config.bakclr }
)
} else {
const tr = __getTxtRect(context, item[i].txt, {
font: config.font, pixel: config.pixel, spaceX, spaceY
})
if ((yy + tr.height) > config.bottom) yy = config.bottom - tr.height
let xx = config.xx
if (config.left && xx < config.left + tr.width / 2) {
xx = config.left + tr.width / 2
}
_drawTxtRect(context, xx, yy, item[i].txt, {
font: config.font,
pixel: config.pixel,
clr: config.clr,
bakclr: config.bakclr,
x: 'center',
y: 'middle',
spaceX,
spaceY
})
}
}
}
// { index:k, unitX: unitX, spaceX:spaceX, unitY:unitY,maxmin:mm},
// data {o,h,l,c}
/**
* draw kbar
* @export
* @param {Object} context canvas's context
* @param {Object} config
* @param {Object} item
*/
export function _drawKBar (context, config, item) {
const xx = config.rect.left + config.index * (config.unitX + config.spaceX)
const xxm = xx + Math.floor(config.unitX / 2)
const yyh = config.rect.top + Math.round((config.maxmin.max - item[1]) * config.unitY)
const yyl = config.rect.top + config.rect.height - Math.round((item[2] - config.maxmin.min) * config.unitY)
let hh
const yy = config.rect.top + Math.round((config.maxmin.max - item[0]) * config.unitY)
if (item[0] === item[3]) {
hh = 0
_drawHline(context, xx, xx + config.unitX, yy)
if (item[1] > item[2]) {
_drawVline(context, xxm, yyh, yyl)
}
} else {
hh = Math.round((item[0] - item[3]) * config.unitY)
_drawVline(context, xxm, yyh, yy)
if (config.filled) {
_fillRect(context, xx, yy, config.unitX, hh, config.fillclr)
} else {
_drawRect(context, xx, yy, config.unitX, hh)
}
_drawVline(context, xxm, yy + hh, yyl)
}
}
// data {o,h,l,c}
/**
* draw volume bar
* @export
* @param {Object} context canvas's context
* @param {Object} config
* @param {Object} value
*/
export function _drawVBar (context, config, value) {
const xx = config.rect.left + config.index * (config.unitX + config.spaceX)
const yy = config.rect.top + Math.round((config.maxmin.max - value) * config.unitY)
const hh = config.rect.top + config.rect.height - yy
if (config.filled) {
_fillRect(context, xx, yy, config.unitX, hh, config.fillclr)
} else {
_drawRect(context, xx, yy, config.unitX, hh)
}
}
// 以下函数为辅助画图的工具函数
// Adjust 灰度
// 为传入的16进制颜色增加透明度 ‘#1F1F2F’ -> rgba(...)
/**
* transfer color to rgba
* @export
* @param {String} scolor
* @param {Number} trans
* @param {String} style
* @return {String}
*/
export function _setTransColor (scolor, trans, style) {
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
let sColor = scolor.toLowerCase()
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = '#'
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
}
sColor = sColorNew
}
// 处理六位的颜色值
const sColorChange = []
for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
}
// 效果处理
switch (style) {
case 'adjust':
const r = sColorChange[0]
const g = sColorChange[1]
const b = sColorChange[2]
sColorChange[0] = (r * 0.272) + (g * 0.534) + (b * 0.131)
sColorChange[1] = (r * 0.349) + (g * 0.686) + (b * 0.168)
sColorChange[2] = (r * 0.393) + (g * 0.769) + (b * 0.189)
break
}
sColor = sColorChange.join(',')
trans = trans || 1
return 'rgba(' + sColor + ',' + trans + ')'
} else {
return sColor
}
}