Positionating and arrow direction in GraphViz - graph

I'm trying to migrate some realy old documentation to our internal wiki using GraphViz.
I'm not used to the Dot language, and needs some help
See following example:
I have experiment a lot, but the best I have come up to so far is this:
digraph CentralPmr {
fontname="Helvetica";
shape=box;
node[shape=box];
graph [splines=ortho]
sg [label="TTD storage group for\nthe logged values"]
vc [label="Value catalogue"]
tc1 [label="Time catalogoue (1)"]
tc2 [label="Time catalogoue (2)"]
sv_ [shape=point,width=0.01,height=0.01];
sv [label=""]
ie [shape=none, label="Initiating event"]
c1 [shape=none, label="The set of values, defined\nby the value catalogue, which\nare freezed out of the TTD\nstorage group of the actual log."]
c2 [shape=none, label="Time catalogue defining\nat what time around the\ninitiating event values\nshould be collected."]
sgf [shape=record, label="{<f0> 1|2|3|4|..}|{ | | | | }"]
sg -> sv_ [penwidth=4, dir=none];
sv_ -> sv -> tc2 [penwidth=4]
sv -> sgf:f0 [penwidth=4]
{vc, tc1} -> sg
c1 -> sv [style=dashed, arrowhead="open"];
{rank=min; ie}
{rank=same; sg c1}
{rank=same; vc sgf}
{rank=max; rc2}
}
It don't have to be exactly the same as the source, but I want it to be understandable.
The problems is:
How do I place the text between "Value catalogue" and "Time catalogue (1)"?
[Edit] How do I force the arrow to "TTD storage group for PMR-freezed value" to go from the side, and not from the above? It is a virtualization of a memory area, and the arrow are pointing to a specific memory post. In other images, it can point to a other memory post in the memory area (eg. 2 , 3, 4..).
Is it possible to create a zigzag line from the "Initiating event"?
How do I place the legends in the bottom that explains the different types of lines?
[edit] How do I add the comments above, under and to the right of the "TTD storage group for PMR-freezed values"?
[Edit] How do I make the "TTD storage group for PMR-freezed value" wider?

This is on top of my first answer in a way that editing that one would create too much confusion. I have tried to take all your needs into consideration and it only works (I believe) if you give up the splines=ortho requirement. Pls refer to the comments below my first answer. Here we go:
digraph CentralPmr {
fontname="Helvetica";
shape=box;
node[shape=box];
// graph [splines=ortho]
sg [label="TTD storage group for\nthe logged values", width = 2.5]
sv[ label="", width = 2]
ie [ shape=none, label="Initiating event", fontsize = 18 ]
c1 [ shape=none, label="The set of values, defined\nby the value catalogue, which\nare freezed out of the TTD\nstorage group of the actual log." ]
sgf[shape=box, margin=0, label=<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
<TR>
<TD BORDER="0" COLSPAN="2">TTD storage group for<BR/>PMR freezed values</TD>
</TR>
<TR>
<TD PORT="f1">1</TD>
<TD BORDER="0" ROWSPAN="6">The set of<BR/>values is<BR/>stored in<BR/>the TTD<BR/>storage<BR/>group</TD>
</TR>
<TR>
<TD>2</TD>
</TR>
<TR>
<TD>3</TD>
</TR>
<TR>
<TD>4</TD>
</TR>
<TR>
<TD>-</TD>
</TR>
<TR>
<TD>-</TD>
</TR>
<TR>
<TD BORDER="0" COLSPAN="2">Up to nine freezing areas<BR/>for defined central PMR</TD>
</TR>
</TABLE>>];
TTD [shape=none, margin=0, label=<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="12">
<TR>
<TD PORT="f1">Value catalogue</TD>
</TR>
<TR>
<TD BORDER="0"></TD>
</TR>
<TR>
<TD PORT="f2">Time catalogue (1)</TD>
</TR>
<TR>
<TD BORDER="0">Time catalogue defining<BR/>at what time around the<BR/>initiating event values<BR/>should be collected</TD>
</TR>
<TR>
<TD PORT="f3">Time catalogue (2)</TD>
</TR>
</TABLE>>];
connector_1[ shape = point height = 0 width = 0 margin = 0 ]
ie -> connector_1[ style = dotted, arrowhead = none ];
{ rank = same; connector_1 c1 }
connector_1 -> c1[ style = invis, minlen = 4 ];
c1 -> sv[ style = dashed, arrowhead = open ];
connector_2[ shape = point height = 0 width = 0 margin = 0 ]
connector_1 -> connector_2[ style = dotted ];
{ rank = same; sg connector_2 sv }
sg -> connector_2[ minlen = 3, penwidth = 4, arrowhead = none ];
connector_2 -> sv[ minlen = 3, penwidth = 4 ];
sg:sw -> TTD:f1:nw[ weight = 5 ];
sg:w -> TTD:f2:w;
sv:sw -> TTD:f3:e[ penwidth = 4 ];
sv:sw -> sgf:f1:w[ penwidth = 4 ];
node[ shape = plaintext ];
leg2[ label = "Data flow" ];
leg4[ label = "Reference" ];
leg6[ label = "Comment" ];
node [ shape = point height = 0 width = 0 margin = 0 ];
leg1 leg3 leg5
TTD:sw -> leg1[ style = invis ];
{ rank = same; leg1 leg2 leg3 leg4 leg5 leg6 }
edge[ minlen = 2 ];
leg1 -> leg2[ penwidth = 4 ];
leg3 -> leg4[ style = dotted ];
leg5 -> leg6[ style = dashed, arrowhead = open ];
}
yields

Not sure whether I understand completely what you want but below my take on it. This is just a first attempt, much more fine-tunig can be done. I would probably use HTML-like nodes where text and "box" need to be closer, in particular for that "TTD Storage Group for PMR freezed values" in the original graph.
My answers to your questions would be:
How do I place the text between "Value catalogue" and "Time catalogue (1)"?
--- See below. I have put it between the two time catalogues as in the original graph but easy to move around.
How do I force the arrow to the record go from the side, and not from the above?
--- See below. You could also use rankdir = LR; to change the orientation if that is your question.
Is it possible to create a zigzag line from the "Initiating event"?
--- There are ways, but a lot of effort (like creating a custom shape). Nothing "out of the box", to the best of my knowledge.
How do I place the legends in the bottom?
I don't really understand, but in general, the answer would be HTML-like labels when we talk about nodes.
Her is what I have done:
digraph CentralPmr
{
fontname="Helvetica";
shape=box;
node[shape=box];
graph [splines=ortho]
sg [label="TTD storage group for\nthe logged values"]
vc [label="Value catalogue"]
tc1 [label="Time catalogoue (1)"]
tc2 [label="Time catalogoue (2)"]
sv_ [shape=point,width=0.01,height=0.01];
sv [label="", width = 2]
ie [shape=none, label="Initiating event"]
c1 [shape=none, label="The set of values, defined\nby the value catalogue, which\nare freezed out of the TTD\nstorage group of the actual log."]
c2 [shape=none, label="Time catalogue defining\nat what time around the\ninitiating event values\nshould be collected."]
sgf [shape=record, label="{<f0> 1|2|3|4|..}|{ | | | | }"]
connector_1[ shape = point height = 0 width = 0 margin = 0 ]
ie -> connector_1[ style = dotted, arrowhead = none ];
{ rank = same; connector_1 c1 }
connector_1 -> c1[ style = invis ];
c1 -> sv[ style = dashed, arrowhead = open ];
connector_2[ shape = point height = 0 width = 0 margin = 0 ]
connector_1 -> connector_2[ style = dotted ];
{ rank = same; sg connector_2 sv }
sg -> connector_2[ minlen = 3, penwidth = 4, arrowhead = none ];
connector_2 -> sv[ minlen = 3, penwidth = 4 ];
vc -> tc1 -> c2 -> tc2[ style = invis, weight = 10 ];
sg -> vc;
sg -> tc1;
sv -> tc2[ penwidth = 4 ];
sv -> sgf;
}
yields

Related

Adding class to item in array based on it's value

so I simply want to change the colour of the text of a list of arrays based on the word that's in the string.
Here's my array of combinations currently being outputted in the view with <%= #output %>:
[{"Mer lec sai ham"=>{:price=>13.1, :points=>53.4}}, {"Mer rus sai ham"=>{:price=>12.1, :points=>32.2}}, {"Fer rus sai ham"=>{:price=>13.1, :points=>31.4}}, {"Mcl rus sai ham"=>{:price=>13.5, :points=>14.9}}]
CSS:
span.mer{color:red;}
span.lec{color:green;}
And then I am just calling it in my view using <%= #combo %>
The ouput should be the list of combinations but this time just the words are coloured correctly. This is currently just showing nothing, it's not throwing errors though.
EDIT
Added my full controller code in case it's needed:
# DEFINE VARIABLE AND TARGET
teams = team_price.keys
drivers = driver_price.keys
target = 13.5
# CREATE METHOD TO SUM BOTH PRICES AND POINTS FOR GIVEN COMBINATION
def add_up(combo, ht, hd)
t, d = combo
ht[t] + hd.values_at(*d).sum
end
# METHOD TO DOUBLE HIGHEST DRIVER POINTS
def add_dbl(combo, team_points, driver_points)
t, ds = combo
dmax = ds.max_by { |d| driver_points[d] }
driver_points[dmax] + add_up(combo, team_points, driver_points)
end
# ALL POSSIBLE COMBINATIONS OF TEAM AND DRIVERS
all_driver_combos = drivers.combination(3).to_a
all_combos = teams.product(all_driver_combos)
# SHOW ALL COMBOS WHERE SUM DOES NOT EXCEED TARGET
valid_combos = all_combos.select do |c|
add_up(c, team_price, driver_price) <= target
end
# SORT VALID COMBOS BY SUM OF POINTS FOR EACH ELEMENT
ordered = valid_combos.sort_by do |c|
-add_dbl(c, team_points, driver_points)
end
# SORT DRIVERS BY HIGHEST POINTS
ordered.each do |_t,ds|
ds.sort_by! { |d| -driver_points[d] }
end
# OUTPUT
output = ordered.map do |c|
{ c.join(" ")=>{ price: add_up(c, team_price, driver_price),
points: add_dbl(c, team_points, driver_points).round(2)} }
end
# CONVERT OUTPUT TO HASH
#output = output.reduce Hash.new, :merge
Table Edit
I would now like to place this combination output in a table as follows (whilst keeping the colour coding solution):
Team
Double
Drivers
Price
Points
Mer
lec
sai ham
13.1
53.4
Mer
rus
sai ham
12.1
32.2
Fer
rus
sai ham
13.1
31.4
In my view, I now have:
<table class="table table-condensed">
<thead>
<tr>
<th>Combo</th>
<th>Price</th>
<th>Points</th>
</tr>
</thead>
<tbody>
<% #output.each do |key,value| %>
<tr>
<td><%= raw(key.gsub(/Mer/, '<span class="mer">Mer</span>')) %></td>
<td><%= value[:price] %></td>
<td><%= value[:points] %></td>
</tr>
<% end %>
</tbody>
</table>
Which is currently outputting:
Combo Price Points
Mer lec sai ham 13.1 53.4
Mer rus sai ham 12.1 32.2
Fer rus sai ham 13.1 31.4
Mcl rus sai ham 13.5 14.9
However as shown in the table example above I would like it so that 'Combo' is split up into the correct columns. Like so:
Before:
Mer lec sai ham | 13.1 | 53.4
After:
Mer | lec | sai ham | 13.1 | 53.4
Price and points work fine, but it's just a case of splitting up the combo. Looking at the table I would like to create, the 'team' (e.g Mer, fer) is always going to be the first value in the array. 'Double' is always the second value in the array (lec in the above case) and then 'Driver' is just the remaining values (sai ham in the above case). Because the array is ordered in this way is there a way to pull each out using something like: .key[1] or somehow assign each key a new name? Thanks for the help!
It looks like you are using assignment (=) when you should be using comparison (==)?
#combo = #output.each do |p|
if p = 'Mer'
class_name = 'red'
elsif p = 'lec'
class_name = 'green'
else
class_name = 'black'
end
end
Instead of
#combo = #output.each do |p|
if p == 'Mer'
class_name = 'red'
elsif p == 'lec'
class_name = 'green'
else
class_name = 'black'
end
end
Also you are passing a hash as p and trying to compare it to a string. You don't need combo at all and should leave interface concerns out of your controller code. I would get rid of #combo. As far as stying things using CSS I try to keep it semantic. So I would name classes like:
span.mer{color:red;}
span.lec{color:green;}
And just leave black as the default.
Edit
Also if you can change your #output to just a simple hash of items with the string as the key and the hash of price and points as the value like this:
#output = {"Mer lec sai ham"=>{:price=>13.1, :points=>53.4}, "Mer rus sai ham"=>{:price=>12.1, :points=>32.2}, "Fer rus sai ham"=>{:price=>13.1, :points=>31.4}, "Mcl rus sai ham"=>{:price=>13.5, :points=>14.9}}
Possible view code(Edited to address request for table):
<table class="table table-condensed">
<thead>
<tr>
<th>Team</th>
<th>Double</th>
<th>Drivers</th>
<th>Price</th>
<th>Points</th>
</tr>
</thead>
<tbody>
<% #output.each do |key,value| %>
<tr>
<% key = key.split(' ') %>
<% key = "<td>#{key[0]}</td><td>#{key[1]}</td><td>#{key[2]} #{key[3]}</td>" %>
<%= raw(key.gsub(/Mer/, '<span class="mer">Mer</span>')) %>
<td><%= value[:price] %></td>
<td><%= value[:points] %></td>
</tr>
<% end %>
</tbody>
</table>
This will wrap any word with a <span> tag that applies the color to that word based on the css defs. If "Lec" could appear as "Lec" or "lec" I would change the regexp in gsub to be case insensitive.
Multiple problems:
you are using asignment (=) instead of comparison (==)
you are comparing (if you change it) a Hash to a string, which will never be true
you are assigning a local variable (class_name) which does not have an effect on the output
it seems you want to highlight each word with the color, not just the full sentence
you have an array of hashes, where the key is a "sentence" (at least multiple words) which you want to highlight seperately and the value is a hash again.
Here is something to get you started (first step is to get code working, next step would be to clean it up. E.g. moving things to helpers or components or decorators).
def whatever_action_this_is
#output_with_colors = #output.map do |item|
item.transform_keys do |key|
words = key.split(' ')
highlighted = words.map do |word|
content_tag(:span, word, class: css_class(word))
end
highlighted.join(' ')
end
end
end
private
def css_class(word)
case word.downcase
when 'mer' then 'red'
when 'lec' then 'green'
else 'black'
end
end
And then in your view, assuming some ERB template
<ul>
<% #output_with_colors.each do |item|
<% item.each do |key, value|
<li>
<%= key.html_safe %>
<br/>
<%= value %>
</li>
<% end %>
<% end %>
</ul>

R diagrammeR using html for formatting while reading text from r variable

I'm creating a flow chart with the R package diagrammer. To get desired formatting (bold, bullet, left-justify) I can write node label in html. However, I also want to populate some of the text by calling variables in R, and I can't figure out how to do both (html formatting + call R variables) at the same time.
In this code snippet, the html formatting works but instead of printing the string assigned to the variable 'text_var', it prints the string 'text_var'.
library(DiagrammeR)
text_var = 'Some text'
grViz("digraph flowchart {
# Node definitions
node [fontname = Helvetica, shape = box]
tab1 [label = <<b> Node 1 </b> <br ALIGN = 'LEFT' /> • text_var <br ALIGN = 'LEFT' />
>]
tab2 [label = 'Node 2']
# Edge definitions
tab1 -> tab2
}")
In this code snippet, I am print the string assigned to the variable 'text_var', but there's no html.
library(DiagrammeR)
text_var = 'Some text'
grViz("digraph flowchart {
# Node definitions
node [fontname = Helvetica, shape = box]
tab1 [label = '##1']
tab2 [label = 'Node 2']
# Edge definitions
tab1 -> tab2
}
[1]: paste0('Node 1 \\n ', text_var)
")
Desired result is the text from the second example with the formatting from the first. Thank you!
Although the solution by Allan Cameron works, it is also possible to use
Graphviz Substitution.
I found the implementation rather buggy however, although ##1 should work in the below example, I found that it took over 3 minutes of full CPU usage before I shut it off. ##1-1 seems to work.
text_var = 'Some text'
grViz("
digraph flowchart {
# Node definitions
node [fontname = Helvetica, shape = box]
tab1 [label = <
<b>Node 1</b>
<br ALIGN = 'LEFT' />
• ##1-1
<br ALIGN = 'LEFT' />
>]
tab2 [label = 'Node 2']
# Edge definitions
tab1 -> tab2
}
[1]: text_var"
)
R doesn't know that you want the string "text_var" inside the string you are passing to grViz to be replaced by the actual variable text_var containing your string. Try this instead:
grViz(gsub("text_var", text_var, "digraph flowchart {
# Node definitions
node [fontname = Helvetica, shape = box]
tab1 [label = <<b> Node 1 </b> <br ALIGN = 'LEFT' /> • text_var <br ALIGN = 'LEFT' />
>]
tab2 [label = 'Node 2']
# Edge definitions
tab1 -> tab2
}"))
```

Index Error: list out of range on html table web-scrape - Python

I appreciate this has been asked many times but I've been stuck here for quite a while.
I'm trying to take all the data from a table on a website and put it into a pandas dataframe.
I've written the code to do the web scraping but for some reason i'm getting the error whilst trying to write to my variable.
import requests
import requests
url = 'http://www.londonstockexchange.com/exchange/prices/stocks/summary/fundamentals.html?fourWayKey=GB00BCDBXK43GBGBXASX1'
page = requests.get(url).text
from bs4 import BeautifulSoup
soup = BeautifulSoup(page)
# print(soup.prettify())
all_tables = soup.find_all('table')
right_table = soup.find_all('table', {'class':'table_dati'})
tbl1 = right_table[0]
A = []
B = []
C = []
D = []
E = []
F = []
for row in tbl1.find_all('tr'):
cells = row.find_all('td')
A.append(cells[0].find(text = True))
B.append(cells[1].find(text = True))
C.append(cells[2].find(text = True))
D.append(cells[3].find(text = True))
E.append(cells[4].find(text = True))
F.append(cells[5].find(text = True))
Here's the error:
A.append(cells[0].find(text = True))
IndexError: list index out of range
Appreciate the help,
Thanks
Well, if you see the html code, your first iteration doesn't have td (is the thead), so when you are trying to get the first element, it doesn't exist, because cells are empty.
This is the first row:
<tr>
<th class="name">Income Statement</th>
<th>
31-May-13 <br>( £
m )
</th>
<th>
31-May-14 <br>( £
m )
</th>
<th>
31-May-15 <br>( £
m )
</th>
<th>
31-May-16 <br>( £
m )
</th>
<th>
31-May-17 <br>( £
m )
</th>
</tr>
You can surround with try, except, or select the tbody.
Based on your code, you can add to the find_all() a list of tags, and
then jump when the length of the cells list is less than 6, but for the future it's better to try to create lists dynamically, instead of everything being fixed.
for row in tbl1.find_all('tr'):
try:
cells = row.find_all(['td', 'th'])
if len(cells) < 6:
continue
A.append(cells[0].find(text = True).strip())
B.append(cells[1].find(text = True).strip())
C.append(cells[2].find(text = True).strip())
D.append(cells[3].find(text = True).strip())
E.append(cells[4].find(text = True).strip())
F.append(cells[5].find(text = True).strip())
except Exception as e:
print(e)
print(A)
The output is:
[
"Income Statement",
"Revenue",
"Operating Profit/(Loss)",
"Net Interest",
"Profit Before Tax",
"Profit After Tax",
"Profit After Tax",
"PROFIT FOR THE PERIOD",
"Minority Interests",
"Equity Holders of Parent Company",
"Earnings per Share - Basic",
"Earnings per Share - Diluted",
"Earnings per Share - Adjusted",
"Earnings per Share - Basic",
"Earnings per Share - Diluted",
"Earnings per Share - Adjusted",
"Dividend per Share"
]

Subgraph doesn't appear in graphviz chart

I can't figure out, why subgraph doesn't work here:
digraph virtPfr {
node [
shape=box
]
Start [
style=rounded,
label="create folder profiles"
]
subgraph asd {
label = "copy files from other profiles"
cpIfDestFilesExist [
label = "Check for file existance"
]
Cp [
label = "Copy"
]
}
Start -> asd
cpIfDestFilesExist -> Start
cpIfDestFilesExist -> Cp
}
but this code works:
digraph G {
node [
shape = "record"
]
Animal [
label = "Animal name and age"
]
subgraph clusterAnimalImpl {
label = "Package animal.tmpl"
Dog [
label = "Dog name and age"
]
Cat [
label = "Cat name and age"
]
}
Dog -> Animal
Cat -> Animal
Dog -> Cat
}
I don't understand, what's different on the top graph, in comparison to the bottom graph, that the bottom works, but the top doesn't. I've already pulled my eyes out. I don't see the problem here.
Please, help
A couple of issues:
Sub-graph names have to start with the keyword cluster.
You can't connect edges directly to a sub-graph, instead you can use the lhead/ltail workaround described here.
For your graph, it could like as follows:
digraph virtPfr {
graph [compound=true]
node [
shape=box
]
Start [
style=rounded,
label="create folder profiles"
]
subgraph cluster_asd {
label = "copy files from other profiles"
cpIfDestFilesExist [
label = "Check for file existance"
]
Cp [
label = "Copy"
]
}
Start -> cpIfDestFilesExist [lhead=cluster_asd]
cpIfDestFilesExist -> Start
cpIfDestFilesExist -> Cp
}
Which generates the following output:

diagrammeR specifying node order and formatting text

I am using diagrammeR grViz to construct a flow chart. I would like to specify the order of some of the nodes that have the same rank. In the following chart, I would like to have Node 1 in the center rather than on the left. In addition, I would like to have "Node 2" underlined, but not "extra detail". Here is the code:
library("DiagrammeR")
grViz("
digraph CFA {
# Multiple level nodes
node [shape = rectangle, color=CornflowerBlue]
a [label = 'Node 1' ];
node [shape = ellipse, color=CornflowerBlue]
T1 [label = 'Node 2\\nextra detail'];
T2 [label = 'Node 3'];
{rank = same; a T1 T2}
# Connect nodes with edges and labels
a -> T1
a -> T2
}
")
Any help would be much appreciated. Also, if there are resources to help me along with these customization issues in diagrammeR, please include a link.
there are several ways to order the nodes. The easiest here is perhaps to have the edge T2 -> a in this order (rather than a -> T2), so node T2 is first and then use dir=back to reverse the arrow. You can use html to underline the node label. (also have to use break, <br/>, instead of newline,\n)
grViz("
digraph CFA {
a [label = 'Node 1', shape = rectangle, color=CornflowerBlue ];
node [shape = ellipse, color=CornflowerBlue]
T1 [label = <Node 2 <br/> <u>extra detail</u>>];
T2 [label = 'Node 3'];
{rank = same; a T1 T2}
# Connect nodes with edges and labels
a -> T1
T2 -> a[dir=back]
}
")
From comment: Is there a way to make a portion of the text in a node a different color (e.g. just the "extra detail", but not "Node 2")?
Yes, from the html link above you can "sets the color of the font within the scope of FONT.../FONT". So for example, change the label of T1 to
label = <Node 2 <br/> <font color='red'> <u>extra detail</u> </font> >

Resources