Drawing "interfaces" with Graphviz - graphviz

In order to model some sort of (nested/layered) system architecture I am trying to figure out how to draw a box that has multiple "entry points" (aka interfaces). The goal is which component uses which interface to communicate with another component.
Here is a hand drawn example to illustrate the goal:
Note that the yellow boxes are systems made of components (blue boxes). The green dots are interfaces of components. If an interface is exposed to components from outside the system, the interface needs to be propagated on system level (red dots).
For the interfaces of components (green dots) a "record-based node" does the trick; a "table cell" in the top row can be used to act as an interface.
What I have not figured out yet is the "system interface" part (red dots). Is there a way how I place for example a "circle" shape explicitly on the border of a subgraph? Or do you have an other solution in mind how I can emulate the representation of a "system interface"?

You can get fairly close by using non-standard "arrowheads" (https://graphviz.org/doc/info/arrows.html), but tough/impossible to get
circles on the border
edges to "line up" (can't declare an edge from an interior node to its own cluster)
see graph below
It is possible to draw the desired graph with Graphviz by explicitly positioning nodes, including the colored points, setting X,Y coordinates (see https://www.graphviz.org/faq/#FaqDotWithNodeCoords). Instead, I'd suggest using one of the PIC language implementation. Specifically, pikchr (https://pikchr.org/home/doc/trunk/homepage.md)
Best dot version (without explicit positioning):

Using additional hidden edges and nodes you can get a partial solution, but surely there are better tools, as already said, in graphviz you can not easy place a point on the boundary of a cluster and keep the code clean of extra nodes (unless it's not neato engine).
Variant with hidden elements, image:
and script:
digraph system_architecture {
graph [
newrank=true
ranksep=0
compound=true
splines=curved]
subgraph cluster_S1 {
style=filled
fillcolor="lightyellow"
label="Sys 1"
labelloc="b"
margin=3
sys1_rest_dot_1 [shape=point height=0]
sys1_rest_dot_2 [shape=point height=0]
comp11_rest_dot [shape=point height=0]
comp12_graphql_dot [shape=point height=0]
subgraph cluster_C11 {
label=""
style="filled"
fillcolor="cyan"
comp11 [label="Comp 1.1" shape=none]
}
subgraph cluster_C12 {
label=""
style="filled"
fillcolor="cyan"
comp12 [label="Comp 1.2" shape=none]
}
}
invis_node [style=invis]
invis_node -> sys1_rest_dot_1 [lhead="cluster_S1" color=none minlen=2]
sys1_rest_dot_1 -> sys1_rest_dot_2 [lhead="cluster_S1" arrowsize=1 penwidth=10 arrowhead=dot color=red headlabel="REST"]
sys1_rest_dot_2 -> comp11_rest_dot [minlen=4 headlabel="REST" arrowhead=vee ]
comp11_rest_dot -> comp11 [lhead="cluster_C11" arrowsize=1 penwidth=10 arrowhead=dot color=lime]
comp11 -> comp12_graphql_dot [minlen=3 lhead="cluster_C12" headlabel="GraphQL" arrowhead=vee]
comp12_graphql_dot -> comp12 [lhead="cluster_C12" arrowsize=1 penwidth=10 arrowhead=dot color=lime]
subgraph cluster_S2 {
style=filled
fillcolor="lightyellow"
label="Sys 2"
labelloc="b"
margin=3
sys2_rest_dot_1 [shape=point height=0]
sys2_rest_dot_2 [shape=point height=0]
comp21_rest_dot [shape=point height=0]
comp22_sql_dot [shape=point height=0]
sys2_soa_dot_1 [shape=point height=0]
sys2_soa_dot_2 [shape=point height=0]
comp22_soa_dot [shape=point height=0]
subgraph cluster_C21 {
label=""
style="filled"
fillcolor="cyan"
comp21 [label="Comp 2.1" shape=none]
}
subgraph cluster_C22 {
label=""
style="filled"
fillcolor="cyan"
comp22 [label="Comp 2.2" shape=none]
}
}
comp12 -> sys2_rest_dot_1 [arrowhead=vee]
sys2_rest_dot_1 -> sys2_rest_dot_2 [lhead="cluster_S2" arrowsize=1 penwidth=10 arrowhead=dot color=red headlabel="REST"]
sys2_rest_dot_2 -> comp21_rest_dot [minlen=4 headlabel="REST" arrowhead=vee ]
comp21_rest_dot -> comp21 [lhead="cluster_C21" arrowsize=1 penwidth=10 arrowhead=dot color=lime]
comp21 -> comp22_sql_dot [minlen=3 lhead="cluster_C22" headlabel="SQL" arrowhead=vee]
comp22_sql_dot -> comp22 [lhead="cluster_C22" arrowsize=1 penwidth=10 arrowhead=dot color=lime]
comp12 -> sys2_soa_dot_1 [arrowhead=vee]
sys2_soa_dot_1 -> sys2_soa_dot_2 [lhead="cluster_S2" arrowsize=1 penwidth=10 arrowhead=dot color=red headlabel="SOA"]
sys2_soa_dot_2 -> comp22_soa_dot [minlen=7 headlabel="SOA" arrowhead=vee]
comp22_soa_dot -> comp22 [ head="cluster_C21" arrowsize=1 penwidth=10 arrowhead=dot color=lime]
}
Variant without tricks (does not fit the question, but code is clean), image:
and script:
digraph system_architecture {
graph [
overlap=false
ranksep=.6
nodesep=.6
compound=true
splines=curved
fontname="Arial"]
subgraph cluster_S1 {
style=filled
fillcolor="lightyellow"
label="Sys 1"
rest_s1 [shape=circle label="" height=.2 fillcolor=red style=filled xlabel=<<FONT FACE="Arial">REST</FONT>>]
subgraph cluster_C11 {
label=""
style="filled"
fillcolor="cyan"
rest_c11 [shape=circle label="" height=.2 fillcolor=lime style=filled xlabel=<<FONT FACE="Arial">REST</FONT>>]
comp11 [label=<<FONT FACE="Arial">Comp 1.1</FONT>> shape=none]
}
subgraph cluster_C12 {
label="";
style="filled"
fillcolor="cyan"
graphql_c12 [shape=circle label="" height=.2 fillcolor=lime style=filled xlabel=<<FONT FACE="Arial">GraphQL</FONT>>]
comp12 [label=<<FONT FACE="Arial">Comp 1.2</FONT>> shape=none]
}
}
rest_s1 -> rest_c11 [arrowhead=vee]
comp11 -> graphql_c12 [ltail="cluster_C11" arrowhead=vee]
subgraph cluster_S2 {
style=filled
fillcolor="lightyellow"
label="Sys 2"
rest_s2 [shape=circle label="" height=.2 fillcolor=red style=filled xlabel=<<FONT FACE="Arial">REST</FONT>>]
soa_s2 [shape=circle label="" height=.2 fillcolor=red style=filled xlabel=<<FONT FACE="Arial">SOA</FONT>>]
subgraph cluster_C21 {
label=""
style="filled"
fillcolor="cyan"
rest_c21 [shape=circle label="" height=.2 fillcolor=lime style=filled xlabel=<<FONT FACE="Arial">REST</FONT>>]
comp21 [label=<<FONT FACE="Arial">Comp 2.1</FONT>> shape=none]
}
subgraph cluster_C22 {
label="";
style="filled"
fillcolor="cyan"
sql_c22 [shape=circle label="" height=.2 fillcolor=lime style=filled xlabel=<<FONT FACE="Arial">SQL</FONT>>]
soa_c22 [shape=circle label="" height=.2 fillcolor=lime style=filled xlabel=<<FONT FACE="Arial">SOA</FONT>>]
comp22 [label="Comp 2.2" shape=none labelfontname="Arial"]
}
}
comp12 -> rest_s2 [ltail="cluster_C12" arrowhead=vee]
comp12 -> soa_s2 [ltail="cluster_C12" arrowhead=vee]
rest_s2 -> rest_c21 [arrowhead=vee]
soa_s2 -> soa_c22 [arrowhead=vee]
comp21 -> sql_c22 [ltail="cluster_C21" arrowhead=vee]
}

Related

Hierarchical State Machine with Graphviz

I'm trying to make a State diagram of the Vendor Machine with the help of Graphviz.
I want a outcome close to this one.
It consists of nested State Machines as well. Here is what I have done so far in Graphviz. Please find the online demo here.
digraph vendorMachine {
compound=true
Entry[shape="point" label=""]
subgraph cluster_cancel {
label = "Cancel"
style = rounded
C0 [label = <
<table cellborder="0" style="rounded">
<tr><td>C0</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C25 [label = <
<table cellborder="0" style="rounded">
<tr><td>C25</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C50 [label = <
<table cellborder="0" style="rounded">
<tr><td>C50</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C75 [label = <
<table cellborder="0" style="rounded">
<tr><td>C75</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
subgraph cluster_DispenseC100Drink {
label = "DispenseC100Drink"
style = rounded
C100 [label = <
<table cellborder="0" style="rounded">
<tr><td>C100</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
subgraph cluster_DispenseC125Drink {
label = "DispenseC125Drink"
style = rounded
C125 [label = <
<table cellborder="0" style="rounded">
<tr><td>C125</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C150 [label = <
<table cellborder="0" style="rounded">
<tr><td>C150</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C175 [label = <
<table cellborder="0" style="rounded">
<tr><td>C175</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C200 [label = <
<table cellborder="0" style="rounded">
<tr><td>C200</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
}
}
}
Entry -> C0
C0 -> C25[label=" Add\n$0.25 "]
C0 -> C50[label=" Add\n$0.50 "]
C25 -> C50[label=" Add\n$0.25 "]
C25 -> C75[label=" Add\n$0.50 "]
C50 -> C75[label=" Add\n$0.25 "]
C50 -> C100[label=" Add\n$0.50 "]
C100 -> C0[label=" Dispense\n $1.00 Drink " ltail="cluster_DispenseC100Drink"]
C75 -> C100[label=" Add\n$0.25 "]
C75 -> C125[label=" Add\n$0.50 "]
C100 -> C125[label=" Add\n$0.25 "]
C100 -> C150[label=" Add\n$0.50 "]
C125 -> C150[label=" Add\n$0.25 "]
C125 -> C175[label=" Add\n$0.50 "]
C125 -> C0[label=" Dispense\n $1.25 Drink " ltail="cluster_DispenseC125Drink"]
C150 -> C175[label=" Add\n$0.25 "]
C150 -> C200[label=" Add\n$0.50 "]
C175 -> C200[label=" Add\n$0.25 "]
C200 -> C0[label=" Dispense\n $2.00 Drink "]
}
So, my queries are:
How to make this similar to the mentioned image?
Is there any other better way to do this apart from my Graphviz code? (Is this the best approach what I have done so far)
One transition is missing in Graphviz code that If we pass Cancel event, it should go to state C0 (Please check in attached image). How can we implement this with Graphviz?
Thanks in advance for the suggestions and answers.
"Straight" Graphviz (command line) produces a different (and "closer") result. Here is you program with just a few tweaks:
digraph vendorMachine {
compound=true
graph [color=red]
node [color=red] // yuck style=filled fillcolor=yellow]
edge [color=red]
Entry[shape="point" label=""]
subgraph cluster_cancel {
label = "Cancel"
style = rounded
E2 [shape=rect label="" width=2 style=invis]
E2:sw->C0 [label=cancel]
C0 [label = <
<table cellborder="0" style="rounded">
<tr><td>C0</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C25 [label = <
<table cellborder="0" style="rounded">
<tr><td>C25</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C50 [label = <
<table cellborder="0" style="rounded">
<tr><td>C50</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C75 [label = <
<table cellborder="0" style="rounded">
<tr><td>C75</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
subgraph cluster_DispenseC100Drink {
label = "DispenseC100Drink"
style = rounded
C100 [label = <
<table cellborder="0" style="rounded">
<tr><td>C100</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
subgraph cluster_DispenseC125Drink {
label = "DispenseC125Drink"
style = rounded
C125 [label = <
<table cellborder="0" style="rounded">
<tr><td>C125</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C150 [label = <
<table cellborder="0" style="rounded">
<tr><td>C150</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C175 [label = <
<table cellborder="0" style="rounded">
<tr><td>C175</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
C200 [label = <
<table cellborder="0" style="rounded">
<tr><td>C200</td></tr>
<hr/>
<tr><td></td></tr>
</table>
> margin=0 shape=none]
}
}
}
Entry -> C0
C0 -> C25[label=" Add\n$0.25 "]
C0 -> C50[label=" Add\n$0.50 "]
C25 -> C50[label=" Add\n$0.25 "]
C25 -> C75[label=" Add\n$0.50 "]
C50 -> C75[label=" Add\n$0.25 "]
C50 -> C100[label=" Add\n$0.50 "]
C100 -> C0[label=" Dispense\n $1.00 Drink " ltail="cluster_DispenseC100Drink"]
C75 -> C100[label=" Add\n$0.25 "]
C75 -> C125[label=" Add\n$0.50 "]
C100 -> C125[label=" Add\n$0.25 "]
C100 -> C150[label=" Add\n$0.50 "]
C125 -> C150[label=" Add\n$0.25 "]
C125 -> C175[label=" Add\n$0.50 "]
C125 -> C0[label=" Dispense\n $1.25 Drink " ltail="cluster_DispenseC125Drink"]
C150 -> C175[label=" Add\n$0.25 "]
C150 -> C200[label=" Add\n$0.50 "]
C175 -> C200[label=" Add\n$0.25 "]
C200 -> C0[label=" Dispense\n $2.00 Drink "]
}
Giving:
Is this close enough? (done by eyeball)
subgraph cluster_cancel {
label = <
<table cellborder="0" border="0">
<tr><td width="478" >Cancel</td></tr>
<hr/>
<tr><td></td></tr>
</table>
>

Using SearchBar with ObservableCollection in MVVM

I've created a search page in my app and I'd like to be able to search through my ObservableCollection of items in the ViewModel and display them onto a CollectionView. So far this is what I've done and I get an exception i.e System.Reflection.TargetInvocationException: 'Exception has been thrown by the target of an invocation.' every time I run the app.
SearchPage XAML
<!--Doctors Search Result-->
<Grid Grid.Row="1">
<CollectionView ItemsSource="{Binding RecentDoctors}">
<CollectionView.ItemsLayout>
<ListItemsLayout Orientation="Vertical" ItemSpacing="15"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<!--Image-->
<Frame BackgroundColor="Black"
HeightRequest="20"
WidthRequest="20"
CornerRadius="100"
Margin="20,0,0,0"
HorizontalOptions="Start"
VerticalOptions="Center"
IsClippedToBounds="True">
<Image HorizontalOptions="Center"
VerticalOptions="Center"/>
</Frame>
<StackLayout Orientation="Vertical"
VerticalOptions="Center"
Spacing="-3">
<!--Fullname-->
<Label Text="{Binding DoctorsName}"
FontSize="19"
FontAttributes="Bold"/>
<!--Specialization-->
<Label Text="{Binding Specialization}"
FontSize="14"
TextColor="LightGray"/>
</StackLayout>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
<!--Doctors Search Bar-->
<Grid Grid.Row="0" ColumnSpacing="0" RowSpacing="0">
<pancake:PancakeView BackgroundColor="#0F8DF4"
HasShadow="True">
<Grid>
<!--The SearchBar-->
<renderers:CustomSearchBar x:Name="doctorsSearchBar"
Placeholder="Search Doctors by Name, Specialization"
VerticalOptions="Center"
FontSize="17"
TextColor="Black"
WidthRequest="320"
Text="{Binding SearchedText}"
SearchCommand="{Binding SearchBarCommand}"
SearchCommandParameter="{Binding Text, Source={x:Reference doctorsSearchBar}}"/>
</Grid>
</pancake:PancakeView>
</Grid>
SearchPage ViewModel
public class TelemedSearchPageViewModel : BaseViewModel
{
private string _searchedText;
public string SearchedText
{
get { return _searchedText; }
set
{
_searchedText = value;
OnPropertyChanged();
Search();
}
}
public ObservableCollection<RecentDoctorsInfo> RecentDoctors { get; set; } = new ObservableCollection<RecentDoctorsInfo>();
public ICommand SearchBarCommand { get; set; }
/// <summary>
/// Main Constructor
/// </summary>
public TelemedSearchPageViewModel()
{
SearchBarCommand = new RelayCommand(Search);
//RecentDoctorsList
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Steven Strange",
Specialization = "Sorcerer Supreme",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Peter Parker",
Specialization = "Spiderman",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Bruce Banner",
Specialization = "The Hulk",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Reed Richards",
Specialization = "Mr.Fantastic",
Location = "177a Bleecker St. | USA"
});
}
#region METHODS
public void Search()
{
if (RecentDoctors != null && RecentDoctors.Count >0)
{
var temp = RecentDoctors.Where(x => x.DoctorsName.ToLower().Contains(SearchedText.ToLower()));
foreach (var item in temp)
{
RecentDoctors.Add(item);
}
}
}
#endregion
}
Edit3:
if (RecentDoctors != null && RecentDoctors.Count > 0)
{
var results = RecentDoctors.Where(x => x.DoctorsName.ToLower().Contains(SearchedText.ToLower()));
SearchResults.Clear();
foreach (RecentDoctorsInfo item in results)
{
SearchResults.Add(item);
}
}
else
{
RecentDoctors.Clear();
}
If you want to execute the search when user type you should use a behavior as doscs suggest
public class SearchBarTextChangedBehavior : Behavior<SearchBar>
{
protected override void OnAttachedTo(SearchBar bindable)
{
base.OnAttachedTo(bindable);
bindable.TextChanged += this.SearchBar_TextChanged;
}
protected override void OnDetachingFrom(SearchBar bindable)
{
base.OnDetachingFrom(bindable);
bindable.TextChanged -= this.SearchBar_TextChanged;
}
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
((SearchBar)sender).SearchBarCommand?.Execute(e.NewTextValue);
}
}
Then attach the behavior to your SearchBar
<renderers:CustomSearchBar
x:Name="doctorsSearchBar"
Placeholder="Search Doctors by Name, Specialization"
VerticalOptions="Center"
FontSize="17"
TextColor="Black"
WidthRequest="320"
Text="{Binding SearchedText}"
SearchCommand="{Binding SearchBarCommand}">
<renderers:CustomSearchBar.Behaviors>
<behaviors:SearchBarTextChangedBehavior />
</renderers:CustomSearchBar.Behaviors>
</renderers:CustomSearchBar>
By the other hand, you should create a private copy of the original list and add the same items as the public collection
private List<RecentDoctorsInfo> originalRecentDoctorsList = new List<RecentDoctorsInfo>();
public ObservableCollection<RecentDoctorsInfo> RecentDoctors { get; set; } = new ObservableCollection<RecentDoctorsInfo>();
public ICommand SearchBarCommand { get; set; }
public TelemedSearchPageViewModel()
{
SearchBarCommand = new RelayCommand(Search);
//RecentDoctorsList
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Steven Strange",
Specialization = "Sorcerer Supreme",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Peter Parker",
Specialization = "Spiderman",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Bruce Banner",
Specialization = "The Hulk",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Reed Richards",
Specialization = "Mr.Fantastic",
Location = "177a Bleecker St. | USA"
});
// Backup copy list.
originalRecentDoctorsList.Add(new RecentDoctorsInfo()
{
DoctorsName = "Steven Strange",
Specialization = "Sorcerer Supreme",
Location = "177a Bleecker St. | USA"
});
originalRecentDoctorsList.Add(new RecentDoctorsInfo()
{
DoctorsName = "Peter Parker",
Specialization = "Spiderman",
Location = "177a Bleecker St. | USA"
});
originalRecentDoctorsList.Add(new RecentDoctorsInfo()
{
DoctorsName = "Bruce Banner",
Specialization = "The Hulk",
Location = "177a Bleecker St. | USA"
});
originalRecentDoctorsList.Add(new RecentDoctorsInfo()
{
DoctorsName = "Reed Richards",
Specialization = "Mr.Fantastic",
Location = "177a Bleecker St. | USA"
});
}
And by last, your Search method should clean the public collection (the one you're showing) and use the private as backup
private void Search()
{
if (!string.IsNullOrEmpty(SearchedText))
{
var filteredDoctors = RecentDoctors
.Where(x =>
x.DoctorsName.ToLower().Contains(SearchedText.ToLower()))
.ToList();
RecentDoctors.Clear();
foreach(var recentDoctor in filteredDoctors)
RecentDoctors.Add(recentDoctor);
}
else
{
// This is when you clean the text from the search
RecentDoctors.Clear();
foreach(var originalRecentDoctor in originalRecentDoctorsList)
RecentDoctors.Add(originalRecentDoctor);
}
}

How to separate picture and label of a node with GraphViz?

I am trying to display a graph with images and labels with GraphViz. I would like to display the label under the image (see labelloc="b" option on the graph) but somehow it doesn't not work. Label and image are overlapped.
Any idea what I am missing?
Below is the DOT code I am using, and the current result.
Thanks!
digraph {
graph [compound=true, labelloc="b"];
node [shape=box];
edge [dir=none];
Label1[label="Label1",image="images/Avatar1.png"];
Label2[label="Label2",image="images/Avatar2.png"];
Label3[label="Label3",image="images/Avatar3.png"];
{
rank=same;
Label1 -> h0 -> Label2;
h0[shape=circle,label="",height=0.01,width=0.01];
}
{
h0_0;
h0_0[shape=circle,label="",height=0.01,width=0.01];
}
h0 -> h0_0;
h0_0 -> Label3;
}
UPD: You need just to add an imagepos attribute to your solution with height:
digraph {
graph [compound=true, labelloc="b"];
node [shape=box];
edge [dir=none];
Label1[
label="Label1"
height="2.1"
imagepos="tc"
labelloc="b"
image="images/Avatar1.png"
];
Label2[
label="Label2"
height="2.1"
imagepos="tc"
labelloc="b"
image="images/Avatar2.png"
];
Label3[
label="Label3"
height="2.1"
imagepos="tc"
labelloc="b"
image="images/Avatar3.png"
];
{
rank=same;
Label1 -> h0 -> Label2;
h0[shape=circle,label="",height=0.01,width=0.01];
}
{
h0_0;
h0_0[shape=circle,label="",height=0.01,width=0.01];
}
h0 -> h0_0;
h0_0 -> Label3;
}
Result:
Or you may also use HTML-like labels, and specifically, tables:
digraph {
graph [compound=true, labelloc="b"];
node [shape=box];
edge [dir=none];
Label1 [
shape=plain
label=<
<table cellspacing="0" border="0" cellborder="1">
<tr><td><img src="images/Avatar1.png" /></td></tr>
<tr><td>Label1</td></tr>
</table>
>
];
Label2 [
shape=plain
label=<
<table cellspacing="0" border="0" cellborder="1">
<tr><td><img src="images/Avatar2.png" /></td></tr>
<tr><td>Label2</td></tr>
</table>
>
];
Label3 [
shape=plain
label=<
<table cellspacing="0" border="0" cellborder="1">
<tr><td><img src="images/Avatar3.png" /></td></tr>
<tr><td>Label3</td></tr>
</table>
>
];
{
rank=same;
Label1 -> h0 -> Label2;
h0[shape=circle,label="",height=0.01,width=0.01];
}
{
h0_0;
h0_0[shape=circle,label="",height=0.01,width=0.01];
}
h0 -> h0_0;
h0_0 -> Label3;
}
The code is a bit more complex (at first glance), but as a bonus you get more flexible control over the borders.
Result:
By specifying a "height" (warning, those are "inches") for the nodes, I get the "labelloc" to work and I can thus move the label out of the picture. I would prefer if the box didn't have that white place at the top, but it's better than before.

Graphviz: make invisible node take no space

Consider the graph
digraph "Graph b9ca21a1-971e-400c-a7f4-cd650986476a" {
graph [ margin=10 ];
node [ shape=point ];
"Invisible" [
//height=0,
//width=0,
//margin=0,
//style=invis,
color="red"
];
subgraph "cluster_1" {
"A";
"B";
"Invisible";
"C";
"D";
}
}
resulting in
I want the red node to be completely invisible, taking up no space, but it has to remain there, such that it can be used for lhead/ltail an other such miscellaneous things.
When uncommenting the commented lines, the result is
As you see, there is still a spatial artifact of this node.
Question: is there a way to remove this completely, without affecting the other nodes' layout in the graph?
You can use nodesep to minimize the node separation (min is 0.02) and instead add invisible peripheries to the visible nodes in order accomplish an approximately equal separation of them.
Here's an example of how to transform an approximation of your first graph into an approximation of the second:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/viz.js#1.8.0/viz.js"></script>
<script src="https://unpkg.com/d3-graphviz#0.1.2/build/d3-graphviz.min.js"></script>
<div id="graph" style="text-align: center;"></div>
<script>
var dots = [
`
digraph "Graph b9ca21a1-971e-400c-a7f4-cd650986476a" {
graph [ margin=10, nodesep=0 ];
node [ shape=point, peripheries=3, penwidth=0 ];
"Invisible" [
//height=0,
//width=0,
//margin=0,
//style=invis,
color="red"
];
subgraph "cluster_1" {
"A";
"B";
"Invisible";
"C";
"D";
}
"X" [color="blue"];
"X" -> "Invisible" [headclip="false"]
}
`, `
digraph "Graph b9ca21a1-971e-400c-a7f4-cd650986476a" {
graph [ margin=10, nodesep=0 ];
node [ shape=point, peripheries=3, penwidth=0 ];
"Invisible" [
peripheries=0,
height=0,
width=0,
// margin=0,
// style=invis,
color="red"
];
subgraph "cluster_1" {
"A";
"B";
"Invisible";
"C";
"D";
}
"X" [color="blue"];
"X" -> "Invisible" [headclip="false"]
}
`
];
var dotIndex = 0;
var graphviz = d3.select("#graph").graphviz();
function render() {
var dot = dots[dotIndex % dots.length];
var transition1 = d3.transition()
.delay(1000)
.duration(1000 + 4000 * dotIndex);
graphviz
.tweenShapes(false)
.engine("dot")
.dot(dot)
.transition(transition1)
.render();
dotIndex += 1;
transition1
.transition()
.duration(0)
.on('end', function () {
if (dotIndex != dots.length) {
render();
}
});
}
render();
</script>

File Browser not as part of the Kendo UI Editor

I am looking for a way to get stand alone File Browser control just like in the Kendo UI Editor.
The presented solution in their forum uses Image Browser which has file type restrictions and thumbnails that we do not need.
If you are looking for file browser not as part of the Kendo UI Rich Text Editor control, not image browser, you can accomplish this (although it is not officially supported by Telerik/Progress).
You can use:
kendoFileBrowser
<div class="file-borwser-js"></div>
$(".file-borwser-js").kendoFileBrowser({
"messages":
{
"deleteFile": "Сигурни ли сте че искате да изтриете \"{0}\"?",
"directoryNotFound": "Директория с посоченото име не бе открита.",
"emptyFolder": "Празна папка",
"invalidFileType": "Избраният файл \"{0}\" не е валиден. Поддържаните файлови формати са {1}.",
"orderBy": "Подреди по:",
"orderBySize": "Големина",
"orderByName": "Име",
"overwriteFile": "Файл с име \"{0}\" вече съществува в тази папка. Искате ли да го презапишете?",
"uploadFile": "Качи файл",
"dropFilesHere": "преместете с мишката файлове тук за да ги качите",
"search": "Търси"
},
"transport":
{
"type": "filebrowser-aspnetmvc",
"read": { "url": "/FileBrowser/Read" },
"uploadUrl": "/FileBrowser/Upload",
"destroy": { "url": "/FileBrowser/Destroy" },
"create": { "url": "/FileBrowser/Create" },
"fileUrl": "/FileBrowser/File?path={0}"
}
});
The way I got this is from the inline Javascript that is downloaded with the Kendo Editor. And copied the "fileBrowser" element. Then I used this element as parameter of kendoFileBrowser()
jQuery(function(){jQuery("#_0__Content").kendoEditor({"tools":[{"name":"bold"},{"name":"italic"},{"name":"underline"},{"name":"strikethrough"},{"name":"justifyLeft"},{"name":"justifyCenter"},{"name":"justifyRight"},{"name":"justifyFull"},{"name":"insertUnorderedList"},{"name":"insertOrderedList"},{"name":"outdent"},{"name":"indent"},{"name":"createLink"},{"name":"unlink"},{"name":"insertImage"},{"name":"insertFile"},{"name":"subscript"},{"name":"superscript"},{"name":"createTable"},{"name":"addColumnLeft"},{"name":"addColumnRight"},{"name":"addRowAbove"},{"name":"addRowBelow"},{"name":"deleteRow"},{"name":"deleteColumn"},{"name":"viewHtml"},{"name":"formatting"},{"name":"cleanFormatting"},{"name":"fontName"},{"name":"fontSize"},{"name":"foreColor"},{"name":"backColor"}],"messages":{"bold":"Получер","italic":"Курсив","underline":"Подчертай","strikethrough":"Зачертай","superscript":"Горен индекс","subscript":"Долен индекс","justifyCenter":"Центрирай","justifyLeft":"Подравни отляво","justifyRight":"Подравни отдясно","justifyFull":"Подравни","insertOrderedList":"Вмъкни номериран списък","insertUnorderedList":"Вмъкни списък","indent":"Добави отстъп","outdent":"Премахни отстъп","createLink":"Направи препратка","unlink":"Премахни препратка","insertImage":"Вмъкни картина","insertFile":"Вмъкни файл","insertHtml":"Вмъкни HTML","fontName":"Шрифт","fontNameInherit":"(наследен шрифт)","fontSize":"Размер на шрифта","fontSizeInherit":"(наследен размер)","formatBlock":"Избери формат","styles":"Стилове","backColor":"Цвят на фона","foreColor":"Цвят","viewHtml":"Виж HTML-а","dialogUpdate":"Обнови","createTable":"Създай таблица"},"imageBrowser":{"messages":{"deleteFile":"Сигурни ли сте че искате да изтриете \"{0}\"?","directoryNotFound":"Директория с посоченото име не бе открита.","emptyFolder":"Празна папка","invalidFileType":"Избраният файл \"{0}\" не е валиден. Поддържаните файлови формати са {1}.","orderBy":"Подреди по:","orderBySize":"Големина","orderByName":"Име","overwriteFile":"Файл с име \"{0}\" вече съществува в тази папка. Искате ли да го презапишете?","uploadFile":"Качи файл","dropFilesHere":"преместете с мишката файлове тук за да ги качите","search":"Търси"},"transport":{"read":{"url":"/ImageBrowser/Read"},"type":"imagebrowser-aspnetmvc","thumbnailUrl":"/ImageBrowser/Thumbnail","uploadUrl":"/ImageBrowser/Upload","destroy":{"url":"/ImageBrowser/Destroy"},"create":{"url":"/ImageBrowser/Create"},"imageUrl":"/ImageBrowser/Image?path={0}"}},"fileBrowser":{"messages":{"deleteFile":"Сигурни ли сте че искате да изтриете \"{0}\"?","directoryNotFound":"Директория с посоченото име не бе открита.","emptyFolder":"Празна папка","invalidFileType":"Избраният файл \"{0}\" не е валиден. Поддържаните файлови формати са {1}.","orderBy":"Подреди по:","orderBySize":"Големина","orderByName":"Име","overwriteFile":"Файл с име \"{0}\" вече съществува в тази папка. Искате ли да го презапишете?","uploadFile":"Качи файл","dropFilesHere":"преместете с мишката файлове тук за да ги качите","search":"Търси"},"transport":{"read":{"url":"/FileBrowser/Read"},"type":"filebrowser-aspnetmvc","uploadUrl":"/FileBrowser/Upload","destroy":{"url":"/FileBrowser/Destroy"},"create":{"url":"/FileBrowser/Create"},"fileUrl":"/FileBrowser/File?path={0}"}}});});
Tested on Kendo UI: 2015.1.318
I have tried to post it on their forum as an answer, but apparently you have to be super premium to do so and I am posting it here.

Resources