vislite
Version:
灵活、快速、简单的数据可视化交互式跨端前端库
333 lines (297 loc) • 9.9 kB
text/typescript
import type { default as SVGConfigType, svgElType, svgBoardType } from "../../../types/SVGConfig"
import { initText, initCircle, initPath, initRect, initArc } from "./config"
import { toNode, setAttribute, getAttribute, full, fill, stroke } from "./tool"
import rotate from "../../rotate"
import { linearGradient, radialGradient } from "./gradient"
import defaultFactory from "./default"
class SVG {
readonly name: string = "SVG"
// 用于记录配置
private __config = defaultFactory()
// 作用的节点
private __useEl: SVGElement
// 路径(和canvas2D的类似)
private __path: string = ""
private __currentPosition: number[] = []
protected __svg: SVGElement
constructor(svg: SVGElement) {
this.__svg = svg
}
// 属性设置或获取
config(params: SVGConfigType) {
if (typeof params !== "object") {
return this.__config[params]
} else {
for (const key in params) {
(this.__config as any)[key] = (params as any)[key]
}
}
return this
}
reset() {
this.config(defaultFactory())
return this
}
/**
* 基础方法
* ---------------------------------
*/
// 标记应用节点
// 也就是后续操作都会作用在此节点
useEl(el: SVGElement) {
this.__useEl = el
return this
}
// 获取当前应用的节点
getEl() {
return this.__useEl
}
// 追加节点
// el可以是结点或字符串,字符串的话表示节点名称
// context可选,表示追加位置,可选,默认根svg
// 此外,和appendBoard等操作一样,执行后新加入的结点会自动变成应用节点
appendEl(el: svgElType, context?: SVGElement) {
context = context || this.__svg
if (typeof el == "string") el = toNode(el)
context.appendChild(el as SVGElement)
this.__useEl = el as SVGElement
return this
}
// 追加绘制板
// 参数和appendEl类似,只是el如果是字符串的话,表示需要绘制对应什么内容,
// 比如el = “arc”,表示画弧(不是路径arc),那么我们会创建path节点,因为我们是使用path实现的
appendBoard(el: svgBoardType, context?: SVGElement) {
let _el: svgBoardType | string = el
if (typeof el == "string")
_el =
{
text: "text",
path: "path",
arc: "path",
circle: "circle",
rect: "rect",
}[el] || ""
if (_el == "") throw new Error("Unsupported drawing method:" + el)
return this.appendEl(_el as svgElType, context)
}
// 删除当前维护的节点
remove() {
if (!this.__useEl) {
throw new Error("Currently, no node can be deleted.")
} else {
((this.__useEl as SVGElement).parentNode as SVGElement).removeChild(
this.__useEl
)
}
return this
}
// 设置或获取节点属性
attr(params: any) {
if (!this.__useEl)
throw new Error("Currently, no node can be modified or viewed.")
if (typeof params !== "object") {
return getAttribute(this.__useEl, params)
} else {
for (const key in params) {
setAttribute(this.__useEl, key, params[key])
}
return this
}
}
/**
* 绘制方法
* ---------------------------------
*/
// 文字
// deg表示文字旋转角度,是角度值,不是弧度
fillText(text: string, x: number, y: number, deg: number = 0) {
initText(this.__useEl, this.__config, x, y, deg)
this.__useEl.textContent = text
fill(this.__useEl, this.__config)
return this
}
strokeText(text: string, x: number, y: number, deg: number = 0) {
initText(this.__useEl, this.__config, x, y, deg)
this.__useEl.textContent = text
stroke(this.__useEl, this.__config)
return this
}
fullText(text: string, x: number, y: number, deg: number = 0) {
initText(this.__useEl, this.__config, x, y, deg)
this.__useEl.textContent = text
full(this.__useEl, this.__config)
return this
}
// 弧
fillArc(
cx: number,
cy: number,
r1: number,
r2: number,
beginDeg: number,
deg: number
) {
initArc(this.__useEl, this.__config, cx, cy, r1, r2, beginDeg, deg)
fill(this.__useEl, this.__config)
return this
}
strokeArc(
cx: number,
cy: number,
r1: number,
r2: number,
beginDeg: number,
deg: number
) {
initArc(this.__useEl, this.__config, cx, cy, r1, r2, beginDeg, deg)
stroke(this.__useEl, this.__config)
return this
}
fullArc(
cx: number,
cy: number,
r1: number,
r2: number,
beginDeg: number,
deg: number
) {
initArc(this.__useEl, this.__config, cx, cy, r1, r2, beginDeg, deg)
full(this.__useEl, this.__config)
return this
}
// 圆形
fillCircle(cx: number, cy: number, r: number) {
initCircle(this.__useEl, cx, cy, r)
fill(this.__useEl, this.__config)
return this
}
strokeCircle(cx: number, cy: number, r: number) {
initCircle(this.__useEl, cx, cy, r)
stroke(this.__useEl, this.__config)
return this
}
fullCircle(cx: number, cy: number, r: number) {
initCircle(this.__useEl, cx, cy, r)
full(this.__useEl, this.__config)
return this
}
// 矩形
fillRect(x: number, y: number, width: number, height: number) {
initRect(this.__useEl, this.__config, x, y, width, height)
fill(this.__useEl, this.__config)
return this
}
strokeRect(x: number, y: number, width: number, height: number) {
initRect(this.__useEl, this.__config, x, y, width, height)
stroke(this.__useEl, this.__config)
return this
}
fullRect(x: number, y: number, width: number, height: number) {
initRect(this.__useEl, this.__config, x, y, width, height)
full(this.__useEl, this.__config)
return this
}
// 路径
beginPath() {
this.__currentPosition = []
this.__path = ""
return this
}
closePath() {
this.__path += "Z"
return this
}
moveTo(x: number, y: number) {
this.__currentPosition = [x, y]
this.__path += "M" + x + " " + y
return this
}
lineTo(x: number, y: number) {
this.__currentPosition = [x, y]
this.__path += (this.__path == "" ? "M" : "L") + x + " " + y
return this
}
fill() {
initPath(this.__useEl, this.__path)
fill(this.__useEl, this.__config)
return this
}
stroke() {
initPath(this.__useEl, this.__path)
stroke(this.__useEl, this.__config)
return this
}
full() {
initPath(this.__useEl, this.__path)
full(this.__useEl, this.__config)
return this
}
arc(x: number, y: number, r: number, beginDeg: number, deg: number) {
const begPosition = rotate(x, y, (beginDeg / 180) * Math.PI, x + r, y)
const endPosition = rotate(
x,
y,
((beginDeg + deg) / 180) * Math.PI,
x + r,
y
)
// 如果当前没有路径,说明是开始的,就移动到正确位置
if (this.__path == "") {
this.__path += "M" + begPosition[0] + "," + begPosition[1]
}
// 如果当前有路径,位置不正确,应该画到正确位置(和canvas保持一致)
else if (
begPosition[0] != this.__currentPosition[0] ||
begPosition[1] != this.__currentPosition[1]
) {
this.__path += "L" + begPosition[0] + "," + begPosition[1]
}
this.__path +=
"A" +
r +
"," +
r +
" 0 " +
(deg > 180 || deg < -180 ? 1 : 0) +
"," +
(deg > 0 ? 1 : 0) +
" " +
endPosition[0] +
"," +
endPosition[1]
return this
}
// 路径 - 贝塞尔曲线
quadraticCurveTo(cpx: number, cpy: number, x: number, y: number) {
this.__path += "Q" + cpx + " " + cpy + "," + x + " " + y
return this
}
bezierCurveTo(
cp1x: number,
cp1y: number,
cp2x: number,
cp2y: number,
x: number,
y: number
) {
this.__path +=
"C" + cp1x + " " + cp1y + "," + cp2x + " " + cp2y + "," + x + " " + y
return this
}
// 绑定事件
bind(eventType: string, callback: (event: Event, target: SVGElement) => void) {
this.__useEl.addEventListener(eventType, function (event: Event) {
callback.call(this, event, this)
}, false)
return this
}
// 线性渐变
createLinearGradient(x0: number, y0: number, x1: number, y1: number) {
return linearGradient(this.__svg, x0, y0, x1, y1)
}
// 环形渐变
createRadialGradient(cx: number, cy: number, r: number) {
return radialGradient(this.__svg, cx, cy, r)
}
}
export default SVG