美文网首页Web
JS实现DBSCAN聚类算法

JS实现DBSCAN聚类算法

作者: 爱吃猫的老虎 | 来源:发表于2020-11-03 15:09 被阅读0次

    效果图:

    image.png
    <template>
        <div class="db-root">
            <div class="db-body">
                <div
                    v-for="point of dataSource"
                    :key="point.id"
                    :style="{ top: `${point.y}px`, left: `${point.x}px`, background: point.color, ...getStyle }"
                    :class="['circle', { 'noisy': point.noisy }]"
                />
            </div>
            <label>
                扫描半径:
                <input type="number" v-model="params.eps" />
            </label>
            <label>
                最小包含点数:
                <input type="number" v-model="params.minPts" />
            </label>
            <button @click="handleStartCalculate" :disabled="disabled">开始计算</button>
            <button @click="resetColor">重置颜色</button>
            <button @click="resetDataSource">重置点分布</button>
        </div>
    </template>
    
    <script>
    import { random, map, isEmpty, filter, forEach, isEqual, some, find } from 'lodash'
    import { v4 as uuid } from 'uuid'
    
    const COLORS = [
        '#297aff',
        '#ff9800',
        '#30af28',
        '#ffcc0d',
        '#00cccc',
        '#66ccff',
        '#01a5ed',
        '#009966',
        '#A0D911',
    ]
    
    const DEFAULT_COLOR = '#000000'
    
    export default {
        name: 'DBSCAN',
        data() {
            return {
                dataSource: [],
                params: {
                    minPts: 2, // 最小包含点数
                    eps: 30, // 半径
                },
                disabled: false,
            }
        },
        created() {
            this.resetDataSource()
        },
        computed: {
            getStyle() {
                const { params: { eps } } = this
                const unit = `${eps}px`
                return {
                    borderTopLeftRadius: unit,
                    borderTopRightRadius: unit,
                    borderBottomLeftRadius: unit,
                    borderBottomRightRadius: unit,
                    width: `${eps * 2}px`,
                    height: `${eps * 2}px`,
                    position: 'absolute',
                    opacity: 0.3
                }
            },
        },
        methods: {
            // 重置点的颜色和状态
            resetColor() {
                map(this.dataSource, point => {
                    point.color = DEFAULT_COLOR
                    point.visited = false
                    point.noisy = false
                })
                this.disabled = false
            },
            // 重新生成随机样本
            resetDataSource() {
                const eps = parseInt(this.params.eps)
                this.dataSource = map(new Array(400), () => (
                    {
                        id: uuid(),
                        x: random(0, 1600 - eps * 2, false),
                        y: random(0, 600 - eps * 2, false),
                        color: DEFAULT_COLOR,
                        visited: false,
                        noisy: false,
                    }
                ))
                this.disabled = false
            },
            /**
             * 判断第二个点是否在该点半径范围内
             * @param center
             * @param x
             * @param y
             */
            isInEps(center, { x, y }) {
                // 利用勾股定理判断点是否在点的半径范围中
                const eps = parseInt(this.params.eps)
                const xOffset = Math.abs(x - center.x)
                const yOffset = Math.abs(y - center.y)
                const distance = Math.sqrt(xOffset ** 2 + yOffset ** 2)
                return distance <= eps
            },
            async handleStartCalculate() {
                const { dataSource, isInEps } = this
                const minPts = parseInt(this.params.minPts)
                let colorIndex = 0
                // 重复查找没有访问过的点
                while (some(dataSource, point => !point.visited)) {
                    const visitedPoints = new Map()
                    // 存放点的栈(广度优先遍历)
                    const pointsStack = []
                    const core = find(dataSource, point => !point.visited)
                    if (core) {
                        const pointsInEps = filter(dataSource, p => !isEqual(core.id, p.id) && isInEps(core, p))
                        // 如果是核心点,将其放入栈中
                        if (pointsInEps.length >= minPts) {
                            pointsStack.push(core)
                            while (!isEmpty(pointsStack)) {
                                // 弹出最后一个元素,将其被标记为已访问过
                                const point = pointsStack.pop()
                                point.visited = true
                                visitedPoints.set(point.id, true)
                                const stackMap = new Map()
                                forEach(pointsStack, p => stackMap.set(p.id, true))
                                // 寻找半径范围内的其他点,将其染色
                                const pointsInEps = filter(dataSource, p => !isEqual(point.id, p.id) && isInEps(point, p) && !visitedPoints.has(p.id) && !stackMap.has(p.id))
                                // 如果是核心点,说明该点颜色还是默认的
                                if (isEqual(point.color, DEFAULT_COLOR)) {
                                    point.color = COLORS[colorIndex % COLORS.length]
                                    forEach(pointsInEps, p => p.color = point.color)
                                    colorIndex++
                                } else {
                                    // 如果不是核心点,直接将该点所有密度相连的点染色
                                    forEach(pointsInEps, p => p.color = point.color)
                                }
                                // 染色过后放进栈中
                                pointsStack.push(...pointsInEps)
                            }
                        } else {
                            // 该点可能是边界点或者噪点
                            core.visited = true
                        }
                    }
                }
                this.disabled = true
            },
        },
    }
    </script>
    
    <style scoped>
        .db-root {
            width: 100%;
            height: 100%;
        }
        .db-body {
            margin: 0 auto;
            width: 1600px;
            height: 600px;
            position: relative;
            border: 1px solid grey;
        }
    </style>
    
    

    相关文章

      网友评论

        本文标题:JS实现DBSCAN聚类算法

        本文链接:https://www.haomeiwen.com/subject/ahabvktx.html