Files
rocm-systems/projects/rocprofiler-compute/grafana_plugins/svg_plugin/src/SVGjsPanel.tsx
T
colramos-amd f0cf74353e Initial commit
[ROCm/rocprofiler-compute commit: 62d130b458]
2022-11-04 14:49:36 -05:00

310 lines
9.9 KiB
TypeScript

// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import React, { PureComponent } from 'react';
import { PanelProps } from '@grafana/data';
import { stylesFactory } from '@grafana/ui';
import { SVG, Element as SVGElement, Dom as SVGDom, extend as SVGExtend, Runner as SVGRunner } from '@svgdotjs/svg.js';
import { SVGOptions, SVGIDMapping } from 'types';
import { css } from 'emotion';
interface MappedElements {
[key: string]: SVGElement | SVGDom;
}
interface Props extends PanelProps<SVGOptions> {}
interface PanelState {
addAllIDs: boolean;
svgNode: SVGElement | SVGDom | null;
svgSource: string | null;
mappedElements: MappedElements | null;
svgMappings: SVGIDMapping[];
initFunctionSource: string;
initFunction: Function | null;
eventFunctionSource: string;
eventFunction: Function | null;
initialized: boolean;
}
interface TextMappedElement extends SVGElement {
textElement: Element;
}
SVGExtend(SVGElement, {
openOnClick: function (this: SVGElement, url: string) {
return window.open(url);
},
animateContRotate: function (this: SVGElement, speed: number) {
return (
this.animate(speed)
//@ts-ignore
.ease('-')
//@ts-ignore
.rotate(360)
.loop()
);
},
showOn: function (this: SVGElement, on: boolean) {
if (on) {
this.show();
} else {
this.hide();
}
},
animateOn: function (this: SVGElement, speed: number, on: boolean, animation: Function) {
if (on) {
//@ts-ignore
if (this.timeline()._runners.length === 0) {
animation(this.animate(speed));
} else {
this.timeline().play();
}
} else {
this.timeline().stop();
}
},
stopAnimation: function (this: SVGRunner) {
this.timeline().stop();
},
getParentNode: function (this: SVGElement) {
return this.node.parentNode;
},
getTopNode: function (this: SVGElement) {
let currentNode: Element = this.node as Element;
while (true) {
if (currentNode.parentNode && !currentNode.className.includes('svg-object')) {
currentNode = currentNode.parentNode as Element;
} else {
return currentNode;
}
}
},
});
SVGExtend(SVGDom, {
updateXHTMLFontText: function (this: SVGDom, newText: string) {
let currentElement: Element | TextMappedElement = this.node;
let i = 0;
while (currentElement.localName !== 'xhtml:font') {
if (currentElement.firstElementChild && i < 10) {
currentElement = currentElement.firstElementChild;
i++;
} else {
return;
}
}
currentElement.innerHTML = newText;
},
});
export class SVGPanel extends PureComponent<Props, PanelState> {
constructor(props: any) {
super(props);
this.state = {
addAllIDs: false,
svgNode: null,
svgSource: null,
svgMappings: [],
mappedElements: null,
initFunctionSource: '',
initFunction: null,
eventFunctionSource: '',
eventFunction: null,
initialized: false,
};
}
initializeMappings(svgNode: SVGElement | SVGDom) {
const svgMappings = this.props.options.svgMappings;
let currentElements: MappedElements = { ...this.state.mappedElements };
currentElements = {};
console.log('Current length is ', svgMappings.length);
for (let i = 0; i < svgMappings.length; i++) {
if (svgMappings[i].mappedName !== '') {
currentElements[this.props.options.svgMappings[i].mappedName] = svgNode.findOne(
`#${this.props.options.svgMappings[i].svgId}`
)!;
}
}
this.setState({ mappedElements: currentElements });
}
mapAllIDs(svgNode: SVGDom) {
let svgMappings: SVGIDMapping[] = [...this.props.options.svgMappings];
let nodeFilterID: NodeFilter = {
acceptNode: (node: Element) => {
if (node.id) {
if (node.id !== '') {
return NodeFilter.FILTER_ACCEPT;
}
}
return NodeFilter.FILTER_REJECT;
},
};
let svgWalker = document.createTreeWalker(svgNode.node, NodeFilter.SHOW_ALL, nodeFilterID);
let currentNode: Element | null = svgWalker.currentNode as Element;
while (currentNode) {
if (currentNode && currentNode.id) {
if (svgMappings.filter((mapping) => (currentNode ? mapping.svgId === currentNode.id : false)).length === 0) {
svgMappings.push({ svgId: currentNode.id, mappedName: '' });
}
}
currentNode = svgWalker.nextNode() as Element;
}
this.setState({ svgMappings: [...svgMappings], initialized: false });
this.props.options.svgMappings = [...svgMappings];
this.props.onOptionsChange(this.props.options);
this.forceUpdate();
}
mappingClickHandler(event: React.MouseEvent<HTMLElement, MouseEvent>) {
if (event.target) {
let clicked = event.target as Element;
let loopCount = 0;
let svgMappings: SVGIDMapping[] = [...this.props.options.svgMappings];
if (clicked.id) {
while (clicked.id === '') {
loopCount++;
if (loopCount > 20) {
return;
}
clicked = clicked.parentNode as Element;
}
for (let i = 0; i < svgMappings.length; i++) {
if (svgMappings[i].svgId === clicked.id) {
return;
}
}
svgMappings.push({ svgId: clicked.id, mappedName: '' });
this.setState({ svgMappings: [...svgMappings], initialized: false });
this.props.options.svgMappings = [...svgMappings];
this.props.onOptionsChange(this.props.options);
this.forceUpdate();
}
}
}
renderSVG(element: SVGSVGElement | SVGDom | null) {
if (element) {
if (
this.props.options.initSource !== this.state.initFunctionSource ||
this.state.addAllIDs !== this.props.options.addAllIDs
) {
this.setState({
initFunctionSource: this.props.options.initSource,
addAllIDs: this.props.options.addAllIDs,
initialized: false,
});
}
if (!this.state.initialized) {
console.log('initializing');
let svgNode = SVG(element);
svgNode.clear();
svgNode.svg(this.props.options.svgSource);
svgNode.size(this.props.width, this.props.height);
if (this.props.options.addAllIDs) {
this.mapAllIDs(svgNode);
}
this.initializeMappings(svgNode);
this.setState({ svgNode: svgNode });
try {
this.setState({
initFunction: Function(
'data',
'options',
'svgnode',
'svgmap',
this.props.replaceVariables(this.props.options.initSource)
),
});
if (this.state.mappedElements && this.state.initFunction) {
this.state.initFunction(this.props.data, this.props.options, this.state.svgNode, this.state.mappedElements);
this.setState({ initialized: true });
}
} catch (e) {
this.setState({ initialized: true });
console.log(`User init code failed: ${e}`);
}
}
try {
let eventFunction = this.state.eventFunction;
if (this.props.options.eventSource !== this.state.eventFunctionSource) {
let eventFunctionSource = this.props.options.eventSource;
eventFunction = Function(
'data',
'options',
'svgnode',
'svgmap',
this.props.replaceVariables(eventFunctionSource)
);
this.setState({ eventFunctionSource: eventFunctionSource, eventFunction: eventFunction, initialized: false });
}
if (this.state.mappedElements && eventFunction) {
eventFunction(this.props.data, this.props.options, this.state.svgNode, this.state.mappedElements);
}
} catch (e) {
console.log(`User event code failed: ${e}`);
}
return this.state.svgNode ? this.state.svgNode.svg() : null;
} else {
return null;
}
}
render() {
const styles = this.getStyles();
return (
<div
className={styles.wrapper}
onClick={this.props.options.captureMappings ? this.mappingClickHandler.bind(this) : undefined}
>
<svg
style={{
width: `${this.props.width}px`,
height: `${this.props.height}px`,
}}
className={'svg-object'}
ref={(ref) => this.renderSVG(ref)}
></svg>
</div>
);
}
getStyles = stylesFactory(() => {
return {
wrapper: css`
position: relative;
`,
svg: css`
position: absolute;
top: 0;
left: 0;
`,
textBox: css`
position: absolute;
bottom: 0;
left: 0;
padding: 10px;
`,
};
});
}