diff --git a/packages/client/graph-scaffolder/src/core/renderer.ts b/packages/client/graph-scaffolder/src/core/renderer.ts index a6d1b0729a..5bc2be8563 100644 --- a/packages/client/graph-scaffolder/src/core/renderer.ts +++ b/packages/client/graph-scaffolder/src/core/renderer.ts @@ -58,8 +58,8 @@ export abstract class Renderer extends EventEmitter { } initialize(element: HTMLDivElement): void { - this.chartSize.width = element.clientWidth; - this.chartSize.height = element.clientHeight; + this.chartSize.width = element.clientWidth || parseFloat(element.style.width); + this.chartSize.height = element.clientHeight || parseFloat(element.style.height); this.svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.svgEl.style.userSelect = 'none'; removeChildren(element).appendChild(this.svgEl); diff --git a/packages/client/hmi-client/src/components/model/petrinet/tera-model-diagram.vue b/packages/client/hmi-client/src/components/model/petrinet/tera-model-diagram.vue index 636ac049a0..c9e6d21cbb 100644 --- a/packages/client/hmi-client/src/components/model/petrinet/tera-model-diagram.vue +++ b/packages/client/hmi-client/src/components/model/petrinet/tera-model-diagram.vue @@ -83,6 +83,7 @@ import { AMRSchemaNames, type FeatureConfig } from '@/types/common'; import { StratifiedMatrix } from '@/types/Model'; import type { Model } from '@/types/Types'; import { observeElementSizeChange } from '@/utils/observer'; +import { svgToImage } from '@/utils/svg'; const props = defineProps<{ model: Model; @@ -122,11 +123,17 @@ async function renderGraph() { // Sanity guard if (mmt.value.templates.length === 0) return; - renderer = getModelRenderer( - mmt.value, - graphElement.value as HTMLDivElement, - stratifiedView.value === StratifiedView.Collapsed - ); + // Abstract the container element so we can render off the DOM + let elem = graphElement.value; + if (props.featureConfig?.isPreview && graphElement.value) { + const originalElem = graphElement.value; + elem = document.createElement('div'); + elem.style.width = `${originalElem.clientWidth}px`; + elem.style.height = `${originalElem.clientHeight}px`; + } + + renderer = getModelRenderer(mmt.value, elem as HTMLDivElement, stratifiedView.value === StratifiedView.Collapsed); + if (renderer.constructor === NestedPetrinetRenderer && renderer.dims?.length) { graphLegendLabels.value = renderer.dims; graphLegendColors.value = renderer.depthColorList; @@ -135,6 +142,41 @@ async function renderGraph() { graphLegendColors.value = []; } + // If interactive, setup events and handlers + if (!props.featureConfig?.isPreview) { + makeGraphInteractive(renderer); + } + + // Prepare data + const graphData = convertToIGraph( + mmt.value, + observableSummary, + isStratified.value && stratifiedView.value === StratifiedView.Collapsed + ); + + // Render graph, this will either render to the DOM or a virutal element + if (renderer) { + renderer.isGraphDirty = true; + await renderer.setData(graphData); + await renderer.render(); + } + + // If not interactive, convert elem buffer into image + if (props.featureConfig?.isPreview && graphElement.value) { + const svg = elem?.querySelector('svg') as SVGElement; + + // Carry background from container + svg.style.background = '#f9fbfa'; + + const image = await svgToImage(svg); + graphElement.value.innerHTML = ''; + graphElement.value.appendChild(image); + elem = null; + } +} + +// eslint-disable-next-line +function makeGraphInteractive(renderer: PetrinetRenderer | NestedPetrinetRenderer) { renderer.on('node-click', (_eventName, _event, selection) => { const { id, data } = selection.datum(); if (data.type === NodeType.Transition && data.isStratified) { @@ -174,19 +216,6 @@ async function renderGraph() { const { data } = selection.datum(); if (data.type === NodeType.Transition && data.isStratified) hoveredTransitionId.value = ''; }); - - // Render graph - const graphData = convertToIGraph( - mmt.value, - observableSummary, - isStratified.value && stratifiedView.value === StratifiedView.Collapsed - ); - - if (renderer) { - renderer.isGraphDirty = true; - await renderer.setData(graphData); - await renderer.render(); - } } async function toggleCollapsedView(view: StratifiedView) { diff --git a/packages/client/hmi-client/src/utils/svg.ts b/packages/client/hmi-client/src/utils/svg.ts index 8b5985b063..60802f598c 100644 --- a/packages/client/hmi-client/src/utils/svg.ts +++ b/packages/client/hmi-client/src/utils/svg.ts @@ -40,11 +40,15 @@ export const svgToImage = (svgElement: SVGElement): Promise => const serializer = new XMLSerializer(); // embedExternalStyles(svgElement); + const w = svgElement.clientWidth || parseFloat(svgElement.getAttribute('width') as string); + const h = svgElement.clientHeight || parseFloat(svgElement.getAttribute('height') as string); + const svgString = serializer.serializeToString(svgElement); const svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' }); const url = URL.createObjectURL(svgBlob); - const img = new Image(svgElement.clientWidth, svgElement.clientHeight); - const finalImg = new Image(svgElement.clientWidth, svgElement.clientHeight); + + const img = new Image(w, h); + const finalImg = new Image(w, h); return new Promise((resolve, reject) => { img.addEventListener('load', (e: any) => {