@furo/util
Version:
521 lines (475 loc) • 14.7 kB
JavaScript
import { LitElement, css } from 'lit';
import { FBP } from '@furo/fbp';
import { SVG } from '@svgdotjs/svg.js';
import '@svgdotjs/svg.panzoom.js/dist/svg.panzoom.esm.js';
/**
* `furo-graph-renderer`
* Paint a SVG from the received graph data
*
* @fires {node} show-tooltip-requested - Fired when
* @fires {node} show-tooltip-requested - Fired on mouseover of a attr node
* @fires {node} show-tooltip-requested - Fired on mouseover of a attr node
*
* @summary paints a dagree graph as svg
* @customElement
* @appliesMixin FBP
*/
export class FuroGraphRenderer extends FBP(LitElement) {
/**
* Draw the graph as svg
* @param {dagre} graph - Dagre graph
*/
draw(graph) {
const sizes = graph.graph();
const graphWidth = sizes.width;
const graphHeight = sizes.height;
// remove old image on redraw
const i = this.shadowRoot.querySelector('svg');
if (i) {
i.remove();
}
const canvas = SVG()
.addTo(this.shadowRoot)
.panZoom({ zoomMin: 0.1, zoomMax: 10, zoomFactor: 0.115 });
canvas.viewbox(0, 0, graphWidth, graphHeight);
this.canvas = canvas;
const nodes = graph.nodes();
nodes.forEach(v => {
const node = graph.node(v);
// boxes for the components
if (node.type === 'component') {
const box = canvas
.rect(node.width, node.height)
.move(node.x - node.width / 2, node.y - node.height / 2)
.fill('none');
box.radius(5);
box.addClass(node.type);
// set tooltip if exist
if (node.node.description !== '') {
// add info
box.addClass('withdescription');
box.mouseover(() => {
const elem = {
duration: 5000,
cr: box.node.getBoundingClientRect(),
label: node.node.description,
};
const customEvent = new Event('show-tooltip-requested', {
composed: true,
bubbles: true,
});
customEvent.detail = elem;
this.dispatchEvent(customEvent);
});
}
/*
box.click((e) => {
console.log(node.node)
})
*/
}
});
// draw the lines
graph.edges().forEach(e => {
const edge = graph.edge(e);
if (edge.type !== 'center') {
const points = edge.points.map(p => [p.x, p.y]);
const line = canvas.polyline(points).addClass('line');
line.addClass(edge.type);
// send tooltip event for nodes with values in the attr
// debounce tooltip on lines
let timout;
let toRegistred = false;
line.mouseover(() => {
if (!toRegistred) {
toRegistred = true;
timout = setTimeout(() => {
const elem = {
duration: 5000,
cr: {
top: e.clientY - 10,
bottom: e.clientY + 10,
x: e.clientX,
y: e.clientY,
left: e.clientX - 10,
width: 20,
height: 20,
},
label: edge.wirename,
};
const customEvent = new Event('show-tooltip-requested', {
composed: true,
bubbles: true,
});
customEvent.detail = elem;
this.dispatchEvent(customEvent);
}, 60);
}
});
// clear the timeout
line.mouseout(() => {
clearTimeout(timout);
toRegistred = false;
});
}
});
// component Labels over the edges
nodes.forEach(v => {
const node = graph.node(v);
if (node.type === 'component') {
const background = canvas
.rect(10, 25)
.move(node.x - node.width / 2, node.y - node.height / 2)
.addClass('boxlabelbg');
background.radius(5);
if (node.label) {
const text = canvas
.text(node.label)
.move(node.x - node.width / 2 + 25, node.y - node.height / 2 + 2);
// width is taken from the image, so wait a moment.
setTimeout(() => {
background.width(text.length() + 50);
}, 90);
// send tooltip event for nodes with values in the attr
text.mouseover(() => {
const elem = {
duration: 5000,
cr: text.node.getBoundingClientRect(),
label: node.label,
};
const customEvent = new Event('show-tooltip-requested', {
composed: true,
bubbles: true,
});
customEvent.detail = elem;
this.dispatchEvent(customEvent);
});
}
}
});
nodes.forEach(v => {
const node = graph.node(v);
if (node.type === 'attribute') {
// add receiving circle
// if(node.attr._type === "method"){
// let r = canvas.circle(12, 12).move((node.x - node.width / 2) -6, (node.y - node.height / 2)+6).addClass('line');
// }
let box = canvas
.rect(node.width, node.height)
.move(node.x - node.width / 2, node.y - node.height / 2);
box.radius(3);
box.addClass(node.type);
box.addClass(node.attr._type);
// add green bar to the left
if (node.attr._type === 'method') {
box = canvas
.rect(10, node.height)
.move(node.x - node.width / 2, node.y - node.height / 2);
box.radius(3);
box.addClass('methodindicator');
}
// add blue bar to the right
if (node.attr._type === 'event') {
box = canvas
.rect(10, node.height)
.move(node.x + node.width / 2 - 10, node.y - node.height / 2);
box.radius(3);
box.addClass('eventindicator');
}
// add classes for ^^bubbling, -^host, ^nonbubbling, set-property
// for bool like flex
if (!node.attr.value) {
box.addClass('flag');
// add black bar to the left
const indicator = canvas
.rect(10, node.height)
.move(node.x - node.width / 2, node.y - node.height / 2);
indicator.radius(3);
indicator.addClass('flagindicator');
// send tooltip event for nodes with values in the attr
box.mouseover(() => {
const elem = {
duration: 5000,
cr: box.node.getBoundingClientRect(),
label: `Flag: ${node.label}`,
};
const customEvent = new Event('show-tooltip-requested', {
composed: true,
bubbles: true,
});
customEvent.detail = elem;
this.dispatchEvent(customEvent);
});
} else {
// send tooltip event for nodes with values in the attr
box.mouseover(() => {
const elem = {
duration: 5000,
cr: box.node.getBoundingClientRect(),
label: `${node.label} = ${node.attr.value}`,
};
const customEvent = new Event('show-tooltip-requested', {
composed: true,
bubbles: true,
});
customEvent.detail = elem;
this.dispatchEvent(customEvent);
});
}
if (node.label) {
// eslint-disable-next-line no-unused-vars
const text = canvas
.text(node.label)
.move(node.x - node.width / 2 + 15, node.y - node.height / 2 + 5);
}
}
if (node.type === 'notarget') {
const circle = canvas
.circle(node.width, node.height)
.move(node.x - node.width / 2, node.y - node.height / 2)
.fill('red');
// send tooltip event
circle.mouseover(() => {
const elem = {
duration: 5000,
cr: circle.node.getBoundingClientRect(),
label: node.wirename,
};
const customEvent = new Event('show-tooltip-requested', {
composed: true,
bubbles: true,
});
customEvent.detail = elem;
this.dispatchEvent(customEvent);
});
}
if (node.type === 'nosource') {
const circle = canvas
.circle(node.width, node.height)
.move(node.x - node.width / 2, node.y - node.height / 2)
.fill('orange');
// send tooltip event
circle.mouseover(() => {
const elem = {
duration: 5000,
cr: circle.node.getBoundingClientRect(),
label: node.wirename,
};
const customEvent = new Event('show-tooltip-requested', {
composed: true,
bubbles: true,
});
customEvent.detail = elem;
this.dispatchEvent(customEvent);
});
}
if (node.type === 'park') {
const box = canvas
.rect(node.width, node.height)
.move(node.x - node.width / 2, node.y - node.height / 2);
box.radius(3);
box.addClass('park');
if (node.label) {
// eslint-disable-next-line no-unused-vars
const text = canvas
.text(node.label)
.move(node.x - node.width / 2 + 15, node.y - node.height / 2 + 5);
}
// send tooltip event
box.mouseover(() => {
const elem = {
duration: 5000,
cr: box.node.getBoundingClientRect(),
label: `park to var ${node.label}`,
};
const customEvent = new Event('show-tooltip-requested', {
composed: true,
bubbles: true,
});
customEvent.detail = elem;
this.dispatchEvent(customEvent);
});
}
if (node.type === 'bubbling') {
const box = canvas
.rect(node.width, node.height)
.move(node.x - node.width / 2, node.y - node.height / 2);
box.radius(3);
box.addClass('bubbling');
if (node.label) {
// eslint-disable-next-line no-unused-vars
const text = canvas
.text(node.label)
.move(node.x - node.width / 2 + 15, node.y - node.height / 2 + 5);
}
// send tooltip event
box.mouseover(() => {
const elem = {
duration: 5000,
cr: box.node.getBoundingClientRect(),
label: `bubbling event ${node.label}`,
};
const customEvent = new Event('show-tooltip-requested', {
composed: true,
bubbles: true,
});
customEvent.detail = elem;
this.dispatchEvent(customEvent);
});
}
if (node.type === 'nonbubbling') {
const box = canvas
.rect(node.width, node.height)
.move(node.x - node.width / 2, node.y - node.height / 2);
box.radius(3);
box.addClass('nonbubbling');
if (node.label) {
// eslint-disable-next-line no-unused-vars
const text = canvas
.text(node.label)
.move(node.x - node.width / 2 + 15, node.y - node.height / 2 + 5);
}
// send tooltip event
box.mouseover(() => {
const elem = {
duration: 5000,
cr: box.node.getBoundingClientRect(),
label: `event ${node.label}`,
};
const customEvent = new Event('show-tooltip-requested', {
composed: true,
bubbles: true,
});
customEvent.detail = elem;
this.dispatchEvent(customEvent);
});
}
if (node.type === 'hostevent') {
const box = canvas
.rect(node.width, node.height)
.move(node.x - node.width / 2, node.y - node.height / 2);
box.radius(3);
box.addClass('hostevent');
if (node.label) {
// eslint-disable-next-line no-unused-vars
const text = canvas
.text(node.label)
.move(node.x - node.width / 2 + 15, node.y - node.height / 2 + 5);
}
// send tooltip event
box.mouseover(() => {
const elem = { duration: 5000, cr: box.node.getBoundingClientRect(), label: node.label };
const customEvent = new Event('show-tooltip-requested', {
composed: true,
bubbles: true,
});
customEvent.detail = elem;
this.dispatchEvent(customEvent);
});
}
});
}
/**
* flow is ready lifecycle method
* @private
*/
_FBPReady() {
super._FBPReady();
// this._FBPTraceWires()
}
/**
* Themable Styles
* @private
* @return {CSSResult}
*/
static get styles() {
// language=CSS
return (
css`
:host {
display: block;
height: 100%;
width: 100%;
overflow: hidden;
}
:host([hidden]) {
display: none;
}
svg {
width: 100%;
height: 100%;
}
.component {
fill: #f6f6f6;
stroke: #67686a;
stroke-width: 2;
}
.boxlabelbg {
fill: white;
stroke: #67686a;
stroke-width: 2;
}
.component.withdescription {
stroke-dasharray: 20 4;
}
.attribute {
fill: white;
stroke: #ffb65b;
stroke-width: 2;
}
.attribute.method {
stroke: #4caf50;
}
.methodindicator {
stroke: #4caf50;
fill: #4caf50;
}
.eventindicator {
stroke: #02a8f4;
fill: #02a8f4;
}
.attribute.event {
stroke: #02a8f4;
}
.attribute.flag {
stroke: #686868;
}
.flagindicator {
stroke: #686868;
fill: #686868;
}
.park {
stroke: #686868;
fill: none;
stroke-width: 3;
}
.bubbling,
.hostevent,
.nonbubbling {
stroke: #fa4600;
fill: none;
stroke-width: 3;
}
.line.event {
stroke: #fa4600;
fill: none;
stroke-width: 4;
}
.line {
stroke: #02a8f4;
fill: none;
stroke-width: 4;
}
.line.park {
stroke: #070707;
fill: none;
stroke-width: 4;
}
.line:hover {
stroke: #f4c633;
}
`
);
}
}
window.customElements.define('furo-graph-renderer', FuroGraphRenderer);