feat(pkg): Add pathfinding edge pkg to packages dir
This commit is contained in:
13
packages/pathfinding-edge/.editorconfig
Normal file
13
packages/pathfinding-edge/.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
8
packages/pathfinding-edge/.eslintrc.js
Normal file
8
packages/pathfinding-edge/.eslintrc.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
rules: {
|
||||
'no-use-before-define': 0,
|
||||
'vue/no-setup-props-destructure': 0,
|
||||
},
|
||||
extends: ['../../.eslintrc.js'],
|
||||
ignorePatterns: ['!**/*'],
|
||||
}
|
||||
99
packages/pathfinding-edge/.gitignore
vendored
Normal file
99
packages/pathfinding-edge/.gitignore
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
/logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# Nuxt generate
|
||||
dist
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
.webpack
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# Service worker
|
||||
sw.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Vim swap files
|
||||
*.swp
|
||||
|
||||
# Db-Data
|
||||
hasura/db_data/
|
||||
|
||||
# Codegen types
|
||||
types/types.ts
|
||||
|
||||
build
|
||||
8
packages/pathfinding-edge/.prettierrc
Normal file
8
packages/pathfinding-edge/.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"semi": false,
|
||||
"quoteProps": "consistent",
|
||||
"bracketSpacing": true,
|
||||
"printWidth": 130
|
||||
}
|
||||
5
packages/pathfinding-edge/LICENSE
Normal file
5
packages/pathfinding-edge/LICENSE
Normal file
@@ -0,0 +1,5 @@
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
82
packages/pathfinding-edge/README.md
Normal file
82
packages/pathfinding-edge/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Vue Flow Pathfinding Edge 🧲
|
||||
|
||||

|
||||
[](https://david-dm.org/bcakmakoglu/vue-flow-pathfinding-edge)
|
||||
[](https://david-dm.org/bcakmakoglu/vue-flow-pathfinding-edge?type=dev)
|
||||

|
||||

|
||||

|
||||
|
||||
### **Custom Edge that avoids crossing other Nodes**
|
||||
|
||||
Check the [documentation](https://vueflow.dev/docs/addons/pathfinding) for more info on how to use this custom edge.
|
||||
|
||||
## 🛠 Setup
|
||||
|
||||
```bash
|
||||
# install
|
||||
$ yarn add @braks/vue-flow-pathfinding-edge
|
||||
|
||||
# or
|
||||
$ npm i --save @braks/vue-flow-pathfinding-edge
|
||||
```
|
||||
|
||||
## 🎮 Quickstart
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
VueFlow,
|
||||
Elements,
|
||||
useVueFlow
|
||||
} from '@braks/vue-flow'
|
||||
import { PathFindingEdge } from '@braks/vue-flow-pathfinding-edge'
|
||||
import initialElements from './initial-elements'
|
||||
|
||||
const elements = ref<Elements>(initialElements)
|
||||
|
||||
// create a new context so we can fetch nodes
|
||||
const { getNodes } = useVueFlow({
|
||||
modelValue: elements.value
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div style="height: 300px">
|
||||
<VueFlow>
|
||||
<template #edge-pathFinding="props">
|
||||
<PathFindingEdge v-bind="props" :nodes="getNodes" />
|
||||
</template>
|
||||
</VueFlow>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
```typescript
|
||||
// initial-elements.ts
|
||||
export default [
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
position: {
|
||||
x: 430,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Node 2',
|
||||
position: {
|
||||
x: 230,
|
||||
y: 90,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'e12',
|
||||
source: '1',
|
||||
target: '2',
|
||||
label: 'Smart Edge',
|
||||
style: { stroke: 'red' },
|
||||
type: 'pathFinding'
|
||||
},
|
||||
]
|
||||
```
|
||||
40
packages/pathfinding-edge/example/App.vue
Normal file
40
packages/pathfinding-edge/example/App.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script lang="ts" setup>
|
||||
import { VueFlow, MiniMap, Controls, Background, Connection, Edge, Elements, FlowInstance, addEdge } from '@braks/vue-flow'
|
||||
import initialElements from './elements'
|
||||
import { PathFindingEdge } from '~/index'
|
||||
|
||||
const elements = ref<Elements>(initialElements)
|
||||
const rfInstance = ref<FlowInstance | null>(null)
|
||||
const onConnect = (params: Edge | Connection) => (elements.value = addEdge(params, elements.value))
|
||||
const onLoad = (flowInstance: FlowInstance) => {
|
||||
flowInstance.fitView({ padding: 1 })
|
||||
rfInstance.value = flowInstance
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div style="height: 100%">
|
||||
<VueFlow v-model="elements" class="vue-flow-basic-example" @connect="onConnect" @pane-ready="onLoad">
|
||||
<template #edge-pathFinding="props">
|
||||
<PathFindingEdge v-bind="props" />
|
||||
</template>
|
||||
<Controls />
|
||||
<MiniMap />
|
||||
<Background color="#aaa" :gap="8" />
|
||||
</VueFlow>
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
@import '../node_modules/@braks/vue-flow/dist/style.css';
|
||||
@import '../node_modules/@braks/vue-flow/dist/theme-default.css';
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
127
packages/pathfinding-edge/example/elements.ts
Normal file
127
packages/pathfinding-edge/example/elements.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { MarkerType, Elements } from '@braks/vue-flow'
|
||||
|
||||
const markerEnd = MarkerType.Arrow
|
||||
|
||||
export default [
|
||||
{
|
||||
id: '1',
|
||||
label: 'Node 1',
|
||||
position: {
|
||||
x: 430,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Node 2',
|
||||
position: {
|
||||
x: 230,
|
||||
y: 90,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2a',
|
||||
label: 'Node 2a',
|
||||
position: {
|
||||
x: 0,
|
||||
y: 180,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2b',
|
||||
label: 'Node 2b',
|
||||
position: {
|
||||
x: 230,
|
||||
y: 180,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2c',
|
||||
label: 'Node 2c',
|
||||
position: {
|
||||
x: 430,
|
||||
y: 180,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2d',
|
||||
label: 'Node 2d',
|
||||
position: {
|
||||
x: 475,
|
||||
y: 270,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
label: 'Node 3',
|
||||
position: {
|
||||
x: 430,
|
||||
y: 90,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'e12',
|
||||
source: '1',
|
||||
target: '2',
|
||||
label: 'Foobar',
|
||||
style: { stroke: 'red' },
|
||||
type: 'pathFinding',
|
||||
markerEnd,
|
||||
},
|
||||
{
|
||||
id: 'e13',
|
||||
source: '1',
|
||||
target: '3',
|
||||
type: 'pathFinding',
|
||||
markerEnd,
|
||||
},
|
||||
{
|
||||
id: 'e22a',
|
||||
source: '2',
|
||||
target: '2a',
|
||||
type: 'pathFinding',
|
||||
markerEnd,
|
||||
},
|
||||
{
|
||||
id: 'e22b',
|
||||
source: '2',
|
||||
target: '2b',
|
||||
type: 'pathFinding',
|
||||
markerEnd,
|
||||
},
|
||||
{
|
||||
id: 'e22c',
|
||||
source: '2',
|
||||
target: '2c',
|
||||
type: 'pathFinding',
|
||||
markerEnd,
|
||||
},
|
||||
{
|
||||
id: 'e2c2d',
|
||||
source: '2c',
|
||||
target: '2d',
|
||||
type: 'pathFinding',
|
||||
markerEnd,
|
||||
},
|
||||
{
|
||||
id: 'e2d2c',
|
||||
source: '2d',
|
||||
target: '2c',
|
||||
type: 'pathFinding',
|
||||
markerEnd,
|
||||
},
|
||||
{
|
||||
id: 'e2d1',
|
||||
source: '2d',
|
||||
target: '1',
|
||||
type: 'pathFinding',
|
||||
markerEnd,
|
||||
},
|
||||
{
|
||||
id: 'e2a2b',
|
||||
source: '2a',
|
||||
target: '2b',
|
||||
type: 'pathFinding',
|
||||
markerEnd,
|
||||
},
|
||||
] as Elements
|
||||
7
packages/pathfinding-edge/example/main.ts
Normal file
7
packages/pathfinding-edge/example/main.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.config.performance = true
|
||||
app.mount('#root')
|
||||
12
packages/pathfinding-edge/index.html
Normal file
12
packages/pathfinding-edge/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>🌊 Vue Flow</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/example/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
59
packages/pathfinding-edge/package.json
Normal file
59
packages/pathfinding-edge/package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@braks/vue-flow-pathfinding-edge",
|
||||
"version": "0.2.1",
|
||||
"private": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bcakmakoglu/vue-flow-pathfinding-edge"
|
||||
},
|
||||
"homepage": "https//vueflow.dev/docs/addons/pathfinding",
|
||||
"license": "MIT",
|
||||
"author": "Burak Cakmakoglu<brainbraks@googlemail.com>",
|
||||
"main": "./dist/vue-flow-pathfinding-edge.cjs.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"module": "./dist/vue-flow-pathfinding-edge.es.js",
|
||||
"unpkg": "./dist/vue-flow-pathfinding-edge.iife.js",
|
||||
"jsdelivr": "./dist/vue-flow-pathfinding-edge.iife.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"prepare": "ts-patch install -s",
|
||||
"dev": "vite",
|
||||
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly && tsc",
|
||||
"postbuild": "shx rm -rf tmp",
|
||||
"prepublishOnly": "yarn build",
|
||||
"test": "exit 0;",
|
||||
"lint:js": "eslint --ext \".js,.jsx,.ts,.tsx\" --fix --ignore-path .gitignore .",
|
||||
"lint": "yarn lint:js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^8.4.2",
|
||||
"pathfinding": "^0.4.18",
|
||||
"perfect-arrows": "^0.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@braks/vue-flow": "^0.4.10",
|
||||
"@types/pathfinding": "^0.0.5",
|
||||
"@vitejs/plugin-vue": "^2.3.3",
|
||||
"np": "^7.5.0",
|
||||
"ts-patch": "^2.0.1",
|
||||
"typescript-transform-paths": "^3.3.1",
|
||||
"unplugin-auto-import": "^0.7.1",
|
||||
"vite": "^2.9.9",
|
||||
"vue-tsc": "^0.34.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@braks/vue-flow": "^0.4.0",
|
||||
"vue": "^3.2.25"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "http://registry.npmjs.org/"
|
||||
},
|
||||
"np": {
|
||||
"branch": "master",
|
||||
"yarn": true,
|
||||
"message": "v%s"
|
||||
}
|
||||
}
|
||||
85
packages/pathfinding-edge/src/arrow/PerfectArrow.vue
Normal file
85
packages/pathfinding-edge/src/arrow/PerfectArrow.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script lang="ts" setup>
|
||||
import { EdgeProps, EdgeTextProps, getEdgeCenter, Position, EdgeText } from '@braks/vue-flow'
|
||||
import { CSSProperties, DefineComponent } from 'vue'
|
||||
import { ArrowOptions, getArrow } from 'perfect-arrows'
|
||||
|
||||
interface PerfectArrowProps extends EdgeProps {
|
||||
id: string
|
||||
source: string
|
||||
target: string
|
||||
sourceX: number
|
||||
sourceY: number
|
||||
targetX: number
|
||||
targetY: number
|
||||
selected?: boolean
|
||||
animated?: boolean
|
||||
sourcePosition: Position
|
||||
targetPosition: Position
|
||||
label?:
|
||||
| string
|
||||
| {
|
||||
component: DefineComponent<EdgeTextProps>
|
||||
props?: EdgeTextProps
|
||||
}
|
||||
labelStyle?: any
|
||||
labelShowBg?: boolean
|
||||
labelBgStyle?: any
|
||||
labelBgPadding?: [number, number]
|
||||
labelBgBorderRadius?: number
|
||||
style?: CSSProperties
|
||||
markerEnd?: string
|
||||
markerStart?: string
|
||||
data?: any
|
||||
sourceHandleId?: string
|
||||
targetHandleId?: string
|
||||
options?: ArrowOptions
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<PerfectArrowProps>(), {
|
||||
options: () => ({
|
||||
padStart: 3,
|
||||
padEnd: 3,
|
||||
stretch: 0.2,
|
||||
}),
|
||||
})
|
||||
|
||||
const centered = computed(() =>
|
||||
getEdgeCenter({
|
||||
...props,
|
||||
}),
|
||||
)
|
||||
|
||||
const arrow = computed(() => {
|
||||
return getArrow(props.sourceX, props.sourceY, props.targetX, props.targetY, {
|
||||
...props.options,
|
||||
})
|
||||
})
|
||||
|
||||
const attrs = useAttrs() as { style: CSSProperties }
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'PerfectArrow',
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<path
|
||||
:style="{ ...props.style, ...attrs.style }"
|
||||
class="vue-flow__edge-path vue-flow__perfect-arrow"
|
||||
:d="`M${arrow[0]},${arrow[1]} Q${arrow[2]},${arrow[3]} ${arrow[4]},${arrow[5]}`"
|
||||
:marker-end="props.markerEnd"
|
||||
:marker-start="props.markerStart"
|
||||
/>
|
||||
<EdgeText
|
||||
v-if="props.label"
|
||||
:x="centered[0]"
|
||||
:y="centered[1]"
|
||||
:label="props.label"
|
||||
:label-style="props.labelStyle"
|
||||
:label-show-bg="props.labelShowBg"
|
||||
:label-bg-style="props.labelBgStyle"
|
||||
:label-bg-padding="props.labelBgPadding"
|
||||
:label-bg-border-radius="props.labelBgBorderRadius"
|
||||
/>
|
||||
</template>
|
||||
186
packages/pathfinding-edge/src/auto-imports.d.ts
vendored
Normal file
186
packages/pathfinding-edge/src/auto-imports.d.ts
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
// Generated by 'unplugin-auto-import'
|
||||
// We suggest you to commit this file into source control
|
||||
declare global {
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
const biSyncRef: typeof import('@vueuse/core')['biSyncRef']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
|
||||
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const refDefault: typeof import('@vueuse/core')['refDefault']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
||||
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toReactive: typeof import('@vueuse/core')['toReactive']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
||||
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
||||
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
||||
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||
const useDark: typeof import('@vueuse/core')['useDark']
|
||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
||||
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
||||
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
||||
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
||||
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
||||
const useElementHover: typeof import('@vueuse/core')['useElementHover']
|
||||
const useElementSize: typeof import('@vueuse/core')['useElementSize']
|
||||
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
|
||||
const useEventBus: typeof import('@vueuse/core')['useEventBus']
|
||||
const useEventListener: typeof import('@vueuse/core')['useEventListener']
|
||||
const useEventSource: typeof import('@vueuse/core')['useEventSource']
|
||||
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
||||
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||
const useFocus: typeof import('@vueuse/core')['useFocus']
|
||||
const useFps: typeof import('@vueuse/core')['useFps']
|
||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
||||
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
||||
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
||||
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
||||
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
||||
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
||||
const useMouse: typeof import('@vueuse/core')['useMouse']
|
||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
||||
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
||||
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
||||
const useNow: typeof import('@vueuse/core')['useNow']
|
||||
const useOnline: typeof import('@vueuse/core')['useOnline']
|
||||
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
|
||||
const useParallax: typeof import('@vueuse/core')['useParallax']
|
||||
const usePermission: typeof import('@vueuse/core')['usePermission']
|
||||
const usePointer: typeof import('@vueuse/core')['usePointer']
|
||||
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
||||
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||
const useScroll: typeof import('@vueuse/core')['useScroll']
|
||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||
const useShare: typeof import('@vueuse/core')['useShare']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
||||
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
||||
const useStorage: typeof import('@vueuse/core')['useStorage']
|
||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||
const useTransition: typeof import('@vueuse/core')['useTransition']
|
||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
||||
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
||||
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
||||
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
||||
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||
const whenever: typeof import('@vueuse/core')['whenever']
|
||||
}
|
||||
export {}
|
||||
135
packages/pathfinding-edge/src/edge/PathFindingEdge.vue
Normal file
135
packages/pathfinding-edge/src/edge/PathFindingEdge.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<script lang="ts" setup>
|
||||
import { Component, CSSProperties, DefineComponent } from 'vue'
|
||||
import { getEdgeCenter, BezierEdge, EdgeText, Position, EdgeProps, EdgeTextProps, GraphNode } from '@braks/vue-flow'
|
||||
import { createGrid, gridRatio } from './createGrid'
|
||||
import { drawSmoothLinePath } from './drawSvgPath'
|
||||
import { generatePath } from './generatePath'
|
||||
import { getBoundingBoxes } from './getBoundingBoxes'
|
||||
import { gridToGraphPoint } from './pointConversion'
|
||||
|
||||
interface PathFindingEdgeProps extends EdgeProps {
|
||||
nodes: GraphNode[]
|
||||
id: string
|
||||
source: string
|
||||
target: string
|
||||
sourceX: number
|
||||
sourceY: number
|
||||
targetX: number
|
||||
targetY: number
|
||||
selected?: boolean
|
||||
animated?: boolean
|
||||
sourcePosition: Position
|
||||
targetPosition: Position
|
||||
label?:
|
||||
| string
|
||||
| {
|
||||
component: Component<EdgeTextProps> | DefineComponent<EdgeTextProps>
|
||||
props?: EdgeTextProps
|
||||
}
|
||||
labelStyle?: any
|
||||
labelShowBg?: boolean
|
||||
labelBgStyle?: any
|
||||
labelBgPadding?: [number, number]
|
||||
labelBgBorderRadius?: number
|
||||
style?: CSSProperties
|
||||
markerEnd?: string
|
||||
markerStart?: string
|
||||
data?: any
|
||||
sourceHandleId?: string
|
||||
targetHandleId?: string
|
||||
}
|
||||
|
||||
const nodePadding = 10
|
||||
const graphPadding = 20
|
||||
const roundCoordinatesTo = gridRatio
|
||||
|
||||
const props = withDefaults(defineProps<PathFindingEdgeProps>(), {
|
||||
selected: false,
|
||||
sourcePosition: Position.Bottom,
|
||||
targetPosition: Position.Top,
|
||||
labelStyle: () => ({}),
|
||||
labelShowBg: true,
|
||||
labelBgStyle: () => ({}),
|
||||
})
|
||||
|
||||
const centered = computed(() =>
|
||||
getEdgeCenter({
|
||||
...props,
|
||||
}),
|
||||
)
|
||||
const source = computed(() => ({
|
||||
x: props.sourceX,
|
||||
y: props.sourceY,
|
||||
position: props.sourcePosition,
|
||||
}))
|
||||
const target = computed(() => ({
|
||||
x: props.targetX,
|
||||
y: props.targetY,
|
||||
position: props.targetPosition,
|
||||
}))
|
||||
|
||||
// We use the nodes information to generate bounding boxes for them
|
||||
// and the graph
|
||||
const bb = computed(() => getBoundingBoxes(props.nodes, nodePadding, graphPadding, roundCoordinatesTo))
|
||||
|
||||
const gridPath = computed(() => {
|
||||
let path: number[][] = []
|
||||
|
||||
if (target.value.x && source.value.x && props.nodes.length) {
|
||||
// We then can use the grid representation to do pathfinding
|
||||
// With this information, we can create a 2D grid representation of
|
||||
// our graph, that tells us where in the graph there is a "free" space or not
|
||||
const { grid, start, end } = createGrid(bb.value.graph, bb.value.nodes, source.value, target.value)
|
||||
path = generatePath(grid, start, end)
|
||||
}
|
||||
|
||||
return path
|
||||
})
|
||||
|
||||
const path = computed(() => {
|
||||
let svgPath = ''
|
||||
if (gridPath.value?.length) {
|
||||
// Here we convert the grid path to a sequence of graph coordinates.
|
||||
const graphPath = gridPath.value.map((gridPoint) => {
|
||||
const [x, y] = gridPoint
|
||||
const graphPoint = gridToGraphPoint({ x, y }, bb.value.graph.xMin, bb.value.graph.yMin)
|
||||
return [graphPoint.x, graphPoint.y]
|
||||
})
|
||||
|
||||
// Finally, we can use the graph path to draw the edge
|
||||
svgPath = drawSmoothLinePath(source.value, target.value, graphPath)
|
||||
}
|
||||
|
||||
return svgPath
|
||||
})
|
||||
const attrs: any = useAttrs()
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'PathFindingEdge',
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<BezierEdge v-if="gridPath && gridPath.length <= 2" v-bind="{ ...props, ...attrs }" />
|
||||
<template v-else>
|
||||
<path
|
||||
:style="{ ...props.style, ...attrs.style }"
|
||||
class="vue-flow__edge-path"
|
||||
:d="path"
|
||||
:marker-end="props.markerEnd"
|
||||
:marker-start="props.markerStart"
|
||||
/>
|
||||
<EdgeText
|
||||
v-if="props.label"
|
||||
:x="centered[0]"
|
||||
:y="centered[1]"
|
||||
:label="props.label"
|
||||
:label-style="props.labelStyle"
|
||||
:label-show-bg="props.labelShowBg"
|
||||
:label-bg-style="props.labelBgStyle"
|
||||
:label-bg-padding="props.labelBgPadding"
|
||||
:label-bg-border-radius="props.labelBgBorderRadius"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
70
packages/pathfinding-edge/src/edge/createGrid.ts
Normal file
70
packages/pathfinding-edge/src/edge/createGrid.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Grid } from 'pathfinding'
|
||||
import { Position } from '@braks/vue-flow'
|
||||
import { guaranteeWalkablePath, getNextPointFromPosition } from './guaranteeWalkablePath'
|
||||
import { graphToGridPoint } from './pointConversion'
|
||||
import { round } from './utils'
|
||||
import type { NodeBoundingBox, GraphBoundingBox } from './getBoundingBoxes'
|
||||
|
||||
export const gridRatio = 10
|
||||
|
||||
export type PointInfo = {
|
||||
x: number
|
||||
y: number
|
||||
position: Position
|
||||
}
|
||||
|
||||
export const createGrid = (graph: GraphBoundingBox, nodes: NodeBoundingBox[], source: PointInfo, target: PointInfo) => {
|
||||
const { xMin, yMin, width, height } = graph
|
||||
|
||||
// Create a grid representation of the graph box, where each cell is
|
||||
// equivalent to 10x10 pixels on the graph. We'll use this simplified grid
|
||||
// to do pathfinding.
|
||||
const mapColumns = width / gridRatio
|
||||
const mapRows = height / gridRatio
|
||||
const grid = new Grid(mapColumns, mapRows)
|
||||
|
||||
// Update the grid representation with the space the nodes take up
|
||||
nodes.forEach((node) => {
|
||||
const nodeStart = graphToGridPoint(node.topLeft, xMin, yMin)
|
||||
const nodeEnd = graphToGridPoint(node.bottomRight, xMin, yMin)
|
||||
|
||||
for (let x = nodeStart.x; x < nodeEnd.x; x++) {
|
||||
for (let y = nodeStart.y; y < nodeEnd.y; y++) {
|
||||
grid.setWalkableAt(x, y, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Convert the starting and ending graph points to grid points
|
||||
const startGrid = graphToGridPoint(
|
||||
{
|
||||
x: round(source.x, gridRatio),
|
||||
y: round(source.y, gridRatio),
|
||||
},
|
||||
xMin,
|
||||
yMin,
|
||||
)
|
||||
|
||||
const endGrid = graphToGridPoint(
|
||||
{
|
||||
x: round(target.x, gridRatio),
|
||||
y: round(target.y, gridRatio),
|
||||
},
|
||||
xMin,
|
||||
yMin,
|
||||
)
|
||||
|
||||
// Guarantee a walkable path between the start and end points, even if the
|
||||
// source or target where covered by another node or by padding
|
||||
const startingNode = grid.getNodeAt(startGrid.x, startGrid.y)
|
||||
guaranteeWalkablePath(grid, startingNode, source.position)
|
||||
const endingNode = grid.getNodeAt(endGrid.x, endGrid.y)
|
||||
guaranteeWalkablePath(grid, endingNode, target.position)
|
||||
|
||||
// Use the next closest points as the start and end points, so
|
||||
// pathfinding does not start too close to the nodes
|
||||
const start = getNextPointFromPosition(startingNode, source.position)
|
||||
const end = getNextPointFromPosition(endingNode, target.position)
|
||||
|
||||
return { grid, start, end }
|
||||
}
|
||||
54
packages/pathfinding-edge/src/edge/drawSvgPath.ts
Normal file
54
packages/pathfinding-edge/src/edge/drawSvgPath.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Draws a SVG path from a list of points, using straight lines.
|
||||
*/
|
||||
import { XYPosition } from '@braks/vue-flow'
|
||||
|
||||
const getMidPoint = (Ax: number, Ay: number, Bx: number, By: number) => {
|
||||
const Zx = (Ax - Bx) / 2 + Bx
|
||||
const Zy = (Ay - By) / 2 + By
|
||||
return [Zx, Zy]
|
||||
}
|
||||
|
||||
export const drawStraightLinePath = (source: XYPosition, target: XYPosition, path: number[][]) => {
|
||||
let svgPathString = `M ${source.x}, ${source.y} `
|
||||
|
||||
path.forEach((point) => {
|
||||
const [x, y] = point
|
||||
svgPathString += `L ${x}, ${y} `
|
||||
})
|
||||
|
||||
svgPathString += `L ${target.x}, ${target.y} `
|
||||
|
||||
return svgPathString
|
||||
}
|
||||
|
||||
const quadraticBezierCurve = (points: number[][]) => {
|
||||
const X = 0
|
||||
const Y = 1
|
||||
let point = points[0]
|
||||
|
||||
const first = points[0]
|
||||
let svgPath = `M${first[X]},${first[Y]}M`
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const next = points[i]
|
||||
const midPoint = getMidPoint(point[X], point[Y], next[X], next[Y])
|
||||
|
||||
svgPath += ` ${midPoint[X]},${midPoint[Y]}`
|
||||
svgPath += `Q${next[X]},${next[Y]}`
|
||||
point = next
|
||||
}
|
||||
|
||||
const last = points[points.length - 1]
|
||||
svgPath += ` ${last[0]},${last[1]}`
|
||||
|
||||
return svgPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a SVG path from a list of points, using rounded lines.
|
||||
*/
|
||||
export const drawSmoothLinePath = (source: XYPosition, target: XYPosition, path: number[][]) => {
|
||||
const points = [[source.x, source.y], ...path, [target.x, target.y]]
|
||||
return quadraticBezierCurve(points)
|
||||
}
|
||||
34
packages/pathfinding-edge/src/edge/generatePath.ts
Normal file
34
packages/pathfinding-edge/src/edge/generatePath.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { AStarFinder, Util, DiagonalMovement, Heuristic } from 'pathfinding'
|
||||
import type { Grid } from 'pathfinding'
|
||||
import { XYPosition } from '@braks/vue-flow'
|
||||
|
||||
// https://www.npmjs.com/package/pathfinding#advanced-usage
|
||||
declare module 'pathfinding' {
|
||||
interface FinderOptions extends Heuristic {
|
||||
diagonalMovement?: DiagonalMovement
|
||||
weight?: number
|
||||
allowDiagonal?: boolean
|
||||
dontCrossCorners?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const generatePath = (grid: Grid, start: XYPosition, end: XYPosition): number[][] => {
|
||||
const finder = new AStarFinder({
|
||||
diagonalMovement: DiagonalMovement.Always,
|
||||
allowDiagonal: true,
|
||||
dontCrossCorners: true,
|
||||
})
|
||||
|
||||
let path: number[][] = []
|
||||
|
||||
try {
|
||||
path = finder.findPath(start.x, start.y, end.x, end.y, grid)
|
||||
path = Util.smoothenPath(grid, path)
|
||||
} catch {
|
||||
// No path was found. This can happen if the end point is "surrounded"
|
||||
// by other nodes, or if the starting and ending nodes are on top of
|
||||
// each other.
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
150
packages/pathfinding-edge/src/edge/getBoundingBoxes.ts
Normal file
150
packages/pathfinding-edge/src/edge/getBoundingBoxes.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { XYPosition, GraphNode } from '@braks/vue-flow'
|
||||
import { roundUp, roundDown } from './utils'
|
||||
|
||||
export type NodeBoundingBox = {
|
||||
id: string
|
||||
width: number
|
||||
height: number
|
||||
topLeft: XYPosition
|
||||
bottomLeft: XYPosition
|
||||
topRight: XYPosition
|
||||
bottomRight: XYPosition
|
||||
}
|
||||
|
||||
export type GraphBoundingBox = {
|
||||
width: number
|
||||
height: number
|
||||
topLeft: XYPosition
|
||||
bottomLeft: XYPosition
|
||||
topRight: XYPosition
|
||||
bottomRight: XYPosition
|
||||
xMax: number
|
||||
yMax: number
|
||||
xMin: number
|
||||
yMin: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bounding box of all nodes and the graph itself, as X/Y coordinates
|
||||
* of all corner points.
|
||||
*
|
||||
* @param storeNodes The node list
|
||||
* @param nodePadding Optional padding to add to the node's bounding boxes
|
||||
* @param graphPadding Optional padding to add to the graph's bounding box
|
||||
* @param roundTo If the coordinates should be rounded to this nearest integer
|
||||
* @returns Graph and nodes bounding boxes.
|
||||
*/
|
||||
export const getBoundingBoxes = (storeNodes: GraphNode[], nodePadding = 0, graphPadding = 0, roundTo = 0) => {
|
||||
// Guarantee that the given parameters are positive integers
|
||||
nodePadding = Math.max(Math.round(nodePadding), 0)
|
||||
graphPadding = Math.max(Math.round(graphPadding), 0)
|
||||
roundTo = Math.max(Math.round(roundTo), 0)
|
||||
|
||||
nodePadding = Number.isInteger(nodePadding) ? nodePadding : 0
|
||||
graphPadding = Number.isInteger(graphPadding) ? graphPadding : 0
|
||||
roundTo = Number.isInteger(roundTo) ? roundTo : 0
|
||||
|
||||
let xMax = Number.MIN_SAFE_INTEGER
|
||||
let yMax = Number.MIN_SAFE_INTEGER
|
||||
let xMin = Number.MAX_SAFE_INTEGER
|
||||
let yMin = Number.MAX_SAFE_INTEGER
|
||||
|
||||
const nodes: NodeBoundingBox[] = storeNodes.map((node) => {
|
||||
const {
|
||||
computedPosition: { x, y },
|
||||
dimensions,
|
||||
} = node
|
||||
const width = Math.max(dimensions.width || 0, 1)
|
||||
const height = Math.max(dimensions.height || 0, 1)
|
||||
|
||||
const position: XYPosition = {
|
||||
x: x || 0,
|
||||
y: y || 0,
|
||||
}
|
||||
const topLeft: XYPosition = {
|
||||
x: position.x - nodePadding,
|
||||
y: position.y - nodePadding,
|
||||
}
|
||||
const bottomLeft: XYPosition = {
|
||||
x: position.x - nodePadding,
|
||||
y: position.y + height + nodePadding,
|
||||
}
|
||||
const topRight: XYPosition = {
|
||||
x: position.x + width + nodePadding,
|
||||
y: position.y - nodePadding,
|
||||
}
|
||||
const bottomRight: XYPosition = {
|
||||
x: position.x + width + nodePadding,
|
||||
y: position.y + height + nodePadding,
|
||||
}
|
||||
|
||||
if (roundTo > 0) {
|
||||
topLeft.x = roundDown(topLeft.x, roundTo)
|
||||
topLeft.y = roundDown(topLeft.y, roundTo)
|
||||
bottomLeft.x = roundDown(bottomLeft.x, roundTo)
|
||||
bottomLeft.y = roundUp(bottomLeft.y, roundTo)
|
||||
topRight.x = roundUp(topRight.x, roundTo)
|
||||
topRight.y = roundDown(topRight.y, roundTo)
|
||||
bottomRight.x = roundUp(bottomRight.x, roundTo)
|
||||
bottomRight.y = roundUp(bottomRight.y, roundTo)
|
||||
}
|
||||
|
||||
if (topLeft.y < yMin) yMin = topLeft.y
|
||||
if (topLeft.x < xMin) xMin = topLeft.x
|
||||
if (bottomRight.y > yMax) yMax = bottomRight.y
|
||||
if (bottomRight.x > xMax) xMax = bottomRight.x
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
width,
|
||||
height,
|
||||
topLeft,
|
||||
bottomLeft,
|
||||
topRight,
|
||||
bottomRight,
|
||||
}
|
||||
})
|
||||
|
||||
xMax = xMax + graphPadding
|
||||
yMax = yMax + graphPadding
|
||||
xMin = xMin - graphPadding
|
||||
yMin = yMin - graphPadding
|
||||
|
||||
const topLeft: XYPosition = {
|
||||
x: xMin,
|
||||
y: yMin,
|
||||
}
|
||||
|
||||
const bottomLeft: XYPosition = {
|
||||
x: xMin,
|
||||
y: yMax,
|
||||
}
|
||||
|
||||
const topRight: XYPosition = {
|
||||
x: xMax,
|
||||
y: yMin,
|
||||
}
|
||||
|
||||
const bottomRight: XYPosition = {
|
||||
x: xMax,
|
||||
y: yMax,
|
||||
}
|
||||
|
||||
const width = Math.abs(topLeft.x - topRight.x)
|
||||
const height = Math.abs(topLeft.y - bottomLeft.y)
|
||||
|
||||
const graph: GraphBoundingBox = {
|
||||
topLeft,
|
||||
bottomLeft,
|
||||
topRight,
|
||||
bottomRight,
|
||||
width,
|
||||
height,
|
||||
xMax,
|
||||
yMax,
|
||||
xMin,
|
||||
yMin,
|
||||
}
|
||||
|
||||
return { nodes, graph }
|
||||
}
|
||||
31
packages/pathfinding-edge/src/edge/guaranteeWalkablePath.ts
Normal file
31
packages/pathfinding-edge/src/edge/guaranteeWalkablePath.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { Grid } from 'pathfinding'
|
||||
import { Position, XYPosition } from '@braks/vue-flow'
|
||||
|
||||
type Direction = 'top' | 'bottom' | 'left' | 'right'
|
||||
|
||||
export const getNextPointFromPosition = (point: XYPosition, position: Direction): XYPosition => {
|
||||
switch (position) {
|
||||
case 'top':
|
||||
return { x: point.x, y: point.y - 1 }
|
||||
case 'bottom':
|
||||
return { x: point.x, y: point.y + 1 }
|
||||
case 'left':
|
||||
return { x: point.x - 1, y: point.y }
|
||||
case 'right':
|
||||
return { x: point.x + 1, y: point.y }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarantee that the path is walkable, even if the point is inside a non
|
||||
* walkable area, by adding a walkable path in the direction of the point's
|
||||
* Position.
|
||||
*/
|
||||
export const guaranteeWalkablePath = (grid: Grid, point: XYPosition, position: Position) => {
|
||||
let node = grid.getNodeAt(point.x, point.y)
|
||||
while (!node.walkable) {
|
||||
grid.setWalkableAt(node.x, node.y, true)
|
||||
const next = getNextPointFromPosition(node, position)
|
||||
node = grid.getNodeAt(next.x, next.y)
|
||||
}
|
||||
}
|
||||
95
packages/pathfinding-edge/src/edge/pointConversion.ts
Normal file
95
packages/pathfinding-edge/src/edge/pointConversion.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { XYPosition } from '@braks/vue-flow'
|
||||
|
||||
const gridRatio = 10
|
||||
|
||||
/**
|
||||
* Each bounding box is a collection of X/Y points in a graph, and we
|
||||
* need to convert them to "occupied" cells in a 2D grid representation.
|
||||
*
|
||||
* The top most position of the grid (grid[0][0]) needs to be equivalent
|
||||
* to the top most point in the graph (the graph.topLeft point).
|
||||
*
|
||||
* Since the top most point can have X/Y values different than zero,
|
||||
* and each cell in a grid represents a 10x10 pixel area in the grid,
|
||||
* there's need to be a conversion between a point in a graph to a point
|
||||
* in the grid.
|
||||
*
|
||||
* We do this conversion by dividing a graph point X/Y values by 10, and
|
||||
* "shifting" their values up or down, depending on the values of the top most
|
||||
* point in the graph. The top most point in the graph will have the smallest
|
||||
* values for X and Y.
|
||||
*
|
||||
* We avoid setting nodes in the border of the grid (x=0 or y=0), so there's
|
||||
* always a "walkable" area around the grid.
|
||||
*/
|
||||
export const graphToGridPoint = (graphPoint: XYPosition, smallestX: number, smallestY: number): XYPosition => {
|
||||
let x = graphPoint.x / gridRatio
|
||||
let y = graphPoint.y / gridRatio
|
||||
|
||||
let referenceX = smallestX / gridRatio
|
||||
let referenceY = smallestY / gridRatio
|
||||
|
||||
if (referenceX < 1) {
|
||||
while (referenceX !== 1) {
|
||||
referenceX++
|
||||
x++
|
||||
}
|
||||
} else if (referenceX > 1) {
|
||||
while (referenceX !== 1) {
|
||||
referenceX--
|
||||
x--
|
||||
}
|
||||
}
|
||||
|
||||
if (referenceY < 1) {
|
||||
while (referenceY !== 1) {
|
||||
referenceY++
|
||||
y++
|
||||
}
|
||||
} else if (referenceY > 1) {
|
||||
while (referenceY !== 1) {
|
||||
referenceY--
|
||||
y--
|
||||
}
|
||||
}
|
||||
|
||||
return { x, y }
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a grid point back to a graph point, using the reverse logic of
|
||||
* graphToGridPoint.
|
||||
*/
|
||||
export const gridToGraphPoint = (gridPoint: XYPosition, smallestX: number, smallestY: number): XYPosition => {
|
||||
let x = gridPoint.x * gridRatio
|
||||
let y = gridPoint.y * gridRatio
|
||||
|
||||
let referenceX = smallestX
|
||||
let referenceY = smallestY
|
||||
|
||||
if (referenceX < gridRatio) {
|
||||
while (referenceX !== gridRatio) {
|
||||
referenceX = referenceX + gridRatio
|
||||
x = x - gridRatio
|
||||
}
|
||||
} else if (referenceX > gridRatio) {
|
||||
while (referenceX !== gridRatio) {
|
||||
referenceX = referenceX - gridRatio
|
||||
x = x + gridRatio
|
||||
}
|
||||
}
|
||||
|
||||
if (referenceY < gridRatio) {
|
||||
while (referenceY !== gridRatio) {
|
||||
referenceY = referenceY + gridRatio
|
||||
y = y - gridRatio
|
||||
}
|
||||
} else if (referenceY > gridRatio) {
|
||||
while (referenceY !== gridRatio) {
|
||||
referenceY = referenceY - gridRatio
|
||||
y = y + gridRatio
|
||||
}
|
||||
}
|
||||
|
||||
return { x, y }
|
||||
}
|
||||
5
packages/pathfinding-edge/src/edge/utils.ts
Normal file
5
packages/pathfinding-edge/src/edge/utils.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const round = (x: number, multiple = 10) => Math.round(x / multiple) * multiple
|
||||
|
||||
export const roundDown = (x: number, multiple = 10) => Math.floor(x / multiple) * multiple
|
||||
|
||||
export const roundUp = (x: number, multiple = 10) => Math.ceil(x / multiple) * multiple
|
||||
2
packages/pathfinding-edge/src/index.ts
Normal file
2
packages/pathfinding-edge/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as PathFindingEdge } from './edge/PathFindingEdge.vue'
|
||||
export { default as PerfectArrow } from './arrow/PerfectArrow.vue'
|
||||
6
packages/pathfinding-edge/src/shims-vue.d.ts
vendored
Normal file
6
packages/pathfinding-edge/src/shims-vue.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
2
packages/pathfinding-edge/src/vite-env.d.ts
vendored
Normal file
2
packages/pathfinding-edge/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-svg-loader" />
|
||||
42
packages/pathfinding-edge/tsconfig.json
Normal file
42
packages/pathfinding-edge/tsconfig.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./tmp",
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"target": "es2020",
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ESNext"
|
||||
],
|
||||
"declaration": true,
|
||||
"declarationDir": "./dist",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"incremental": false,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": false,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"types": [
|
||||
"vite/client"
|
||||
],
|
||||
"paths": {
|
||||
"~/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
// Transform paths in output .d.ts files (Include this line if you output declarations files)
|
||||
{ "transform": "typescript-transform-paths", "afterDeclarations": true }
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"build",
|
||||
"dist",
|
||||
"examples"
|
||||
]
|
||||
}
|
||||
47
packages/pathfinding-edge/vite.config.ts
Normal file
47
packages/pathfinding-edge/vite.config.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { resolve } from 'path'
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': resolve('src'),
|
||||
},
|
||||
dedupe: ['vue'],
|
||||
extensions: ['.ts', '.vue'],
|
||||
},
|
||||
build: {
|
||||
minify: 'esbuild',
|
||||
emptyOutDir: false,
|
||||
lib: {
|
||||
formats: ['es', 'cjs', 'iife'],
|
||||
entry: resolve(__dirname, 'src/index.ts'),
|
||||
name: 'vueFlowPathfindingEdge',
|
||||
},
|
||||
rollupOptions: {
|
||||
// make sure to externalize deps that shouldn't be bundled
|
||||
// into your library
|
||||
external: ['vue'],
|
||||
output: {
|
||||
dir: './dist',
|
||||
// Provide global variables to use in the UMD build
|
||||
// for externalized deps
|
||||
globals: {
|
||||
vue: 'Vue',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
imports: ['vue', '@vueuse/core'],
|
||||
dts: 'src/auto-imports.d.ts',
|
||||
}),
|
||||
],
|
||||
optimizeDeps: {
|
||||
include: ['vue', '@vueuse/core'],
|
||||
},
|
||||
})
|
||||
5497
packages/pathfinding-edge/yarn.lock
Normal file
5497
packages/pathfinding-edge/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user