D3JS 点击按钮生成圆点,并实现属性的动态修改

目录

步骤一:点击工具栏(Tools组件)内按钮触发画布(D3Canvas组件)绘制图元的方法:

工具栏(Tools)组件:

画布(D3Canvas)组件:

画布(D3Canvas)组件内所引用的画布实例(不全):

步骤二:双击图元,属性栏可加载出该图元所对应的属性:

画布实例补全

属性(attr)的pinia管理      

属性栏(Attributes)组件:

属性栏(Attributes)组件内所引用的圆属性的表单:


D3.js绘图工具系列文章总提纲:【传送门】

效果图:

步骤一:点击工具栏(Tools组件)内按钮触发画布(D3Canvas组件)绘制图元的方法:

工具栏(Tools)组件:

@/views/D3/tools/index.vue

<template>
    <div class="left-menu">
        <div>
            <el-button type="primary" @click="addCircle">圆点</el-button>
        </div>
    </div>
</template>

<script setup>
const emit = defineEmits(['addCircle']);

function addCircle() {
    emit('addCircle');
}
</script>

画布(D3Canvas)组件:

@/views/D3/canvas/D3Canvas.vue 

<template>
    <div id="d3CanvasBox" class="main"></div>
</template>

<script setup>
import d3Canvas from '@/D3/main.js';

const svg = new d3Canvas('d3CanvasBox');
const drawCircle = () => svg.drawCircle();

onMounted(() => {
    svg.init();
});

defineExpose({
    drawCircle
})
</script>

<style lang="scss" scoped>
.main {
    width: 100%;
    height: 100%;
}
</style>

画布(D3Canvas)组件内所引用的画布实例(不全)

@/D3/main.js

import deepCompare from '@/D3/utils/deepCompare';

export default class d3Canvas {
    constructor(parentNodeId) {
        this.parentNodeId = parentNodeId;
        this.svg = reactive({});
        this.currentGraph = ref(null);
        this.currentWatch = null;
    }
    init() {
        // 获取父节点
        var parentNode = document.getElementById(this.parentNodeId);
        if (!parentNode) {
            console.error('父节点不存在');
            return;
        }

        // 初始化画布元素
        var d3Canvas = document.createElement('div');
        // 默认id为d3Canvas
        d3Canvas.id = 'd3Canvas';
        parentNode.appendChild(d3Canvas);

        this.svg = this.initD3Canvas();
    }

    initD3Canvas() {
        // 获取容积
        const parentNode = document.getElementById(this.parentNodeId);
        const currentWidth = parentNode.clientWidth;
        const currentHeight = parentNode.clientHeight;

        const d3Canvas = d3
            .select('#d3Canvas')
            .append("svg")
            .attr("id", "svg")
            .attr("width", currentWidth)
            .attr("height", currentHeight)
            .attr("viewBox", "0 0 " + currentWidth + " " + currentHeight);

        return d3Canvas;
    }


    // 模拟id
    generateUniqueId() {
        // 生成随机数作为ID的一部分
        // 生成6位长度的随机字符串
        const randomPart = Math.random().toString(36).substr(2, 6); 
        // 获取当前时间戳作为ID的另一部分
        // 取最后2位作为时间戳的字符串表示
        const timestampPart = new Date().getTime().toString(36).substr(-2); 
        // 将随机数和时间戳组合成ID
        const uniqueId = randomPart + timestampPart;

        return uniqueId;
    }

    // 绘制圆
    drawCircle() {
        const _this = this;
        let id = this.generateUniqueId();
        let circle = this.svg.append("circle")
            .attr("cx", 5)
            .attr("cy", 5)
            .attr("r", 2)
            .style("fill", "blue")
            .attr("id", `circle-${id}`);
    }
}

步骤二:双击图元,属性栏可加载出该图元所对应的属性:

        需要对图元添加的点击事件,即再上述@/D3/main.js所构造的d3Canvas中,添加点击事件方法并绑定于所绘制的图元上。

画布实例补全

@/D3/main.js

import useAttrStore from '@/store/modules/attr';
import deepCompare from '@/D3/utils/deepCompare';

export default class d3Canvas {
    constructor(parentNodeId) {
        ……
    }

    ……省略

    // 绘制圆形
    drawCircle() {
        ……
        let circle = 
            ……
            .on("dblclick", function (event) {
                let arg = d3.select(this);
                _this.clickedEvent(event, arg, 'circle')
            });
    }

    // 点击元素时执行的函数
    clickedEvent(event, arg, type) {
        const attrStore = useAttrStore();

        if (this.currentGraph.value !== null) {
            this.currentGraph.value = null;
            attrStore.cleanAttr();

            // 停止之前的 watch 监听器
            if (this.currentWatch !== null) {
                this.currentWatch();
                this.currentWatch = null;
            }
        }

        // 显示输入框
        document.getElementById(`${type}Form`).style.display = "block";

        // 获取被点击的圆形元素
        const clickedArg = arg;
        this.currentGraph.value = clickedArg;

        var args = {
            id: clickedArg.attr("id"),
            cx: clickedArg.attr("cx"),
            cy: clickedArg.attr("cy"),
            r: clickedArg.attr("r"),
            fill: clickedArg.style("fill"),
        }

        var keysArray = Object.keys(args);
        keysArray.forEach(key => {
            attrStore.setAttr(key, args[key])
        })

        // 调用attrStore中的mutations内的方法
        attrStore.setAttrType(type);

        // 旧属性数据
        let oldAttrList = [];

        this.currentWatch = watch(
            () => attrStore.attrList,
            (newValue) => {
                if (attrStore.attrType != '') {
                    for (let i = 0; i < newValue.length; i++) {
                        if (!deepCompare(newValue[i], oldAttrList[i])) {
                            if (newValue[i].key == 'fill') {
                                clickedArg.style("fill", newValue[i].value);
                            } else {
                                clickedArg.attr(newValue[i].key, newValue[i].value);
                            }
                        }
                    }
                    // 更新旧值
                    oldAttrList = newValue.map(item => ({ ...item }));
                }
            },
            { deep: true } // 深层监听
        );
    }
}

属性(attr)的pinia管理      

        此处涉及到属性的存储及传递,我的处理方式是将参数属性均暂存于 pinia 中管理。

        pinia官网【传送门】

        @/store/modules/attr 

const useAttrStore = defineStore(
    'attr',
    {
        state: () => ({
            attrType: '',
            attrList: new Array(),
        }),

        // actions
        actions: {
            getAttr(_key) {
                if (_key == null && _key == "") {
                    return null;
                }
                try {
                    for (let i = 0; i < this.attrList.length; i++) {
                        if (this.attrList[i].key == _key) {
                            return this.attrList[i].value;
                        }
                    }
                } catch (e) {
                    return null;
                }
            },
            setAttr(_key, value) {
                if (_key !== null && _key !== "") {
                    this.attrList.push({
                        key: _key,
                        value: value
                    });
                }
            },
            changeAttr(_key, _value) {
                let _index = this.attrList.findIndex(item => item.key === _key);
                this.attrList[_index].key = _key;
                this.attrList[_index].value = _value;
            },
            setAttrType(_type) {
                this.attrType = _type;
            },
            // 删除属性
            removeAttr(_key) {
                var bln = false;
                try {
                    for (let i = 0; i < this.attrList.length; i++) {
                        if (this.attrList[i].key == _key) {
                            this.attrList.splice(i, 1);
                            return true;
                        }
                    }
                } catch (e) {
                    bln = false;
                }
                return bln;
            },
            // 清空属性列表
            cleanAttr() {
                this.attrType = '';
                this.attrList = new Array();
            },
            // 初始化属性列表
            initAttr() {
            }
        },

        // getters
        getters: {
            attrObject(state) {
                // 将数组转为对象
                let obj = {};
                for (let i = 0; i < state.attrList.length; i++) {
                    obj[state.attrList[i].key] = state.attrList[i].value;
                }
                return obj;
            }
        }
    }
);

export default useAttrStore;

        在属性栏中监听所对应的参数

属性栏(Attributes)组件:

@/views/D3/attributes/index.vue 

<template>
    <div class="right-side">
        <circleForm id="circleForm" :style="{ display: circleVisiable ? 'block' : 'none' }" />
    </div>
</template>

<script setup>
import circleForm from './circle.vue';

const props = defineProps({
    visiable: { type: Object, default: () => ({ circleVisiable: true }) }
})

const { circleVisiable } = toRefs(props.visiable);

</script>

属性栏(Attributes)组件内所引用的圆属性的表单:

@/views/D3/attributes/circle.vue 

<template>
    <div class="container">
        <header>圆</header>
        <section>
            <el-form :model="form" label-width="auto" style="max-width: 600px">
                <el-form-item label="cx">
                    <el-input v-model="form.cx" id="cx" @change="change('cx')" />
                </el-form-item>
                <el-form-item label="cy">
                    <el-input v-model="form.cy" id="cy" @change="change('cy')" />
                </el-form-item>
                <el-form-item label="r">
                    <el-input v-model="form.r" id="r" @change="change('r')" />
                </el-form-item>
                <el-form-item label="fill">
                    <el-input v-model="form.fill" id="fill" @change="change('fill')" />
                </el-form-item>
            </el-form>
        </section>
    </div>
</template>

<script setup>
import useAttrStore from '@/store/modules/attr'
import { reactive } from 'vue';

const attrStore = useAttrStore();
let attrObject = reactive({});
let form = reactive(
    {
        x: 0,
        y: 0,
        r: 0,
        fill: ''
    }
);

// 订阅函数
attrStore.$subscribe((mutation, state) => {
    if (state.attrType === 'circle') {
        attrObject = attrStore.attrObject;
        for (let key in attrObject) {
            form[key] = attrObject[key];
        }
    }
})

function change(_key) {
    attrStore.changeAttr(_key, form[_key]);
}
</script>

        通过订阅的形式更新 input  内所对应的各项参数,且监听 input  @change事件更新 pinia 中的 attrList 参数,并通过在绘制图元时所绑定的双击事件中的watch监听事件判定,若参数有变化,则修改图元所对应的属性。

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值