You might want to look at record layout. If I understand the need correctly, you can get this format to align nicely using rankdir=LR. The columns can still be endpoints for edges, and the records can be in clusters, so you wouldn’t need a node for each column, but still be able to show the relationship I think. Here’s a (not small) example I recently did to show the data flow for a mapping from a set of database tables to generic format for further processing as a single text file. The dot code is a bit messy, but it would contain what you need to use this format. I can’t seem to embed this to auto-render, so you can copy out the code and give it a try.
[dot]
digraph structs {
layout=dot
// graph [pack=true,packmode=“graph”]
// splines=ortho
rankdir=LR
// Two color schemes here. Uncomment the one you want:
// White background:
// node [shape=record];
// edge [penwidth=1.0,arrowsize=0.5,color=blue,headclip=false,tailclip=false]
// graph [label=“2025-05-06 17:19:48-0400 Clutter”,labelloc=b,fontsize=7]
// subgraph clusterall {graph [label=“TDX Diagram Processing”,labelloc=t,fontsize=25,color=transparent]
// Black background:
node [shape=record,color=white,fontcolor=white];
edge [penwidth=1.0,arrowsize=0.5,color=white,headclip=false,tailclip=false]
graph [bgcolor=black]
graph [label=“2025-05-06 13:32:00-0400 Clutter”,labelloc=b,fontsize=7,fontc
olor=white]
subgraph clusterall {graph [label=“TDX Table Pre-Processing”,labelloc=t,fontsi
ze=25,fontcolor=white,color=transparent]
// End of color schemes
// TDX TABLES
{
node [color=firebrick3]
relations [label=“{
TDX\nConfiguration\nRelationships|
{
{CR_ID}|
{<CR_ParentID> CR_ParentID}|
{<CR_ChildID> CR_ChildID}|
{<CR_RelTypeID> CR_RelTypeID}}
}”]
relationshipType [label=“{
TDX\nConfiguration\nRelationship\nTypes|
{
{<CRT_ID> CRT_ID}|
{<CRT_InverseDescription> CRT_InverseDescription}}
}”]
CI [label=“{
TDX\nConfiguration\nItems|{
{<CI_ID> CI_ID}|
{<CI_Name> CI_Name}|
{<CI_TypeID> CI_TypeID}|
{<CI_ItemID> CI_ItemID\nBased on CI_ComponentID}|
{<CI_ComponentID> CI_ComponentID}|
{CI_IsActive}|
{<CI_OwningGroupID> CI_OwningGroupID}}
}”]
assets [label=“{
TDX\nAssets|{
{<AS_ID> AS_ID}|
{<AS_ProductModelID> AS_ProductModelID}}
}”]
models [label=“{
TDX\nProduct\nModels|{
{<PM_ID> PM_ID}|
{<PM_Name> PM_Name}|
{<PM_ManufacturerID> PM_ManufacturerID}}
}”]
vendors [label=“{
TDX\nVendors\n$0.Vendors|{
{<VND_ID> VND_ID}|
{<VND_Name> VND_Name}}
}”]
components [label=“{
TDX\nComponents\n$0.CMPNT|{
{<CMPNT_ID> CMPNT_ID}|
{<CMPNT_Name> CMPNT_Name}}
}”]
CItypes [label=“{
TDX\nConfiguration\nItem Types\n$0.CIT|{
{<CIT_ID> CIT_ID}|
{<CIT_Name> CIT_Name}}
}”]
groups [label=“{
TDX\nGroups\n$0.Grp|{
{<Grp_ID> Grp_ID}|
{<Grp_Name> Grp_Name}}
}”]
attributes [label=“{
TDX\nAttributes\n$0.Att|{
{<Att_ID> Att_ID}|
{<Att_Name> Att_Name}}
}”]
attributeValues [label=“{
TDX\nAttribute\nValues|{
{AV_ID}|
{<AV_ItemID> AV_ItemID\nBased on AV_ItemComponentID}|
{<AV_ItemComponentID> AV_ItemComponentID}|
{<AV_ATTID> AV_ATTID}|
{<AV_Value> AV_Value}|
{<Value_Types> Value_Types\n(Computed)|{
{AV_ValueText}|
{AV_ValueInt}|
{AV_ValueDecimal}|
{AV_ValueDateTime}}}}
}”]
attributeChoices [label=“{
TDX\nAttribute\nChoices\n$0.ATTC|{
{<ATTC_ID> ATTC_ID}|
{<ATTC_Name> ATTC_Name}}
}”]
} // End of TDX table scope
// Intermediate in-memory tables
{
node [color=cyan3]
intermediateValues [label=“{
STREAM\nAttribute\nValues\nProcessing|{
{<AV_ItemComponentID+AV_ItemID> AV_ItemComponentID+AV_ItemID}|
{ comment}|
{<asset_type> asset_type(for component ID 27)}|
{<owning_group> owning_group}}
}”]
} // end of intermediate table scope
// TEMP FILES
{
node [color=darkgoldenrod2]
// MakeModel temp file
MakeModel [label=“{
TEMP\n$0.MakeModel|{
{<PM_ID> PM_ID}|
{<PM_Name> PM_Name (Model)}|
{<PM_Manufacturer_ID> PM_Manufacturer_ID}|
{<VND_Name> VND_Name (Make)}}
}”]
// Assets temp file
tempAssets [label=“{
TEMP\n$0.assets|{
{<27+AS_ID> 27+AS_ID}|
{<PM_Name> PM_Name (Model)}|
{<VND_Name> VND_Name (Make)}}
}”]
// Configuration Items temp file
tempCI [label=“{
TEMP\n$0.CI|{
{<CI_ComponentID+CI_ItemID> CI_ComponentID+CI_ItemID}|
{<CI_ID> CI_ID}|
{<CI_Name> CI_Name}|
{<CI_TypeID> CI_TypeID}|
{<CI_ItemID> CI_ItemID}|
{<CI_ComponentID> CI_ComponentID}|
{<Grp_Name> Grp_Name}|
{<CIT_Name> CIT_Name}|
{<CMPNT_Name> CMPNT_Name}}
}”]
} // End of temp files scope
// OUTPUT FILES
{
node [color=forestgreen]
asset [label=“{
OUTPUT\nasset.tsv|{
{ id\nCI_ID}|
{<asset_name> asset_name\nCI_Name}|
{ rev\n(blank)}|
{ owner\nGrp_Name}|
{ category\n(blank)}|
{<asset_type> asset_type\nAsset: ATTC_Name where Att_Name==Type\n&& AV_ItemCompo
nentID==27\nCI: CIT_Name\nElse: CMPNT_Name}|
{ comment\nAll custom attribute names/values}|
{ level\n(blank)}|
{ location\n(blank)}|
{ deleted\n(blank)}|
{ expiration\n(blank)}|
{ deleted_time\n(blank)}|
{ created_time\n(blank)}|
{ uuid\n(blank)}}
}”]
depend [label=“{
OUTPUT\ndepend.tsv|{
{<asset_a> asset_a\nCR_ChildID}|
{<asset_b> asset_b\nCR_ParentID}|
{<link_type> link_type\nCRT_InverseDescription}}
}”]
} // End of output file scope
// CI connection to asset
CI:CI_ItemID → assets:AS_ID:w [color=firebrick3]
// Asset connection to Product Models
assets:AS_ProductModelID → models:PM_ID:w [color=firebrick3]
// Relations to relationship types
relations:CR_RelTypeID → relationshipType:CRT_ID:w [color=firebrick3]
// Resolve owning group ID to get name for CI
CI:CI_OwningGroupID → groups:Grp_ID:w [color=firebrick3]
// Resolve attribute ID to get name for attribute value
attributeValues:AV_ATTID → attributes:Att_ID:w [color=firebrick3]
// Attribute Values Item ID to Configuration Items and Assets
attributeValues:AV_ItemID → CI:CI_ID:w [headlabel=“63”] [color=firebrick3]
attributeValues:AV_ItemID → assets:AS_ID:w [headlabel=“27”] [color=firebrick3
]
// Resolve AV_Value to a name if the attribute value came from selection list
attributeValues:AV_Value → attributeChoices:ATTC_ID:w [color=firebrick3]
// Relationship Parent and Child ID to depend.tsv
relations:CR_ParentID → depend:asset_b:w [color=forestgreen]
relations:CR_ChildID → depend:asset_a:w [color=forestgreen]
// Relationship Type to depend.tsv
relationshipType:CRT_InverseDescription:e → depend:link_type:w [color=forestgre
en]
// Resolving the type of component:
// If type 63 (CI) use ConfigurationItemTypes name
// CItypes:CIT_Name → asset:asset_type:w
// If Component ID is neither 63 (CI) nor 27 (Assets) we use the Component
// Name for the asset type
//components:CMPNT_Name → asset:asset_type:w
// Combine product models and vendors into MakeModel temp file
models:PM_ID:e → MakeModel:PM_ID:w [color=darkgoldenrod2]
models:PM_Name:e → MakeModel:PM_Name:w [color=darkgoldenrod2]
models:PM_ManufacturerID:e → MakeModel:PM_Manufacturer_ID:w [color=darkgoldenro
d2]
MakeModel:PM_Manufacturer_ID:e → vendors:VND_ID:w [color=firebrick3]
vendors:VND_Name:w → MakeModel:VND_Name:e [color=darkgoldenrod2]
// Now put together the asset temp file
assets:AS_ID:e → tempAssets:“27+AS_ID”:w [color=darkgoldenrod2]
MakeModel:PM_Name:e → tempAssets:PM_Name:w [color=darkgoldenrod2]
MakeModel:VND_Name:e → tempAssets:VND_Name:w [color=darkgoldenrod2]
// Configuration Item temp file
CI:CI_ComponentID:e → tempCI:“CI_ComponentID+CI_ItemID”:w [color=darkgoldenrod2
]
CI:CI_ItemID:e → tempCI:“CI_ComponentID+CI_ItemID”:w [color=darkgoldenrod2]
CI:CI_ID:e → tempCI:CI_ID:w [color=darkgoldenrod2]
CI:CI_Name:e → tempCI:CI_Name:w [color=darkgoldenrod2]
CI:CI_TypeID:e → tempCI:CI_TypeID:w [color=darkgoldenrod2]
CI:CI_ItemID:e → tempCI:CI_ItemID:w [color=darkgoldenrod2]
CI:CI_ComponentID:e → tempCI:CI_ComponentID:w [color=darkgoldenrod2]
tempCI:CI_TypeID:e → CItypes:CIT_ID:w [color=firebrick3]
CItypes:CIT_Name:w → tempCI:CIT_Name:e [color=darkgoldenrod2]
groups:Grp_Name:e → tempCI:Grp_Name:w [color=darkgoldenrod2]
tempCI:CI_ComponentID:e → components:CMPNT_ID:w [color=firebrick3]
components:CMPNT_Name:w → tempCI:CMPNT_Name:e [color=darkgoldenrod2]
// Attribute Values in-memory temp table
{
edge [color=cyan3]
// Populate index
attributeValues:AV_ItemID:e → intermediateValues:“AV_ItemComponentID+AV_ItemID”
:w
attributeValues:AV_ItemComponentID:e → intermediateValues:“AV_ItemComponentID+A
V_ItemID”:w
// Attribute name goes to working comment field
attributes:Att_Name:e → intermediateValues:comment:w
// Attribute Choice table value goes to working comment field
attributeChoices:ATTC_Name:e → intermediateValues:comment:w
// if value is the asset type
attributeChoices:ATTC_Name:e → intermediateValues:asset_type:w
// if value is the owning group
attributeChoices:ATTC_Name:e → intermediateValues:owning_group:w
// add Make and Model from the asset temp file
tempAssets:PM_Name:e → intermediateValues:comment:w
tempAssets:VND_Name:e → intermediateValues:comment:w
// Add the value types
attributeValues:Value_Types → intermediateValues:comment:w
}
// Now join with the temp CI table to output the asset.tsv file
// The CI ID
tempCI:CI_ID:e → asset:id:w [color=forestgreen]
// The CI name
tempCI:CI_Name:e → asset:asset_name:w [color=forestgreen]
// The CI group name (for ownership)
tempCI:Grp_Name:e → asset:owner:w [color=forestgreen] [color=forestgreen]
// The Owning Group name (for ownership) from attribute values table
intermediateValues:owning_group:e → asset:owner:w [color=forestgreen]
// Component type for componentID 27 (assets)
intermediateValues:asset_type:e → asset:asset_type:w [color=forestgreen]
// Component type for compinentID (63) (Configuration Item, CIT_Name)
tempCI:CIT_Name:e → asset:asset_type:w [color=forestgreen]
// Component type if not asset or CI (CMPNT_Name)
tempCI:CMPNT_Name:e → asset:asset_type:w [color=forestgreen]
// the comment field
intermediateValues:comment:e → asset:comment:w [color=forestgreen]
} // End subgraph all
} // End graph
[/dot]