1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-06-12 13:19:31 +08:00

v1.0.0-beta 源码正式开源

This commit is contained in:
小莫唐尼
2022-12-06 15:08:29 +08:00
commit 636ae7b169
461 changed files with 116817 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,377 @@
import Style from './style.class'
import transition from '../transition'
import {
deepClone,
getRotatePointPos,
getScalePointPos,
getTranslatePointPos,
checkPointIsInRect
} from '../plugin/util'
/**
* @description Class Graph
* @param {Object} graph Graph default configuration
* @param {Object} config Graph config
* @return {Graph} Instance of Graph
*/
export default class Graph {
constructor (graph, config) {
config = deepClone(config, true)
config = {animationFrame:24,...config};
const defaultConfig = {
/**
* @description Weather to render graph
* @type {Boolean}
* @default visible = true
*/
visible: true,
/**
* @description Whether to enable drag
* @type {Boolean}
* @default drag = false
*/
drag: false,
/**
* @description Whether to enable hover
* @type {Boolean}
* @default hover = false
*/
hover: false,
/**
* @description Graph rendering index
* Give priority to index high graph in rendering
* @type {Number}
* @example index = 1
*/
index: 1,
/**
* @description Animation delay time(ms)
* @type {Number}
* @default animationDelay = 0
*/
animationDelay: 0,
/**
* @description Number of animation frames
* @type {Number}
* @default animationFrame = 30
*/
animationFrame: 30,
/**
* @description Animation dynamic curve (Supported by transition)
* @type {String}
* @default animationCurve = 'linear'
* @link https://github.com/jiaming743/Transition
*/
animationCurve: 'linear',
/**
* @description Weather to pause graph animation
* @type {Boolean}
* @default animationPause = false
*/
animationPause: false,
/**
* @description Rectangular hover detection zone
* Use this method for hover detection first
* @type {Null|Array}
* @default hoverRect = null
* @example hoverRect = [0, 0, 100, 100] // [Rect start x, y, Rect width, height]
*/
hoverRect: null,
/**
* @description Mouse enter event handler
* @type {Function|Null}
* @default mouseEnter = null
*/
mouseEnter: null,
/**
* @description Mouse outer event handler
* @type {Function|Null}
* @default mouseOuter = null
*/
mouseOuter: null,
/**
* @description Mouse click event handler
* @type {Function|Null}
* @default click = null
*/
click: null
}
const configAbleNot = {
status: 'static',
animationRoot: [],
animationKeys: [],
animationFrameState: [],
cache: {}
}
if (!config.shape) config.shape = {}
if (!config.style) config.style = {}
const shape = Object.assign({}, graph.shape, config.shape)
Object.assign(defaultConfig, config, configAbleNot)
Object.assign(this, graph, defaultConfig)
this.shape = shape
this.style = new Style(config.style)
this.addedProcessor()
}
}
/**
* @description Processor of added
* @return {Undefined} Void
*/
Graph.prototype.addedProcessor = function () {
if (typeof this.setGraphCenter === 'function') this.setGraphCenter(null, this)
// The life cycle 'added"
if (typeof this.added === 'function') this.added(this)
}
/**
* @description Processor of draw
* @param {CRender} render Instance of CRender
* @param {Graph} graph Instance of Graph
* @return {Undefined} Void
*/
Graph.prototype.drawProcessor = function (render, graph) {
const { ctx } = render
const { shape } = graph
graph.style.initStyle(ctx,shape)
if (typeof this.beforeDraw === 'function') this.beforeDraw(this, render)
graph.draw(render, graph)
// ctx.draw(true)
if (typeof this.drawed === 'function') this.drawed(this, render)
graph.style.restoreTransform(ctx)
}
/**
* @description Processor of hover check
* @param {Array} position Mouse Position
* @param {Graph} graph Instance of Graph
* @return {Boolean} Result of hover check
*/
Graph.prototype.hoverCheckProcessor = function (position, { hoverRect, style, hoverCheck }) {
const { graphCenter, rotate, scale, translate } = style
if (graphCenter) {
if (rotate) position = getRotatePointPos(-rotate, position, graphCenter)
if (scale) position = getScalePointPos(scale.map(s => 1 / s), position, graphCenter)
if (translate) position = getTranslatePointPos(translate.map(v => v * -1), position)
}
if (hoverRect) return checkPointIsInRect(position, ...hoverRect)
return hoverCheck(position, this)
}
/**
* @description Processor of move
* @param {Event} e Mouse movement event
* @return {Undefined} Void
*/
Graph.prototype.moveProcessor = function (e) {
this.move(e, this)
if (typeof this.beforeMove === 'function') this.beforeMove(e, this)
if (typeof this.setGraphCenter === 'function') this.setGraphCenter(e, this)
if (typeof this.moved === 'function') this.moved(e, this)
}
/**
* @description Update graph state
* @param {String} attrName Updated attribute name
* @param {Any} change Updated value
* @return {Undefined} Void
*/
Graph.prototype.attr = function (attrName, change = undefined) {
if (!attrName || change === undefined) return false
const isObject = typeof this[attrName] === 'object'
if (isObject) change = deepClone(change, true)
const { render } = this
if (attrName === 'style') {
this.style.update(change)
} else if (isObject) {
Object.assign(this[attrName], change)
} else {
this[attrName] = change
}
if (attrName === 'index') render.sortGraphsByIndex()
render.drawAllGraph()
}
/**
* @description Update graphics state (with animation)
* Only shape and style attributes are supported
* @param {String} attrName Updated attribute name
* @param {Any} change Updated value
* @param {Boolean} wait Whether to store the animation waiting
* for the next animation request
* @return {Promise} Animation Promise
*/
Graph.prototype.animation = async function (attrName, change, wait = false) {
if (attrName !== 'shape' && attrName !== 'style') {
console.error('Only supported shape and style animation!')
return
}
change = deepClone(change, true)
if (attrName === 'style') this.style.colorProcessor(change)
const changeRoot = this[attrName]
const changeKeys = Object.keys(change)
const beforeState = {}
changeKeys.forEach(key => (beforeState[key] = changeRoot[key]))
const { animationFrame, animationCurve, animationDelay } = this
const animationFrameState = transition(animationCurve, beforeState, change, animationFrame, true)
this.animationRoot.push(changeRoot)
this.animationKeys.push(changeKeys)
this.animationFrameState.push(animationFrameState)
if (wait) return
if (animationDelay > 0) await delay(animationDelay)
const { render } = this
return new Promise(async resolve => {
await render.launchAnimation()
resolve()
})
}
/**
* @description Extract the next frame of data from the animation queue
* and update the graph state
* @return {Undefined} Void
*/
Graph.prototype.turnNextAnimationFrame = function (timeStamp) {
const { animationDelay, animationRoot, animationKeys, animationFrameState, animationPause } = this
if (animationPause) return
if (Date.now() - timeStamp < animationDelay) return
animationRoot.forEach((root, i) => {
animationKeys[i].forEach(key => {
root[key] = animationFrameState[i][0][key]
})
})
animationFrameState.forEach((stateItem, i) => {
stateItem.shift()
const noFrame = stateItem.length === 0
if (noFrame) animationRoot[i] = null
if (noFrame) animationKeys[i] = null
})
this.animationFrameState = animationFrameState.filter(state => state.length)
this.animationRoot = animationRoot.filter(root => root)
this.animationKeys = animationKeys.filter(keys => keys)
}
/**
* @description Skip to the last frame of animation
* @return {Undefined} Void
*/
Graph.prototype.animationEnd = function () {
const { animationFrameState, animationKeys, animationRoot, render } = this
animationRoot.forEach((root, i) => {
const currentKeys = animationKeys[i]
const lastState = animationFrameState[i].pop()
currentKeys.forEach(key => (root[key] = lastState[key]))
})
this.animationFrameState = []
this.animationKeys = []
this.animationRoot = []
return render.drawAllGraph()
}
/**
* @description Pause animation behavior
* @return {Undefined} Void
*/
Graph.prototype.pauseAnimation = function () {
this.attr('animationPause', true)
}
/**
* @description Try animation behavior
* @return {Undefined} Void
*/
Graph.prototype.playAnimation = function () {
const { render } = this
this.attr('animationPause', false)
return new Promise(async resolve => {
await render.launchAnimation()
resolve()
})
}
/**
* @description Processor of delete
* @param {CRender} render Instance of CRender
* @return {Undefined} Void
*/
Graph.prototype.delProcessor = function (render) {
const { graphs } = render
const index = graphs.findIndex(graph => graph === this)
if (index === -1) return
if (typeof this.beforeDelete === 'function') this.beforeDelete(this)
graphs.splice(index, 1, null)
if (typeof this.deleted === 'function') this.deleted(this)
}
/**
* @description Return a timed release Promise
* @param {Number} time Release time
* @return {Promise} A timed release Promise
*/
function delay (time) {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
@@ -0,0 +1,467 @@
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
}