mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-07-18 20:45:24 +00:00
feat: add graph for topology
This commit is contained in:
parent
a994a76ede
commit
1a6ab74be8
52
src/store/modules/topology.ts
Normal file
52
src/store/modules/topology.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { store } from "@/store";
|
||||||
|
import { Node, Call } from "@/types/topology";
|
||||||
|
|
||||||
|
interface TopologyState {
|
||||||
|
node: Node | null;
|
||||||
|
call: Call | null;
|
||||||
|
calls: Call[];
|
||||||
|
nodes: Node[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const topologyStore = defineStore({
|
||||||
|
id: "topology",
|
||||||
|
state: (): TopologyState => ({
|
||||||
|
calls: [],
|
||||||
|
nodes: [],
|
||||||
|
node: null,
|
||||||
|
call: null,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
setNode(node: Node) {
|
||||||
|
this.node = node;
|
||||||
|
},
|
||||||
|
setLink(link: Call) {
|
||||||
|
this.call = link;
|
||||||
|
},
|
||||||
|
setTopology(nodes: Node[], links: Call[]) {
|
||||||
|
this.nodes = nodes;
|
||||||
|
this.calls = links;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useTopologyStore(): any {
|
||||||
|
return topologyStore(store);
|
||||||
|
}
|
41
src/types/topology.d.ts
vendored
Normal file
41
src/types/topology.d.ts
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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 interface Call {
|
||||||
|
avgResponseTime: number;
|
||||||
|
cpm: number;
|
||||||
|
isAlert: boolean;
|
||||||
|
source: string | any;
|
||||||
|
target: string | any;
|
||||||
|
id: string;
|
||||||
|
detectPoints: string[];
|
||||||
|
type?: string;
|
||||||
|
sourceObj?: any;
|
||||||
|
}
|
||||||
|
export interface Node {
|
||||||
|
apdex: number;
|
||||||
|
avgResponseTime: number;
|
||||||
|
cpm: number;
|
||||||
|
id: string;
|
||||||
|
isAlarm: boolean;
|
||||||
|
name: string;
|
||||||
|
numOfServer: number;
|
||||||
|
numOfServerAlarm: number;
|
||||||
|
numOfServiceAlarm: number;
|
||||||
|
sla: number;
|
||||||
|
type: string;
|
||||||
|
isReal: boolean;
|
||||||
|
}
|
@ -31,7 +31,7 @@ limitations under the License. -->
|
|||||||
:destroy-on-close="true"
|
:destroy-on-close="true"
|
||||||
@closed="dashboardStore.setTopology(false)"
|
@closed="dashboardStore.setTopology(false)"
|
||||||
>
|
>
|
||||||
topology
|
<Graph />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -41,6 +41,7 @@ import GridLayout from "./panel/Layout.vue";
|
|||||||
// import { LayoutConfig } from "@/types/dashboard";
|
// import { LayoutConfig } from "@/types/dashboard";
|
||||||
import Tool from "./panel/Tool.vue";
|
import Tool from "./panel/Tool.vue";
|
||||||
import ConfigEdit from "./configuration/ConfigEdit.vue";
|
import ConfigEdit from "./configuration/ConfigEdit.vue";
|
||||||
|
import Graph from "./related/topology/Graph.vue";
|
||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
|
|
||||||
|
@ -13,9 +13,252 @@ 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 class="micro-topo-chart"></div>
|
<div ref="chart" class="micro-topo-chart"></div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||||
|
import type { PropType } from "vue";
|
||||||
|
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 "./utils/zoom";
|
||||||
|
import { simulationInit, simulationSkip } from "./utils/simulation";
|
||||||
|
import nodeElement from "./utils/nodeElement";
|
||||||
|
import { linkElement, anchorElement } from "./utils/linkElement";
|
||||||
|
import tool from "./utils/tool";
|
||||||
|
import topoLegend from "./utils/legend";
|
||||||
|
import { Node, Call } from "@/types/topology";
|
||||||
|
import { useTopologyStore } from "@/store/modules/topology";
|
||||||
|
|
||||||
|
/*global defineProps, Nullable */
|
||||||
|
const props = defineProps({
|
||||||
|
current: {
|
||||||
|
type: Object as PropType<{ [key: string]: number[] }>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
nodes: { type: Array as PropType<Node[]>, default: () => [] },
|
||||||
|
links: { type: Array as PropType<Call[]>, default: () => [] },
|
||||||
|
});
|
||||||
|
const { t } = useI18n();
|
||||||
|
const topologyStore = useTopologyStore();
|
||||||
|
// const height = ref<number>(600);
|
||||||
|
const simulation = ref<any>("");
|
||||||
|
const svg = ref<Nullable<any>>(null);
|
||||||
|
const chart = ref<any>(null);
|
||||||
|
const tip = ref<any>(null);
|
||||||
|
const graph = ref<any>(null);
|
||||||
|
const node = ref<any>(null);
|
||||||
|
const link = ref<any>(null);
|
||||||
|
const anchor = ref<any>(null);
|
||||||
|
const tools = ref<any>(null);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener("resize", resize);
|
||||||
|
svg.value = d3
|
||||||
|
.select(chart.value)
|
||||||
|
.append("svg")
|
||||||
|
.attr("class", "topo-svg")
|
||||||
|
.attr("height", chart.value.clientHeight);
|
||||||
|
tip.value = (d3tip as any)().attr("class", "d3-tip").offset([-8, 0]);
|
||||||
|
graph.value = svg.value.append("g").attr("class", "topo-svg_graph");
|
||||||
|
graph.value.call(tip.value);
|
||||||
|
simulation.value = simulationInit(d3, props.nodes, props.links, ticked);
|
||||||
|
node.value = graph.value.append("g").selectAll(".topo-node");
|
||||||
|
link.value = graph.value.append("g").selectAll(".topo-line");
|
||||||
|
anchor.value = graph.value.append("g").selectAll(".topo-line-anchor");
|
||||||
|
// tools.value = tool(graph.value, [
|
||||||
|
// { icon: "API", click: handleGoEndpoint },
|
||||||
|
// { icon: "INSTANCE", click: handleGoInstance },
|
||||||
|
// { icon: "TRACE", click: handleGoTrace },
|
||||||
|
// { icon: "ALARM", click: handleGoAlarm },
|
||||||
|
// { icon: "ENDPOINT", click: handleGoEndpointDependency },
|
||||||
|
// { icon: "" },
|
||||||
|
// ]);
|
||||||
|
});
|
||||||
|
function resize() {
|
||||||
|
svg.value.attr("height", chart.value.clientHeight);
|
||||||
|
}
|
||||||
|
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: 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 dragstart(d: any) {
|
||||||
|
node.value._groups[0].forEach((g: any) => {
|
||||||
|
g.__data__.fx = g.__data__.x;
|
||||||
|
g.__data__.fy = g.__data__.y;
|
||||||
|
});
|
||||||
|
if (!(d3 as any).event.active) {
|
||||||
|
simulation.value.alphaTarget(0.1).restart();
|
||||||
|
}
|
||||||
|
(d3 as any).event.sourceEvent.stopPropagation();
|
||||||
|
}
|
||||||
|
function dragged(d: any) {
|
||||||
|
d.fx = (d3 as any).event.x;
|
||||||
|
d.fy = (d3 as any).event.y;
|
||||||
|
d.x = d.fx;
|
||||||
|
d.y = d.fy;
|
||||||
|
}
|
||||||
|
function dragended() {
|
||||||
|
if (!(d3 as any).event.active) {
|
||||||
|
simulation.value.alphaTarget(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleNodeClick(d: any) {
|
||||||
|
const { x, y, vx, vy, fx, fy, index, ...rest } = d;
|
||||||
|
topologyStore.setNode(rest);
|
||||||
|
topologyStore.setLink({});
|
||||||
|
}
|
||||||
|
function handleLinkClick(event: any, d: any) {
|
||||||
|
event.stopPropagation();
|
||||||
|
topologyStore.setNode({});
|
||||||
|
topologyStore.setLink(d);
|
||||||
|
}
|
||||||
|
function update() {
|
||||||
|
// node element
|
||||||
|
node.value = node.value.data(props.nodes, (d: any) => d.id);
|
||||||
|
node.value.exit().remove();
|
||||||
|
node.value = nodeElement(
|
||||||
|
d3,
|
||||||
|
node.value.enter(),
|
||||||
|
tools.value,
|
||||||
|
{
|
||||||
|
dragstart: dragstart,
|
||||||
|
dragged: dragged,
|
||||||
|
dragended: dragended,
|
||||||
|
handleNodeClick: handleNodeClick,
|
||||||
|
},
|
||||||
|
tip.value
|
||||||
|
).merge(node.value);
|
||||||
|
// line element
|
||||||
|
link.value = link.value.data(props.links, (d: any) => d.id);
|
||||||
|
link.value.exit().remove();
|
||||||
|
link.value = linkElement(link.value.enter()).merge(link.value);
|
||||||
|
// anchorElement
|
||||||
|
anchor.value = anchor.value.data(props.links, (d: any) => d.id);
|
||||||
|
anchor.value.exit().remove();
|
||||||
|
anchor.value = anchorElement(
|
||||||
|
anchor.value.enter(),
|
||||||
|
{
|
||||||
|
handleLinkClick: handleLinkClick,
|
||||||
|
$tip: (data: any) =>
|
||||||
|
`
|
||||||
|
<div class="mb-5"><span class="grey">${t("cpm")}: </span>${
|
||||||
|
data.cpm
|
||||||
|
}</div>
|
||||||
|
<div class="mb-5"><span class="grey">${t("latency")}: </span>${
|
||||||
|
data.latency
|
||||||
|
}</div>
|
||||||
|
<div><span class="grey">${t(
|
||||||
|
"detectPoint"
|
||||||
|
)}:</span>${data.detectPoints.join(" | ")}</div>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
tip.value
|
||||||
|
).merge(anchor.value);
|
||||||
|
// force element
|
||||||
|
simulation.value.nodes(props.nodes);
|
||||||
|
simulation.value
|
||||||
|
.force("link")
|
||||||
|
.links(props.links)
|
||||||
|
.id((d: any) => d.id);
|
||||||
|
simulationSkip(d3, simulation.value, ticked);
|
||||||
|
const loopMap: any = {};
|
||||||
|
for (let i = 0; i < props.links.length; i++) {
|
||||||
|
const link: any = props.links[i];
|
||||||
|
link.loopFactor = 1;
|
||||||
|
for (let j = 0; j < props.links.length; j++) {
|
||||||
|
if (i === j || loopMap[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const otherLink = props.links[j];
|
||||||
|
if (
|
||||||
|
link.source.id === otherLink.target.id &&
|
||||||
|
link.target.id === otherLink.source.id
|
||||||
|
) {
|
||||||
|
link.loopFactor = -1;
|
||||||
|
loopMap[j] = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener("resize", resize);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.micro-topo-chart {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.topo-svg {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topo-line {
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-width: 1.3px !important;
|
||||||
|
stroke-dasharray: 13 7;
|
||||||
|
fill: none;
|
||||||
|
animation: topo-dash 1s linear infinite !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topo-line-anchor {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topo-text {
|
||||||
|
font-family: "Lato", "Source Han Sans CN", "Microsoft YaHei", sans-serif;
|
||||||
|
fill: #ddd;
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topo-tool {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topo-tool-i {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.tool-hexagon {
|
||||||
|
fill: #3f4450;
|
||||||
|
stroke: #217ef2;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.tool-hexagon {
|
||||||
|
stroke-opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes topo-dash {
|
||||||
|
from {
|
||||||
|
stroke-dashoffset: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -16,19 +16,22 @@
|
|||||||
*/
|
*/
|
||||||
const requireComponent = require.context("../../assets", false, /\.png$/);
|
const requireComponent = require.context("../../assets", false, /\.png$/);
|
||||||
|
|
||||||
const result = {};
|
const result: { [key: string]: string } = {};
|
||||||
function capitalizeFirstLetter(str) {
|
function capitalizeFirstLetter(str: string) {
|
||||||
return str.toUpperCase();
|
return str.toUpperCase();
|
||||||
}
|
}
|
||||||
function validateFileName(str) {
|
function validateFileName(str: string): string | undefined {
|
||||||
return (
|
if (/^\S+\.png$/.test(str)) {
|
||||||
/^\S+\.png$/.test(str) &&
|
return str.replace(/^\S+\/(\w+)\.png$/, (rs, $1) =>
|
||||||
str.replace(/^\S+\/(\w+)\.png$/, (rs, $1) => capitalizeFirstLetter($1))
|
capitalizeFirstLetter($1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
requireComponent.keys().forEach((filePath) => {
|
}
|
||||||
|
requireComponent.keys().forEach((filePath: string) => {
|
||||||
const componentConfig = requireComponent(filePath);
|
const componentConfig = requireComponent(filePath);
|
||||||
const fileName = validateFileName(filePath);
|
const fileName = validateFileName(filePath);
|
||||||
|
if (fileName) {
|
||||||
result[fileName] = componentConfig;
|
result[fileName] = componentConfig;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
export default result;
|
export default result;
|
@ -16,7 +16,11 @@
|
|||||||
*/
|
*/
|
||||||
import icons from "./icons";
|
import icons from "./icons";
|
||||||
|
|
||||||
export default function topoLegend(graph, clientHeight, clientWidth) {
|
export default function topoLegend(
|
||||||
|
graph: any,
|
||||||
|
clientHeight: number,
|
||||||
|
clientWidth: number
|
||||||
|
) {
|
||||||
for (const item of ["CUBE", "CUBEERROR"]) {
|
for (const item of ["CUBE", "CUBEERROR"]) {
|
||||||
graph
|
graph
|
||||||
.append("image")
|
.append("image")
|
@ -14,26 +14,28 @@
|
|||||||
* 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.
|
||||||
*/
|
*/
|
||||||
export const linkElement = (graph) => {
|
export const linkElement = (graph: any) => {
|
||||||
const linkEnter = graph
|
const linkEnter = graph
|
||||||
.append("path")
|
.append("path")
|
||||||
.attr("class", "topo-line")
|
.attr("class", "topo-line")
|
||||||
.attr("stroke", (d) => (d.cpm ? "#217EF25f" : "#6a6d7777"));
|
.attr("stroke", (d: { cpm: number }) =>
|
||||||
|
d.cpm ? "#217EF25f" : "#6a6d7777"
|
||||||
|
);
|
||||||
return linkEnter;
|
return linkEnter;
|
||||||
};
|
};
|
||||||
export const anchorElement = (graph, funcs, tip) => {
|
export const anchorElement = (graph: any, funcs: any, tip: any) => {
|
||||||
const linkEnter = graph
|
const linkEnter = graph
|
||||||
.append("circle")
|
.append("circle")
|
||||||
.attr("class", "topo-line-anchor")
|
.attr("class", "topo-line-anchor")
|
||||||
.attr("r", 5)
|
.attr("r", 5)
|
||||||
.attr("fill", (d) => (d.cpm ? "#217EF25f" : "#6a6d7777"))
|
.attr("fill", (d: { cpm: number }) => (d.cpm ? "#217EF25f" : "#6a6d7777"))
|
||||||
.on("mouseover", function (d) {
|
.on("mouseover", function (d: unknown) {
|
||||||
tip.html(funcs.$tip).show(d, this);
|
tip.html(funcs.$tip).show(d, this);
|
||||||
})
|
})
|
||||||
.on("mouseout", function () {
|
.on("mouseout", function () {
|
||||||
tip.hide(this);
|
tip.hide(this);
|
||||||
})
|
})
|
||||||
.on("click", (d) => {
|
.on("click", (d: unknown) => {
|
||||||
funcs.handleLinkClick(d);
|
funcs.handleLinkClick(d);
|
||||||
});
|
});
|
||||||
return linkEnter;
|
return linkEnter;
|
@ -17,7 +17,7 @@
|
|||||||
import icons from "./icons";
|
import icons from "./icons";
|
||||||
|
|
||||||
icons["KAFKA-CONSUMER"] = icons.KAFKA;
|
icons["KAFKA-CONSUMER"] = icons.KAFKA;
|
||||||
export default (d3, graph, tool, funcs, tip) => {
|
export default (d3: any, graph: any, tool: any, funcs: any, tip: any) => {
|
||||||
const nodeEnter = graph
|
const nodeEnter = graph
|
||||||
.append("g")
|
.append("g")
|
||||||
.call(
|
.call(
|
||||||
@ -27,22 +27,22 @@ export default (d3, graph, tool, funcs, tip) => {
|
|||||||
.on("drag", funcs.dragged)
|
.on("drag", funcs.dragged)
|
||||||
.on("end", funcs.dragended)
|
.on("end", funcs.dragended)
|
||||||
)
|
)
|
||||||
.on("mouseover", function (d) {
|
.on("mouseover", function (d: any) {
|
||||||
tip.html((data) => `<div>${data.name}</div>`).show(d, this);
|
tip.html((data: any) => `<div>${data.name}</div>`).show(d, this);
|
||||||
})
|
})
|
||||||
.on("mouseout", function () {
|
.on("mouseout", function () {
|
||||||
tip.hide(this);
|
tip.hide(this);
|
||||||
})
|
})
|
||||||
.on("click", (d) => {
|
.on("click", (event: any, d: any) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
tool.attr("style", "display: none");
|
// tool.attr("style", "display: none");
|
||||||
funcs.handleNodeClick(d);
|
funcs.handleNodeClick(d);
|
||||||
if (d.isReal) {
|
// if (d.isReal) {
|
||||||
tool
|
// tool
|
||||||
.attr("transform", `translate(${d.x},${d.y - 20})`)
|
// .attr("transform", `translate(${d.x},${d.y - 20})`)
|
||||||
.attr("style", "display: block");
|
// .attr("style", "display: block");
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
nodeEnter
|
nodeEnter
|
||||||
.append("image")
|
.append("image")
|
||||||
@ -51,7 +51,7 @@ export default (d3, graph, tool, funcs, tip) => {
|
|||||||
.attr("x", 2)
|
.attr("x", 2)
|
||||||
.attr("y", 10)
|
.attr("y", 10)
|
||||||
.attr("style", "cursor: move;")
|
.attr("style", "cursor: move;")
|
||||||
.attr("xlink:href", (d) =>
|
.attr("xlink:href", (d: { isReal: number; sla: number; cpm: number }) =>
|
||||||
d.sla < 95 && d.isReal && d.cpm > 1 ? icons.CUBEERROR : icons.CUBE
|
d.sla < 95 && d.isReal && d.cpm > 1 ? icons.CUBEERROR : icons.CUBE
|
||||||
);
|
);
|
||||||
nodeEnter
|
nodeEnter
|
||||||
@ -68,7 +68,7 @@ export default (d3, graph, tool, funcs, tip) => {
|
|||||||
.attr("height", 18)
|
.attr("height", 18)
|
||||||
.attr("x", 13)
|
.attr("x", 13)
|
||||||
.attr("y", -7)
|
.attr("y", -7)
|
||||||
.attr("xlink:href", (d) =>
|
.attr("xlink:href", (d: { type: string }) =>
|
||||||
!d.type || d.type === "N/A"
|
!d.type || d.type === "N/A"
|
||||||
? icons.UNDEFINED
|
? icons.UNDEFINED
|
||||||
: icons[d.type.toUpperCase().replace("-", "")]
|
: icons[d.type.toUpperCase().replace("-", "")]
|
||||||
@ -79,7 +79,7 @@ export default (d3, graph, tool, funcs, tip) => {
|
|||||||
.attr("text-anchor", "middle")
|
.attr("text-anchor", "middle")
|
||||||
.attr("x", 22)
|
.attr("x", 22)
|
||||||
.attr("y", 70)
|
.attr("y", 70)
|
||||||
.text((d) =>
|
.text((d: { name: string }) =>
|
||||||
d.name.length > 20 ? `${d.name.substring(0, 20)}...` : d.name
|
d.name.length > 20 ? `${d.name.substring(0, 20)}...` : d.name
|
||||||
);
|
);
|
||||||
return nodeEnter;
|
return nodeEnter;
|
@ -14,7 +14,12 @@
|
|||||||
* 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.
|
||||||
*/
|
*/
|
||||||
export const simulationInit = (d3, data_nodes, data_links, ticked) => {
|
export const simulationInit = (
|
||||||
|
d3: any,
|
||||||
|
data_nodes: any,
|
||||||
|
dataLinks: any,
|
||||||
|
ticked: any
|
||||||
|
) => {
|
||||||
const simulation = d3
|
const simulation = d3
|
||||||
.forceSimulation(data_nodes)
|
.forceSimulation(data_nodes)
|
||||||
.force(
|
.force(
|
||||||
@ -26,7 +31,7 @@ export const simulationInit = (d3, data_nodes, data_links, ticked) => {
|
|||||||
.force("charge", d3.forceManyBody().strength(-520))
|
.force("charge", d3.forceManyBody().strength(-520))
|
||||||
.force(
|
.force(
|
||||||
"link",
|
"link",
|
||||||
d3.forceLink(data_links).id((d) => d.id)
|
d3.forceLink(dataLinks).id((d: { id: string }) => d.id)
|
||||||
)
|
)
|
||||||
.force(
|
.force(
|
||||||
"center",
|
"center",
|
||||||
@ -38,7 +43,7 @@ export const simulationInit = (d3, data_nodes, data_links, ticked) => {
|
|||||||
return simulation;
|
return simulation;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const simulationSkip = (d3, simulation, ticked) => {
|
export const simulationSkip = (d3: any, simulation: any, ticked: any) => {
|
||||||
d3.timeout(() => {
|
d3.timeout(() => {
|
||||||
const n = Math.ceil(
|
const n = Math.ceil(
|
||||||
Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())
|
Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())
|
@ -15,40 +15,43 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
const requireComponent = require.context("./tool", false, /\.png$/);
|
const requireComponent = require.context("./tool", false, /\.png$/);
|
||||||
|
const icons: { [key: string]: string } = {};
|
||||||
|
|
||||||
const icons = {};
|
function capitalizeFirstLetter(str: string) {
|
||||||
function capitalizeFirstLetter(str) {
|
|
||||||
return str.toUpperCase();
|
return str.toUpperCase();
|
||||||
}
|
}
|
||||||
function validateFileName(str) {
|
function validateFileName(str: string): string | undefined {
|
||||||
return (
|
if (/^\S+\.png$/.test(str)) {
|
||||||
/^\S+\.png$/.test(str) &&
|
return str.replace(/^\S+\/(\w+)\.png$/, (rs, $1) =>
|
||||||
str.replace(/^\S+\/(\w+)\.png$/, (rs, $1) => capitalizeFirstLetter($1))
|
capitalizeFirstLetter($1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
requireComponent.keys().forEach((filePath) => {
|
}
|
||||||
|
requireComponent.keys().forEach((filePath: string) => {
|
||||||
const componentConfig = requireComponent(filePath);
|
const componentConfig = requireComponent(filePath);
|
||||||
const fileName = validateFileName(filePath);
|
const fileName = validateFileName(filePath);
|
||||||
|
if (fileName) {
|
||||||
icons[fileName] = componentConfig;
|
icons[fileName] = componentConfig;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Hexagon = (side, r, cx, cy) => {
|
const Hexagon = (side: number, r: number, cx: number, cy: number) => {
|
||||||
let path = "";
|
let path = "";
|
||||||
for (let i = 0; i < side; i += 1) {
|
for (let i = 0; i < side; i += 1) {
|
||||||
let x = Math.cos(((2 / side) * i + 1 / side) * Math.PI) * r + cx;
|
const x = Math.cos(((2 / side) * i + 1 / side) * Math.PI) * r + cx;
|
||||||
let y = -Math.sin(((2 / side) * i + 1 / side) * Math.PI) * r + cy;
|
const y = -Math.sin(((2 / side) * i + 1 / side) * Math.PI) * r + cy;
|
||||||
path += !i ? `M${x},${y} ` : `L${x},${y} `;
|
path += !i ? `M${x},${y} ` : `L${x},${y} `;
|
||||||
if (i == side - 1) path += "Z";
|
if (i == side - 1) path += "Z";
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (graph, data) => {
|
export default (graph: any, data: any) => {
|
||||||
const tool = graph.append("g").attr("class", "topo-tool");
|
const tool = graph.append("g").attr("class", "topo-tool");
|
||||||
const side = 6;
|
const side = 6;
|
||||||
for (let i = 0; i < data.length; i += 1) {
|
for (let i = 0; i < data.length; i += 1) {
|
||||||
let x = Math.cos((2 / side) * i * Math.PI) * 34;
|
const x = Math.cos((2 / side) * i * Math.PI) * 34;
|
||||||
let y = -Math.sin((2 / side) * i * Math.PI) * 34;
|
const y = -Math.sin((2 / side) * i * Math.PI) * 34;
|
||||||
const tool_g = tool
|
const tool_g = tool
|
||||||
.append("g")
|
.append("g")
|
||||||
.attr("class", "topo-tool-i")
|
.attr("class", "topo-tool-i")
|
@ -14,7 +14,7 @@
|
|||||||
* 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.
|
||||||
*/
|
*/
|
||||||
export default (d3, graph) =>
|
export default (d3: any, graph: any) =>
|
||||||
d3
|
d3
|
||||||
.zoom()
|
.zoom()
|
||||||
.scaleExtent([0.3, 10])
|
.scaleExtent([0.3, 10])
|
Loading…
Reference in New Issue
Block a user