mirror of
https://github.com/octoleo/plantuml.git
synced 2024-11-21 20:45:10 +00:00
Add support for interactive mouseover/mouseout focus on SVG to focusing on a node and its edges in a complex diagram. This is enabled with !pragma svginteractive.
This commit is contained in:
parent
9c79c1256c
commit
61b0f7b138
@ -988,6 +988,7 @@ public class SvgGraphics {
|
||||
return SignatureUtils.getMD5Hex(comment);
|
||||
}
|
||||
|
||||
|
||||
public void addComment(String comment) {
|
||||
final String signature = getMD5Hex(comment);
|
||||
comment = "MD5=[" + signature + "]\n" + comment;
|
||||
@ -995,6 +996,31 @@ public class SvgGraphics {
|
||||
getG().appendChild(commentElement);
|
||||
}
|
||||
|
||||
public void addScriptTag(String url) {
|
||||
final Element script = document.createElement("script");
|
||||
script.setAttribute("type", "text/javascript");
|
||||
script.setAttribute("xlink:href", url);
|
||||
root.appendChild(script);
|
||||
}
|
||||
|
||||
public void addScript(String scriptTextPath) {
|
||||
final Element script = document.createElement("script");
|
||||
final String scriptText = getData(scriptTextPath);
|
||||
final CDATASection cDATAScript = document.createCDATASection(scriptText);
|
||||
script.appendChild(cDATAScript);
|
||||
root.appendChild(script);
|
||||
}
|
||||
|
||||
public void addStyle(String cssStylePath) {
|
||||
final Element style = simpleElement("style");
|
||||
final String text = getData(cssStylePath);
|
||||
|
||||
final CDATASection cdata = document.createCDATASection(text);
|
||||
style.setAttribute("type", "text/css");
|
||||
style.appendChild(cdata);
|
||||
root.appendChild(style);
|
||||
}
|
||||
|
||||
public void openLink(String url, String title, String target) {
|
||||
Objects.requireNonNull(url);
|
||||
|
||||
|
@ -255,7 +255,7 @@ public class ImageBuilder {
|
||||
/ 96.0;
|
||||
if (scaleFactor <= 0)
|
||||
throw new IllegalStateException("Bad scaleFactor");
|
||||
UGraphic ug = createUGraphic(fileFormatOption, dim, animationArg, dx, dy, scaleFactor, titledDiagram.getPragma());
|
||||
UGraphic ug = createUGraphic(fileFormatOption, dim, animationArg, dx, dy, scaleFactor, titledDiagram !=null ? titledDiagram.getPragma() : new Pragma());
|
||||
maybeDrawBorder(ug, dim);
|
||||
if (randomPixel) {
|
||||
drawRandomPoint(ug);
|
||||
|
@ -71,6 +71,7 @@ public class UGraphicSvg extends AbstractUGraphic<SvgGraphics> implements ClipCo
|
||||
|
||||
private final boolean textAsPath2;
|
||||
private final String target;
|
||||
private final Pragma pragma;
|
||||
|
||||
public double dpiFactor() {
|
||||
return 1;
|
||||
@ -85,6 +86,7 @@ public class UGraphicSvg extends AbstractUGraphic<SvgGraphics> implements ClipCo
|
||||
super(other);
|
||||
this.textAsPath2 = other.textAsPath2;
|
||||
this.target = other.target;
|
||||
this.pragma = other.pragma;
|
||||
register();
|
||||
}
|
||||
|
||||
@ -94,7 +96,7 @@ public class UGraphicSvg extends AbstractUGraphic<SvgGraphics> implements ClipCo
|
||||
this(defaultBackground, minDim, colorMapper,
|
||||
new SvgGraphics(colorMapper.toSvg(defaultBackground), svgDimensionStyle, minDim, scale, hover, seed,
|
||||
preserveAspectRatio, lengthAdjust, DarkStrategy.IGNORE_DARK_COLOR, pragma),
|
||||
textAsPath, linkTarget, stringBounder);
|
||||
textAsPath, linkTarget, stringBounder, pragma);
|
||||
if (defaultBackground instanceof HColorGradient) {
|
||||
final SvgGraphics svg = getGraphicObject();
|
||||
svg.paintBackcolorGradient(colorMapper, (HColorGradient) defaultBackground);
|
||||
@ -117,10 +119,11 @@ public class UGraphicSvg extends AbstractUGraphic<SvgGraphics> implements ClipCo
|
||||
}
|
||||
|
||||
private UGraphicSvg(HColor defaultBackground, Dimension2D minDim, ColorMapper colorMapper, SvgGraphics svg,
|
||||
boolean textAsPath, String linkTarget, StringBounder stringBounder) {
|
||||
boolean textAsPath, String linkTarget, StringBounder stringBounder, Pragma pragma) {
|
||||
super(defaultBackground, colorMapper, stringBounder, svg);
|
||||
this.textAsPath2 = textAsPath;
|
||||
this.target = linkTarget;
|
||||
this.pragma = pragma;
|
||||
register();
|
||||
}
|
||||
|
||||
@ -152,6 +155,14 @@ public class UGraphicSvg extends AbstractUGraphic<SvgGraphics> implements ClipCo
|
||||
if (metadata != null)
|
||||
getGraphicObject().addComment(metadata);
|
||||
|
||||
if (pragma.isDefine("svginteractive") && Boolean.valueOf(pragma.getValue("svginteractive"))) {
|
||||
// For performance reasons and also because we want the entire graph DOM to be create so we can register
|
||||
// the event handlers on them we will append to the end of the document
|
||||
getGraphicObject().addStyle("onmouseinteractivefooter.css");
|
||||
getGraphicObject().addScriptTag("https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js@3.0/dist/svg.min.js");
|
||||
getGraphicObject().addScript("onmouseinteractivefooter.js");
|
||||
}
|
||||
|
||||
getGraphicObject().createXml(os);
|
||||
} catch (TransformerException e) {
|
||||
throw new IOException(e.toString());
|
||||
|
6
svg/onmouseinteractivefooter.css
Normal file
6
svg/onmouseinteractivefooter.css
Normal file
@ -0,0 +1,6 @@
|
||||
[data-mouse-over-selected="false"] {
|
||||
opacity: 0.2;
|
||||
}
|
||||
[data-mouse-over-selected="true"] {
|
||||
opacity: 1.0;
|
||||
}
|
84
svg/onmouseinteractivefooter.js
Normal file
84
svg/onmouseinteractivefooter.js
Normal file
@ -0,0 +1,84 @@
|
||||
(function (){
|
||||
/**
|
||||
* @param {SVG.G} node
|
||||
* @param {SVG.G} topG
|
||||
* @return {{node: Set<SVG.G>, edges:Set<SVG.G>}}
|
||||
*/
|
||||
function getEdgesAndDistance1Nodes(node, topG) {
|
||||
const nodeName = node.attr("id").match(/elem_(.+)/)[1];
|
||||
const selector = "[id^=link_]"
|
||||
const candidates = topG.find(selector)
|
||||
let edges = new Set();
|
||||
let nodes = new Set();
|
||||
for (let link of candidates) {
|
||||
const res = link.attr("id").match(/link_([A-Za-z\d]+)_([A-Za-z\d]+)/);
|
||||
if (res && res.length==3) {
|
||||
const N1 = res[1];
|
||||
const N2 = res[2];
|
||||
if (N1==nodeName) {
|
||||
const N2selector = `[id^=elem_${N2}]`;
|
||||
nodes.add(topG.findOne(N2selector));
|
||||
edges.add(link);
|
||||
} else if (N2==nodeName) {
|
||||
const N1selector = `[id^=elem_${N1}]`;
|
||||
nodes.add(topG.findOne(N1selector));
|
||||
edges.add(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
"nodes" : nodes,
|
||||
"edges" : edges
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SVG.G} node
|
||||
* @param {function(SVG.Dom)}
|
||||
* @return {{node: Set<SVG.G>, edges:Set<SVG.G>}}
|
||||
*/
|
||||
function walk(node, func) {
|
||||
let children = node.children();
|
||||
for (let child of children) {
|
||||
walk(child, func)
|
||||
}
|
||||
func(node);
|
||||
}
|
||||
let s = SVG("svg > g")
|
||||
/**
|
||||
* @param {SVGElement} domEl
|
||||
* @return {{SVGElement}}
|
||||
*/
|
||||
function findEnclosingG(domEl) {
|
||||
let curEl = domEl;
|
||||
while (curEl.nodeName != "g") {
|
||||
curEl = curEl.parentElement;
|
||||
}
|
||||
return curEl;
|
||||
}
|
||||
function onMouseOverElem(domEl) {
|
||||
let e = SVG(findEnclosingG(domEl.target));
|
||||
walk(s,
|
||||
e => { if (SVG(e)!=s)
|
||||
SVG(e).attr('data-mouse-over-selected',"false");
|
||||
});
|
||||
walk(e, e => SVG(e).attr('data-mouse-over-selected',"true"));
|
||||
let {nodes, edges} = getEdgesAndDistance1Nodes(SVG(e), s);
|
||||
for (let node of nodes) {
|
||||
walk(node, e => SVG(e).attr('data-mouse-over-selected',"true"));
|
||||
}
|
||||
for (let edge of edges) {
|
||||
walk(edge, e => SVG(e).attr('data-mouse-over-selected',"true"));
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseOutElem(domEl) {
|
||||
let e = SVG(findEnclosingG(domEl.target));
|
||||
walk(s, e => e.attr('data-mouse-over-selected',null));
|
||||
}
|
||||
let gs = s.find("g[id^=elem_]");
|
||||
for (let g of gs) {
|
||||
g.on("mouseover", onMouseOverElem);
|
||||
g.on("mouseout", onMouseOutElem);
|
||||
}
|
||||
})();
|
Loading…
Reference in New Issue
Block a user