draw line

This commit is contained in:
Fine 2022-08-17 16:33:12 +08:00
parent 7dfc87324d
commit 57e701ee4e
10 changed files with 233 additions and 56 deletions

View File

@ -70,7 +70,7 @@ export const networkProfilingStore = defineStore({
}, },
setTopology(data: { nodes: ProcessNode[]; calls: Call[] }) { setTopology(data: { nodes: ProcessNode[]; calls: Call[] }) {
const obj = {} as any; const obj = {} as any;
const calls = (data.calls || []).reduce((prev: Call[], next: Call) => { let calls = (data.calls || []).reduce((prev: Call[], next: Call) => {
if (!obj[next.id]) { if (!obj[next.id]) {
obj[next.id] = true; obj[next.id] = true;
next.value = next.value || 1; next.value = next.value || 1;
@ -87,7 +87,16 @@ export const networkProfilingStore = defineStore({
} }
return prev; return prev;
}, []); }, []);
calls = calls.map((d: any) => {
d.sourceId = d.source;
d.targetId = d.target;
d.source = d.sourceObj;
d.target = d.targetObj;
delete d.sourceObj;
delete d.targetObj;
return d;
});
console.log(calls);
this.calls = calls; this.calls = calls;
this.nodes = data.nodes; this.nodes = data.nodes;
}, },

View File

@ -0,0 +1,93 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const linkElement = (graph: any) => {
const linkEnter = graph
.append("path")
.attr("class", "topo-call")
.attr("marker-end", "url(#arrow)")
.attr("stroke", "#afc4dd")
.attr("d", (d: any) => {
const controlPos = computeControlPoint(
[d.source.x, d.source.y - 5],
[d.target.x, d.target.y - 5],
1
);
return (
"M" +
d.source.x +
" " +
(d.source.y - 5) +
" " +
"Q" +
controlPos[0] +
" " +
controlPos[1] +
" " +
d.target.x +
" " +
(d.target.y - 5)
);
});
return linkEnter;
};
export const anchorElement = (graph: any, funcs: any, tip: any) => {
const linkEnter = graph
.append("circle")
.attr("class", "topo-line-anchor")
.attr("r", 5)
.attr("fill", "#217EF25f")
.on("mouseover", function (event: unknown, d: unknown) {
tip.html(funcs.tipHtml).show(d, this);
})
.on("mouseout", function () {
tip.hide(this);
})
.on("click", (event: unknown, d: unknown) => {
funcs.handleLinkClick(event, d);
});
return linkEnter;
};
export const arrowMarker = (graph: any) => {
const defs = graph.append("defs");
const arrow = defs
.append("marker")
.attr("id", "arrow")
.attr("class", "topo-line-arrow")
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", "6")
.attr("markerHeight", "6")
.attr("viewBox", "0 0 12 12")
.attr("refX", "10")
.attr("refY", "6")
.attr("orient", "auto");
const arrowPath = "M2,2 L10,6 L2,10 L6,6 L2,2";
arrow.append("path").attr("d", arrowPath).attr("fill", "#afc4dd");
return arrow;
};
function computeControlPoint(ps: number[], pe: number[], arc = 0.5) {
const deltaX = pe[0] - ps[0];
const deltaY = pe[1] - ps[1];
const theta = Math.atan(deltaY / deltaX);
const len = (Math.sqrt(deltaX * deltaX + deltaY * deltaY) / 2) * arc;
const newTheta = theta - Math.PI / 2;
return [
(ps[0] + pe[0]) / 2 + len * Math.cos(newTheta),
(ps[1] + pe[1]) / 2 + len * Math.sin(newTheta),
];
}

View File

@ -37,16 +37,16 @@ export default (d3: any, graph: any, funcs: any, tip: any) => {
.append("image") .append("image")
.attr("width", 35) .attr("width", 35)
.attr("height", 35) .attr("height", 35)
.attr("x", (d: any) => d.x) .attr("x", (d: any) => d.x - 15)
.attr("y", (d: any) => d.y) .attr("y", (d: any) => d.y - 15)
.attr("style", "cursor: move;") .attr("style", "cursor: move;")
.attr("xlink:href", icons.CUBE); .attr("xlink:href", icons.CUBE);
nodeEnter nodeEnter
.append("text") .append("text")
.attr("fill", "#000") .attr("fill", "#000")
.attr("text-anchor", "middle") .attr("text-anchor", "middle")
.attr("x", (d: any) => d.x + 15) .attr("x", (d: any) => d.x + 5)
.attr("y", (d: any) => d.y + 42) .attr("y", (d: any) => d.y + 28)
.text((d: { name: string }) => .text((d: { name: string }) =>
d.name.length > 10 ? `${d.name.substring(0, 10)}...` : d.name d.name.length > 10 ? `${d.name.substring(0, 10)}...` : d.name
); );

View File

@ -13,16 +13,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div ref="chart" class="micro-topo-chart"></div> <div ref="chart" class="process-topo"></div>
<div <div
class="switch-icon ml-5" class="switch-icon-edit ml-5"
title="Settings" title="Settings"
@click="setConfig" @click="setConfig"
v-if="dashboardStore.editMode" v-if="dashboardStore.editMode"
> >
<Icon size="middle" iconName="settings" /> <Icon size="middle" iconName="settings" />
</div> </div>
<div class="setting" v-if="showSettings && dashboardStore.editMode"> <div class="process-setting" v-if="showSettings && dashboardStore.editMode">
<Settings @update="updateSettings" @updateNodes="freshNodes" /> <Settings @update="updateSettings" @updateNodes="freshNodes" />
</div> </div>
</template> </template>
@ -37,12 +37,8 @@ import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import d3tip from "d3-tip"; import d3tip from "d3-tip";
import { import { linkElement, anchorElement, arrowMarker } from "./Graph/linkProcess";
linkElement, import nodeElement from "./Graph/nodeProcess";
anchorElement,
arrowMarker,
} from "../../components/D3Graph/linkElement";
import nodeElement from "../../components/D3Graph/nodeProcess";
import { Call } from "@/types/topology"; import { Call } from "@/types/topology";
// import zoom from "../../components/D3Graph/zoom"; // import zoom from "../../components/D3Graph/zoom";
import { ProcessNode } from "@/types/ebpf"; import { ProcessNode } from "@/types/ebpf";
@ -109,7 +105,7 @@ function drawGraph() {
.attr("transform", `translate(300, 300)`); .attr("transform", `translate(300, 300)`);
graph.value.call(tip.value); graph.value.call(tip.value);
node.value = graph.value.append("g").selectAll(".topo-node"); node.value = graph.value.append("g").selectAll(".topo-node");
link.value = graph.value.append("g").selectAll(".topo-line"); link.value = graph.value.append("g").selectAll(".topo-call");
anchor.value = graph.value.append("g").selectAll(".topo-line-anchor"); anchor.value = graph.value.append("g").selectAll(".topo-line-anchor");
arrow.value = graph.value.append("g").selectAll(".topo-line-arrow"); arrow.value = graph.value.append("g").selectAll(".topo-line-arrow");
// svg.value.call(zoom(d3, graph.value)); // svg.value.call(zoom(d3, graph.value));
@ -157,7 +153,7 @@ function update() {
if (!node.value || !link.value) { if (!node.value || !link.value) {
return; return;
} }
const obj: any = (chart.value && chart.value.getBoundingClientRect()) || {}; // const obj: any = (chart.value && chart.value.getBoundingClientRect()) || {};
const p = { const p = {
hexagonParam: [27, 0.04, 5, 0.04, 0], hexagonParam: [27, 0.04, 5, 0.04, 0],
count: 1, count: 1,
@ -180,7 +176,7 @@ function update() {
const centers = hexGrid(p.count, 100, origin); // cube centers const centers = hexGrid(p.count, 100, origin); // cube centers
const cubeCenters = []; const cubeCenters = [];
for (let i = 0; i < centers.length; i++) { for (let i = 0; i < centers.length; i++) {
// const polygon = createPolygon(90, 6, 0); // const polygon = createPolygon(100, 6, 0);
// const vertices: any = []; // a hexagon vertices // const vertices: any = []; // a hexagon vertices
// for (let v = 0; v < polygon.length; v++) { // for (let v = 0; v < polygon.length; v++) {
// vertices.push([ // vertices.push([
@ -196,9 +192,27 @@ function update() {
// .attr("stroke", "#ccc") // .attr("stroke", "#ccc")
// .attr("stroke-width", 1) // .attr("stroke-width", 1)
// .style("fill", "none"); // .style("fill", "none");
const c = hexGrid(p.count, 25, centers[i]); const c = hexGrid(p.count, 30, centers[i]);
cubeCenters.push(...c); cubeCenters.push(...c);
} }
// for (let i = 0; i < cubeCenters.length; i++) {
// const polygon = createPolygon(30, 6, 0);
// const vertices: any = []; // a hexagon vertices
// for (let v = 0; v < polygon.length; v++) {
// vertices.push([
// cubeCenters[i][0] + polygon[v][0],
// cubeCenters[i][1] + polygon[v][1],
// ]);
// }
// const linePath = d3.line();
// linePath.curve(d3.curveLinearClosed);
// graph.value
// .append("path")
// .attr("d", linePath(vertices))
// .attr("stroke", "#ccc")
// .attr("stroke-width", 1)
// .style("fill", "none");
// }
shuffleArray(cubeCenters); shuffleArray(cubeCenters);
const pos = hexGrid(p.count, 30, [p.radius * 2 + 20]); // cube centers const pos = hexGrid(p.count, 30, [p.radius * 2 + 20]); // cube centers
const nodeArr = networkProfilingStore.nodes.filter( const nodeArr = networkProfilingStore.nodes.filter(
@ -211,7 +225,6 @@ function update() {
nodeArr[v].y = y; nodeArr[v].y = y;
} }
node.value = node.value.data(nodeArr, (d: ProcessNode) => d.id); node.value = node.value.data(nodeArr, (d: ProcessNode) => d.id);
node.value.exit().remove(); node.value.exit().remove();
node.value = nodeElement( node.value = nodeElement(
d3, d3,
@ -223,6 +236,50 @@ function update() {
}, },
tip.value tip.value
).merge(node.value); ).merge(node.value);
// line element
const obj = {} as any;
const calls = networkProfilingStore.calls.reduce((prev: any[], next: any) => {
if (
!(
obj[next.sourceId + next.targetId] && obj[next.targetId + next.sourceId]
)
) {
obj[next.sourceId + next.targetId] = true;
obj[next.targetId + next.sourceId] = true;
prev.push(next);
}
return prev;
}, []);
link.value = link.value.data(calls, (d: Call) => d.id);
link.value.exit().remove();
link.value = linkElement(link.value.enter()).merge(link.value);
// anchorElement
// anchor.value = anchor.value.data(
// networkProfilingStore.calls,
// (d: Call) => d.id
// );
// anchor.value.exit().remove();
// anchor.value = anchorElement(
// anchor.value.enter(),
// {
// handleLinkClick: handleLinkClick,
// tipHtml: (data: Call) => {
// const html = `<div><span class="grey">${t(
// "detectPoint"
// )}:</span>${data.detectPoints.join(" | ")}</div>`;
// return html;
// },
// },
// tip.value
// ).merge(anchor.value);
// arrow marker
arrow.value = arrow.value.data(
networkProfilingStore.calls,
(d: Call) => d.id
);
arrow.value.exit().remove();
arrow.value = arrowMarker(arrow.value.enter()).merge(arrow.value);
} }
function shuffleArray(array: number[][]) { function shuffleArray(array: number[][]) {
@ -261,27 +318,6 @@ function handleLinkClick(event: any, d: Call) {
window.open(routeUrl.href, "_blank"); window.open(routeUrl.href, "_blank");
} }
function ticked() {
link.value.attr(
"d",
(d: Call | any) =>
`M${d.source.x} ${d.source.y} Q ${(d.source.x + d.target.x) / 2} ${
(d.target.y + d.source.y) / 2 - d.loopFactor * 90
} ${d.target.x} ${d.target.y}`
);
anchor.value.attr(
"transform",
(d: Call | any) =>
`translate(${(d.source.x + d.target.x) / 2}, ${
(d.target.y + d.source.y) / 2 - d.loopFactor * 45
})`
);
node.value.attr(
"transform",
(d: Node | any) => `translate(${d.x - 22},${d.y - 22})`
);
}
function updateSettings(config: any) { function updateSettings(config: any) {
config.value = config; config.value = config;
} }
@ -324,8 +360,8 @@ watch(
} }
); );
</script> </script>
<style lang="scss" scoped> <style lang="scss">
.micro-topo-chart { .process-topo {
width: calc(100% - 10px); width: calc(100% - 10px);
margin: 0 5px 5px 0; margin: 0 5px 5px 0;
height: 100%; height: 100%;
@ -340,7 +376,7 @@ watch(
cursor: move; cursor: move;
} }
.switch-icon { .switch-icon-edit {
cursor: pointer; cursor: pointer;
transition: all 0.5ms linear; transition: all 0.5ms linear;
background-color: #252a2f99; background-color: #252a2f99;
@ -353,7 +389,7 @@ watch(
right: 10px; right: 10px;
} }
.setting { .process-setting {
position: absolute; position: absolute;
top: 65px; top: 65px;
right: 10px; right: 10px;
@ -366,4 +402,22 @@ watch(
color: #ccc; color: #ccc;
transition: all 0.5ms linear; transition: all 0.5ms linear;
} }
.topo-call {
stroke-linecap: round;
stroke-width: 2px;
stroke-dasharray: 13 7;
fill: none;
animation: topo-dash 0.5s linear infinite;
}
@keyframes topo-dash {
from {
stroke-dashoffset: 20;
}
to {
stroke-dashoffset: 0;
}
}
</style> </style>

View File

@ -94,17 +94,10 @@ import {
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import * as d3 from "d3"; import * as d3 from "d3";
import d3tip from "d3-tip"; import d3tip from "d3-tip";
import zoom from "../../components/D3Graph/zoom"; import zoom from "./utils/zoom";
import { import { simulationInit, simulationSkip } from "./utils/simulation";
simulationInit, import nodeElement from "./utils/nodeElement";
simulationSkip, import { linkElement, anchorElement, arrowMarker } from "./utils/linkElement";
} from "../../components/D3Graph/simulation";
import nodeElement from "../../components/D3Graph/nodeElement";
import {
linkElement,
anchorElement,
arrowMarker,
} from "../../components/D3Graph/linkElement";
import { Node, Call } from "@/types/topology"; import { Node, Call } from "@/types/topology";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { useTopologyStore } from "@/store/modules/topology"; import { useTopologyStore } from "@/store/modules/topology";

View File

@ -0,0 +1,28 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default (d3: any, graph: any) =>
d3
.zoom()
.scaleExtent([0.3, 10])
.on("zoom", (d: any) => {
graph
.attr("transform", d3.zoomTransform(graph.node()))
.attr(
`translate(${d.transform.x},${d.transform.y})scale(${d.transform.k})`
);
});