This chapter starts to add some basic graphics drawing, such as drawing: straight lines, curves, circles/ellipses, rectangles. This chapter shares how this example starts to draw a graphic with the goal of drawing a circle/ellipse as an implementation.
Please give me a free Star!
If you find any bugs, please feel free to raise an Issue!
github source code
gitee source code
sample address (computing)
Next main point:
- UI
- Graph
- canvas2svg Patch
- Inflection point rotation repair
UI - Graphics Drawing Type Switching
Start by finding a few icons and adding buttons that represent drawing shapes: straight lines, curves, circles/ellipses, rectangles:
Once the graphic type is selected, you can draw the graphic by dragging it (clearing the selection when drawing is complete):
Define the graphic type:
// src/Render/
/**
* Graphics type
*/
export enum GraphType {
Line = 'Line', // straight line
Curve = 'Curve', // curve
Rect = 'Rect', // rectangle
Circle = 'Circle' // circle/ellipse
}
Records the current graphic type in Render and provides modification methods and events:
// src/Render/
// summarize
// Drawing type
graphType: | undefined = undefined
// summarize
// 改变Drawing type
changeGraphType(type?: ) {
= type
('graph-type-change', )
}
Toolbar Button Newsletter:
// src/components/main-header/
// summarize
const emit = defineEmits([/* summarize */, 'update:graphType'])
const props = withDefaults(defineProps<{
// summarize
graphType?:
}>(), {
// summarize
});
// summarize
watch(() => , () => {
if () {
// summarize
?.on('graph-type-change', (value) => {
emit('update:graphType', value)
})
}
}, {
immediate: true
})
// summarize
function onGraph(type: ) {
emit('update:graphType', === type ? undefined : type)
Above is the toolbar entry for drawing graphics.
Graph - Graph definition and its related implementation
Related code files:
1. src/Render/graphs/ - Abstract class: defines generic properties, logic, external interface definitions.
2. src/Render/graphs/ Inherit BaseGraph - construct circle/ellipsoid; handle creation of part of the interactive information; implementation of key logic.
3. src/Render/handlers/ - collects the interaction information needed for the creation of the graphic and passes it to the Circle static handlers.
4. src/Render/draws/ - draws graphics, adjustment points - draws anchor points for adjustment points; collects and processes interaction information, then and hands it off to Circle static processing methods.
BaseGraph abstract class
// src/Render/graphs/
// summarize
/**
* graphics class
* Examples are mainly used when creating new graphics,Includes new and simultaneous size dragging。
* Static methods are mainly used after new,pass (a bill or inspection etc) adjustment point Logical definition of adjustment
*/
export abstract class BaseGraph {
/**
* update depiction (used form a nominal expression) adjustment point (used form a nominal expression) anchor location
* @param width depiction (used form a nominal expression) height
* @param height depiction (used form a nominal expression) high degree
* @param rotate depiction (used form a nominal expression) angle of rotation
* @param anchorShadows depiction (used form a nominal expression) adjustment point (used form a nominal expression) anchor point
*/
static updateAnchorShadows(
width: number,
height: number,
rotate: number,
anchorShadows: []
) {
('Please realize updateAnchorShadows', width, height, anchorShadows)
}
/**
* update depiction (used form a nominal expression) connection point (used form a nominal expression) anchor location
* @param width depiction (used form a nominal expression) height
* @param height depiction (used form a nominal expression) high degree
* @param rotate depiction (used form a nominal expression) angle of rotation
* @param anchors depiction (used form a nominal expression) adjustment point (used form a nominal expression) anchor point
*/
static updateLinkAnchorShadows(
width: number,
height: number,
rotate: number,
linkAnchorShadows: []
) {
('Please realize updateLinkAnchorShadows', width, height, linkAnchorShadows)
}
/**
* generating adjustment point
* @param render Rendering Example
* @param graph depiction
* @param anchor adjustment point define
* @param anchorShadow adjustment point anchor point
* @param adjustingId 正在操作(used form a nominal expression) adjustment point id
* @returns
*/
static createAnchorShape(
render: Render,
graph: ,
anchor: ,
anchorShadow: ,
adjustType: string,
adjustGroupId: string
): {
('Please realize createAnchorShape', render, graph, anchor, anchorShadow, adjustingId, adjustGroupId)
return new ()
}
/**
* align depiction
* @param render Rendering Example
* @param graph depiction
* @param graphSnap depiction (used form a nominal expression) backing up
* @param rect be facing (us) adjustment point
* @param rects possess adjustment point
* @param startPoint Mouse Down Position
* @param endPoint Mouse Drag Position
*/
static adjust(
render: Render,
graph: ,
graphSnap: ,
rect: ,
rects: [],
startPoint: Konva.Vector2d,
endPoint: Konva.Vector2d
) {
('Please realize updateAnchorShadows', render, graph, rect, startPoint, endPoint)
}
//
protected render: Render
group:
id: string // just like group (used form a nominal expression)id
/**
* Mouse Down Position
*/
protected dropPoint: Konva.Vector2d = { x: 0, y: 0 }
/**
* adjustment point define
*/
protected anchors: [] = []
/**
* adjustment point (used form a nominal expression) anchor point
*/
protected anchorShadows: [] = []
/**
* adjustment point define
*/
protected linkAnchors: [] = []
/**
* connection point (used form a nominal expression) anchor point
*/
protected linkAnchorShadows: [] = []
constructor(
render: Render,
dropPoint: Konva.Vector2d,
config: {
anchors: []
linkAnchors: []
}
) {
= render
= dropPoint
= nanoid()
= new ({
id: ,
name: 'asset',
assetType:
})
// adjustment point define
= ((o) => ({
...o,
// Additional information
name: 'anchor',
groupId: ()
}))
// recorded in group center
('anchors', )
// newly built adjustment point (used form a nominal expression) anchor point
for (const anchor of ) {
const circle = new ({
adjustType: ,
name: ,
radius: 0
// radius: (1),
// fill: 'red'
})
(circle)
(circle)
}
// connection point define
= (
(o) =>
({
...o,
id: nanoid(),
groupId: (),
visible: false,
pairs: [],
direction: ,
alias:
}) as
)
// connection point信息
({
points:
})
// newly built connection point (used form a nominal expression) anchor point
for (const point of ) {
const circle = new ({
name: 'link-anchor',
id: ,
x: ,
y: ,
radius: (1),
stroke: 'rgba(0,0,255,1)',
strokeWidth: (2),
visible: false,
direction: ,
alias:
})
(circle)
(circle)
}
('mouseenter', () => {
// demonstrate connection point
(true, )
})
('mouseleave', () => {
// harbor (i.e. keep sth hidden) connection point
(false, )
// harbor (i.e. keep sth hidden) hover circle (i.e. draw a circle around sth)
('#hoverRect')?.visible(false)
})
()
()
}
/**
* align进行时
* @param point mouse position relative position
*/
abstract drawMove(point: Konva.Vector2d): void
/**
* align结束
*/
abstract drawEnd(): void
}
Here:
- Static methods, rather than defining the necessary tool methods for drawing graphics, the specific implementation is left to the definition of the specific graphics class;
- This is followed by the attributes necessary for drawing graphics and their initialization;
- Finally, abstract methods constrain the methods necessary for graph instances.
Drawing Circles/Ellipses
The graphic is adjustable, here the circle/ellipse has 8 adjustment points:
Consider also that the graphic is rotated and still reasonably adjustable:
The tweak itself supports tiles:
Graphics also supports connection points:
Graphics Class - Circle
// src/Render/graphs/
// summarize
/**
* depiction unit of Chinese currency (Yuan)/椭unit of Chinese currency (Yuan)
*/
export class Circle extends BaseGraph {
// realization:update depiction (used form a nominal expression) adjustment point (used form a nominal expression) anchor location
static override updateAnchorShadows(
width: number,
height: number,
rotate: number,
anchorShadows: []
): void {
for (const shadow of anchorShadows) {
switch () {
case 'top':
({
x: width / 2,
y: 0
})
break
case 'bottom':
({
x: width / 2,
y: height
})
break
case 'left':
({
x: 0,
y: height / 2
})
break
case 'right':
({
x: width,
y: height / 2
})
break
case 'top-left':
({
x: 0,
y: 0
})
break
case 'top-right':
({
x: width,
y: 0
})
break
case 'bottom-left':
({
x: 0,
y: height
})
break
case 'bottom-right':
({
x: width,
y: height
})
break
}
}
}
// realization:update depiction (used form a nominal expression) connection point (used form a nominal expression) anchor location
static override updateLinkAnchorShadows(
width: number,
height: number,
rotate: number,
linkAnchorShadows: []
): void {
for (const shadow of linkAnchorShadows) {
switch () {
case 'top':
({
x: width / 2,
y: 0
})
break
case 'bottom':
({
x: width / 2,
y: height
})
break
case 'left':
({
x: 0,
y: height / 2
})
break
case 'right':
({
x: width,
y: height / 2
})
break
case 'center':
({
x: width / 2,
y: height / 2
})
break
}
}
}
// realization:generating adjustment point
static createAnchorShape(
render: ,
graph: ,
anchor: ,
anchorShadow: ,
adjustType: string,
adjustGroupId: string
): {
// stage state of affairs
const stageState = ()
const x = (().x - ),
y = (().y - )
const offset = + 5
const shape = new ({
name: 'anchor',
anchor: anchor,
//
// stroke: colorMap[] ?? 'rgba(0,0,255,0.2)',
stroke:
adjustType === && () === adjustGroupId
? 'rgba(0,0,255,0.8)'
: 'rgba(0,0,255,0.2)',
strokeWidth: (2),
// placement
x,
y,
// trails
points:
{
'top-left': _.flatten([
[-offset, offset / 2],
[-offset, -offset],
[offset / 2, -offset]
]),
top: _.flatten([
[-offset, -offset],
[offset, -offset]
]),
'top-right': _.flatten([
[-offset / 2, -offset],
[offset, -offset],
[offset, offset / 2]
]),
right: _.flatten([
[offset, -offset],
[offset, offset]
]),
'bottom-right': _.flatten([
[-offset / 2, offset],
[offset, offset],
[offset, -offset / 2]
]),
bottom: _.flatten([
[-offset, offset],
[offset, offset]
]),
'bottom-left': _.flatten([
[-offset, -offset / 2],
[-offset, offset],
[offset / 2, offset]
]),
left: _.flatten([
[-offset, -offset],
[-offset, offset]
])
}[] ?? [],
// angle of rotation
rotation: ()
})
('mouseenter', () => {
('rgba(0,0,255,0.8)')
= 'move'
})
('mouseleave', () => {
( ? 'rgba(0,0,255,0.8)' : 'rgba(0,0,255,0.2)')
= ? 'move' : 'default'
})
return shape
}
// realization:align depiction
static override adjust(
render: ,
graph: ,
graphSnap: ,
shapeRecord: ,
shapeRecords: [],
startPoint: Konva.Vector2d,
endPoint: Konva.Vector2d
) {
// goal unit of Chinese currency (Yuan)/椭unit of Chinese currency (Yuan)
const circle = ('.graph') as
// mirroring
const circleSnap = ('.graph') as
// adjustment point anchor point
const anchors = (('.anchor') ?? []) as []
// connection point anchor point
const linkAnchors = (('.link-anchor') ?? []) as []
const { shape: adjustShape } = shapeRecord
if (circle && circleSnap) {
let [graphWidth, graphHeight] = [(), ()]
const [graphRotation, anchorId, ex, ey] = [
(()),
?.id,
,
]
let anchorShadow: | undefined, anchorShadowAcross: | undefined
switch (anchorId) {
case 'top':
{
anchorShadow = (`#top`)
anchorShadowAcross = (`#bottom`)
}
break
case 'bottom':
{
anchorShadow = (`#bottom`)
anchorShadowAcross = (`#top`)
}
break
case 'left':
{
anchorShadow = (`#left`)
anchorShadowAcross = (`#right`)
}
break
case 'right':
{
anchorShadow = (`#right`)
anchorShadowAcross = (`#left`)
}
break
case 'top-left':
{
anchorShadow = (`#top-left`)
anchorShadowAcross = (`#bottom-right`)
}
break
case 'top-right':
{
anchorShadow = (`#top-right`)
anchorShadowAcross = (`#bottom-left`)
}
break
case 'bottom-left':
{
anchorShadow = (`#bottom-left`)
anchorShadowAcross = (`#top-right`)
}
break
case 'bottom-right':
{
anchorShadow = (`#bottom-right`)
anchorShadowAcross = (`#top-left`)
}
break
}
if (anchorShadow && anchorShadowAcross) {
const { x: sx, y: sy } = ()
const { x: ax, y: ay } = ()
// anchorShadow:它是当前操作(used form a nominal expression) adjustment point anchor point
// anchorShadowAcross:它是当前操作(used form a nominal expression) adjustment point 反方向对面(used form a nominal expression) anchor point
// alignadults and children
{
// summarize
// Complicated calculations,Not necessarily optimal,See project code for details。
// basic logic:
// 1、By mouse movement,计算当前鼠标placement、当前操作(used form a nominal expression) adjustment point anchor point placement(原placement) Respectively, with anchorShadowAcross(原placement)(used form a nominal expression)距离;
// 2、 preserve anchorShadowAcross placement固定,通过上面两距离(used form a nominal expression)变化比例,计算最新(used form a nominal expression)宽高adults and children;
// 3、期间要约束不同角度不同方向(used form a nominal expression)宽高处理,有(used form a nominal expression)只改变宽、有(used form a nominal expression)只改变高、有(used form a nominal expression)同时改变宽和高。
}
// alignplacement
{
// summarize
// Complicated calculations,Not necessarily optimal,See project code for details。
// basic logic:
// Using trigonometric functions,通过最新(used form a nominal expression)宽高,aligndepiction(used form a nominal expression)坐标。
}
}
// update unit of Chinese currency (Yuan)/椭unit of Chinese currency (Yuan) adults and children
(graphWidth / 2)
(graphWidth / 2)
(graphHeight / 2)
(graphHeight / 2)
// update adjustment point (used form a nominal expression) anchor point placement
(graphWidth, graphHeight, graphRotation, anchors)
// update depiction (used form a nominal expression) connection point (used form a nominal expression) anchor location
(graphWidth, graphHeight, graphRotation, linkAnchors)
// stage state of affairs
const stageState = ()
// update adjustment point placement
for (const anchor of anchors) {
for (const { shape } of shapeRecords) {
if (?.adjustType === ) {
const anchorShadow = (`#${}`)
if (anchorShadow) {
({
x: (().x - ),
y: (().y - )
})
(())
}
}
}
}
}
}
/**
* 默认depictionadults and children
*/
static size = 100
/**
* unit of Chinese currency (Yuan)/椭unit of Chinese currency (Yuan) 对应(used form a nominal expression) Konva an actual example
*/
private circle:
constructor(render: , dropPoint: Konva.Vector2d) {
super(render, dropPoint, {
// defined 8 classifier for individual things or people, general, catch-all classifier adjustment point
anchors: [
{ adjustType: 'top' },
{ adjustType: 'bottom' },
{ adjustType: 'left' },
{ adjustType: 'right' },
{ adjustType: 'top-left' },
{ adjustType: 'top-right' },
{ adjustType: 'bottom-left' },
{ adjustType: 'bottom-right' }
].map((o) => ({
adjustType: , // adjustment point type definition
type: // Record affiliation depiction
})),
linkAnchors: [
{ x: 0, y: 0, alias: 'top', direction: 'top' },
{ x: 0, y: 0, alias: 'bottom', direction: 'bottom' },
{ x: 0, y: 0, alias: 'left', direction: 'left' },
{ x: 0, y: 0, alias: 'right', direction: 'right' },
{ x: 0, y: 0, alias: 'center' }
] as []
})
// newly built unit of Chinese currency (Yuan)/椭unit of Chinese currency (Yuan)
= new ({
name: 'graph',
x: 0,
y: 0,
radiusX: 0,
radiusY: 0,
stroke: 'black',
strokeWidth: 1
})
// become a member
()
// 鼠标按下placement as a starting point
()
}
// realization:drag-and-drop (computing)
override drawMove(point: Konva.Vector2d): void {
// Mouse Drag Offset
let offsetX = - ,
offsetY = -
// Make sure it doesn't flip over
if (offsetX < 1) {
offsetX = 1
}
if (offsetY < 1) {
offsetY = 1
}
// radius
const radiusX = offsetX / 2,
radiusY = offsetY / 2
// unit of Chinese currency (Yuan)/椭unit of Chinese currency (Yuan) placementadults and children
(radiusX)
(radiusY)
(radiusX)
(radiusY)
// group adults and children
({
width: offsetX,
height: offsetY
})
// update depiction (used form a nominal expression) adjustment point (used form a nominal expression) anchor location
(offsetX, offsetY, 1, )
// update depiction (used form a nominal expression) connection point (used form a nominal expression) anchor location
(offsetX, offsetY, 1, )
// repaint
([, ])
}
// realization:End of Drag
override drawEnd(): void {
if (() <= 1 && () <= 1) {
// become a member只点击,no drag
// 默认adults and children
const width = ,
height = width
const radiusX = / 2,
radiusY = radiusX
// unit of Chinese currency (Yuan)/椭unit of Chinese currency (Yuan) placementadults and children
(radiusX)
(radiusY)
(radiusX - ())
(radiusY - ())
// group adults and children
({
width,
height
})
// update depiction (used form a nominal expression) adjustment point (used form a nominal expression) anchor location
(width, height, 1, )
// update depiction (used form a nominal expression) connection point (used form a nominal expression) anchor location
(width, height, 1, )
// alignments cleared
()
// repaint
([, ])
}
}
}
GraphHandlers
// src/Render/handlers/
// summarize
export class GraphHandlers implements {
// summarize
/**
* In New Graphics
*/
graphing = false
/**
* Current New Graphic Type
*/
currentGraph: | undefined
/**
* Get mouse position,and processed as relative size
* @param attract Calculation with Magnetic Stickers
* @returns
*/
getStagePoint(attract = false) {
const pos = ()
if (pos) {
const stageState = ()
if (attract) {
// paste paste (e.g. on a computer screen)
const { pos: transformerPos } = (pos)
return {
x: ( - ),
y: ( - )
}
} else {
return {
x: ( - ),
y: ( - )
}
}
}
return null
}
handlers = {
stage: {
mousedown: (e: <GlobalEventHandlersEventMap['mousedown']>) => {
if () {
// Selected graphic type,commencement
if ( === ) {
= true
()
const point = ()
if (point) {
if ( === ) {
// newly built unit of Chinese currency (Yuan)/椭unit of Chinese currency (Yuan) an actual example
= new (, point)
}
}
}
}
},
mousemove: () => {
if () {
if () {
const pos = (true)
if (pos) {
// newly built并马上调整图形
(pos)
}
}
}
},
mouseup: () => {
if () {
if () {
// End of adjustment
()
}
// End of adjustment
= false
// Clear graphic type selection
()
// alignments cleared
()
// repaint
([, ])
}
}
}
}
}
GraphDraw
// src/Render/draws/
// summarize
export interface GraphDrawState {
/**
* in the pipeline
*/
adjusting: boolean
/**
* in the pipeline id
*/
adjustType: string
}
// summarize
export class GraphDraw extends implements {
// summarize
state: GraphDrawState = {
adjusting: false,
adjustType: ''
}
/**
* Mouse over adjustment point placement
*/
startPoint: Konva.Vector2d = { x: 0, y: 0 }
/**
* depiction group mirroring
*/
graphSnap: | undefined
constructor(render: , layer: , option: GraphDrawOption) {
super(render, layer)
= option
()
}
/**
* 获取鼠标placement,and processed as relative size
* @param attract Calculation with Magnetic Stickers
* @returns
*/
getStagePoint(attract = false) {
const pos = ()
if (pos) {
const stageState = ()
if (attract) {
// paste paste (e.g. on a computer screen)
const { pos: transformerPos } = (pos)
return {
x: ( - ),
y: ( - )
}
} else {
return {
x: ( - ),
y: ( - )
}
}
}
return null
}
// align preprocessing、Locating static methods
adjusts(
shapeDetailList: {
graph:
shapeRecords: { shape: ; anchorShadow: }[]
}[]
) {
for (const { shapeRecords, graph } of shapeDetailList) {
for (const { shape } of shapeRecords) {
('adjusting', false)
}
for (const shapeRecord of shapeRecords) {
const { shape } = shapeRecord
// Mouse over
('mousedown', () => {
= true
= ?.adjustType
= ()
('adjusting', true)
const pos = ()
if (pos) {
= pos
// depiction group mirroring,用于计算placement、Offset in size
= ()
}
})
// in the pipeline
('mousemove', () => {
if ( && ) {
if (?.type === ) {
// align unit of Chinese currency (Yuan)/椭unit of Chinese currency (Yuan) depiction
if () {
const pos = (true)
if (pos) {
// utilization unit of Chinese currency (Yuan)/椭unit of Chinese currency (Yuan) Static processing methods
(
,
graph,
,
shapeRecord,
shapeRecords,
,
pos
)
// repaint
([, ])
}
}
}
}
})
// align结束
('mouseup', () => {
= false
= ''
= ''
// Restore the display of all adjustment point
for (const { shape } of shapeRecords) {
(1)
('adjusting', false)
('rgba(0,0,255,0.2)')
= 'default'
}
// destroy (by melting or burning) mirroring
?.destroy()
// alignments cleared
()
})
(shape)
}
}
}
override draw() {
()
// 所有depiction
const graphs =
.find('.asset')
.filter((o) => === ) as []
const shapeDetailList: {
graph:
shapeRecords: { shape: ; anchorShadow: }[]
}[] = []
for (const graph of graphs) {
// Displayed only when not selected adjustment point
if (!) {
const anchors = ( ?? []) as []
const shapeRecords: { shape: ; anchorShadow: }[] = []
// according to adjustment point text,establish
for (const anchor of anchors) {
// adjustment point displayed relies on its hidden anchor point placement、大小等text
const anchorShadow = (`#${}`) as
if (anchorShadow) {
const shape = (
,
graph,
anchor,
anchorShadow,
,
)
({ shape, anchorShadow })
}
}
({
graph,
shapeRecords
})
}
}
(shapeDetailList)
}
}
Slightly bloated, slowly optimize later -_-
canvas2svg Patch
The above has been implemented to draw graphics (circle/ellipse), but it reported an error when exporting the svg. After locating the error and reading the source code, it was found:
1、It is not possible to export svg file when it contains
2、When adjusting properties such as radiusX and radiusY, the path path cannot be output correctly.
1. canvas2svg tries to give the path attribute to the g node, resulting in an exception.
Now add the scenario where nodeName === 'g' is handled by hacking the __applyCurrentDefaultPath method.
2. Check the source code of . _sceneFunc method source code, Konva draws Ellipse by canvas arc + scale, corresponding to code comment A.
In practice, this does not mimic the average scale of canvas, and the stroke will be of different thicknesses.
Therefore, trying to fix this issue by recognizing the scale modification path feature.
// src/Render/tools/
. __applyCurrentDefaultPath = function () {
// Patch: Fix the following issues:
// 1. Can't export svg file when include.
// 2. When adjusting properties such as radiusX, radiusY, the path path could not be exported correctly.
//
// PS.
// 1. canvas2svg attempted to assign a path attribute to the g node, resulting in an error.
// Now hack the __applyCurrentDefaultPath method to handle the nodeName === 'g' scenario.
// The nodeName === 'g' is now added by hacking __applyCurrentDefaultPath.
// 2. _sceneFunc method source code.
// Konva draws the Ellipse by using arc + scale on the canvas, which corresponds to code comment A. // The actual effect can't be modeled after this method.
// In practice, it's not possible to emulate the average scale of canvas, and the strokes are not of the same thickness.
// Therefore, we tried to fix this by modifying the path feature to recognize the scale.
// (The above hack is for the example only.
// (The above hack only deals with the characteristics of the example graphic, and does not delve into the logic of why canvas2svg gets into the wrong place).
if (this.__currentElement.nodeName === 'g') {
const g = this.__currentElement.querySelector('g')
if (g) {
// Annotate A
// const d = this.__currentDefaultPath
// const path = ('http:///2000/svg', 'path') as SVGElement
// ('d', d)
// ('fill', 'none')
// (path)
const scale = ('transform')
if (scale) {
const match = (/scale\(([^),]+),([^)]+)\)/)
if (match) {
const [sx, sy] = [parseFloat(match[1]), parseFloat(match[2])]
let d = this.__currentDefaultPath
const reg = /A ([^ ]+) ([^ ]+) /
const match2 = (reg)
if (match2) {
const [rx, ry] = [parseFloat(match2[1]), parseFloat(match2[2])]
d = (reg, `A ${rx * sx} ${ry * sy} `)
const path = (
'http:///2000/svg',
'path'
) as SVGElement
('d', d)
('fill', 'none')
this.__currentElement.append(path)
}
}
} else {
const d = this.__currentDefaultPath
const path = ('http:///2000/svg', 'path') as SVGElement
('d', d)
('fill', 'none')
this.__currentElement.append(path)
}
}
(
'[Hacked] Attempted to apply path command to node ' + this.__currentElement.nodeName
)
return
}
// Original logic
if (this.__currentElement.nodeName === 'path') {
const d = this.__currentDefaultPath
this.__currentElement.setAttribute('d', d)
} else {
throw new Error('Attempted to apply path command to node ' + this.__currentElement.nodeName)
}
}
The above hack only deals with the characteristics of the example graphic as it is drawn, and does not delve into the logic of why canvas2svg gets into the wrong place.
Inflection point rotation repair
Tests showed that the inflection points of the connecting lines did not follow the rotation angle, so a fix was added:
// src/Render/handlers/
// summarize
/**
* matrix transformation:A point in the coordinate system,Rotate around another point
* - - - - - - - -
* |x`| |cos -sin| |x-a| |a|
* | | = | | | | +
* |y`| |sin cos| |y-b| |b|
* - - - - - - - -
* @param x Target node coordinates x
* @param y Target node coordinates y
* @param centerX Coordinates of the point around which x
* @param centerY Coordinates of the point around which y
* @param angle angle of rotation
* @returns
*/
rotatePoint(x: number, y: number, centerX: number, centerY: number, angle: number) {
// Converting angles to radians
const radians = (angle * ) / 180
// Calculate the rotated coordinates
const newX = (radians) * (x - centerX) - (radians) * (y - centerY) + centerX
const newY = (radians) * (x - centerX) + (radians) * (y - centerY) + centerY
return { x: newX, y: newY }
}
lastRotation = 0
// summarize
handlers = {
// summarize
transformer: {
transform: () => {
// rotating,The inflection point has to follow suit
const back = ('.back')
if (back) {
// stage state of affairs
const stageState = ()
const { x, y, width, height } = ()
const rotation = () -
const centerX = x + width / 2
const centerY = y + height / 2
const groups = ()
const points = ((ps, group) => {
return ((('points')) ? ('points') : [])
}, [] as [])
const pairs = ((ps, point) => {
return ( ? ((o) => !) : [])
}, [] as [])
for (const pair of pairs) {
const fromGroup = ((o) => () === )
const toGroup = ((o) => () === )
// Must move in pairs to be recorded
if (fromGroup && toGroup) {
// mobility
if ( && ) {
let manualPoints = []
const manualPointsBefore = []
if ((manualPoints) && (manualPointsBefore)) {
manualPoints = ((o: ) => {
const { x, y } = (
() + ,
() + ,
centerX,
centerY,
rotation
)
return {
x: (x - ),
y: (y - )
}
})
('manualPointsMap', {
...,
[]: manualPoints
})
}
}
}
}
}
// repaint
([, , ])
}
}
// summarize
}
Thanks watching~
More Stars please!
source code (computing)
gitee source code
sample address (computing)