Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request for svg concept id in export/html report #821

Open
oswalds opened this issue Feb 18, 2022 · 23 comments
Open

Feature request for svg concept id in export/html report #821

oswalds opened this issue Feb 18, 2022 · 23 comments

Comments

@oswalds
Copy link

oswalds commented Feb 18, 2022

This feature request is to add the concept-id of diagram model elements to the svg export and generate an html report using the svg formatted diagrams.

The feature extends the functionality of the html_report, enabling more robust interaction with the diagram by leveraging the capabilities of svg. The image map interaction of the current html_report only allows selecting the rectangles associated with an element to display documentation/properties in a separate iframe and no extensibility of the html report. Adding the concept id to elements in the svg dom enables enhanced interaction with the diagram model elements within the browser environment. The svg representation of the diagram enables selecting any element or relationship, using the concept id to highlight the selected item, and enabling end-user extensions for interacting with the diagrams.

I have a working implementation that is shown in the attached videos and would be happy to submit a pull request for review.

The approach was to extend/modify a couple classes to enable adding an attribute of "archi-id" for elements added to the svg DOM managed by Batik and exported by the com.archimatetool.export.svg and ...svg.graphiti packages. A few classes in the ...editor.diagram packages also needed some modification to get the concept id for the "archi-id". The video archiId_exportSvg_20220218 shows the export and svg dom produced with the concept id as the attribute "archi-id" for elements and relationships. Text is exported as text nodes instead of paths and the viewbox is included to ensure an initial 1:1 scale.

The .../reports.html package is extended to add a maintSvg.stg and frameSvg.stg along with a new entry on the menu to generate either svg or imageMap format. The frameSvg.stg includes populating javascript objects with the nodes and edges of the diagram graph. The svg dom is exported with an onload script action to enable initialization of event listeners (for pan/zoom, pick highlights, etc.) and extensions that can use the concept ids to access graph elements. The second video (archiId_htmlReport_20220218) demonstrates the capabilities enabled with the svg support, including documentation/property views on relationships, image manipulation of panning/zooming, highlighting objects selected, and javascript extension to support tracing of graphs represented in the diagram.

archiSvgId_exportSvg_20220218.mp4
archiSvgId_htmlReport_20220218.mp4

(previously put earlier version of this feature request in issue #46)

@Phillipus
Copy link
Member

Thanks for this.

There's a lot going on here that needs to be unpacked. Using SVG digrams instead of PNG is certainly something that users have requested, as has being able to select connections. Then there's the additional functionality of showing paths between concepts and context menus. So, before getting into the technical code aspects, can we have a clear list of proposed features?

@oswalds
Copy link
Author

oswalds commented Feb 22, 2022

I guess there are 3 features: extend the SVG export to provide the concept ids on diagram elements, modify the HTML report to use the SVG export, and enable graph analysis. The 3 features with more detail:

  1. Modify the SVG export to add concept ids as an attribute (archi-id) on all model concepts in the diagram and include a few more attributes on the <svg> element:

    • Ideally, each diagram model object would be rendered as a group element <g> that would be attributed with the archi-id and all elements of the figure as individual components of the group (e.g., <path>, <rect>, <text>, etc.) within it. (I could not coerce Batik to do this, so every component of the figure shares the archi-id in my implementation).
    • By default, attribute the <svg> element with the viewbox attribute.
    • Add the width/height attributes to the <svg> element - this ensures the default view of the SVG is scaled 1:1 and simplifies zoom implementations.
    • By default, use SVG text instead of shapes for text (perhaps add this as an option for generating shapes for Mac - if it's still an issue).
    • Add the ability to include a reference to an "onload" javascript method - ideally as user input - when getting the SVG as a string (perhaps with the export, too). (In my implementation, I overloaded the getSVGString method to include the name of the onload method so it could be called from the HTML report in a "hardwired" fashion).
  2. Modify the HTML report to use the SVG version of the diagram:

    • Add an option in the Report menu to export using SVG vs. using image map.
    • Add a controller for the SVG diagram that provides:
      • pan on mousedown, mousemove, mouseup events
      • zoom on mousewheel events
      • highlight of a selected element(s)
      • display element info for selected element in "element" iframe.
  3. Enhance the HTML SVG report with graph capabilities (if the "onload" method can be specified as a user provided input, then all of the graph analysis could be enabled by the user's graph implementation, or other extensions):

    • Add scripting within the diagram view pages to populate arrays of nodes (concepts) & edges (connections):
      • nodes: [{name:'conceptName', properties:[{key:'propKey', value:'propValue},...]},...]
      • edges: [{name:'relationshipName', type:'relationshipType', sourceName:'sourceName', source:'sourceId', targetName:'targetName', directed:'true/false', accessType:'accessType'},...]
    • Add a javascript class to perform graph operations to add edges, simple depth first (shortest path), and breadth first operations.
    • Add context menu on element selection that enables:
      • selection of source/target nodes for a depth first analysis ("Path from here" & "Path to here")
      • selection of a source for a breadth first analysis ("Reachable from here")
      • a secondary menu for managing the connections used in path analysis:
        • shuttle relationship types from available (not used) to selected (used)
        • control whether directed relationships are analyzed
    • Add a tab for displaying the results of the graph analysis
      • displays ordered list of nodes with properties encountered during trace
      • nodes are constructed with <a> reference to view element documentation/properties

You're right, there is a lot to unpack. I'm sure I've glossed over things and may have missed others, but got the gist of it, I think.

@Phillipus
Copy link
Member

Thanks for the details.

Do you have a fork of the Archi code on GitHub with your changes in that I could browse?

@oswalds
Copy link
Author

oswalds commented Feb 25, 2022 via email

@Phillipus
Copy link
Member

Phillipus commented Feb 25, 2022

@oswalds I had a quick look at your branch:

oswalds@b59dd0b

There should be a different way to get/set the IDs of Figures. The Figure classes should not have their paintFigure() methods modified. In fact, the main Archi code should know nothing about anything downstream like SVG.

Perhaps there is a better way to map concept IDs to figures? I think the IDs can be found at export time.

@Phillipus
Copy link
Member

Phillipus commented Feb 25, 2022

IDs of objects can be collected at the point of SVG export from the main root figure:

private void getIDs(IFigure figure) {
    if(figure instanceof IArchimateFigure) {
        System.out.println(((IArchimateFigure)figure).getDiagramModelArchimateObject().getId());
    }
    
    for(Object child : figure.getChildren()) {
        getIDs((IFigure)child);
    }
}

Perhaps these could be collected in a lookup table and mapped to figures?

@oswalds
Copy link
Author

oswalds commented Feb 28, 2022

I don't think that's possible with the use of the Batik SVG project - at least, I can't think of a way. The SVG elements are created during the painting by the Graphics object, and as far as I can tell, by the time it is rendering the figure, it only has access to the geometric objects that make up the figure, and there's no identifier that could be used to correlate back to the figure via a lookup table. Here's a high-level/abstract view of how I interpret the processing - in SVG format populated with archi-id :)
svgId

The Batik DOMGroupManager enables modification of the element prior to adding it to the SVG DOM, so that's where the attribute can be added for the concept-id.

The approach I came up with was to introduce an interface (ISvgIdentifiable) in the diagram.figure package that enables setting the concept-id in the Graphics class implementing the interface. This lets the id be cached by the Graphics object (SVGIdentifiableGraphics2D) until the element is added to the SVG DOM by the Batik DOMGroupManager. There are 6 diagram.figure classes modified to implement the interface: AbstractContainerFigure, AbstractDiagramConnectionFigure, elements.JunctionFigure, diagram.DiagramModelReferenceFigure, diagram.GroupFigure, and diagram.NoteFigure.

I also posted the updates to the HTML report for using the SVG export with the archi-ids.

@Phillipus
Copy link
Member

The approach I came up with was to introduce an interface (ISvgIdentifiable) in the diagram.figure package that enables setting the concept-id in the Graphics class implementing the interface. This lets the id be cached by the Graphics object (SVGIdentifiableGraphics2D) until the element is added to the SVG DOM by the Batik DOMGroupManager. There are 6 diagram.figure classes modified to implement the interface: AbstractContainerFigure, AbstractDiagramConnectionFigure, elements.JunctionFigure, diagram.DiagramModelReferenceFigure, diagram.GroupFigure, and diagram.NoteFigure.

I really don't like modifying these core classes to support a downstream plug-in. It makes maintenance harder and there is a hard coupling between the core graphics code and downstream SVG rendering. I think it's worth thinking about an alternate way to do this, or at least something more generic in the graphics classes. Setting the current ID temporarily seems a bit of a kludge.

@oswalds
Copy link
Author

oswalds commented Mar 4, 2022

I really don't like modifying these core classes to support a downstream plug-in. It makes maintenance harder ...

I see that there has been some accommodation for downstream SVG processing in quite a few classes (40+) where the line width is set to prevent SVG rendering "overspill."

... and there is a hard coupling between the core graphics code and downstream SVG rendering. I think it's worth thinking about an alternate way to do this, or at least something more generic in the graphics classes. Setting the current ID temporarily seems a bit of a kludge.

The graphics classes are called from the core diagram figures and only receive geometric primitives to render. For the SVG export, the SVGGraphics2D classes render the geometric primitives in the same way the draw2d classes render the geometric primitives to the canvas. The only place where attributes can be added to the DOM is through the methods provided by the DOMGroupManager after the geometric primitive is rendered as an SVG element. To get model attributes (e.g., the concept-id) assigned to the geometric primitives, a bridge is required between the time when the rendering begins and when the element is added to the DOM. I implemented that bridge in the extended SVGGraphics2D class by introducing a new interface in the diagram figures package which the graphics classes can implement. The figure drawing methods then use the loose coupling of the interface to determine if it's possible to set the concept id into the graphics provider. So, the export svg project is dependent upon that interface in the editor project (not the other way around).

I took some time to explore alternatives to figure out a more elegant solution, but I don't see another way to support the feature outside of writing a new SVG export engine, which would be a lot of work. In my opinion, the approach does leverage the generic capabilities of the SVGGraphics2D package with minimal maintenance impact (1 new interface, 6 class modifications with just a few lines of code) to the archimatetool diagram editor package.

So, I guess it's a matter of value vs. effort. I think the value provided by adding the concept-id to the SVG DOM combined with the HTML report modifications is significant. Beyond the demonstration of the graph analysis, new views on the diagrams (like heat maps) are enabled when the SVG HTML report can access components in the diagrams and link them to other information sources (e.g., application catalog, project portfolio, system monitoring, etc.). I think this provides a new and powerful capability for Archi.

@Phillipus
Copy link
Member

This is a kludge:

   protected void paintFigure(Graphics graphics) {
        if (graphics instanceof ISvgIdentifiable) {
        	IDiagramModelObject dmo = this.getDiagramModelObject();
        	String id = "";
        	try {
        		IDiagramModelArchimateObject dmao = (IDiagramModelArchimateObject)dmo;
        		id = dmao.getArchimateConcept().getId();
        	} catch (Exception e) {
        		id = "";
        	}        	
        	((ISvgIdentifiable)graphics).setId(id);
        }  

There needs to be another way to set IDs.

@Phillipus
Copy link
Member

Phillipus commented Mar 4, 2022

I see that there has been some accommodation for downstream SVG processing in quite a few classes (40+) where the line width is set to prevent SVG rendering "overspill."

A comment that is copied and pasted several times to remind myself of one of the side effects (and which may actually no longer apply) is not "accommodation for downstream SVG processing"

And when I find myself having to defend my code like this I think it's time for me to move on.

Thank-you for your offer of contribution but unless there is a better way of mapping IDs I won't be spending any more time on this.

@Phillipus
Copy link
Member

To summarise the issue:

  • Concept and diagram object IDs need to be written to the corresponding graphical shapes in the SVG file
  • These IDs can be accessed from the Draw2d Figure classes via getDiagramModelArchimateObject() or getDiagramModelObject()
  • The IDs can be written by extending the Batik SVGGraphics2D class and adding them in the DOMGroupManager#addElement() methods

The problem is mapping the IDs to the elements which it seems can only be done when the Figures are painted here:

figure.paint(graphicsAdaptor);

Ideally it would be good to do the mapping only in the SVG Export code without interfering with the core Archi diagram code. I think some more investigation should be done in case there is a way to do this.

If it turns out that this is not possible, then we should consider a more generic approach to notifying when a paint event occurs to interested classes, a bit like SWT's Paint Listener.

I've created a new branch, painfigure-listener, with some experimental code:

  • Add a com.archimatetool.editor.diagram.figures.IPaintFigureListener Interface
  • In diagram figure classes in their paintFigure(Graphics) methods if Graphics implements IPaintFigureListener then call its notifyPaint(IFigure) method
  • ExtendedGraphicsToGraphics2DAdaptor implements IPaintFigureListener and passes the IFigure in notifyPaint(IFigure) to ExtendedSVGGraphics2D#setCurrentFigure(IFigure)
  • ExtendedSVGGraphics2D extends SVGGraphics2D and has a setCurrentFigure(IFigure) method where the current Figure is stored and can be queried for the underlying ID in DOMGroupManager#addElement()

This code is much more generic and is not concerned with IDs but rather generally notifying that a paint event is occurring for a given figure.

As I said, I think we should continue to explore if it's possible to map IDs without resorting to this.

@oswalds
Copy link
Author

oswalds commented Mar 7, 2022

I explored directly traversing the figure tree, painting each object below the layers (SVGFigure in my fork) but it would only work for top-level figures (with nested components getting a top-level id) and it isn't a viable approach. The nested figures would need to have their transformation tracked so they align spatially, and I don't know how the upper level figures could be painted without duplication of elements in the DOM.

I'm doubtful that there is a way to map the IDs. I think your paintfigure-listener design is the solution.

If you go with the paintfigure-listener, I think the connection id should be set in the setConceptId method of the custom DOMGroupManager, something like (works in my code, but may not be concise enough):

else if (figure instanceof IDiagramConnectionFigure) {
IDiagramModelConnection idmc = ((IDiagramConnectionFigure)figure).getModelConnection();
if (idmc instanceof IDiagramModelArchimateConnection) {
id = ((IDiagramModelArchimateConnection)idmc).getArchimateConcept().getId();
}
}

Also, I think there needs to be a way to distinguish between an archi-id that references a concept, and one that references a view (DiagramModelReferenceFigure) or something else (if there are others). Perhaps a convention of prefixing the archi-id attribute value with "view:" (which is what I had done), e.g., <rect ... archi-id="view:id-5e5009...">.

@Phillipus
Copy link
Member

I've pushed some more changes and refactored/rebased some branches:

  1. Pushed to master branch a new commit for a convenience interface IArchimateConnectionFigure
  2. Refactored and rebased the paintfigure-listener branch/commit to add only changes in the core figure code
  3. On top of the paintfigure-listener branch added a new branch, extended-svg with only SVG changes

AbstractExportProvider has an optional boolean so we can use original code or use extended code:

// TODO: allow to set this option
private boolean isExtended = true;

ExtendedSVGGraphics2D adds IDs for objects, connections and diagram refs. ID prefixes are set like this but can use other system if required:

private static final String CONCEPT_ID = "concept-id";
private static final String VIEW_ID = "view-id";

@oswalds
Copy link
Author

oswalds commented Mar 14, 2022

I spent some time over the weekend to incorporate into my PoC of the HTML SVG Report the changes you made and it looks good to me. There are a few additions to the export.svg package I think are necessary to enable functionality in the HTML SVG Report.

  1. Add to the svg root the width and height attributes in the SVGExportProvider class - this will ensure that the svg's default rendering is scaled at 1:1 and simplifies controls for panning and zooming.
  2. Add a method to enable setting an onload attribute to the svg root in the SVGExportProvider class - this allows the consumer application of the svg to initialize its javascript methods. In my PoC, the method was an overload of the getSVGString, e.g., getSVGString(IdiagramModel diagramModel, String onloadJS) which also always sets the viewbox, though perhaps that should still be passed.
  3. Provide a way to set the text handling boolean in the AbstractExportProvider so that text is not drawn as shapes (perhaps always with the isExtended option and the SVGGraphics2D instantiation).

It might be a good feature if these were also added as preferences set in the export dialog.

@Phillipus
Copy link
Member

Phillipus commented Mar 15, 2022

(1) Not setting the viewBox attribute seems to achieve the same result

(2) I don't understand what String onloadJS is?

(3) There was a problem with some fonts not being displayed correctly so this was set to draw text as shapes - see #628 (comment)

@oswalds
Copy link
Author

oswalds commented Mar 28, 2022

Sorry for the delay ...
The intent of these 3 items is to support the generation of the HTML report with an embedded SVG DOM. The first 2 reflect how I implemented the HTML report.

  1. In my PoC, I was unable to get the SVG to properly display when embedded in the HTML (frame.stg) - the simplest solution for me was to set viewBox along with the width & height.
  2. SVG event processing supports the "onload" event. The intent is for the onloadJS value to be set on the SVG root element, e.g., root.setAttributeNS(null,"onload",onloadJS);. When the SVG is loaded, the script specified in the "onload" attribute will be executed. The script I specified in my PoC is where I initialize all the event listeners for interacting with the SVG DOM. I also envision this as a potential means for the user to extend the functionality of the SVG interaction.
  3. In our environment, this is not an issue and the quality of the stroked characters is considerably less than the fonts. The stroking of characters also bloats the size of the SVG export. I think it would be a good option to provide the end user.

@Phillipus
Copy link
Member

I've refactored and rebased the extended-svg branch.

  1. Add a ExtendedSVGExportProvider class
  2. Added new options to AbstractExportProvider

So you should be able to do something like:

ExtendedSVGExportProvider provider = new ExtendedSVGExportProvider();
provider.setDrawTextAsShapes(false);

Map<String, String> atts = new HashMap<>();
atts.put("onload", "some onload string");
provider.setElementRootAttributes(atts);

provider.doSomethingElse()...

@jbsarrodie
Copy link
Member

Hi,

Discovering this issue and going through it. Some remarks:

  • Very interesting topic. I like the ability to interact with the diagram and select relationships.
  • I agree with Phil on the code impact and the proposed listener idea
  • Graph analysis is nice but should not be included as part of the PR IMHO (would need a dedicated discussion to shape its goals)
  • A first good step could be to match current feature set and only provide a way to select relationships.
  • In my experience, most of the time, people who use the HTML export don't have the fonts installed locally, so we have to take care of this in a way or another, and simply removing it is not an option.
  • It seems that we should provide a simple way for people to extend HTML export by adding "JS plugins" (a bit like we added the search and zoom features recently)

@oswalds
Copy link
Author

oswalds commented Apr 8, 2022

I haven't yet tested Phil's latest refactoring, but it looks like it wraps up the features needed (in my opinion) to address the 1st enhancement I outlined in my post on Feb21.
The 2nd item outlines the minimum enhancements for the SVG into the HTML Report (a controller to highlight picked items, pan, zoom, etc.). I’d prefer to see the tabs in the diagram iframe at the top (as in my demo). Perhaps the dialog that prompts for the report location could be modified to enable options for choosing SVG vs ImageMap, handling the fonts, etc.
I agree the Graph implementation isn't a priority (item 3) - but I think some way to make the nodes & edges data available to the JS plugins is important for enabling users to implement their own analyses (e.g., graph analysis, heat maps, etc.).
I really like the concept of extensibility via JS plugins!

@Phillipus
Copy link
Member

@oswalds I think the way to proceed is for you to test the changes in ExtendedSVGExportProvider and then we can figure out what else needs to change to support this.

@oswalds
Copy link
Author

oswalds commented Aug 23, 2023

circling back on this request. We've been using the ExtendedSVGExportProvider (from extended-svg branch) over the last year, upgrading with each release for our modifications to the html reporting (com.archimate.reports) and have not encountered any issues.

@Phillipus
Copy link
Member

This seems to have fallen by the wayside. I'll take a look at it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants