Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,39 @@ All notable changes to PSGraph are documented here. This file starts at
(2.1.38, August 2019), the point at which `themodulecollective/PSGraph`
forked from the then-dormant `KevinMarquette/PSGraph`.

## 3.2.0 - 2026-08-30

### Added

- `Graph -Strict` emits `strict digraph`/`strict graph`, telling GraphViz
to merge duplicate edges instead of drawing them separately.
- `New-GraphAttributeSet` (alias `GraphAttributes`): a graph/cluster-level
counterpart to `New-NodeAttributeSet`/`New-EdgeAttributeSet`, exposing
`-RankDir`, `-Splines`, `-BgColor`/`-GradientAngle`/`-Style radial`
(gradient fills), `-NodeSep`/`-RankSep`, `-Concentrate`, `-Compound`,
`-ColorScheme`, `-Ratio`, `-Size`, and graph-level font/label
attributes, with the same tab completion as the existing two builders.
`rankdir` previously had no dedicated parameter anywhere in the module.
- `New-NodeAttributeSet`: added `-Peripheries`, `-GradientAngle`,
`-Tooltip`, `-URL` (alias `-Href`), `-XLabel`, `-ColorScheme`; added
`'radial'` to `-Style` (gradient fills) and `'record'`/`'Mrecord'` to
`-Shape`.
- `New-EdgeAttributeSet`: added `-Weight`, `-MinLen`, `-Tooltip`, `-URL`
(alias `-Href`), `-XLabel`.
- `Export-PSGraph -OutputFormat`: widened the `ValidateSet` from 10 to 26
formats (adds `svgz`, `svg_inline`, `eps`, `ps`, `ps2`, `xdot`,
`dot_json`, `xdot_json`, `json0`, `canon`, `gv`, `fig`, `bmp`,
`tif`/`tiff`, `wbmp`, `pic`, `plain-ext`) — the previous list rejected
several valid `dot -T` values outright. Formats needing extra native
libraries not universally present across platforms (`webp`, `gd`,
`gd2`, `pov`, `gtk`, `xlib`, `exr`, `psd`) are deliberately excluded;
actual availability always depends on the local build (`dot -T?`). Two
formats from the original plan (`vml`, `vmlz`) turned out to no longer
be present in current Graphviz output-format lists at all and were
dropped rather than added speculatively.
- `Export-PSGraph -LayoutEngine`: added `osage` and `patchwork`, the two
Graphviz layout engines that were previously unreachable.

## 3.1.0 - 2026-08-29

### Added
Expand Down
28 changes: 24 additions & 4 deletions PSGraph/PSGraph.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'PSGraph.psm1'

# Version number of this module.
ModuleVersion = '3.1.0'
ModuleVersion = '3.2.0'

# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
Expand Down Expand Up @@ -74,8 +74,8 @@
# delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(
'Cells', 'Edge', 'Entity', 'Export-PSGraph', 'Graph', 'Inline', 'Install-GraphViz',
'New-EdgeAttributeSet', 'New-NodeAttributeSet', 'Node', 'Rank', 'Record', 'Row',
'Set-NodeFormatScript', 'Show-PSGraph', 'SubGraph'
'New-EdgeAttributeSet', 'New-GraphAttributeSet', 'New-NodeAttributeSet', 'Node', 'Rank',
'Record', 'Row', 'Set-NodeFormatScript', 'Show-PSGraph', 'SubGraph'
)

# Cmdlets to export from this module, for best performance, do not use wildcards and do not
Expand All @@ -88,7 +88,7 @@
# Aliases to export from this module, for best performance, do not use wildcards and do not
# delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @(
'digraph', 'NodeAttributes', 'EdgeAttributes',
'digraph', 'NodeAttributes', 'EdgeAttributes', 'GraphAttributes',
'jpgGraph', 'pngGraph', 'gifGraph', 'imapGraph', 'cmapxGraph',
'jp2Graph', 'jsonGraph', 'pdfGraph', 'plainGraph', 'dotGraph', 'svgGraph'
)
Expand Down Expand Up @@ -122,6 +122,26 @@

# ReleaseNotes of this module
ReleaseNotes = @'
3.2.0 20260830
* Export-PSGraph: -OutputFormat now accepts a much wider set of GraphViz
output formats (svgz, svg_inline, eps, ps, ps2, xdot, dot_json,
xdot_json, json0, canon, gv, fig, bmp, tif/tiff, wbmp, pic, plain-ext)
in addition to the original 10 - actual availability still depends on
how the local GraphViz build was compiled (see `dot -T?`)
* Export-PSGraph: -LayoutEngine now accepts osage and patchwork, the two
packing-layout engines that were previously unreachable
* Graph: added -Strict switch, emitting 'strict digraph'/'strict graph'
so GraphViz merges duplicate edges
* New-GraphAttributeSet (alias GraphAttributes): new command for
building case-correct GraphViz graph/cluster attribute hashtables
(RankDir, Splines, BgColor, gradients, spacing, ...), with the same
tab completion as the Node/Edge attribute-set builders
* New-NodeAttributeSet: added -Peripheries, -GradientAngle, -Tooltip,
-URL/-Href, -XLabel, -ColorScheme; added 'radial' to -Style and
'record'/'Mrecord' to -Shape
* New-EdgeAttributeSet: added -Weight, -MinLen, -Tooltip, -URL/-Href,
-XLabel

3.1.0 20260829
* Format-Value: fixed HTML-like label detection so any valid GraphViz
HTML-like label round-trips, not just ones starting with <table (#100)
Expand Down
2 changes: 1 addition & 1 deletion PSGraph/PSGraph.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ $functions = Get-ChildItem -Path "$PSScriptRoot\Public" -Filter '*.ps1' -Recurse

# Keep in sync with PSGraph.psd1's AliasesToExport
Export-ModuleMember -Function $functions.BaseName -Alias @(
'DiGraph', 'NodeAttributes', 'EdgeAttributes',
'DiGraph', 'NodeAttributes', 'EdgeAttributes', 'GraphAttributes',
'jpgGraph', 'pngGraph', 'gifGraph', 'imapGraph', 'cmapxGraph',
'jp2Graph', 'jsonGraph', 'pdfGraph', 'plainGraph', 'dotGraph', 'svgGraph'
)
2 changes: 2 additions & 0 deletions PSGraph/Private/Get-LayoutEngine.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ function Get-LayoutEngine
sfdp = 'sfdp'
twopi = 'twopi'
circo = 'circo'
osage = 'osage'
patchwork = 'patchwork'
}

$layoutEngine[$Name]
Expand Down
13 changes: 10 additions & 3 deletions PSGraph/Public/Export-PSGraph.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,13 @@ function Export-PSGraph
[string]
$DestinationPath,

# The file type used when generating an image
[ValidateSet('jpg', 'png', 'gif', 'imap', 'cmapx', 'jp2', 'json', 'pdf', 'plain', 'dot', 'svg')]
# The file type used when generating an image. Availability depends on how the local
# GraphViz build was compiled - run 'dot -T?' to see what your install actually supports.
[ValidateSet(
'jpg', 'png', 'gif', 'imap', 'cmapx', 'jp2', 'json', 'pdf', 'plain', 'dot', 'svg',
'svgz', 'svg_inline', 'eps', 'ps', 'ps2', 'xdot', 'dot_json', 'xdot_json', 'json0',
'canon', 'gv', 'fig', 'bmp', 'tif', 'tiff', 'wbmp', 'pic', 'plain-ext'
)]
[string]
$OutputFormat = 'png',

Expand All @@ -90,7 +95,9 @@ function Export-PSGraph
'fdp',
'sfdp',
'twopi',
'circo'
'circo',
'osage',
'patchwork'
)]
[string]
$LayoutEngine,
Expand Down
19 changes: 17 additions & 2 deletions PSGraph/Public/Graph.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ function Graph
edge hello world
}

.Example

graph g -Strict {
edge a b
edge a b
}

-Strict emits 'strict digraph' so GraphViz merges the duplicate a->b edge into one.

.Notes
The output is a string so it can be saved to a variable or piped to other commands
#>
Expand Down Expand Up @@ -97,7 +106,12 @@ support; it isn't an oversight."

# Keyword that initiates the graph
[string]
$Type = 'digraph'
$Type = 'digraph',

# Emits 'strict digraph'/'strict graph', which tells GraphViz to merge duplicate edges
# instead of drawing them separately. Only meaningful on the top-level graph.
[switch]
$Strict
)

begin
Expand All @@ -115,7 +129,8 @@ support; it isn't an oversight."
$script:SubGraphList = @{}
}

"{0}{1} {2} {{" -f (Get-Indent), $Type, $name
$typeKeyword = if ( $Strict ) { "strict $Type" } else { $Type }
"{0}{1} {2} {{" -f (Get-Indent), $typeKeyword, $name
$script:indent++

if ($null -ne $Attributes)
Expand Down
26 changes: 24 additions & 2 deletions PSGraph/Public/New-EdgeAttributeSet.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ doesn't change any external/persistent state, so ShouldProcess doesn't apply."
[double]
$Length,

# Minimum rank difference (in ranks) enforced between the edge's two nodes
[int16]
$MinLen,

# Width of the pen, in points, used to draw lines and curves
[double]
$PenWidth,
Expand All @@ -110,7 +114,24 @@ doesn't change any external/persistent state, so ShouldProcess doesn't apply."

# Text label placed near the tail of the edge
[string]
$TailLabel
$TailLabel,

# Tooltip text shown on hover in SVG/interactive output formats
[string]
$Tooltip,

# Hyperlink attached to the edge in SVG/PostScript/map output formats
[Alias('Href')]
[string]
$URL,

# How strongly GraphViz's layout should try to keep the edge close to its preferred length
[double]
$Weight,

# External label placed near the edge without affecting layout
[string]
$XLabel
)

$values = @{}
Expand All @@ -137,7 +158,8 @@ doesn't change any external/persistent state, so ShouldProcess doesn't apply."
# Passed through unchanged - numeric, boolean, or free-form text where case is meaningful
$passthroughParams = @(
'ArrowSize', 'Constraint', 'FontName', 'FontSize', 'HeadLabel', 'Label',
'LabelFontName', 'LabelFontSize', 'PenWidth', 'TailLabel'
'LabelFontName', 'LabelFontSize', 'MinLen', 'PenWidth', 'TailLabel', 'Tooltip', 'URL',
'Weight', 'XLabel'
)
foreach ($param in $passthroughParams)
{
Expand Down
151 changes: 151 additions & 0 deletions PSGraph/Public/New-GraphAttributeSet.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
function New-GraphAttributeSet
{
<#
.SYNOPSIS
Builds a GraphViz attribute hashtable for the Graph/SubGraph commands.

.DESCRIPTION
Graph and SubGraph take a hashtable of attributes, but GraphViz attribute names and
values are case-sensitive and easy to get wrong ('blue' works, 'Blue' does not). This
command exposes the common graph-level attributes (rankdir, splines, background,
spacing, ...) as PowerShell parameters - with tab completion for color/font values - and
normalizes casing for the ones GraphViz requires lowercase.

.EXAMPLE
$graphAttributeSetSplat = @{
RankDir = 'LR'
BgColor = 'lightyellow'
FontName = 'Calibri'
}
$attrs = New-GraphAttributeSet @graphAttributeSetSplat
graph g -Attributes $attrs { edge a b }

.NOTES
Follows the same lowercase/passthrough split as New-NodeAttributeSet and
New-EdgeAttributeSet. RankDir's TB/LR/BT/RL values are left in their required
uppercase form - unlike most other GraphViz enum values, rankdir is not lowercase.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
"PSUseShouldProcessForStateChangingFunctions", "",
Justification = "Despite the New- verb, this builds and returns a hashtable in memory - it
doesn't change any external/persistent state, so ShouldProcess doesn't apply."
)]
[CmdletBinding()]
[Alias('GraphAttributes')]
[OutputType([hashtable])]
param(
# Background color for the graph/cluster; supports a two-color 'c1:c2' gradient
[string]
$BgColor,

# Namespace GraphViz resolves BgColor/FontColor small-integer values against,
# e.g. 'blues9' (a Brewer palette) or 'x11' (the default). No tab completion is provided -
# see https://graphviz.org/doc/info/colors.html#brewer for the full scheme list.
[string]
$ColorScheme,

# If false, forces edges between clusters to attach to the actual node instead of the
# cluster boundary, even when Edge would otherwise rewrite them with lhead/ltail
[bool]
$Compound,

# Merges edges sharing an endpoint into a single line where the layout allows it
[switch]
$Concentrate,

# Font color used for the graph's Label
[string]
$FontColor,

# Font used for the graph's Label
[string]
$FontName,

# Font size, in points, used for the graph's Label
[double]
$FontSize,

# Angle, in degrees, controlling the direction of a BgColor gradient fill
[double]
$GradientAngle,

# Text label for the graph/cluster
[string]
$Label,

# Placement of Label: 't' (top) or 'b' (bottom)
[ValidateSet('t', 'b')]
[string]
$LabelLoc,

# Minimum space, in inches, between adjacent nodes on the same rank
[double]
$NodeSep,

# Direction of graph layout: top-to-bottom, left-to-right, bottom-to-top, right-to-left
[ValidateSet('TB', 'LR', 'BT', 'RL')]
[string]
$RankDir,

# Minimum space, in inches, between adjacent ranks
[double]
$RankSep,

# Aspect-ratio hint for the final layout - a number (e.g. 0.5) or a keyword such as
# 'fill'/'compress'/'expand'/'auto'
[string]
$Ratio,

# Maximum drawing size, e.g. '8,8' or '8,8!' (the trailing '!' forces scaling up too)
[string]
$Size,

# Splines/edge-routing mode, e.g. curved, ortho, polyline, none
[ValidateSet('line', 'polyline', 'curved', 'ortho', 'spline', 'none', 'true', 'false')]
[string]
$Splines,

# Style for the graph/cluster background, e.g. filled, rounded, radial (gradient fill)
[ValidateSet('filled', 'striped', 'rounded', 'radial')]
[string]
$Style
)

$values = @{}

# GraphViz requires these lowercase; user input may not be
$lowercaseParams = @('BgColor', 'ColorScheme', 'FontColor', 'Splines', 'Style')
foreach ($param in $lowercaseParams)
{
if ($PSBoundParameters.ContainsKey($param))
{
$values[$param.ToLower()] = $PSBoundParameters[$param].ToLower()
}
}

# Passed through unchanged - numeric, boolean, free-form text, or an enum GraphViz requires
# in a case other than lowercase (RankDir's TB/LR/BT/RL)
$passthroughParams = @(
'FontName', 'FontSize', 'GradientAngle', 'Label', 'LabelLoc', 'NodeSep', 'RankDir',
'RankSep', 'Ratio', 'Size'
)
foreach ($param in $passthroughParams)
{
if ($PSBoundParameters.ContainsKey($param))
{
$values[$param.ToLower()] = $PSBoundParameters[$param]
}
}

if ($PSBoundParameters.ContainsKey('Compound'))
{
$values['compound'] = $Compound
}

if ($Concentrate)
{
$values['concentrate'] = $true
}

$values
}
Loading
Loading