You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

468 lines
12 KiB
JavaScript

import { getRgbaValue, getColorFromRgbValue } from '../color'
import { deepClone } from '../plugin/util'
/**
* @description Class Style
* @param {Object} style Style configuration
* @return {Style} Instance of Style
*/
export default class Style {
constructor (style) {
this.colorProcessor(style)
const defaultStyle = {
/**
* @description Rgba value of graph fill color
* @type {Array}
* @default fill = [0, 0, 0, 1]
*/
fill: [0, 0, 0, 1],
/**
* @description Rgba value of graph stroke color
* @type {Array}
* @default stroke = [0, 0, 0, 1]
*/
stroke: [0, 0, 0, 0],
/**
* @description Opacity of graph
* @type {Number}
* @default opacity = 1
*/
opacity: 1,
/**
* @description LineCap of Ctx
* @type {String}
* @default lineCap = null
* @example lineCap = 'butt'|'round'|'square'
*/
lineCap: null,
/**
* @description Linejoin of Ctx
* @type {String}
* @default lineJoin = null
* @example lineJoin = 'round'|'bevel'|'miter'
*/
lineJoin: null,
/**
* @description LineDash of Ctx
* @type {Array}
* @default lineDash = null
* @example lineDash = [10, 10]
*/
lineDash: null,
/**
* @description LineDashOffset of Ctx
* @type {Number}
* @default lineDashOffset = null
* @example lineDashOffset = 10
*/
lineDashOffset: null,
/**
* @description ShadowBlur of Ctx
* @type {Number}
* @default shadowBlur = 0
*/
shadowBlur: 0,
/**
* @description Rgba value of graph shadow color
* @type {Array}
* @default shadowColor = [0, 0, 0, 0]
*/
shadowColor: [0, 0, 0, 0],
/**
* @description ShadowOffsetX of Ctx
* @type {Number}
* @default shadowOffsetX = 0
*/
shadowOffsetX: 0,
/**
* @description ShadowOffsetY of Ctx
* @type {Number}
* @default shadowOffsetY = 0
*/
shadowOffsetY: 0,
/**
* @description LineWidth of Ctx
* @type {Number}
* @default lineWidth = 0
*/
lineWidth: 0,
/**
* @description Center point of the graph
* @type {Array}
* @default graphCenter = null
* @example graphCenter = [10, 10]
*/
graphCenter: null,
/**
* @description Graph scale
* @type {Array}
* @default scale = null
* @example scale = [1.5, 1.5]
*/
scale: null,
/**
* @description Graph rotation degree
* @type {Number}
* @default rotate = null
* @example rotate = 10
*/
rotate: null,
/**
* @description Graph translate distance
* @type {Array}
* @default translate = null
* @example translate = [10, 10]
*/
translate: null,
/**
* @description Cursor status when hover
* @type {String}
* @default hoverCursor = 'pointer'
* @example hoverCursor = 'default'|'pointer'|'auto'|'crosshair'|'move'|'wait'|...
*/
hoverCursor: 'pointer',
/**
* @description Font style of Ctx
* @type {String}
* @default fontStyle = 'normal'
* @example fontStyle = 'normal'|'italic'|'oblique'
*/
fontStyle: 'normal',
/**
* @description Font varient of Ctx
* @type {String}
* @default fontVarient = 'normal'
* @example fontVarient = 'normal'|'small-caps'
*/
fontVarient: 'normal',
/**
* @description Font weight of Ctx
* @type {String|Number}
* @default fontWeight = 'normal'
* @example fontWeight = 'normal'|'bold'|'bolder'|'lighter'|Number
*/
fontWeight: 'normal',
/**
* @description Font size of Ctx
* @type {Number}
* @default fontSize = 10
*/
fontSize: 10,
/**
* @description Font family of Ctx
* @type {String}
* @default fontFamily = 'Arial'
*/
padding:0,
lineHeight:1,//行高
wrap:true,//是否自动断行。
ellipsis:false,//一行,超过省略号。
letterSpacing:0,
fontFamily: 'Arial',
textDecoration:'none',
/**
* @description TextAlign of Ctx
* @type {String}
* @default textAlign = 'center'
* @example textAlign = 'start'|'end'|'left'|'right'|'center'
*/
textAlign: 'left',
/**
* @description TextBaseline of Ctx
* @type {String}
* @default textBaseline = 'middle'
* @example textBaseline = 'top'|'bottom'|'middle'|'alphabetic'|'hanging'
*/
textBaseline: 'middle',
/**
* @description The color used to create the gradient
* @type {Array}
* @default gradientColor = null
* @example gradientColor = ['#000', '#111', '#222']
*/
gradientColor: null,
/**
* @description Gradient type
* @type {String}
* @default gradientType = 'linear'
* @example gradientType = 'linear' | 'radial'
*/
gradientType: 'linear',
/**
* @description Gradient params
* @type {Array}
* @default gradientParams = null
* @example gradientParams = [x0, y0, x1, y1] (Linear Gradient)
* @example gradientParams = [x0, y0, r0, x1, y1, r1] (Radial Gradient)
*/
gradientParams: null,
/**
* @description When to use gradients
* @type {String}
* @default gradientWith = 'stroke'
* @example gradientWith = 'stroke' | 'fill'
*/
gradientWith: 'stroke',
/**
* @description Gradient color stops
* @type {String}
* @default gradientStops = 'auto'
* @example gradientStops = 'auto' | [0, .2, .3, 1]
*/
gradientStops: 'auto',
/**
* @description Extended color that supports animation transition
* @type {Array|Object}
* @default colors = null
* @example colors = ['#000', '#111', '#222', 'red' ]
* @example colors = { a: '#000', b: '#111' }
*/
colors: null
}
Object.assign(this, defaultStyle, style)
}
}
/**
* @description Set colors to rgba value
* @param {Object} style style config
* @param {Boolean} reverse Whether to perform reverse operation
* @return {Undefined} Void
*/
Style.prototype.colorProcessor = function (style, reverse = false) {
const processor = reverse ? getColorFromRgbValue : getRgbaValue
const colorProcessorKeys = ['fill', 'stroke', 'shadowColor']
const allKeys = Object.keys(style)
const colorKeys = allKeys.filter(key => colorProcessorKeys.find(k => k === key))
colorKeys.forEach(key => (style[key] = processor(style[key])))
const { gradientColor, colors } = style
if (gradientColor) style.gradientColor = gradientColor.map(c => processor(c))
if (colors) {
const colorsKeys = Object.keys(colors)
colorsKeys.forEach(key => (colors[key] = processor(colors[key])))
}
}
/**
* @description Init graph style
* @param {Object} ctx Context of canvas
* @return {Undefined} Void
*/
Style.prototype.initStyle = function (ctx,shape) {
initTransform(ctx, this)
initGraphStyle(ctx, this)
initGradient(ctx, this,shape)
}
/**
* @description Init canvas transform
* @param {Object} ctx Context of canvas
* @param {Style} style Instance of Style
* @return {Undefined} Void
*/
function initTransform (ctx, style) {
ctx.save()
const { graphCenter, rotate, scale, translate } = style
if (!(graphCenter instanceof Array)) return
if(graphCenter.length>0) ctx.translate(...graphCenter);
if (rotate) ctx.rotate(rotate * Math.PI / 180)
if (scale instanceof Array) ctx.scale(...scale)
if (translate) ctx.translate(...translate)
ctx.translate(-graphCenter[0], -graphCenter[1])
}
const autoSetStyleKeys = [
'lineCap', 'lineJoin', 'lineDashOffset',
'shadowOffsetX', 'shadowOffsetY', 'lineWidth',
'textAlign', 'textBaseline'
]
/**
* @description Set the style of canvas ctx
* @param {Object} ctx Context of canvas
* @param {Style} style Instance of Style
* @return {Undefined} Void
*/
function initGraphStyle (ctx, style) {
let { fill, stroke, shadowColor, opacity } = style
autoSetStyleKeys.forEach(key => {
if (key || typeof key === 'number') ctx[key] = style[key]
})
fill = [...fill]
stroke = [...stroke]
shadowColor = [...shadowColor]
fill[3] *= opacity
stroke[3] *= opacity
shadowColor[3] *= opacity
ctx.fillStyle = getColorFromRgbValue(fill)
ctx.strokeStyle = getColorFromRgbValue(stroke)
ctx.shadowColor = getColorFromRgbValue(shadowColor)
let { lineDash, shadowBlur } = style
if (lineDash) {
lineDash = lineDash.map(v => v >= 0 ? v : 0)
ctx.setLineDash(lineDash)
}
if (typeof shadowBlur === 'number') ctx.shadowBlur = shadowBlur > 0 ? shadowBlur : 0.001
const { fontStyle, fontVarient, fontWeight, fontSize, fontFamily } = style
ctx.font = fontStyle + ' ' + fontVarient + ' ' + fontWeight + ' ' + fontSize + 'px' + ' ' + fontFamily
}
/**
* @description Set the gradient color of canvas ctx
* @param {Object} ctx Context of canvas
* @param {Style} style Instance of Style
* @return {Undefined} Void
*/
function initGradient (ctx, style,shape) {
if (!gradientValidator(style)) return
let { gradientColor, gradientParams, gradientType, gradientWith, gradientStops, opacity } = style
gradientColor = gradientColor.map(color => {
let colorOpacity = color[3] * opacity
let clonedColor = [...color]
clonedColor[3] = colorOpacity
return clonedColor
})
gradientColor = gradientColor.map(c => getColorFromRgbValue(c))
if (gradientStops === 'auto') gradientStops = getAutoColorStops(gradientColor)
let gra = [...gradientParams];
if(gradientType =='linear'){
gra[0] =shape.x + gradientParams[0]
gra[1] =shape.y + gradientParams[1]
gra[2] =shape.x +gradientParams[2]
gra[3] =shape.y +gradientParams[3]
}
const gradient = ctx[`create${gradientType.slice(0, 1).toUpperCase() + gradientType.slice(1)}Gradient`](...gra)
gradientStops.forEach((stop, i) => gradient.addColorStop(stop, gradientColor[i]))
ctx[`${gradientWith}Style`] = gradient
}
/**
* @description Check if the gradient configuration is legal
* @param {Style} style Instance of Style
* @return {Boolean} Check Result
*/
function gradientValidator (style) {
const { gradientColor, gradientParams, gradientType, gradientWith, gradientStops } = style
if (!gradientColor || !gradientParams) return false
if (gradientColor.length === 1) {
console.warn('The gradient needs to provide at least two colors')
return false
}
if (gradientType !== 'linear' && gradientType !== 'radial') {
console.warn('GradientType only supports linear or radial, current value is ' + gradientType)
return false
}
const gradientParamsLength = gradientParams.length
if (
(gradientType === 'linear' && gradientParamsLength !== 4) ||
(gradientType === 'radial' && gradientParamsLength !== 6)
) {
console.warn('The expected length of gradientParams is ' + (gradientType === 'linear' ? '4' : '6'))
return false
}
if (gradientWith !== 'fill' && gradientWith !== 'stroke') {
console.warn('GradientWith only supports fill or stroke, current value is ' + gradientWith)
return false
}
if (gradientStops !== 'auto' && !(gradientStops instanceof Array)) {
console.warn(`gradientStops only supports 'auto' or Number Array ([0, .5, 1]), current value is ` + gradientStops)
return false
}
return true
}
/**
* @description Get a uniform gradient color stop
* @param {Array} color Gradient color
* @return {Array} Gradient color stop
*/
function getAutoColorStops (color) {
const stopGap = 1 / (color.length - 1)
return color.map((foo, i) => stopGap * i)
}
/**
* @description Restore canvas ctx transform
* @param {Object} ctx Context of canvas
* @return {Undefined} Void
*/
Style.prototype.restoreTransform = function (ctx) {
ctx.restore()
}
/**
* @description Update style data
* @param {Object} change Changed data
* @return {Undefined} Void
*/
Style.prototype.update = function (change) {
this.colorProcessor(change)
Object.assign(this, change)
}
/**
* @description Get the current style configuration
* @return {Object} Style configuration
*/
Style.prototype.getStyle = function () {
const clonedStyle = deepClone(this, true)
this.colorProcessor(clonedStyle, true)
return clonedStyle
}