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

Flowchart arrow heads are not colored according to linkStyle settings #1236

Open
GDFaber opened this issue Feb 2, 2020 · 16 comments
Open

Flowchart arrow heads are not colored according to linkStyle settings #1236

GDFaber opened this issue Feb 2, 2020 · 16 comments
Labels
Contributor needed Graph: Flow Status: Approved Is ready to be worked on Type: Bug / Error Something isn't working or is incorrect

Comments

@GDFaber
Copy link
Member

GDFaber commented Feb 2, 2020

Describe the bug
The arrow head color is not affected by link style stroke settings.

To Reproduce
Steps to reproduce the behavior:
see live editor example

Expected behavior
Arrow heads should be colored according to link style settings as well.

Additional context
Tested with mermaid 8.4.6

@GDFaber GDFaber added Type: Bug / Error Something isn't working or is incorrect Graph: Flow labels Feb 2, 2020
@github-actions github-actions bot added the Status: Triage Needs to be verified, categorized, etc label Feb 2, 2020
@GDFaber GDFaber self-assigned this Feb 17, 2020
@PickaForm
Copy link

Any news regarding the possibility to style the arrowhead properly? (at least having the same color as the stroke)

@glasgowm148
Copy link

Please change the default to grey at a minimum - black arrow head is lost on dark websites.

@dwhelan
Copy link

dwhelan commented Oct 3, 2022

I believe the challenge here is that the arrowheads (markers) are actually shared by all lines that use them whereas the line is unique. So updating the color of the marker would affect all lines using the marker.

I think this could be done by dynamically creating a unique marker when the the line is styled and applying the requested style to the new marker.

@jgreywolf jgreywolf added Contributor needed Status: Approved Is ready to be worked on and removed Status: Triage Needs to be verified, categorized, etc labels Mar 7, 2023
@jvsteiner
Copy link

Any update on this?

@darkoni
Copy link

darkoni commented Jul 31, 2023

I have found a workaround for styling arrow-head :-)

You can add the following CSS into your webpage:

<style>
  #flowchart-pointEnd { stroke:#0f0 !important; fill:#f00 !important; stroke-width:3px !important; }
</style>

Notice the !important CSS rules.

You can define styles for other types of markers:
#flowchart-pointEnd
#flowchart-pointStart
#flowchart-circleEnd
#flowchart-circleStart
#flowchart-crossEnd
#flowchart-crossStart

@Nezteb
Copy link

Nezteb commented Oct 28, 2023

If you want to style all arrow heads to be a single color, you can do this:

%%{
  init: {
    'themeVariables': {
      'lineColor': '#0f0'
    }
  }
}%%

graph TD
    A --> B
%%{
  init: {
    'themeVariables': {
      'lineColor': '#0f0'
    }
  }
}%%

graph TD
    A --> B
Loading

Example: playground link

However, I've still not found a good way to only do it for specific arrow heads. 😅

@theory
Copy link

theory commented Oct 31, 2023

I was able to style the lines and arrows to match the background color and text of my blog with this CSS (all my Mermaid diagrams are in <div class="mermaid"> elements):

div.mermaid .edgeLabel {
  background-color: #151515 !important;
  color:#eaeaea !important;
}

div.mermaid .marker {
  fill: #eaeaea !important;
  stroke: #eaeaea !important;
}

div.mermaid .marker.cross {
  stroke: #eaeaea !important;
}
div.mermaid .edgePath .path {
  stroke: #eaeaea !important;
}

div.mermaid .flowchart-link {
  stroke: #eaeaea !important;
}

Was able to figure this out thanks to your comment, @Nezteb: I copied the style element of the diagram before and after applying your %%{}%% command to see what elements would need styling.

@quilicicf
Copy link

Hi all,

There is a way to fix this in the SVG spec, as explained in this SO thread.

Basically, markers in the SVG that Mermaid outputs should have the attribute fill: context-stroke which tells the renderer that the color should be inherited from the element using the marker. There's a working example in the spec.

Looks like this:

<svg>
  <g>
    <marker id="pointy-head" ...>
      <path d="..." fill="context-stroke"></path><!-- That's where the fix is done -->
    </marker>
  </g>
  ...
  <g class="edgePaths">
    <path id="L-left-right-0" class="..." d="..." marker-end="url(#pointy-head)"></path><!-- That's where the marker is used -->
  </g>
</svg>

The only issue is that... not many browsers support this at the time of writing :-(

If your audience is 100% FF users it works though 🙃

@flowchartsman
Copy link

flowchartsman commented Jan 8, 2024

I have created a workaround for this in Javascript cribbed initially from this stackoverflow answer, and modified to work with the current mermaid svg output and both start and end markers.

function fixMermaidMarkerColors() (click to expand)
// fixMermaidMarkerColors modifies a mermaid-generated SVG to match
// end-of-line markers such as arrowheads to their stroke style.
// Algorithm:
// 1) Scan the SVG for all paths that have marker-start or marker-end
//    attributes set.
// 2) Examine the current markers to see if they already match. If so,
//    leave them alone.
// 3) Get the stroke color of the path as a string and simplify it to
//    create a suffix for a new marker ID. For example, if the current
//    stroke color is "rgb(255, 99, 71)" and the marker's ID id is
//    #markerABC, the new marker will be #markerABC_rgb_255__99__71_
// 4) Create a clone of the old marker, along with all of its paths and
//    modify them so that the fill and/or stroke matches the current
//    stroke color.
// 5) Insert the new marker into the SVG as a sibling of the old marker.
// 6) Replace the URL of the original marker with the new one.
function fixMermaidMarkerColors() {
  document
    .querySelectorAll("svg[aria-roledescription=flowchart-v2]")
    .forEach((svg) => {
      // Remember all of the new markers created for this SVG so that they
      // can be re-used.
      let newMarkers = new Set();
      svg.querySelectorAll("path").forEach((path) => {
        // skip the marker paths themselves
        if (path.parentElement.nodeName == "marker") {
          return;
        }
        // skip paths that have no markers
        if (
          !path.hasAttribute("marker-start") &&
          !path.hasAttribute("marker-end")
        ) {
          return;
        }
        let pathStroke = path.style.stroke;
        // skip paths that do not have a stroke set
        if ([undefined, "none"].includes(pathStroke)) {
          return;
        }
        // create a suffix for the marker ID from the stroke
        let markerSuffix = pathStroke.replace(/[\s,)(]/gi, "_");
        // inspect the markers currently assigned to this path
        ["marker-start", "marker-end"].forEach((markerPos) => {
          // paths may only have one marker, so skip if the one is not
          // assigned
          if (!path.hasAttribute(markerPos)) {
            return;
          }
          // get the ID of the old marker and retrieve it
          let oldMarkerID = path.getAttribute(markerPos).slice(4, -1);
          let oldMarker = svg.querySelector(oldMarkerID);
          // oldMarkerID is still a selector, so we get the ID without '#'
          oldMarkerID = oldMarker.id;

          // markers can have multiple paths (like the X marker), but we
          // assume they are all the same style, so we only look at the
          // first one to see if it already matches.
          let oldStroke = oldMarker.firstChild.style.stroke;
          // if the existing marker stroke already matches (or if it has no
          // stroke, but the fill matches), skip it, since it will already
          // match the line
          switch (oldStroke) {
            case pathStroke:
              return;
            case "none":
            case undefined:
              if (oldMarker.firstChild.style.fill == pathStroke) return;
          }
          // create new marker ID suffix from the current path's stroke
          // color.
          let newMarkerID = `${oldMarkerID}_${markerSuffix}`;
          // don't create a new marker if we've already made one for this
          // color.
          if (!newMarkers.has(newMarkerID)) {
            // deep clone the old marker so marker paths are cloned too.
            let newMarker = oldMarker.cloneNode(true);
            newMarker.id = newMarkerID;
            // modify all marker shapes to match the current path.
            newMarker
              .querySelectorAll(
                "path, rect, circle, ellipse, line, polyline, polygon"
              )
              .forEach((markerShape) => {
                let changed = false;
                ["fill", "stroke"].forEach((attr) => {
                  // only replace fill or stroke, don't add
                  if (
                    ![undefined, "none"].includes(markerShape.style[attr])
                  ) {
                    markerShape.style[attr] = pathStroke;
                    changed = true;
                  }
                });
                // if the old marker has neither fill nor stroke set, we
                // assign one depending on the svg element type.
                // Paths and lines get assigned "stroke", while every other
                // shape gets assigned "fill".
                if (!changed) {
                  let attr = "fill";
                  switch (markerShape.nodeName) {
                    case "path":
                    case "line":
                    case "polyline":
                      attr = "stroke";
                  }
                  markerShape.style[attr] = pathStroke;
                }
              });
            // place the new marker in the same container as the old one.
            oldMarker.parentElement.appendChild(newMarker);
            // Record the new colored marker so it can be re-used on other
            // paths with the same color.
            newMarkers.add(newMarkerID);
          }
          // Finally, update the path so that its marker URLs target the
          // new colored marker.
          path.setAttribute(markerPos, `url(#${newMarkerID})`);
        });
      });
    });
}

You use it by supplying a selector for the mermaid svgs you want to fix. For example: fixMermaidMarkerColors('svg[id^="my-diagram-"]'); or fixMermaidMarkerColors('.diagram'); It should do the right thing for non-svgs and ignore them, but it doesn't have a good way (yet) to identify mermaid-generated svgs versus other svg elements, so make sure your selector is specific enough. Feel free to chime in if anyone knows of a reliable way to do this.

Before

image

After

image

Further Work

I'll probably submit a patch at some point to address this, which probably won't be too bad, depending on whether the markers are accessible at that point in the svg generation or not. Mermaid basically creates an invisible group with all of the different marker styles pre-created without any styling so, as long as they are accessible, it shouldn't be too hard to do something similar to what the javascript above does and just clone a new marker with some sort of unique ID (either by style or simply line number).

Updates

  • (1/9/23) I've ported the function to vanilla JS and further improved it to account for multiple paths in markers, and also SVG shapes in markers (like the circle arrowhead).
  • (1/9/23) Since this is (as far as I can tell), only a flowcharts thing, I've modified the function to automatically target mermaid flowcharts as suggested by @quilicicf.
  • (1/10/23) Removed now-spurious check against svg element type.

@quilicicf
Copy link

@flowchartsman If you want to detect Mermaid-made flowcharts, you can check for svg[aria-roledescription=flowchart-v2] which would allow your function to find all flowcharts without having to specify where they are manually.

@flowchartsman
Copy link

flowchartsman commented Jan 9, 2024

Huh, you know, originally I was thinking I could make this work for other diagram types, but now that I think about it, I guess flowcharts are really the only place you can color style links anyway.

@quilicicf
Copy link

Exactly.

And I hope that the same attribute is set to other recognizable values for other graph types.
Or if you were to contribute to Mermaid, it'd make sense to add a new attribute to specify that the svg was generated by Mermaid?
Anyway, I'm not even sure that other graph types use the same classes/attributes so I'm not convinced that the code would work on other graphs as is.

Also, a nitpick: since you changed the selector to svg[aria-roledescriptions=flowhart-v2], I think that checking svg.nodeName != "svg" isn't useful anymore.

@0xdf223
Copy link

0xdf223 commented Jan 10, 2024

Would also love to have control over the arrow head color. Looking to use this on my site with a dark background. the black heads just disappear.

image

@flowchartsman
Copy link

Would also love to have control over the arrow head color. Looking to use this on my site with a dark background. the black heads just disappear.

@0xdf223 you may want to try my function above. It retroactively fixes the arrow colors.

@0xdf223
Copy link

0xdf223 commented Jan 12, 2024

Yup, I've got it running on my site now, and it works nicely. Still seems like a cludgy solution to have to include javascript to fix it :)

@sandy-fairsupply
Copy link

Basically, markers in the SVG that Mermaid outputs should have the attribute fill: context-stroke
[...]
The only issue is that... not many browsers support this at the time of writing :-(

This is supported in Chrome and Edge since April, and Opera since May (caniuse). Safari doesn't support it yet, but at almost 70% of web users, maybe it's worth implementing this now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Contributor needed Graph: Flow Status: Approved Is ready to be worked on Type: Bug / Error Something isn't working or is incorrect
Projects
None yet
Development

Successfully merging a pull request may close this issue.