Связи
Ребро — это кривая Безье между центрами двух узлов. По умолчанию стиль задаёт класс, выведенный из link.type, а цвета живут в токенах темы, а не в инлайновом stroke, — поэтому при переключении темы ребро перекрашивается само. Если объявить виды соединений, вместо этого применяются ваши цвет и штрих.
Типы и направление
const links: AssetLink[] = [
{ id: 'l1', source: 'internet', target: 'router', layers: ['physical'] },
{ id: 'l2', source: 'cloudflare', target: 'nginx', layers: ['physical'], type: 'dashed', directed: true },
]type попадает в класс vam-edge--type-{type}, который вы стилизуете из своего CSS. directed добавляет стрелку и ограничивает обходы, считающиеся по направлению, — см. Обходы графа.
Если нужны цвет, штрих и человеческое название на каждый вид — а с ними легенда и выбор типа при правке связи, — вместо ручной стилизации классов объявите linkTypes. Одно объявление управляет всеми тремя, поэтому разойтись они не могут. См. Соединения и правка.
Показать исходник
<script setup lang="ts">
import { AssetMap } from 'vue-asset-map'
import type { AssetLink } from 'vue-asset-map'
import { dagreLayout } from 'vue-asset-map/layout-dagre'
import { useSiteTheme } from '../theme/useSiteTheme'
import { useSiteLocale } from '../theme/useSiteLocale'
import { entities, links, views, statusColors, kindIcons } from './fixture'
const theme = useSiteTheme()
const locale = useSiteLocale()
// `type` selects the edge style through a CSS class; `directed` adds an arrow.
// Nothing here is hardcoded in the package — both come from the data. A
// left-to-right layered layout reads a directed arrow more clearly than a
// radial one, where every arrow points along a different curve.
const styled: AssetLink[] = links.map((link) =>
link.layers.includes('logical') ? { ...link, type: 'dashed', directed: true } : link,
)
</script>
<template>
<AssetMap
class="demo-map"
view="logical"
:entities="entities"
:links="styled"
:views="views"
:theme="theme"
:locale="locale"
:theme-config="{ statusColors, kindIcons }"
:layout="{ engine: dagreLayout, options: { rankdir: 'LR', ranksep: 110 } }"
auto-fit-view
>
<template #edge-label="{ link, aggregated, count }">
<span class="edge-label">
{{ aggregated ? `${count} links` : (link?.label ?? link?.type ?? '') }}
</span>
</template>
</AssetMap>
</template>
<style scoped>
.edge-label {
padding: 1px 6px;
border-radius: 4px;
background: var(--vp-c-bg);
font-size: 11px;
}
</style>
Клик по связи
Клик открывает окно соединения: что оно соединяет, его вид, слои и meta. Набор открытых окон — v-model:openLinks, тело подменяется слотом #link-details. Агрегированное ребро не открывает ничего: за ним стоит сразу несколько связей и нет одной записи, которую можно было бы показать, — но событие edge-click приходит по-прежнему.
Правый клик вешает на связь меню, если включён showContextMenu, а с editable в нём появляется удаление. Всё это описано в Соединениях и правке.
Сама линия толщиной в пиксель — не мишень для клика, поэтому у каждого ребра есть ещё невидимый путь шириной 16px по тому же маршруту. Именно он принимает указатель: без него клик в девяти случаях из десяти попадал бы в полотно.
Привязка к конкретной ручке
Узел, который рисует больше одной ручки — порты коммутатора, слоты в стойке, объекты внутри блока, — может иметь линии, соединяющие конкретную пару, а не два узла целиком:
{ id: 'l1', source: 'switch-a', target: 'switch-b', layers: ['physical'],
sourceHandle: 'port-3', targetHandle: 'port-1' }Это id из пропа id компонентов <Handle> внутри вашего узла. Без них линия садится на первую ручку подходящего типа — ровно то, что нужно узлу с одной ручкой, поэтому без явной просьбы ничего не меняется.
Когда связь рисуют перетаскиванием, реально соединённые ручки приезжают на кандидате в событии link-create. Это единственный момент, когда эта информация существует: по паре id узлов уже не восстановить, что именно соединили.
Подписи
Своя подпись связи рисуется, если она есть. У агрегированных рёбер — тех, что получились из свёртки групп, — вместо неё показывается счётчик. Ниже порога LOD подписи не рисуются вовсе.
Слот #edge-label подменяет отрисовку:
<template #edge-label="{ link, aggregated, count }">
<span class="edge-label">{{ aggregated ? `${count} связей` : link?.meta?.bandwidth }}</span>
</template>Параллельные связи сохраняются
Две связи между одной парой с разными id — это два ребра. Так и задумано: второй аплинк к тому же роутеру считается фактом об инфраструктуре, а не дублем, который надо объединить.
Агрегация в один AggregatedLink происходит только когда свёртка поставила на один из концов группу, и тогда count и sourceLinkIds фиксируют, что в неё вошло:
import { isAggregatedLink } from 'vue-asset-map/core'
if (isAggregatedLink(link)) {
console.log(link.count, link.sourceLinkIds)
}Свой компонент ребра
Зарегистрируйте компонент под type — так же, как с узлами:
<!-- TunnelEdge.vue -->
<script setup lang="ts">
import { computed } from 'vue'
import { BaseEdge, getBezierPath, Position } from '@vue-flow/core'
import { useMapContext } from 'vue-asset-map'
import type { AssetEdgeData } from 'vue-asset-map'
const props = defineProps<{
id: string
sourceX: number
sourceY: number
targetX: number
targetY: number
data?: AssetEdgeData
}>()
defineOptions({ inheritAttrs: false })
const map = useMapContext()
const path = computed(() => {
const [d] = getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
targetX: props.targetX,
targetY: props.targetY,
sourcePosition: Position.Bottom,
targetPosition: Position.Top,
})
return d
})
</script>
<template>
<BaseEdge :id="id" :path="path" :class="{ dimmed: map.isEdgeDimmed(id) }" />
</template><AssetMap :entities="entities" :links="links" :edge-types="{ tunnel: TunnelEdge }" />defineOptions({ inheritAttrs: false }) здесь обязателен. Vue Flow передаёт ребру десятки собственных пропсов, и без этой опции Vue пишет в консоль предупреждение о лишних атрибутах на каждое ребро карты.
Гашение и подсветку решает карта, а не ребро: правило зависит от режима — поиск, выделение или наведение на маркер, — а ребро о режимах знать не должно. Читайте их через map.isEdgeDimmed(id), map.isEdgeHighlighted(id) и map.isEdgeInfected(id).