Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
V
vue3-quasar-ts-study01
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
hucy
vue3-quasar-ts-study01
Commits
4485971c
Commit
4485971c
authored
Apr 14, 2023
by
hucy
☘
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix:向量
parent
3f2391a4
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
797 additions
and
41 deletions
+797
-41
config.ts
src/layouts/config.ts
+7
-0
IndexPage.vue
src/modules/vector/IndexPage.vue
+122
-0
route.ts
src/modules/vector/route.ts
+12
-0
utils.ts
src/modules/vector/utils.ts
+158
-0
IndexPage2.vue
src/modules/vue-konva/IndexPage2.vue
+208
-41
utils.ts
src/modules/vue-konva/utils.ts
+288
-0
routes.ts
src/router/routes.ts
+2
-0
No files found.
src/layouts/config.ts
View file @
4485971c
...
@@ -136,4 +136,11 @@ export const MenuList = [
...
@@ -136,4 +136,11 @@ export const MenuList = [
link
:
'/vue-konva'
,
link
:
'/vue-konva'
,
active
:
false
,
active
:
false
,
},
},
{
title
:
'向量'
,
caption
:
'JavaScript 线性代数:向量'
,
icon
:
require
(
'./menuListIcons/amis.svg'
),
link
:
'/vector'
,
active
:
false
,
},
];
];
src/modules/vector/IndexPage.vue
0 → 100644
View file @
4485971c
<!--
* JavaScript 线性代数:向量
* https://juejin.cn/post/6844903859689619469
-->
<
script
setup
lang=
"ts"
>
import
{
reactive
,
onMounted
}
from
'vue'
;
import
{
Vector
}
from
'./utils'
;
interface
Point
{
x
:
number
;
y
:
number
;
gap
?:
number
;
[
proppName
:
string
]:
any
;
}
const
stageSize
=
reactive
({
width
:
1200
,
height
:
800
,
gridGap
:
100
,
});
const
state
=
reactive
({
canvasDom
:
null
as
any
,
pen
:
null
as
any
,
});
onMounted
(()
=>
{
state
.
canvasDom
=
document
.
getElementById
(
'canvas-grid'
);
state
.
pen
=
state
.
canvasDom
.
getContext
(
'2d'
);
drawGrid
();
const
pointO
=
{
x
:
500
,
y
:
300
,
name
:
'O'
};
const
pointA
=
{
x
:
600
,
y
:
200
,
name
:
'A'
};
const
pointB
=
{
x
:
400
,
y
:
200
,
name
:
'B'
};
drawVector
(
pointO
,
pointA
);
drawVector
(
pointO
,
pointB
);
// 加法
const
pointC
=
new
Vector
(
pointO
,
pointA
).
add
(
pointB
);
const
len
=
new
Vector
(
pointO
,
pointB
).
length
;
console
.
log
(
'OB向量长度'
,
len
);
drawVector
(
pointO
,
pointC
,
'#21BA45'
);
// 减
// const pointD = new Vector(pointO, pointB).subtract(pointA);
// console.log('pointD', pointD);
// drawVector(pointO, pointD, '#FFA000');
// 求夹角
const
angle
=
new
Vector
(
pointO
,
pointA
).
dotProduct
(
pointB
);
console
.
log
(
'夹角'
,
angle
);
});
function
drawVector
(
pointX
:
Point
,
pointY
:
Point
,
color
=
'#F44336'
)
{
const
pen
=
state
.
pen
;
pen
.
save
();
pen
.
beginPath
();
pen
.
moveTo
(
pointX
.
x
,
pointX
.
y
);
pen
.
lineTo
(
pointY
.
x
,
pointY
.
y
);
pen
.
lineWidth
=
4
;
pen
.
strokeStyle
=
color
;
pen
.
stroke
();
pen
.
fillText
(
pointX
.
name
,
pointX
.
x
,
pointX
.
y
);
pen
.
fillText
(
pointY
.
name
,
pointY
.
x
,
pointY
.
y
);
pen
.
restore
();
}
// 网格线
function
drawGrid
()
{
let
canvas
:
any
=
document
.
getElementById
(
'canvas-grid'
);
let
pen
=
canvas
.
getContext
(
'2d'
);
// 绘制网格
const
step
=
stageSize
.
gridGap
;
const
h
=
stageSize
.
height
;
const
w
=
stageSize
.
width
;
const
w_l
=
w
/
step
;
const
h_l
=
h
/
step
;
// 横着的线
pen
.
save
();
for
(
let
i
=
0
;
i
<=
h_l
;
i
++
)
{
pen
.
beginPath
();
pen
.
moveTo
(
0
,
i
*
step
);
pen
.
lineTo
(
w
,
i
*
step
);
pen
.
stroke
();
}
// 竖着的线
for
(
let
i
=
0
;
i
<=
w_l
;
i
++
)
{
pen
.
beginPath
();
pen
.
moveTo
(
i
*
step
,
0
);
pen
.
lineTo
(
i
*
step
,
h
);
pen
.
stroke
();
}
pen
.
restore
();
}
</
script
>
<
template
>
<div
class=
"center"
>
<div
class=
"canvas-box"
>
<canvas
id=
"canvas-grid"
:width=
"stageSize.width"
:height=
"stageSize.height"
style=
"position: absolute"
></canvas>
</div>
</div>
</
template
>
<
style
lang=
"scss"
scoped
>
.canvas-box
{
box-sizing
:
border-box
;
width
:
1200px
;
height
:
800px
;
border
:
1px
solid
#000
;
position
:
relative
;
}
</
style
>
src/modules/vector/route.ts
0 → 100644
View file @
4485971c
export
default
[
{
path
:
'vector'
,
name
:
'VECTOR'
,
component
:
()
=>
import
(
'./IndexPage.vue'
),
meta
:
{
title
:
'向量'
,
permission
:
[
'*'
],
keepalive
:
true
,
},
},
];
src/modules/vector/utils.ts
0 → 100644
View file @
4485971c
interface
Point
{
x
:
number
;
y
:
number
;
[
proppName
:
string
]:
any
;
}
/**
* 创建一个2维度向量类
*/
export
class
Vector
{
components
:
any
;
length
:
number
;
constructor
(...
components
:
any
)
{
this
.
components
=
components
;
const
pointO
=
components
[
0
];
const
pointA
=
components
[
1
];
const
diffX
=
pointA
.
x
-
pointO
.
x
;
const
diffY
=
pointA
.
y
-
pointO
.
y
;
this
.
length
=
Math
.
sqrt
(
diffX
*
diffX
+
diffY
*
diffY
);
}
// 加法
add
(
components
:
any
)
{
const
pointO
=
this
.
components
[
0
];
const
pointA
=
this
.
components
[
1
];
const
pointB
=
components
;
return
{
x
:
pointA
.
x
+
pointB
.
x
-
pointO
.
x
,
y
:
pointA
.
y
+
pointB
.
y
-
pointO
.
y
,
};
}
// 减
subtract
(
components
:
any
)
{
const
pointO
=
this
.
components
[
0
];
const
pointA
=
this
.
components
[
1
];
const
pointC
=
components
;
return
{
x
:
pointA
.
x
-
pointC
.
x
+
pointO
.
x
,
y
:
pointA
.
y
-
pointC
.
y
+
pointO
.
y
,
};
}
// 向量点乘:(内积)
// https://zhuanlan.zhihu.com/p/359975221
dotProduct
(
components
:
any
)
{
const
pointO
=
this
.
components
[
0
];
const
pointA
=
this
.
components
[
1
];
const
pointB
=
components
;
const
_pointA
=
{
x
:
pointA
.
x
-
pointO
.
x
,
y
:
pointA
.
y
-
pointO
.
y
};
const
_pointB
=
{
x
:
pointB
.
x
-
pointO
.
x
,
y
:
pointB
.
y
-
pointO
.
y
};
// 求向量OA与OB的夹角
// AB向量 = OB向量 - OA向量
// const ab_x = _pointB.x - _pointA.x;
// const ab_y = _pointB.y - _pointA.y;
// OA向量 * OB向量 > 0,夹角在0~90度之间
// OA向量 * OB向量 = 0,正交,相互垂直
// OA向量 * OB向量 < 0,夹角在90度~180度之间
//
// OA向量 * OB向量 = x1*x2 + y1*y2;
const
abMultiplication
=
_pointA
.
x
*
_pointB
.
x
+
_pointA
.
y
*
_pointB
.
y
;
// OA向量的模
const
abs_OA
=
new
Vector
({
x
:
0
,
y
:
0
},
_pointA
).
length
;
// OB向量的模
const
abs_OB
=
new
Vector
({
x
:
0
,
y
:
0
},
_pointB
).
length
;
// 得到弧度值
let
result
=
Math
.
acos
(
abMultiplication
/
(
abs_OA
*
abs_OB
));
// 转为角度值
result
=
toAngle
(
result
);
const
arr
=
[];
arr
.
push
(
_pointA
);
arr
.
push
({
x
:
0
,
y
:
0
});
arr
.
push
(
_pointB
);
const
is_clockwise
=
isClockwise
(
arr
);
if
(
is_clockwise
)
{
// 顺时针方向 大于180度
result
=
360
-
result
;
}
else
{
}
return
result
;
}
}
/**
* 判断坐标数组是否顺时针(默认为false)
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
* @returns {boolean} 是否顺时针
*/
export
function
isClockwise
(
points
:
Point
[])
{
// 三个点可以判断矢量是顺时针旋转还是逆时针旋转的,但由于可能存在凹边,所以并不是任意三点都可以正确反映多边形的走向
// 因此需要取多边形中绝对是凸边的点来判断,
// 多边形中的极值点(x最大或x最小或y最大或y最小)它与相邻两点构成的边必然是凸边,因此我们先取出多边形中的极值点,再由极值点和其前后两点去判断矢量的走向,从而判断出多边形的走向。
if
(
!
Array
.
isArray
(
points
)
||
points
.
length
<
3
)
{
console
.
error
(
'多边形坐标集合不能少于3个'
);
return
false
;
}
let
coords
=
JSON
.
parse
(
JSON
.
stringify
(
points
));
if
(
coords
[
0
]
===
coords
[
coords
.
length
-
1
])
{
coords
=
coords
.
slice
(
0
,
coords
.
length
-
1
);
}
coords
=
coords
.
reverse
();
let
maxXIndex
=
0
;
let
maxX
=
parseFloat
(
coords
[
maxXIndex
].
x
);
let
c1
;
let
c2
;
let
c3
;
for
(
let
i
=
0
;
i
<
coords
.
length
;
i
++
)
{
if
(
parseFloat
(
coords
[
i
].
x
)
>
maxX
)
{
maxX
=
parseFloat
(
coords
[
i
].
x
);
maxXIndex
=
i
;
}
}
if
(
maxXIndex
===
0
)
{
c1
=
coords
[
coords
.
length
-
1
];
c2
=
coords
[
maxXIndex
];
c3
=
coords
[
maxXIndex
+
1
];
}
else
if
(
maxXIndex
===
coords
.
length
-
1
)
{
c1
=
coords
[
maxXIndex
-
1
];
c2
=
coords
[
maxXIndex
];
c3
=
coords
[
0
];
}
else
{
c1
=
coords
[
maxXIndex
-
1
];
c2
=
coords
[
maxXIndex
];
c3
=
coords
[
maxXIndex
+
1
];
}
const
x1
=
parseFloat
(
c1
.
x
);
const
y1
=
parseFloat
(
c1
.
y
);
const
x2
=
parseFloat
(
c2
.
x
);
const
y2
=
parseFloat
(
c2
.
y
);
const
x3
=
parseFloat
(
c3
.
x
);
const
y3
=
parseFloat
(
c3
.
y
);
const
s
=
(
x1
-
x3
)
*
(
y2
-
y3
)
-
(
x2
-
x3
)
*
(
y1
-
y3
);
return
s
<
0
;
}
/**
* 转为弧度值
*/
export
function
toRadian
(
val
:
number
)
{
return
(
val
*
Math
.
PI
)
/
180
;
}
/**
* 转角度值
*/
export
function
toAngle
(
val
:
number
)
{
return
(
val
*
180
)
/
Math
.
PI
;
}
src/modules/vue-konva/IndexPage2.vue
View file @
4485971c
...
@@ -4,15 +4,18 @@
...
@@ -4,15 +4,18 @@
* @Date: 2023-03-29
* @Date: 2023-03-29
-->
-->
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
reactive
,
onMounted
}
from
'vue'
;
import
{
re
f
,
re
active
,
onMounted
}
from
'vue'
;
import
{
getBoundingBox
}
from
'src/common/utils'
;
import
{
getBoundingBox
}
from
'src/common/utils'
;
import
{
scalePolygon
}
from
'./utils'
;
import
Konva
from
'konva'
;
import
Konva
from
'konva'
;
import
Decimal
from
'decimal.js'
;
import
Decimal
from
'decimal.js'
;
const
startSelected
=
ref
(
true
);
const
state
=
reactive
({
const
state
=
reactive
({
stage
:
null
as
any
,
stage
:
null
as
any
,
layer
:
null
as
any
,
layer
:
null
as
any
,
group
:
null
as
any
,
group
:
new
Map
(),
myMap
:
new
Map
(),
// shape: null as any,
// shape: null as any,
// boundingBox: null as any,
// boundingBox: null as any,
// box: null as any,
// box: null as any,
...
@@ -20,33 +23,47 @@ const state = reactive({
...
@@ -20,33 +23,47 @@ const state = reactive({
{
{
name
:
'test1'
,
name
:
'test1'
,
color
:
'#00D2FF'
,
color
:
'#00D2FF'
,
selected
:
true
,
path
:
[
path
:
[
{
x
:
100
,
y
:
100
},
{
x
:
100
,
y
:
100
,
gap
:
20
},
{
x
:
200
,
y
:
100
},
{
x
:
200
,
y
:
100
,
gap
:
10
},
{
x
:
200
,
y
:
200
},
{
x
:
200
,
y
:
200
,
gap
:
40
},
{
x
:
0
,
y
:
300
},
{
x
:
0
,
y
:
300
,
gap
:
10
},
],
},
{
name
:
'test2'
,
color
:
'#4CAF50'
,
path
:
[
{
x
:
300
,
y
:
200
},
{
x
:
200
,
y
:
400
},
{
x
:
100
,
y
:
600
},
{
x
:
400
,
y
:
600
},
{
x
:
500
,
y
:
200
},
],
},
{
name
:
'test3'
,
color
:
'#FF8A80'
,
path
:
[
{
x
:
600
,
y
:
200
},
{
x
:
500
,
y
:
400
},
{
x
:
300
,
y
:
100
},
],
],
},
},
// {
// name: 'test2',
// color: '#4CAF50',
// selected: true,
// path: [
// { x: 300, y: 200 },
// { x: 200, y: 400 },
// { x: 100, y: 600 },
// { x: 400, y: 600 },
// { x: 500, y: 200 },
// ],
// },
// {
// name: 'test3',
// color: '#FF8A80',
// selected: true,
// path: [
// { x: 600, y: 200 },
// { x: 500, y: 400 },
// { x: 300, y: 100 },
// ],
// },
// {
// name: 'test4',
// color: '#FFC107',
// selected: true,
// path: [
// { x: 1000, y: 0 },
// { x: 1100, y: 0 },
// { x: 1100, y: 100 },
// { x: 1000, y: 100 },
// ],
// },
]
as
any
[],
]
as
any
[],
});
});
...
@@ -74,11 +91,16 @@ onMounted(() => {
...
@@ -74,11 +91,16 @@ onMounted(() => {
state
.
layer
=
new
Konva
.
Layer
();
state
.
layer
=
new
Konva
.
Layer
();
state
.
stage
.
add
(
state
.
layer
);
state
.
stage
.
add
(
state
.
layer
);
let
index
=
0
;
for
(
const
item
of
state
.
shapePath
)
{
for
(
const
item
of
state
.
shapePath
)
{
let
shape
=
createShape
(
item
);
let
shape
=
createShape
(
item
);
if
(
shape
)
{
if
(
shape
)
{
state
.
layer
.
add
(
shape
);
state
.
layer
.
add
(
shape
);
state
.
group
.
set
(
shape
.
_id
,
index
);
// 储存 state.shapePath的下标
state
.
myMap
.
set
(
item
.
name
,
shape
.
_id
);
}
}
index
++
;
}
}
state
.
layer
.
on
(
'dragmove'
,
layerDragmove
);
state
.
layer
.
on
(
'dragmove'
,
layerDragmove
);
...
@@ -107,7 +129,7 @@ function createShape(itemData: any) {
...
@@ -107,7 +129,7 @@ function createShape(itemData: any) {
height
:
boundingBox
.
height
,
height
:
boundingBox
.
height
,
stroke
:
'red'
,
stroke
:
'red'
,
// fill: 'grey',
// fill: 'grey',
strokeWidth
:
4
,
strokeWidth
:
2
,
});
});
// 如果添加到group,则坐标的相对位置时基于group的位置
// 如果添加到group,则坐标的相对位置时基于group的位置
...
@@ -136,21 +158,71 @@ function createShape(itemData: any) {
...
@@ -136,21 +158,71 @@ function createShape(itemData: any) {
// stroke: 'white',
// stroke: 'white',
// strokeWidth: 10,
// strokeWidth: 10,
});
});
// 向外扩大 start
const
extraPath
=
scalePolygon
(
path
)
as
any
[];
const
shape2
:
any
=
new
Konva
.
Shape
({
sceneFunc
:
function
(
context
,
shape
)
{
context
.
beginPath
();
let
index
=
0
;
for
(
const
i
of
extraPath
)
{
const
x
=
i
.
x
;
const
y
=
i
.
y
;
if
(
index
===
0
)
{
context
.
moveTo
(
x
,
y
);
}
else
{
context
.
lineTo
(
x
,
y
);
}
// context.quadraticCurveTo(150, 100, 260, 170); 弧线
index
++
;
}
context
.
closePath
();
// (!) Konva specific method, it is very important
context
.
fillStrokeShape
(
shape
);
},
// fill: fillColor,
name
,
stroke
:
'#21BA45'
,
strokeWidth
:
2
,
});
// 向外扩大 end
// 先添加的边界框矩形,再添加的自定义图像,
// 顺序和下面layerDragmove时获取children的顺序保持一致
group
.
add
(
rectBox
);
group
.
add
(
rectBox
);
group
.
add
(
shape
);
group
.
add
(
shape
);
group
.
add
(
shape2
);
return
group
;
return
group
;
}
}
}
}
function
layerDragmove
(
e
:
any
)
{
function
layerDragmove
(
e
:
any
)
{
let
target
=
e
.
target
;
const
target
=
e
.
target
;
state
.
layer
.
children
.
forEach
(
function
(
group
:
any
)
{
const
targetRectAttr
=
target
.
children
[
0
].
attrs
;
// do not check intersection with itself
// const targetShapeAttr = target.children[1].attrs;
// 不检查与自身相交
if
(
group
.
_id
==
target
.
_id
)
{
const
diffX
=
target
.
attrs
.
x
;
return
;
const
diffY
=
target
.
attrs
.
y
;
}
});
const
taIndex
=
state
.
group
.
get
(
target
.
_id
);
const
ta
=
state
.
shapePath
[
taIndex
];
const
_boundingBox
=
ta
.
boundingBox
;
_boundingBox
.
min_x
=
targetRectAttr
.
x
+
diffX
;
_boundingBox
.
min_y
=
targetRectAttr
.
y
+
diffY
;
// _boundingBox.max_x += diffX;
// _boundingBox.min_y += diffY;
// _boundingBox.max_y += diffY;
// state.layer.children.forEach(function (group: any) {
// // do not check intersection with itself
// // 不检查与自身相交
// if (group._id == target._id) {
// return;
// }
// });
}
}
function
handleData
()
{
function
handleData
()
{
...
@@ -160,32 +232,78 @@ function handleData() {
...
@@ -160,32 +232,78 @@ function handleData() {
}
}
}
}
// 一条线上有多个点,只保留起点和终点
// 1.一条线上有多个点,只保留起点和终点
// 2.计算边距上的点
// 注意!path的最后一个点不与第一个点重合
function
removeAngleRepeat
(
path
:
any
[])
{
function
removeAngleRepeat
(
path
:
any
[])
{
let
length
=
path
.
length
;
let
length
=
path
.
length
;
if
(
length
>=
2
)
{
if
(
length
>=
2
)
{
let
myMap
=
new
Map
();
let
myMap
=
new
Map
();
// 计算A点两条邻边相交的点A'
// 计算过A点垂直于边距线上的点,这样的点计算两个,也就是A点两条邻边上各一点
//
let
index
=
1
;
let
index
=
1
;
for
(
const
item
of
path
)
{
for
(
const
item
of
path
)
{
let
x1
,
y1
,
x2
,
y2
;
let
x1
,
y1
,
x2
,
y2
;
// let last_x, last_y, gap_1, gap_2;
if
(
index
<
length
)
{
if
(
index
<
length
)
{
x1
=
item
.
x
;
x1
=
item
.
x
;
y1
=
item
.
y
;
y1
=
item
.
y
;
x2
=
path
[
index
].
x
;
x2
=
path
[
index
].
x
;
y2
=
path
[
index
].
y
;
y2
=
path
[
index
].
y
;
// 边距 start1
// gap_1 = item.gap || 0;
// if (index === 1) {
// // 第一个点,另一条邻边是与最后一个点组成
// last_x = path[length - 1].x;
// last_y = path[length - 1].y;
// gap_2 = path[length - 1].gap;
// } else {
// last_x = path[index - 2].x;
// last_y = path[index - 2].y;
// gap_2 = path[index - 2].gap;
// }
// 边距 end1
}
else
{
}
else
{
// 最后一个点连第一个点
// 最后一个点连第一个点
x1
=
item
.
x
;
x1
=
item
.
x
;
y1
=
item
.
y
;
y1
=
item
.
y
;
x2
=
path
[
0
].
x
;
x2
=
path
[
0
].
x
;
y2
=
path
[
0
].
y
;
y2
=
path
[
0
].
y
;
// 边距 start2
// gap_1 = item.gap || 0;
// gap_2 = path[index - 2].gap;
// last_x = path[index - 2].x;
// last_y = path[index - 2].y;
// 边距 end2
}
}
// toAngle:false弧度 true角度
let
du
=
getAngle
(
x1
,
y1
,
x2
,
y2
,
true
);
let
du
=
getAngle
(
x1
,
y1
,
x2
,
y2
,
true
);
item
.
du
=
du
;
if
(
!
myMap
.
has
(
du
))
{
if
(
!
myMap
.
has
(
du
))
{
myMap
.
set
(
du
,
item
);
myMap
.
set
(
du
,
item
);
// 边距 start3
// const du2 = getAngle(x1, y1, last_x, last_y, true);
// const gapParams = {
// x1,
// y1,
// gap_1,
// x2,
// y2,
// last_x,
// last_y,
// gap_2,
// du1: du,
// du2,
// };
// handleGap(gapParams);
// 边距 end3
}
}
// console.log('du', du);
// console.log('du', du);
...
@@ -199,18 +317,19 @@ function removeAngleRepeat(path: any[]) {
...
@@ -199,18 +317,19 @@ function removeAngleRepeat(path: any[]) {
return
newArr
;
return
newArr
;
}
else
{
}
else
{
console
.
warn
(
'小于两个点,不能形成夹角'
);
return
path
;
return
path
;
}
}
}
}
/**
/**
* 计算从
x1y1到x2y2
的直线,与水平线形成的夹角
* 计算从
A点[x1,y1]到B点[x2,y2]
的直线,与水平线形成的夹角
* 计算规则为
顺时针从左侧0°到与该直线
形成的夹角
* 计算规则为
以A为旋转点,将AB线顺时针旋转到-X轴
形成的夹角
* @param {Object} x1
* @param {Object} x1
* @param {Object} y1
* @param {Object} y1
* @param {Object} x2
* @param {Object} x2
* @param {Object} y2
* @param {Object} y2
* @param {Boolean} toAngle
是否转换为角度值,默认false
* @param {Boolean} toAngle
默认false【弧度值】,true【角度值】
*/
*/
function
getAngle
(
function
getAngle
(
x1
:
number
,
x1
:
number
,
...
@@ -230,7 +349,7 @@ function getAngle(
...
@@ -230,7 +349,7 @@ function getAngle(
let
res
;
let
res
;
// 角度
// 角度
值
// let angle = (180 + (Math.atan2(-y, -x) * 180) / Math.PI + 360) % 360;
// let angle = (180 + (Math.atan2(-y, -x) * 180) / Math.PI + 360) % 360;
let
_atan2
=
Decimal
.
atan2
(
y
.
negated
(),
x
.
negated
());
let
_atan2
=
Decimal
.
atan2
(
y
.
negated
(),
x
.
negated
());
let
_angle
=
Decimal
.
div
(
Decimal
.
mul
(
_atan2
,
180
),
Math
.
PI
);
let
_angle
=
Decimal
.
div
(
Decimal
.
mul
(
_atan2
,
180
),
Math
.
PI
);
...
@@ -239,11 +358,36 @@ function getAngle(
...
@@ -239,11 +358,36 @@ function getAngle(
res
=
Decimal
.
sub
(
360
,
angle
);
res
=
Decimal
.
sub
(
360
,
angle
);
if
(
!
toAngle
)
{
if
(
!
toAngle
)
{
// 弧度值
res
=
Decimal
.
mul
(
res
,
Math
.
PI
).
div
(
180
);
res
=
Decimal
.
mul
(
res
,
Math
.
PI
).
div
(
180
);
}
}
return
res
.
toNumber
();
return
res
.
toNumber
();
}
}
/**
* 计算边距
* param里面包含3个点的坐标,及2条边距
* A [x1,y1] 当前目标点
* B [x2,y2] 在A点下一条邻边上的点
* C [last_x,last_y] 在A点上一条邻边上的点
* gap_1 AB上的边距; du1 AB与-X轴的夹角,传入角度值
* gap_2 AC上的边距; du2 AC与-X轴的夹角,传入角度值
*/
// function handleGap(param: any) {
// const { x1, y1, x2, y2, last_x, last_y, gap_1, gap_2, du1, du2 } = param;
// // console.log('du1', du1);
// }
// 批量选择
function
batchSelection
(
value
:
boolean
)
{
if
(
value
)
{
startSelected
.
value
=
true
;
}
else
{
startSelected
.
value
=
false
;
state
.
shapePath
.
forEach
((
i
)
=>
(
i
.
selected
=
true
));
}
}
// 网格线
// 网格线
function
drawGrid
()
{
function
drawGrid
()
{
let
canvas
:
any
=
document
.
getElementById
(
'canvas-grid'
);
let
canvas
:
any
=
document
.
getElementById
(
'canvas-grid'
);
...
@@ -274,7 +418,13 @@ function drawGrid() {
...
@@ -274,7 +418,13 @@ function drawGrid() {
</
script
>
</
script
>
<
template
>
<
template
>
<div
class=
"konva-main-page container-height center"
>
<div
class=
"konva-main-page container-height center"
>
<div>
666
</div>
<div
class=
"column"
>
<q-toggle
v-model=
"startSelected"
label=
"批量选择"
@
update:model-value=
"batchSelection"
/>
</div>
<div
class=
"canvas-box"
>
<div
class=
"canvas-box"
>
<canvas
<canvas
id=
"canvas-grid"
id=
"canvas-grid"
...
@@ -282,7 +432,20 @@ function drawGrid() {
...
@@ -282,7 +432,20 @@ function drawGrid() {
:height=
"stageSize.height"
:height=
"stageSize.height"
style=
"position: absolute"
style=
"position: absolute"
></canvas>
></canvas>
<div
id=
"stage-container"
></div>
<div
id=
"stage-container"
></div>
<template
v-for=
"(item, index) in state.shapePath"
:key=
"index"
>
<q-checkbox
v-model=
"item.selected"
dense
class=
"my-checkbox"
v-if=
"startSelected && item.boundingBox"
:style=
"
{
left: item.boundingBox.min_x + 'px',
top: item.boundingBox.min_y + 'px',
}"
/>
</
template
>
</div>
</div>
</div>
</div>
</template>
</template>
...
@@ -296,5 +459,9 @@ function drawGrid() {
...
@@ -296,5 +459,9 @@ function drawGrid() {
height
:
800px
;
height
:
800px
;
border
:
1px
solid
#000
;
border
:
1px
solid
#000
;
// background: pink;
// background: pink;
position
:
relative
;
}
.my-checkbox
{
position
:
absolute
;
}
}
</
style
>
</
style
>
src/modules/vue-konva/utils.ts
0 → 100644
View file @
4485971c
/*
* @Description: 多边形核心算法
* 源码Gitee:https://gitee.com/dhzx/js-polygon-algorithm
*/
interface
Point
{
x
:
number
;
y
:
number
;
gap
?:
number
;
[
proppName
:
string
]:
any
;
}
/**
* 获取多边形中心点
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
*/
export
function
getPolygonCenter
(
points
:
Point
[])
{
if
(
!
Array
.
isArray
(
points
)
||
points
.
length
<
3
)
{
console
.
error
(
'多边形坐标集合不能少于3个'
);
return
;
}
const
result
=
{
x
:
0
,
y
:
0
};
points
.
forEach
((
p
)
=>
{
result
.
x
+=
p
.
x
;
result
.
y
+=
p
.
y
;
});
result
.
x
/=
points
.
length
;
result
.
y
/=
points
.
length
;
return
result
;
}
/**
* 获取多边形重心(质心)
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
*/
export
function
getPolygonBaryCenter
(
points
:
Point
[])
{
if
(
!
Array
.
isArray
(
points
)
||
points
.
length
<
3
)
{
console
.
error
(
'多边形坐标集合不能少于3个'
);
return
;
}
const
result
=
{
x
:
0
,
y
:
0
};
let
area
=
0
;
for
(
let
i
=
1
;
i
<=
points
.
length
;
i
++
)
{
const
curX
=
points
[
i
%
points
.
length
].
x
;
const
curY
=
points
[
i
%
points
.
length
].
y
;
const
nextX
=
points
[
i
-
1
].
x
;
const
nextY
=
points
[
i
-
1
].
y
;
const
temp
=
(
curX
*
nextY
-
curY
*
nextX
)
/
2
;
area
+=
temp
;
result
.
x
+=
(
temp
*
(
curX
+
nextX
))
/
3
;
result
.
y
+=
(
temp
*
(
curY
+
nextY
))
/
3
;
}
result
.
x
/=
area
;
result
.
y
/=
area
;
return
result
;
}
/**
* 判断点是否在多边形内部
* @param {Point} point 点坐标
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
* @returns
*/
export
function
isInPolygon
(
point
:
Point
,
points
:
Point
[])
{
if
(
!
Array
.
isArray
(
points
)
||
points
.
length
<
3
)
{
console
.
error
(
'多边形坐标集合不能少于3个'
);
return
;
}
const
n
=
points
.
length
;
let
nCross
=
0
;
for
(
let
i
=
0
;
i
<
n
;
i
++
)
{
const
p1
=
points
[
i
];
const
p2
=
points
[(
i
+
1
)
%
n
];
// 求解 y=p.y 与 p1 p2 的交点
// p1p2 与 y=p0.y平行
if
(
p1
.
y
===
p2
.
y
)
continue
;
// 交点在p1p2延长线上
if
(
point
.
y
<
Math
.
min
(
p1
.
y
,
p2
.
y
))
continue
;
// 交点在p1p2延长线上
if
(
point
.
y
>=
Math
.
max
(
p1
.
y
,
p2
.
y
))
continue
;
// 求交点的 X 坐标
const
x
=
((
point
.
y
-
p1
.
y
)
*
(
p2
.
x
-
p1
.
x
))
/
(
p2
.
y
-
p1
.
y
)
+
p1
.
x
;
// 只统计单边交点
if
(
x
>
point
.
x
)
nCross
++
;
}
return
nCross
%
2
===
1
;
}
/**
* 缩放多边形坐标
* @decoration 需配合顺时针判断方法一起使用
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
* @param {number} extra 外延大小。为正: 向外扩; 为负: 向内缩
* @return {Point[]} 扩展或缩小后的多边形点坐标数组
*/
export
function
scalePolygon2
(
points
:
Point
[],
extra
:
number
)
{
if
(
!
Array
.
isArray
(
points
)
||
points
.
length
<
3
)
{
console
.
error
(
'多边形坐标集合不能少于3个'
);
return
;
}
const
ps
=
points
;
// 通过顺时针判断取正值还是负值
const
extra0
=
isClockwise
(
ps
)
?
-
extra
:
extra
;
const
norm
=
(
x
:
number
,
y
:
number
)
=>
Math
.
sqrt
(
x
*
x
+
y
*
y
);
const
len
=
ps
.
length
;
const
polygon
=
[];
for
(
let
i
=
0
;
i
<
len
;
i
++
)
{
const
point
=
ps
[
i
];
const
point1
=
ps
[
i
===
0
?
len
-
1
:
i
-
1
];
const
point2
=
ps
[
i
===
len
-
1
?
0
:
i
+
1
];
// 向量PP1
const
vectorX1
=
point1
.
x
-
point
.
x
;
// 向量PP1 横坐标
const
vectorY1
=
point1
.
y
-
point
.
y
;
// 向量PP1 纵坐标
const
n1
=
norm
(
vectorX1
,
vectorY1
);
// 向量的平方根 为了对向量PP1做单位化
let
vectorUnitX1
=
vectorX1
/
n1
;
// 向量单位化 横坐标
let
vectorUnitY1
=
vectorY1
/
n1
;
// 向量单位化 纵坐标
// 向量PP2
const
vectorX2
=
point2
.
x
-
point
.
x
;
// 向量PP2 横坐标
const
vectorY2
=
point2
.
y
-
point
.
y
;
// 向量PP2 纵坐标
const
n2
=
norm
(
vectorX2
,
vectorY2
);
// 向量的平方根 为了对向量PP1做单位化
let
vectorUnitX2
=
vectorX2
/
n2
;
// 向量单位化 横坐标
let
vectorUnitY2
=
vectorY2
/
n2
;
// 向量单位化 纵坐标
// PQ距离
const
vectorLen
=
-
extra0
/
Math
.
sqrt
(
(
1
-
(
vectorUnitX1
*
vectorUnitX2
+
vectorUnitY1
*
vectorUnitY2
))
/
2
);
// 根据向量的叉乘积来判断角是凹角还是凸角
if
(
vectorX1
*
vectorY2
+
-
1
*
vectorY1
*
vectorX2
<
0
)
{
vectorUnitX2
*=
-
1
;
vectorUnitY2
*=
-
1
;
vectorUnitX1
*=
-
1
;
vectorUnitY1
*=
-
1
;
}
// PQ的方向
const
vectorX
=
vectorUnitX1
+
vectorUnitX2
;
const
vectorY
=
vectorUnitY1
+
vectorUnitY2
;
const
n
=
vectorLen
/
norm
(
vectorX
,
vectorY
);
const
vectorUnitX
=
vectorX
*
n
;
const
vectorUnitY
=
vectorY
*
n
;
const
polygonX
=
vectorUnitX
+
point
.
x
;
const
polygonY
=
vectorUnitY
+
point
.
y
;
polygon
[
i
]
=
{
x
:
polygonX
,
y
:
polygonY
};
}
return
polygon
;
}
/**
* 缩放多边形坐标
* @decoration 需配合顺时针判断方法一起使用
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
* @param {number} extra 外延大小。为正: 向外扩; 为负: 向内缩
* @return {Point[]} 扩展或缩小后的多边形点坐标数组
*/
export
function
scalePolygon
(
points
:
Point
[],
extra
=
0
)
{
if
(
!
Array
.
isArray
(
points
)
||
points
.
length
<
3
)
{
console
.
error
(
'多边形坐标集合不能少于3个'
);
return
;
}
const
ps
=
points
;
// 通过顺时针判断取正值还是负值
// const extra0 = isClockwise(ps) ? -extra : extra;
const
norm
=
(
x
:
number
,
y
:
number
)
=>
Math
.
sqrt
(
x
*
x
+
y
*
y
);
const
len
=
ps
.
length
;
const
polygon
=
[];
for
(
let
i
=
0
;
i
<
len
;
i
++
)
{
let
extra0
;
if
(
extra
)
{
extra0
=
isClockwise
(
ps
)
?
-
extra
:
extra
;
}
else
{
const
_gap
=
ps
[
i
].
gap
||
0
;
extra0
=
isClockwise
(
ps
)
?
-
_gap
:
_gap
;
}
const
point
=
ps
[
i
];
const
point1
=
ps
[
i
===
0
?
len
-
1
:
i
-
1
];
const
point2
=
ps
[
i
===
len
-
1
?
0
:
i
+
1
];
// 向量PP1
const
vectorX1
=
point1
.
x
-
point
.
x
;
// 向量PP1 横坐标
const
vectorY1
=
point1
.
y
-
point
.
y
;
// 向量PP1 纵坐标
const
n1
=
norm
(
vectorX1
,
vectorY1
);
// 向量的平方根 为了对向量PP1做单位化
let
vectorUnitX1
=
vectorX1
/
n1
;
// 向量单位化 横坐标
let
vectorUnitY1
=
vectorY1
/
n1
;
// 向量单位化 纵坐标
// 向量PP2
const
vectorX2
=
point2
.
x
-
point
.
x
;
// 向量PP2 横坐标
const
vectorY2
=
point2
.
y
-
point
.
y
;
// 向量PP2 纵坐标
const
n2
=
norm
(
vectorX2
,
vectorY2
);
// 向量的平方根 为了对向量PP1做单位化
let
vectorUnitX2
=
vectorX2
/
n2
;
// 向量单位化 横坐标
let
vectorUnitY2
=
vectorY2
/
n2
;
// 向量单位化 纵坐标
// PQ距离
const
vectorLen
=
-
extra0
/
Math
.
sqrt
(
(
1
-
(
vectorUnitX1
*
vectorUnitX2
+
vectorUnitY1
*
vectorUnitY2
))
/
2
);
// 根据向量的叉乘积来判断角是凹角还是凸角
if
(
vectorX1
*
vectorY2
+
-
1
*
vectorY1
*
vectorX2
<
0
)
{
vectorUnitX2
*=
-
1
;
vectorUnitY2
*=
-
1
;
vectorUnitX1
*=
-
1
;
vectorUnitY1
*=
-
1
;
}
// PQ的方向
const
vectorX
=
vectorUnitX1
+
vectorUnitX2
;
const
vectorY
=
vectorUnitY1
+
vectorUnitY2
;
const
n
=
vectorLen
/
norm
(
vectorX
,
vectorY
);
const
vectorUnitX
=
vectorX
*
n
;
const
vectorUnitY
=
vectorY
*
n
;
const
polygonX
=
vectorUnitX
+
point
.
x
;
const
polygonY
=
vectorUnitY
+
point
.
y
;
polygon
[
i
]
=
{
x
:
polygonX
,
y
:
polygonY
};
}
return
polygon
;
}
/**
* 判断坐标数组是否顺时针(默认为false)
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
* @returns {boolean} 是否顺时针
*/
export
function
isClockwise
(
points
:
Point
[])
{
// 三个点可以判断矢量是顺时针旋转还是逆时针旋转的,但由于可能存在凹边,所以并不是任意三点都可以正确反映多边形的走向
// 因此需要取多边形中绝对是凸边的点来判断,
// 多边形中的极值点(x最大或x最小或y最大或y最小)它与相邻两点构成的边必然是凸边,因此我们先取出多边形中的极值点,再由极值点和其前后两点去判断矢量的走向,从而判断出多边形的走向。
if
(
!
Array
.
isArray
(
points
)
||
points
.
length
<
3
)
{
console
.
error
(
'多边形坐标集合不能少于3个'
);
return
false
;
}
let
coords
=
JSON
.
parse
(
JSON
.
stringify
(
points
));
if
(
coords
[
0
]
===
coords
[
coords
.
length
-
1
])
{
coords
=
coords
.
slice
(
0
,
coords
.
length
-
1
);
}
coords
=
coords
.
reverse
();
let
maxXIndex
=
0
;
let
maxX
=
parseFloat
(
coords
[
maxXIndex
].
x
);
let
c1
;
let
c2
;
let
c3
;
for
(
let
i
=
0
;
i
<
coords
.
length
;
i
++
)
{
if
(
parseFloat
(
coords
[
i
].
x
)
>
maxX
)
{
maxX
=
parseFloat
(
coords
[
i
].
x
);
maxXIndex
=
i
;
}
}
if
(
maxXIndex
===
0
)
{
c1
=
coords
[
coords
.
length
-
1
];
c2
=
coords
[
maxXIndex
];
c3
=
coords
[
maxXIndex
+
1
];
}
else
if
(
maxXIndex
===
coords
.
length
-
1
)
{
c1
=
coords
[
maxXIndex
-
1
];
c2
=
coords
[
maxXIndex
];
c3
=
coords
[
0
];
}
else
{
c1
=
coords
[
maxXIndex
-
1
];
c2
=
coords
[
maxXIndex
];
c3
=
coords
[
maxXIndex
+
1
];
}
const
x1
=
parseFloat
(
c1
.
x
);
const
y1
=
parseFloat
(
c1
.
y
);
const
x2
=
parseFloat
(
c2
.
x
);
const
y2
=
parseFloat
(
c2
.
y
);
const
x3
=
parseFloat
(
c3
.
x
);
const
y3
=
parseFloat
(
c3
.
y
);
const
s
=
(
x1
-
x3
)
*
(
y2
-
y3
)
-
(
x2
-
x3
)
*
(
y1
-
y3
);
return
s
<
0
;
}
src/router/routes.ts
View file @
4485971c
...
@@ -5,6 +5,7 @@ import TREE from '../modules/tree/route';
...
@@ -5,6 +5,7 @@ import TREE from '../modules/tree/route';
import
AMIS
from
'../modules/amis/route'
;
import
AMIS
from
'../modules/amis/route'
;
import
VUE_STUDY
from
'../modules/vue-study/route'
;
import
VUE_STUDY
from
'../modules/vue-study/route'
;
import
VUE_KONVA
from
'../modules/vue-konva/route'
;
import
VUE_KONVA
from
'../modules/vue-konva/route'
;
import
VECTOR
from
'../modules/vector/route'
;
const
routes
:
RouteRecordRaw
[]
=
[
const
routes
:
RouteRecordRaw
[]
=
[
{
{
...
@@ -133,6 +134,7 @@ const routes: RouteRecordRaw[] = [
...
@@ -133,6 +134,7 @@ const routes: RouteRecordRaw[] = [
...
AMIS
,
...
AMIS
,
...
VUE_STUDY
,
...
VUE_STUDY
,
...
VUE_KONVA
,
...
VUE_KONVA
,
...
VECTOR
,
],
],
},
},
],
],
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment