Skip to content

Commit

Permalink
Merge pull request terrastruct#2120 from alixander/gradient-fill
Browse files Browse the repository at this point in the history
d2render: support gradient values
  • Loading branch information
alixander authored Sep 27, 2024
2 parents 7f2e1f4 + e6b1a72 commit 5fac1c2
Show file tree
Hide file tree
Showing 11 changed files with 910 additions and 6 deletions.
1 change: 1 addition & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Autoformat: Reserved keywords are formatted to be lowercase [#2098](https://github.com/terrastruct/d2/pull/2098)
- Misc: characters in the unicode range for Latin-1 and geometric shapes are measured more accurately [#2100](https://github.com/terrastruct/d2/pull/2100)
- Imports: can now import from absolute file paths [#2113](https://github.com/terrastruct/d2/pull/2113)
- Render: linear and radial gradients are now available for `fill`, `stroke` and `font-color` [#2120](https://github.com/terrastruct/d2/pull/2120)

#### Improvements 🧹

Expand Down
12 changes: 6 additions & 6 deletions d2graph/d2graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,16 @@ func (s *Style) Apply(key, value string) error {
if s.Stroke == nil {
break
}
if !go2.Contains(color.NamedColors, strings.ToLower(value)) && !color.ColorHexRegex.MatchString(value) {
return errors.New(`expected "stroke" to be a valid named color ("orange") or a hex code ("#f0ff3a")`)
if !color.ValidColor(value) {
return errors.New(`expected "stroke" to be a valid named color ("orange"), a hex code ("#f0ff3a"), or a gradient ("linear-gradient(red, blue)")`)
}
s.Stroke.Value = value
case "fill":
if s.Fill == nil {
break
}
if !go2.Contains(color.NamedColors, strings.ToLower(value)) && !color.ColorHexRegex.MatchString(value) {
return errors.New(`expected "fill" to be a valid named color ("orange") or a hex code ("#f0ff3a")`)
if !color.ValidColor(value) {
return errors.New(`expected "fill" to be a valid named color ("orange"), a hex code ("#f0ff3a"), or a gradient ("linear-gradient(red, blue)")`)
}
s.Fill.Value = value
case "fill-pattern":
Expand Down Expand Up @@ -348,8 +348,8 @@ func (s *Style) Apply(key, value string) error {
if s.FontColor == nil {
break
}
if !go2.Contains(color.NamedColors, strings.ToLower(value)) && !color.ColorHexRegex.MatchString(value) {
return errors.New(`expected "font-color" to be a valid named color ("orange") or a hex code ("#f0ff3a")`)
if !color.ValidColor(value) {
return errors.New(`expected "font-color" to be a valid named color ("orange"), a hex code ("#f0ff3a"), or a gradient ("linear-gradient(red, blue)")`)
}
s.FontColor.Value = value
case "animated":
Expand Down
28 changes: 28 additions & 0 deletions d2renderers/d2svg/d2svg.go
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,11 @@ func renderDoubleOval(tl *geo.Point, width, height float64, fill, fillStroke, st
return renderOval(tl, width, height, fill, fillStroke, stroke, style) + renderOval(innerTL, width-10, height-10, fill, "", stroke, style)
}

func defineGradients(writer io.Writer, cssGradient string) {
gradient, _ := color.ParseGradient(cssGradient)
fmt.Fprint(writer, fmt.Sprintf(`<defs>%s</defs>`, color.GradientToSVG(gradient)))
}

func defineShadowFilter(writer io.Writer) {
fmt.Fprint(writer, `<defs>
<filter id="shadow-filter" width="200%" height="200%" x="-50%" y="-50%">
Expand Down Expand Up @@ -1824,6 +1829,29 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
}
}

if color.IsGradient(diagram.Root.Fill) {
defineGradients(buf, diagram.Root.Fill)
}
if color.IsGradient(diagram.Root.Stroke) {
defineGradients(buf, diagram.Root.Stroke)
}
for _, s := range diagram.Shapes {
if color.IsGradient(s.Fill) {
defineGradients(buf, s.Fill)
}
if color.IsGradient(s.Stroke) {
defineGradients(buf, s.Stroke)
}
if color.IsGradient(s.Color) {
defineGradients(buf, s.Color)
}
}
for _, c := range diagram.Connections {
if color.IsGradient(c.Stroke) {
defineGradients(buf, c.Stroke)
}
}

// Apply hash on IDs for targeting, to be specific for this diagram
diagramHash, err := diagram.HashID()
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions d2themes/element.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,17 @@ func (el *ThemableElement) Render() string {
if color.IsThemeColor(el.Stroke) {
class += fmt.Sprintf(" stroke-%s", el.Stroke)
} else if len(el.Stroke) > 0 {
if color.IsGradient(el.Stroke) {
el.Stroke = fmt.Sprintf("url('#%s')", color.UniqueGradientID(el.Stroke))
}
out += fmt.Sprintf(` stroke="%s"`, el.Stroke)
}
if color.IsThemeColor(el.Fill) {
class += fmt.Sprintf(" fill-%s", el.Fill)
} else if len(el.Fill) > 0 {
if color.IsGradient(el.Fill) {
el.Fill = fmt.Sprintf("url('#%s')", color.UniqueGradientID(el.Fill))
}
out += fmt.Sprintf(` fill="%s"`, el.Fill)
}
if color.IsThemeColor(el.BackgroundColor) {
Expand Down
178 changes: 178 additions & 0 deletions e2etests/testdata/txtar/gradient/dagre/board.exp.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5fac1c2

Please sign in to comment.