jonascarpay / calligraphy

haskell source code visualizer
BSD 3-Clause "New" or "Revised" License
97 stars 13 forks source link

Support Mermaid output #24

Closed jonascarpay closed 1 year ago

jonascarpay commented 1 year ago

Fixes #13 Based on #23

Proof of concept for Mermaid rendering, mostly here just to test #23.

Much like GraphViz, Mermaid comes with a set of quirks and different things that look good and bad. We'll have to figure out what to go for, and what options to support. @Kleidukos input/thoughts are very welcome here.

Here, by way of experiment, I've implemented rendering as more of a bigraph than would be possible with GraphViz, since Mermaid has support for drawing edges between subgraphs. This better reflects the structure of the source file, but doesn't allow nodes to be styled based on the kind of declaration. The way subgraphs are styled is also just kind of annoying to look at. Overall, I don't think this makes for a good default, but I like that we're able to represent to source structure this directly.

flowchart TD
    subgraph module_0 [Calligraphy]
        subgraph node_0 [AppConfig]
            subgraph node_1 [AppConfig]
                node_2[debugConfig]
                node_3[dependencyFilterConfig]
                node_4[edgeFilterConfig]
                node_5[graphVizConfig]
                node_6[nodeFilterConfig]
                node_7[outputConfig]
                node_8[renderConfig]
                node_9[searchConfig]
            end
        end
        subgraph node_10 [DebugConfig]
            subgraph node_11 [DebugConfig]
                node_12[dumpFinal]
                node_13[dumpHieFile]
                node_14[dumpLexicalTree]
            end
        end
        subgraph node_15 [OutputConfig]
            subgraph node_16 [OutputConfig]
                node_17[outputDotPath]
                node_18[outputEngine]
                node_19[outputMermaidPath]
                node_20[outputPngPath]
                node_21[outputStdout]
                node_22[outputSvgPath]
            end
        end
        subgraph node_23 [StdoutFormat]
            node_24[StdoutDot]
            node_25[StdoutMermaid]
            node_26[StdoutNone]
        end
        subgraph node_27 [main]
            node_28[versionP]
        end
        subgraph node_29 [mainWithConfig]
            node_30[cgCleaned]
            node_31[cgCollapsed]
            node_32[debug]
            node_33[renderConfig']
        end
        subgraph node_34 [output]
            node_35[hasOutput]
            node_36[runDot]
        end
        node_37[pConfig]
        node_38[pDebugConfig]
        node_39[pOutputConfig]
        node_40[pStdoutFormat]
        node_41[printDie]
        node_42[printStderr]
    end
    node_2 --> node_10
    node_21 --> node_23
    node_27 --> node_28
    node_27 --> node_29
    node_27 --> node_37
    node_29 --> node_1
    node_29 --> node_12
    node_29 --> node_13
    node_29 --> node_14
    node_29 --> node_30
    node_29 --> node_31
    node_29 --> node_32
    node_29 --> node_33
    node_29 --> node_34
    node_29 --> node_41
    node_32 --> node_42
    node_34 --> node_16
    node_34 --> node_24
    node_34 --> node_25
    node_34 --> node_26
    node_34 --> node_35
    node_34 --> node_36
    node_35 --> node_16
    node_35 --> node_26
    node_37 --> node_1
    node_37 --> node_38
    node_37 --> node_39
    node_38 --> node_11
    node_39 --> node_16
    node_39 --> node_40
    node_40 --> node_24
    node_40 --> node_25
    node_40 --> node_26
    node_41 --> node_42
    node_7 --> node_15
    node_29 -.-> node_0
    node_32 -.-> node_10
    node_34 -.-> node_15
    node_35 -.-> node_15
    node_37 -.-> node_0
    node_38 -.-> node_10
    node_39 -.-> node_15
    node_40 -.-> node_23
flowchart TD
    subgraph module_0 [Calligraphy]
        subgraph node_0 [AppConfig]
            subgraph node_1 [AppConfig]
                node_2[debugConfig]
                node_3[dependencyFilterConfig]
                node_4[edgeFilterConfig]
                node_5[graphVizConfig]
                node_6[nodeFilterConfig]
                node_7[outputConfig]
                node_8[renderConfig]
                node_9[searchConfig]
            end
        end
        subgraph node_10 [DebugConfig]
            subgraph node_11 [DebugConfig]
                node_12[dumpFinal]
                node_13[dumpHieFile]
                node_14[dumpLexicalTree]
            end
        end
        subgraph node_15 [OutputConfig]
            subgraph node_16 [OutputConfig]
                node_17[outputDotPath]
                node_18[outputEngine]
                node_19[outputMermaidPath]
                node_20[outputPngPath]
                node_21[outputStdout]
                node_22[outputSvgPath]
            end
        end
        subgraph node_23 [StdoutFormat]
            node_24[StdoutDot]
            node_25[StdoutMermaid]
            node_26[StdoutNone]
        end
        subgraph node_27 [main]
            node_28[versionP]
        end
        subgraph node_29 [mainWithConfig]
            node_30[cgCleaned]
            node_31[cgCollapsed]
            node_32[debug]
            node_33[renderConfig']
        end
        subgraph node_34 [output]
            node_35[hasOutput]
            node_36[runDot]
        end
        node_37[pConfig]
        node_38[pDebugConfig]
        node_39[pOutputConfig]
        node_40[pStdoutFormat]
        node_41[printDie]
        node_42[printStderr]
    end
    node_2 --> node_10
    node_21 --> node_23
    node_27 --> node_28
    node_27 --> node_29
    node_27 --> node_37
    node_29 --> node_1
    node_29 --> node_12
    node_29 --> node_13
    node_29 --> node_14
    node_29 --> node_30
    node_29 --> node_31
    node_29 --> node_32
    node_29 --> node_33
    node_29 --> node_34
    node_29 --> node_41
    node_32 --> node_42
    node_34 --> node_16
    node_34 --> node_24
    node_34 --> node_25
    node_34 --> node_26
    node_34 --> node_35
    node_34 --> node_36
    node_35 --> node_16
    node_35 --> node_26
    node_37 --> node_1
    node_37 --> node_38
    node_37 --> node_39
    node_38 --> node_11
    node_39 --> node_16
    node_39 --> node_40
    node_40 --> node_24
    node_40 --> node_25
    node_40 --> node_26
    node_41 --> node_42
    node_7 --> node_15
    node_29 -.-> node_0
    node_32 -.-> node_10
    node_34 -.-> node_15
    node_35 -.-> node_15
    node_37 -.-> node_0
    node_38 -.-> node_10
    node_39 -.-> node_15
    node_40 -.-> node_23
jonascarpay commented 1 year ago

The graph above, cleaned up with --no-cluster-modules and --collapse-data. It looks much better, but does show that the bigraph structure makes edges between a parent and its children a little awkward.

flowchart TD
    node_0[AppConfig]
    node_10[DebugConfig]
    node_15[OutputConfig]
    node_23[StdoutFormat]
    subgraph node_27 [main]
        node_28[versionP]
    end
    subgraph node_29 [mainWithConfig]
        node_30[cgCleaned]
        node_31[cgCollapsed]
        node_32[debug]
        node_33[renderConfig']
    end
    subgraph node_34 [output]
        node_35[hasOutput]
        node_36[runDot]
    end
    node_37[pConfig]
    node_38[pDebugConfig]
    node_39[pOutputConfig]
    node_40[pStdoutFormat]
    node_41[printDie]
    node_42[printStderr]
    node_0 --> node_10
    node_0 --> node_15
    node_15 --> node_23
    node_27 --> node_28
    node_27 --> node_29
    node_27 --> node_37
    node_29 --> node_0
    node_29 --> node_10
    node_29 --> node_30
    node_29 --> node_31
    node_29 --> node_32
    node_29 --> node_33
    node_29 --> node_34
    node_29 --> node_41
    node_32 --> node_42
    node_34 --> node_15
    node_34 --> node_23
    node_34 --> node_35
    node_34 --> node_36
    node_35 --> node_15
    node_35 --> node_23
    node_37 --> node_0
    node_37 --> node_38
    node_37 --> node_39
    node_38 --> node_10
    node_39 --> node_15
    node_39 --> node_40
    node_40 --> node_23
    node_41 --> node_42
    node_32 -.-> node_10
Kleidukos commented 1 year ago

Oh I much prefer the second rendering :D

jonascarpay commented 1 year ago

It's not stable yet, but the https://mermaid.js.org/syntax/c4c.html look like a better fit than the flowchart diagrams. Worth keeping an eye on.

Kleidukos commented 1 year ago

@jonascarpay I would advise having the flowchart + these options supported for the Mermaid format, and advertise C4 as being experimental. :)

jonascarpay commented 1 year ago

Oh I much prefer the second rendering :D

I think in large part that's due to there being fewer nodes. I've been playing around with different output styles and in general, mermaid is just really bad with larger graphs it seems :( On top of that, certain things will cause graphical issues, above you can see a couple of places where the arrowhead doesn't connect properly (although for whatever reason that issue is not present on https://mermaid.live/, so maybe github is using an older version or something) So, I'm still looking for the golden path here.

The good news is that for simple graphs, I think it's pretty decent!

cabal exec calligraphy -- '*.GraphViz' --stdout-mermaid --collapse-data

flowchart TD
    node_0[.=]
    style node_0 stroke:#777,fill-opacity:0,stroke-dasharray: 5 5
    node_1([Attributes])
    style node_1 stroke:#777,fill-opacity:0,stroke-dasharray: 5 5
    node_2([GraphVizConfig])
    style node_2 stroke:#777,fill-opacity:0
    node_8[edge]
    style node_8 stroke:#777,fill-opacity:0,stroke-dasharray: 5 5
    node_9[nodeShape]
    style node_9 stroke:#777,fill-opacity:0,stroke-dasharray: 5 5
    node_10[pGraphVizConfig]
    style node_10 stroke:#777,fill-opacity:0
    node_11[renderAttrs]
    style node_11 stroke:#777,fill-opacity:0,stroke-dasharray: 5 5
    subgraph node_12[renderGraphViz]
        style node_12 stoke:#777,fill-opacity:0
        node_13[printModule]
        style node_13 stroke:#777,fill-opacity:0,stroke-dasharray: 5 5
        subgraph node_14[printNode]
            style node_14 stoke:#777,fill-opacity:0,stroke-dasharray: 5 5
            node_15[attrs]
            style node_15 stroke:#777,fill-opacity:0,stroke-dasharray: 5 5
            node_16[nodeStyle]
            style node_16 stroke:#777,fill-opacity:0,stroke-dasharray: 5 5
        end
        subgraph node_17[printTree]
            style node_17 stoke:#777,fill-opacity:0,stroke-dasharray: 5 5
            node_18[wrapCluster]
            style node_18 stroke:#777,fill-opacity:0,stroke-dasharray: 5 5
        end
    end
    node_10 --> node_2
    node_12 --> node_0
    node_12 --> node_13
    node_12 --> node_17
    node_12 --> node_2
    node_12 --> node_8
    node_13 --> node_17
    node_14 --> node_11
    node_14 --> node_15
    node_15 --> node_0
    node_15 --> node_16
    node_15 --> node_9
    node_17 --> node_0
    node_17 --> node_14
    node_17 --> node_18
    node_17 --> node_8
    node_8 --> node_11
    node_11 -.-> node_1
    node_15 -.-> node_1
    node_8 -.-> node_1
Kleidukos commented 1 year ago

so maybe github is using an older version or something

Not a problem, we can always tell people in the README that they can install the mermaid tool locally and get the .png directly instead

From 4 days ago: https://github.com/community/community/discussions/37498#discussioncomment-5271639

If nothing changed in the meantime, GitHub would indeed still be on Mermaid 9.x

jonascarpay commented 1 year ago

I think I'm happy with where this is PR is. It's bad at large graphs, and there are certain graphs that for whatever reason just don't render, but I think those are just fundamental issues with Mermaid, and this is about as good as we can expect.

So, the takeaway is that I think that you should generally prefer graphviz where possible. I added a --stdout-svg flag to make it quicker to make inline SVGs in HTML, but unfortunately GFM does not support inline SVGs.