Представления
Представление — это объект конфигурации, который определяет проекцию графа на холст: какие сущности и связи до него доходят и как они раскладываются. Представления являются данными: пакет не содержит ни одного своего представления и не знает заранее ни одного имени. physical и logical в примерах — это id, которые выбрали вы.
interface AssetView {
id: string
label: string
linkFilter?: (link: AssetLink) => boolean
entityFilter?: (entity: AssetEntity) => boolean
showSublabels?: boolean
layout?: { engine: string; options?: unknown }
hideIsolated?: boolean
}Фильтрация по слоям
Без linkFilter связь попадает в представление, если её layers содержат id представления:
const links: AssetLink[] = [
{ id: 'l1', source: 'router', target: 'laptop', layers: ['physical'] },
{ id: 'l2', source: 'vpn', target: 'laptop', layers: ['logical'] },
{ id: 'l3', source: 'internet', target: 'router', layers: ['physical', 'logical'] },
]l3 виден в обоих представлениях, остальные связи — в одном каждая. Один массив связей описывает несколько топологий.
Порядок применения
Порядок проекции зафиксирован и наблюдаем:
entityFilterотбирает сущности-кандидаты.linkFilterоставляет связи, у которых оба конца прошли шаг 1.hideIsolatedубирает сущности, у которых не осталось ни одной видимой связи.
hideIsolated учитывает только связи: у сущности без видимой инцидентной связи узел не рисуется. Предки не спасают потомков, потомки не спасают предков — иерархия в проекции не участвует. Она нужна свёртке, а связность карты при поиске обеспечивает keepPathToRoot из фильтра.
Переключение представлений
<AssetMap v-model:view="view" :entities="entities" :links="links" :views="views" />Встроенный переключатель появляется в тулбаре, как только представлений становится два и больше. Неизвестный или отсутствующий id откатывается к первому представлению, а не рисует пустую карту: значение, сохранённое от старой конфигурации, не может обнулить экран.
Переключение сбрасывает выделение и подсветку пути. Узел, выделенный в одном представлении, в другом может не существовать вовсе, а невидимое выделение всё равно гасит всё вокруг себя. С включённым autoFitView заодно пересчитывается и вид: другое представление — другой граф, а прежний вид описывает раскладку, которой больше нет.
Показать исходник
<script setup lang="ts">
import { computed, ref } from 'vue'
import { AssetMap } from 'vue-asset-map'
import type { AssetView } from 'vue-asset-map'
import { projectGraph, buildGraph, resolveView } from 'vue-asset-map/core'
import { dagreLayout } from 'vue-asset-map/layout-dagre'
import { useSiteTheme } from '../theme/useSiteTheme'
import { useSiteLocale } from '../theme/useSiteLocale'
import { entities, links, views as sourceViews, statusColors, kindIcons } from './fixture'
const theme = useSiteTheme()
const locale = useSiteLocale()
// A view can carry its own layout (see the "Views" guide page): copied here so
// each projection is not just a filtered subset of the same picture but visibly
// its own shape. The shared `sourceViews` array is never mutated — each entry
// is spread into a new object.
const views: AssetView[] = sourceViews.map((view) =>
view.id === 'logical'
? { ...view, layout: { engine: dagreLayout, options: { rankdir: 'LR', ranksep: 90 } } }
: { ...view, layout: { engine: 'radial', options: { rootId: 'internet', radius: 170, ringGap: 140 } } },
)
const view = ref('physical')
// The same projection the map runs internally, used here only for the counter.
const graph = buildGraph(entities, links)
const visible = computed(() => {
const result = projectGraph(graph, resolveView(views, view.value))
return { nodes: result.entityIds.size, links: result.linkIds.size }
})
</script>
<template>
<AssetMap
v-model:view="view"
class="demo-map"
:entities="entities"
:links="links"
:views="views"
:theme="theme"
:locale="locale"
:theme-config="{ statusColors, kindIcons }"
auto-fit-view
>
<template #toolbar-right>
<span class="demo-toolbar-button">{{ visible.nodes }} nodes · {{ visible.links }} links</span>
</template>
</AssetMap>
</template>
Счётчик в этом демо считает приложение, а не карта — той же функцией ядра, которой пользуется карта:
import { buildGraph, projectGraph, resolveView } from 'vue-asset-map/core'
const graph = buildGraph(entities, links)
const result = projectGraph(graph, resolveView(views, 'physical'))
result.entityIds.size
result.linkIds.sizeРаскладка представления
const views: AssetView[] = [
{ id: 'physical', label: 'Физическая', layout: { engine: 'radial', options: { radius: 240 } } },
{ id: 'logical', label: 'VPN-оверлей', layout: { engine: 'dagre', options: { rankdir: 'LR' } } },
]Prop layout у компонента сильнее раскладки представления: props ближе к потребителю.
Свои фильтры
linkFilter и entityFilter, если заданы, полностью заменяют правило по умолчанию:
const criticalOnly: AssetView = {
id: 'critical',
label: 'Критичные активы',
entityFilter: (entity) => entity.meta?.severity === 'critical',
hideIsolated: false,
}С hideIsolated: false выжившие сущности остаются на карте, даже если фильтр обрезал все связи между ними. Это нужно представлению, построенному на свойствах сущностей, а не на топологии.
Подписи
showSublabels: false прячет строки с адресами в конкретном представлении. Геометрия узла при этом не меняется: плашка подписей фиксированного размера, поэтому переключение представления ничего не масштабирует и не дёргает раскладку.