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

Possibility to export links in SVG? #148

Open
mauricioszabo opened this issue Mar 31, 2021 · 10 comments
Open

Possibility to export links in SVG? #148

mauricioszabo opened this issue Mar 31, 2021 · 10 comments

Comments

@mauricioszabo
Copy link

Hi, I'm using Nomnoml to experiment things on the Atom editor, and one killer feature for me would be to be able to export <a...> links in SVG - this would allow me to generate a diagram of a specific piece of code, for example, and then add interactivity (like, clicking on a box to go to the definition of that current function/method).

Is it possible? Would it be too hard to implement? If it's not so hard, I maybe could handle the implementation, just point me to the right place :)

Thanks for creating this great tool by the way :)

@skanaar
Copy link
Owner

skanaar commented Apr 9, 2021

Have a look at #139 that discusses the same use case.

Basically, the svg shapes have data-name attributes that can be leverages to make interactive diagrams.

document.querySelector('svg').onclick = function (e) { console.log(e.target.attributes['data-name']) }

How to map the node name to a link have to reside somewhere. If your application have access to the diagram source you can keep that mapping in comments within the nomnoml document, how that is solved is up to you.

@mauricioszabo
Copy link
Author

@skanaar that's a good start, but not really enough. I'm doing a diagram that have repeating names (think of java packages and class names, with packages pointing to multiple class names). This means that, only by looking at a single element, I can't know for sure what's the "package" because that resides on another node.

I could change the code to keep the java package on, but then the nodes become too big to be useful. Also, I believe I could solve this same issue by replacing e.target.attributes['data-name'] with e.target.innerHTML, right?

Maybe if there's a way to use another string as data-name instead of always using name we passed...

@skanaar
Copy link
Owner

skanaar commented Apr 13, 2021

I am open to add features to support these kind of things.

Perhaps we could use the node type brackets for attaching additional information:

[<class link=xyz> MyClass]

Pull Requests are always welcome if you would like to take a shot at implementing something like this?

@mauricioszabo
Copy link
Author

Do you think like:

[<data name="org.package.MyClass"> MyClass]

To allow adding a data-name is a good syntax? This way you could add any data-* attributes just by varying the brackets.

@skanaar
Copy link
Owner

skanaar commented Apr 13, 2021

I think there is a potential demand for other meta-data that can control the layout and rendering in different ways.

If we add key-values to the node type field then it would probably be a mistake to say that every key-value is exported verbatim to data- attributes. It might interfere with other uses.

@darrencruse
Copy link

fyi I've taken a stab to see if I could implement something like the above re this issue and #139 and (my) #121

atm it's just on local branch I've not made a PR yet

The reason I decided to try is I have similar issues as @mauricioszabo that keep the "data-name" approach from working for me (we have super long names so long that I break them onto multiple lines - but what comes out as the "data-name" is only the part before the first line break which means it's not unique esp. since our long names often start with a common prefix).

Screen Shot 2021-09-25 at 11 44 53 AM

i.e. in the above "data-name" comes as "Velocity Place Plots:" and in the larger diagram there can be other boxes with that same prefix.

Another doubt I had with the "data-name" approach is do I follow there's no way for the cursor to change over the clickable shape with that? Or e.g. to have like a hover style change the colors when you are over a clickable shape?

For the above reasons the first thing I tried (building off @skanaar's comments above) was just if I could assign "id" and (css) "class" using that approach e.g.:

[<abstract id="myAbstract" class="myClass">Marauder]

with my local changes the above assigns that id and css class to the rectangle (but not the text):

  <rect x="36.5" y="36.5" height="31" width="92" id="myAbstract" class="myClass" data-name="Marauder" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;stroke-width:3;"></rect>
  <text x="82.5" y="58" style="fill: #33322E;font:normal italic 12pt Helvetica, Helvetica, sans-serif;text-anchor: middle;" data-name="Marauder">Marauder</text>

I confess I realize now I'd had a misconception that the SVG uses nesting more like html does i.e. I'd imagined the generated SVG being more like:

  <rect x="36.5" y="36.5" height="31" width="92" id="myAbstract" class="myClass" data-name="Marauder" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;stroke-width:3;">
    <text x="82.5" y="58" style="fill: #33322E;font:normal italic 12pt Helvetica, Helvetica, sans-serif;text-anchor: middle;" data-name="Marauder">Marauder</text>
  </rect>

i.e. the id and css class feels more logical that way right

googling I see there is support for SVG "groups" that use hierarchy?

but obviously nomnoml is not using those right... could/should it?

building on the above I added support for "href" in addition to "id" and "class" e.g.

[<hello href="https://www.amazon.com">Arrrrrr]

but similiar to how I did with id and class I'm currently just wrapping the initial element in an anchor:

  <a xlink:href="https://www.amazon.com">
    <rect x="50.5" y="344.5" height="31" width="63"  data-name="Arrrrrr" style="stroke:teal;fill:#ffa;stroke-dasharray:none;stroke-width:3;"></rect>
  </a>
  <text x="82" y="366" style="fill: teal;font:normal  12pt Helvetica, Helvetica, sans-serif;text-anchor: middle;" data-name="Arrrrrr">Arrrrrr</text>

that's working - the biggest downfall is you have to click right on the border you lose the hand cursor when you are over what's inside

so similar with id and class I now realize I'd mistakenly imagined the SVG being more like this:

  <a xlink:href="https://www.amazon.com">
    <rect x="50.5" y="344.5" height="31" width="63"  data-name="Arrrrrr" style="stroke:teal;fill:#ffa;stroke-dasharray:none;stroke-width:3;">
      <text x="82" y="366" style="fill: teal;font:normal  12pt Helvetica, Helvetica, sans-serif;text-anchor: middle;" data-name="Arrrrrr">Arrrrrr</text>
    </rect>
  </a>

anyway atm I'm a little unsure where this leaves me

tbh I struggled to get the above working but I've learned quite a bit

not sure how close I am to something worthy of merging happy to hear feedback

@darrencruse
Copy link

darrencruse commented Sep 27, 2021

@skanaar following up -

I googled and learned a little more about groups and the <g> tag

haven't changed the code to do it yet but just thinking and editing that test/output.svg by hand I'm leaning toward making it so e.g.:

[<class id="myShip" class="myShipClass" href="https://www.google.com">Ship|
  [<class id="myPirate" class="myPirateClass" href="https://www.newtek.com">Pirate|
    [a]--[b]
    [a]-:>[c]
  ]
]

would generate like:

  <g id="myShip" class="myShipClass">
    <a xlink:href="https://www.google.com">
      <rect x="28.5" y="28.5" height="276" width="203" data-name="Ship" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;stroke-width:3;"></rect>
      <path d="M28.5 59.5 L231.5 59.5" data-name="Ship" style="stroke:#33322E;fill:none;stroke-dasharray:none;stroke-width:3;"></path>
      <text x="130" y="50" style="fill: #33322E;font:bold  12pt Helvetica, Helvetica, sans-serif;text-anchor: middle;" data-name="Ship">Ship</text>
      <g id="myPirate" class="myPirateClass">
        <a xlink:href="https://www.newtek.com">
          <rect x="56.5" y="87.5" height="189" width="147" data-name="Pirate" style="stroke:#33322E;fill:#fdf6e3;stroke-dasharray:none;stroke-width:3;"></rect>
          <path d="M56.5 118.5 L203.5 118.5" data-name="Pirate" style="stroke:#33322E;fill:none;stroke-dasharray:none;stroke-width:3;"></path>
          <text x="130" y="109" style="fill: #33322E;font:bold  12pt Helvetica, Helvetica, sans-serif;text-anchor: middle;" data-name="Pirate">Pirate</text>
        </a>
      </g>
    </a>
  </g>

re the implementation I'm not 100% sure yet

I guess the full blown approach would be if the GraphicsSvg Element's "content" could be not just a string but a list of child Elements i.e. so that "elements" list became a true tree not just a list

I'm a little worried that might get into more changes than I can handle (any thoughts?).

At first instead of that I was thinking maybe I'd just add to Element a "tagType" e.g. "opening", "closing", "empty" (empty being the default)

And just changing renderer.ts "renderNode" function so that it adds "opening" <g>,<a> Elements to the elements list at the top of the function, and the corresponding "closing" </a>, </g> elements at the end of the function before it returns.

i.e. where function toHtml would look at this "tagType" and only put out the opening, closing, or both tags

and where the related calls on the canvas side will just no-op

That approach looks super easy (ignoring indentation atm) whereas changing elements to be a true tree I thought might add more effort/complexity/risk of me breaking things.

@skanaar
Copy link
Owner

skanaar commented Sep 28, 2021

The current SVG approach of outputting a flat list of element is indeed a problem.
I think the proper way to go about it is to output a real hierarchy - it would also allow us to apply layout transforms in a nicer way.

But you proposed approach might be a nice step toward that goal, it will be an improvement.
I would love to see your branch when you feel it is presentable.

I am on paternity leave right now and is therefore a bit preoccupied.

@darrencruse
Copy link

darrencruse commented Sep 28, 2021

ok thanks

I can give a little more thought to the Element hierarchy approach if I think it's easy I can try otherwise stick with that other "open"/"close" element approach that leaves things a list

I would think I should be able to post a PR in 2-3 weeks or so (these changes are just a weekend thing so not 100% sure but hopefully).

The one additional thing I might have shared is fwiw I did not change your grammar at all to read these new "attributes" I just extended the regular expression bit that was already there parsing the angle bracketed classifier/node type name.

Also at first I put them under a key "attributes" on the parse tree node but later I noticed downstream the name "attributes" was becoming overloaded/confusing between these "attributes" and other "attributes" so I renamed the key "metadata" partly referring to your April 13 comment above you used the name "metadata" (certainly can change just fwiw).

Also I should say atm I used "href" instead of "link" as you'd mentioned above on the thought that I would probably be adding "target" i.e. I'm betting people will want the ability with hyperlinks to control if it displays in the same browser window versus doing target="_blank" to make it open in a new tab. I don't have a strong preference though let me know if you still prefer more abstracted as "link" and if so do you have any thoughts on how to let them specify to open in a new tab or not?

Anyway this is the relevant section re the parsing part (in nomnoml.jison):

class
  : '[' parts ']'          {
           var type = 'CLASS';
           var id = $2[0][0];
           var metadata = {}
           var typeMatch = $2[0][0].match('^\s*<([a-z]*)([^>]*)>(.*)');
           if (typeMatch) {
               type = typeMatch[1].toUpperCase();
               if (typeMatch[2]) {
                 metadata = typeMatch[2].trim().split(' ').reduce((accum, nameAndValue) => {
                   var attrMatch = nameAndValue.trim().match('(id|class|href)\s*=\s*[\'"]([^\'"]*)[\'"]');
                   return attrMatch ? { ...accum, [attrMatch[1]]: attrMatch[2] } : accum 
                 }, {})
               }
               id = typeMatch[3].trim();
           }
           $2[0][0] = id;
           $$ = {type:type, metadata:metadata, id:id, parts:$2};
  };

And congrats on the baby!!

@skanaar
Copy link
Owner

skanaar commented Apr 11, 2022

Finally nomnoml generates a proper SVG hierarchy with the release of v1.5.0 🎉

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