mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-07-18 18:45:23 +00:00
draw line
This commit is contained in:
parent
7dfc87324d
commit
57e701ee4e
@ -70,7 +70,7 @@ export const networkProfilingStore = defineStore({
|
||||
},
|
||||
setTopology(data: { nodes: ProcessNode[]; calls: Call[] }) {
|
||||
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]) {
|
||||
obj[next.id] = true;
|
||||
next.value = next.value || 1;
|
||||
@ -87,7 +87,16 @@ export const networkProfilingStore = defineStore({
|
||||
}
|
||||
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.nodes = data.nodes;
|
||||
},
|
||||
|
@ -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),
|
||||
];
|
||||
}
|
@ -37,16 +37,16 @@ export default (d3: any, graph: any, funcs: any, tip: any) => {
|
||||
.append("image")
|
||||
.attr("width", 35)
|
||||
.attr("height", 35)
|
||||
.attr("x", (d: any) => d.x)
|
||||
.attr("y", (d: any) => d.y)
|
||||
.attr("x", (d: any) => d.x - 15)
|
||||
.attr("y", (d: any) => d.y - 15)
|
||||
.attr("style", "cursor: move;")
|
||||
.attr("xlink:href", icons.CUBE);
|
||||
nodeEnter
|
||||
.append("text")
|
||||
.attr("fill", "#000")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("x", (d: any) => d.x + 15)
|
||||
.attr("y", (d: any) => d.y + 42)
|
||||
.attr("x", (d: any) => d.x + 5)
|
||||
.attr("y", (d: any) => d.y + 28)
|
||||
.text((d: { name: string }) =>
|
||||
d.name.length > 10 ? `${d.name.substring(0, 10)}...` : d.name
|
||||
);
|
@ -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
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div ref="chart" class="micro-topo-chart"></div>
|
||||
<div ref="chart" class="process-topo"></div>
|
||||
<div
|
||||
class="switch-icon ml-5"
|
||||
class="switch-icon-edit ml-5"
|
||||
title="Settings"
|
||||
@click="setConfig"
|
||||
v-if="dashboardStore.editMode"
|
||||
>
|
||||
<Icon size="middle" iconName="settings" />
|
||||
</div>
|
||||
<div class="setting" v-if="showSettings && dashboardStore.editMode">
|
||||
<div class="process-setting" v-if="showSettings && dashboardStore.editMode">
|
||||
<Settings @update="updateSettings" @updateNodes="freshNodes" />
|
||||
</div>
|
||||
</template>
|
||||
@ -37,12 +37,8 @@ import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import d3tip from "d3-tip";
|
||||
import {
|
||||
linkElement,
|
||||
anchorElement,
|
||||
arrowMarker,
|
||||
} from "../../components/D3Graph/linkElement";
|
||||
import nodeElement from "../../components/D3Graph/nodeProcess";
|
||||
import { linkElement, anchorElement, arrowMarker } from "./Graph/linkProcess";
|
||||
import nodeElement from "./Graph/nodeProcess";
|
||||
import { Call } from "@/types/topology";
|
||||
// import zoom from "../../components/D3Graph/zoom";
|
||||
import { ProcessNode } from "@/types/ebpf";
|
||||
@ -109,7 +105,7 @@ function drawGraph() {
|
||||
.attr("transform", `translate(300, 300)`);
|
||||
graph.value.call(tip.value);
|
||||
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");
|
||||
arrow.value = graph.value.append("g").selectAll(".topo-line-arrow");
|
||||
// svg.value.call(zoom(d3, graph.value));
|
||||
@ -157,7 +153,7 @@ function update() {
|
||||
if (!node.value || !link.value) {
|
||||
return;
|
||||
}
|
||||
const obj: any = (chart.value && chart.value.getBoundingClientRect()) || {};
|
||||
// const obj: any = (chart.value && chart.value.getBoundingClientRect()) || {};
|
||||
const p = {
|
||||
hexagonParam: [27, 0.04, 5, 0.04, 0],
|
||||
count: 1,
|
||||
@ -180,7 +176,7 @@ function update() {
|
||||
const centers = hexGrid(p.count, 100, origin); // cube centers
|
||||
const cubeCenters = [];
|
||||
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
|
||||
// for (let v = 0; v < polygon.length; v++) {
|
||||
// vertices.push([
|
||||
@ -196,9 +192,27 @@ function update() {
|
||||
// .attr("stroke", "#ccc")
|
||||
// .attr("stroke-width", 1)
|
||||
// .style("fill", "none");
|
||||
const c = hexGrid(p.count, 25, centers[i]);
|
||||
const c = hexGrid(p.count, 30, centers[i]);
|
||||
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);
|
||||
const pos = hexGrid(p.count, 30, [p.radius * 2 + 20]); // cube centers
|
||||
const nodeArr = networkProfilingStore.nodes.filter(
|
||||
@ -211,7 +225,6 @@ function update() {
|
||||
nodeArr[v].y = y;
|
||||
}
|
||||
node.value = node.value.data(nodeArr, (d: ProcessNode) => d.id);
|
||||
|
||||
node.value.exit().remove();
|
||||
node.value = nodeElement(
|
||||
d3,
|
||||
@ -223,6 +236,50 @@ function update() {
|
||||
},
|
||||
tip.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[][]) {
|
||||
@ -261,27 +318,6 @@ function handleLinkClick(event: any, d: Call) {
|
||||
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) {
|
||||
config.value = config;
|
||||
}
|
||||
@ -324,8 +360,8 @@ watch(
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.micro-topo-chart {
|
||||
<style lang="scss">
|
||||
.process-topo {
|
||||
width: calc(100% - 10px);
|
||||
margin: 0 5px 5px 0;
|
||||
height: 100%;
|
||||
@ -340,7 +376,7 @@ watch(
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.switch-icon {
|
||||
.switch-icon-edit {
|
||||
cursor: pointer;
|
||||
transition: all 0.5ms linear;
|
||||
background-color: #252a2f99;
|
||||
@ -353,7 +389,7 @@ watch(
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.setting {
|
||||
.process-setting {
|
||||
position: absolute;
|
||||
top: 65px;
|
||||
right: 10px;
|
||||
@ -366,4 +402,22 @@ watch(
|
||||
color: #ccc;
|
||||
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>
|
||||
|
@ -94,17 +94,10 @@ import {
|
||||
import { useI18n } from "vue-i18n";
|
||||
import * as d3 from "d3";
|
||||
import d3tip from "d3-tip";
|
||||
import zoom from "../../components/D3Graph/zoom";
|
||||
import {
|
||||
simulationInit,
|
||||
simulationSkip,
|
||||
} from "../../components/D3Graph/simulation";
|
||||
import nodeElement from "../../components/D3Graph/nodeElement";
|
||||
import {
|
||||
linkElement,
|
||||
anchorElement,
|
||||
arrowMarker,
|
||||
} from "../../components/D3Graph/linkElement";
|
||||
import zoom from "./utils/zoom";
|
||||
import { simulationInit, simulationSkip } from "./utils/simulation";
|
||||
import nodeElement from "./utils/nodeElement";
|
||||
import { linkElement, anchorElement, arrowMarker } from "./utils/linkElement";
|
||||
import { Node, Call } from "@/types/topology";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useTopologyStore } from "@/store/modules/topology";
|
||||
|
@ -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})`
|
||||
);
|
||||
});
|
Loading…
Reference in New Issue
Block a user