HierarchicalGraphMachine hiding nested states - pytransitions

I've been experimenting with the HierarchicalGraphMachine class to help visualise the machine structures as I edit them.
from transitions.extensions import HierarchicalGraphMachine as Machine
count_states = ['1', '2', '3', 'done']
count_trans = [
['increase', '1', '2'],
['increase', '2', '3'],
['decrease', '3', '2'],
['decrease', '2', '1'],
['done', '3', 'done'],
['reset', '*', '1']
]
counter = Machine(states=count_states, transitions=count_trans, initial='1')
states = ['waiting', 'collecting', {'name': 'counting', 'children': counter, 'initial': '1'}]
transitions = [
['collect', '*', 'collecting'],
['wait', '*', 'waiting'],
['count', 'collecting', 'counting']
]
collector = Machine(states=states, transitions=transitions, initial='waiting')
collector.get_graph(show_roi=False).draw('count1.png', prog='dot')
This generates the expected graphic showing both the parent and nested states in full (I'm not yet authorised to upload the graphics).
Is there a way to generate a the full parent state machine graphic without expanding the nested states? For example reducing the nested states to an empty box.
I've tried "show_roi=True", but this only shows the current transition event, and removes all other states.

Depending on whether you use the pygraphviz (default in 0.8.8 and prior) or graphviz backend, get_graph may return a pygraphiv.AGraph object or a custom transitions.Graph. An AGraph is easier to manipulate while the second is basically the pure graph notation in dot. However, you can manipulate both according to your needs. For instance, you could filter edges and nodes from an AGraph and rebuild a 'flat' version of it:
# your code here ...
collector.collect()
graph = collector.get_graph()
# iterate over all edges; We know that parent and child states are connected
# with an underscore. We just collect the root element of each source
# and target element of each edge. Furthermore, we collect the edge color,
# and the label which is stored either in 'label', 'taillabel' or 'headlabel'
new_edges = [(edge[0].split('_')[0],
edge[1].split('_')[0],
edge.attr['color'],
edge.attr['label']
or edge.attr['taillabel']
or edge.attr['headlabel']) for edge in graph.edges()]
# States with children are noted as subgraphs. We collect their name and their
# current color.
new_nodes = [(sgraph.graph_attr['label'], sgraph.graph_attr['color'])
for sgraph in graph.subgraphs()]
# We add all states that have no children and also do not contain an
# underscore in their name. An underscore would suggest that this node/state
# is a child/substate.
new_nodes += [(node.name, node.attr['color'])
for node in graph.nodes() if '_' not in node.name]
# remove everything from the graph obeject
graph.clear()
# and add nodes and edges again
for name, color in new_nodes:
graph.add_node(name, color=color)
for start, target, color, label in new_edges:
if label:
graph.add_edge(start, target, color=color, label=label)
graph.draw('agraph.png', prog='dot')
This results in the following graph:
You see that I also collected the edge and node color to visualize the last transition but graph.clear() removed all the 'default' styling attributes.
They could be copied and restored as well or we could only remove nodes, edges and subgraphs. This depends on how much you are willing to mess with (py)graphviz.

Related

Unmerge and Assign Values Only Vertically or Horizontally Openpyxl

Using the answer provided by aka863 here: How to split merged Excel cells with Python?
I can unmerge, fill values and copy the styling. My questions is how to make the value assigning/filling process configurable.
I want the user to be able to choose whether the values will be filled vertically/horizontally.
I have tried changing the last loop where we assign the top_left_cell_values to unmerged cells. However I couldn't find a way to make it horizontal/vertical configurable. (I'm planning to use radio buttons and tkinter for this)
Its certainly possible to have the code de-merge cells and fill cells in whichever direction, vertically or horizontally regardless of which way the merge was originally. Or not fill at all, so only the top left cell retains the 'value' of the previously merged cells, which is default on unmerge.
Changing the direction of the fill requires some change and re-calculation on the max row and column values in the iter_rows loop, but is simple enough.
However it seems in your last comment you just want to give the user the option to fill or not fill on horizontal merges. In that case you just need to ask the question, and then run the iter_rows loop only if the response is yes.
The code sample below is based on the answer referenced question.
I'm assuming only single line horizontal merges since you dont mention what if anything should be done with vertical merges in the comment.
The code does initially check and indicate the merge direction either vertically or horizontally so it can be included take some action if a merge is vertical.
On code run after displaying the range and direction of the merge, the question is asked to fill, yes or no. If yes the cells are de-merged and all cells filled with the top left cell value using the iter_rows loop. If answer no then the cells are just de-merged.
from openpyxl import load_workbook
from openpyxl.utils.cell import range_boundaries
wb = load_workbook(filename='foo.xlsx')
st = wb['Sheet1']
mcr_coord_list = [mcr.coord for mcr in st.merged_cells.ranges]
direction_dict = {'v': 'vertical', 'h': 'horizontal'}
for mcr in mcr_coord_list:
print('---------------------------------------------------\n')
merge_direction = ''
min_col, min_row, max_col, max_row = range_boundaries(mcr)
top_left_cell_value = st.cell(row=min_row, column=min_col).value
if min_col == max_col:
merge_direction = 'v'
elif min_row == max_row:
merge_direction = 'h'
print(f"The cell range {mcr} is merged {direction_dict[merge_direction]}ly with the data '{top_left_cell_value}'")
while True:
demerge_fill = input('Do you want the de-merge to fill all cells(y|n)? ')
if demerge_fill.lower() in ["y", "n"]:
break
else:
print('Invalid response')
st.unmerge_cells(mcr)
if demerge_fill == 'y':
for row in st.iter_rows(min_col=min_col, min_row=min_row, max_col=max_col, max_row=max_row):
for cell in row:
cell.value = top_left_cell_value
else:
print(f"Only the top left cell {mcr.split(':')[0]} will contain the data!")
wb.save('merged_tmp.xlsx')

plotly go Scattermapbox is not showing markers

I some how have encountered some odd bug. When i try to create a Scattermapbox the markers dont render. This bug came out of no where, It was working perfectly fine then my internet went out and now for the last 8 hours it has not been working.
Ive tried running it in different IDE's
running it in google colab to make sure its not my machine
different data sets.
i am unsure what i have done wrong
The tooltips do display however when i hover over the invisible points.
and if use the export to png button everything is shown.
but no matter what it wont show up on the actual map itself and i am at my wits end.
I will include the callback function bellow.
#app.callback(
Output('2dmap','figure'),
[Input('2dgraph', 'clickData'),
Input('checklist', 'value')])
def update_map_2d(clickData,checklist):
# =============================================================================
# P1. Render Map when no point is clicked
# =============================================================================
# If No point has been clicked
if clickData is None:
#make a map
maps2d = go.Figure(go.Scattermapbox(
lat=[], # set lat and long
lon=[],
mode='markers',
marker =({'size':5.5}) # make markers size variable
))
# set up map layout
maps2d.update_layout(
autosize=True, # Autosize
hovermode='closest', # Show info on the closest marker
showlegend=True, # show legend of colors
mapbox=dict(
accesstoken=mapbox_access_token, # token
bearing=0, # starting facing direction
# starting location
center=dict(
lat=td.cLat,
lon=td.cLong
),
#angle and zoom
pitch=0,
zoom=12
),
#height and width
width=1000,
height=1000
)
return maps2d
else:
xCoord = int(clickData['points'][0]['x'])
yCoord = int(clickData['points'][0]["y"])
solutionRow = preatoFrontier.loc[(preatoFrontier['x'] == xCoord)&(preatoFrontier['y'] == yCoord)]
solId = int(solutionRow['SolId'])
#solId = 49
solution = td.getSolution(solutions, solId)
color = []
for row in solution['upGrade']:
if row == 0:
color.append('grey')
if row == 1:
color.append('green')
if row == 2:
color.append('blue')
if row == 3:
color.append('red')
solution['color'] = color
solution2 = solution[solution['color'].isin(checklist)]
maps2d = go.Figure(go.Scattermapbox(
lat=solution2['lat'],
lon=solution2['long'],
mode='markers',
#marker =({'color':solution['color']},{'size':5.5})
marker=dict(
size=12,
color=solution2['color'], #set color equal to a variable
colorscale='Viridis', # one of plotly colorscales
showscale=True
)
))
#=============================================================================
# P3. Map Layout
#=============================================================================
#set up map layout
maps2d.update_layout(
autosize=False, # Autosize
hovermode='closest', # Show info on the closest marker
showlegend=True, # show legend of colors
mapbox=dict(
accesstoken=mapbox_access_token, # token
bearing=0, # starting facing direction
# starting location
center=dict(
lat=td.cLat,
lon=td.cLong
),
#angle and zoom
pitch=0,
zoom=10
),
#height and width
width=1000,
height=1000
)
return maps2d
After a lot of hair pulling, i thought to try creating a new venv and uploading packages one by one and running to see how and where it fails. The last package i installed before it broke was dash-tools and sure enough some how that was causing mapbox to bug out hard. So dont install dash-tools

Editing the labels on a flow chart with DiagrammeR

I’m trying to make a flow chart with R. Attached is the chart I made in word (which is what I'm trying to get to). I don’t want to copy and paste it, I want to actually make it in R. I’ve been using DiagrammeR to try, and the code is below.
I'm having the main trouble with the labels, how to change some parts to bold and make them a nice distance away from the nodes. I've added in the blue and pink boxes in my code, which I like.
Code:
library(DiagrammeR)
graph <- "
digraph boxes_and_circles{
# Add node statements
# This states that all following nodes have a box shape
node[
shape=box,
style=rounded,
fontname=Helvetica,
penwidth=2,
fixedsize = true
width=4
]
# Connect the nodes with edge statements
edge[
arrowhead = normal,
arrowtail = none
]
# These are the main nodes at top of graph
'##1'->'##2'
[label=' Cleaning Function:
Text to lower case
Contractions expanded
Numbers replaced
Abbreviations expanded (Qdap)
NA’s ignored
Kerns replaced
White space removed', fontname=Helvetica, fontsize=20, fontweight=bold]
'##2'->'##3'
'##2'->'##4'
# Make subnodes with boxes around for tidy text grouping
# graph, node, and edge definitions
graph [
compound = true,
nodesep = 1,
ranksep = 0.25,
color = pink
]
# subgraph for tidy text, direct the flow
subgraph cluster0 {
'##3'->'##5'
[label=' -Tokenization
-Lemetisation
-Stop words removed', fontname=Helvetica, fontsize=20, fontweight=bold]
}
# Make subnodes with boxes around for Dictionary grouping
# graph, node, and edge definitions
graph [
compound = true,
nodesep = 1,
ranksep = .25,
color = blue
]
# subgraph for Dictionary direct the flow
subgraph cluster1 {
node [
fixedsize = true,
width = 3
]
'##4'->'##6' [label=' Scoring function (sentimentr)
Inner Join (dplyr)',fontname=Helvetica]
'##6'->'##7' [label=' Grouping
Summarise (dplyr)',fontname=Helvetica]
'##7'->'##8'
}
#Add a graph statement to change the properties of the graph
graph[nodesep=1] #this modifies distance between nodes
}
# Name the nodes
[1]: 'Response Data'
[2]: 'Clean Data'
[3]: 'Tidy Text'
[4]: 'Dictionary Creation'
[5]: 'Visualisation'
[6]: 'Sentiment Lexicon'
[7]: 'Summarised Text'
[8]: 'Visualisation and Statistics'
"

How to keep selected data persistent through callback in Dash/Plotly's clustered bar chart

I'm using Dash to plot some data. I currently have a clustered bar chart with two data sets (one for each bar in the clusters.) These data sets have their name and the corresponding color of the bars displayed in the top, left-hand corner of the figure. They can be clicked to be toggled on and off, which will remove their corresponding bars from the chart.
Separately, I have a checklist of items that can be displayed in the chart. I am using a callback to update the graph so that it only displays what the user has checked. This updates the graph as expected, however, it also resets the bars/datasets such that both are enabled. Ie. if you select only one of the bars, then select some new checklist items, it will display the new checklist items and both of the bars.
My thinking is that the logical way to do this is to pass some variable as a second input to the callback function, then set up the outputted figure within the function to only display the proper bars. However, I can't seem to find a variable that contains this data.
From what I can tell, the accessible properties of the Plotly graph object are 'id', 'clickData', 'clickAnnotationData', 'hoverData', 'clear_on_unhover', 'selectedData', 'relayoutData', 'figure', 'style', 'className', 'animate', 'animation_options', 'config', and 'loading_state'.
I've investigated all of these, and it seems that none hold the data that I am looking for. Does anyone know of an easy way to access this data?
This is how my callback is working right now:
#app.callback(
dash.dependencies.Output('theGraph', 'figure'),
[dash.dependencies.Input('theChecklist','values'),
dash.dependencies.Input('theGraph', 'clickData')
]
)
def updateGraph(checklistValues, figureInput):
#print to see what the variables hold
print(checklistValues)
print(figureInput)
figure=go.Figure(
data = [
go.Bar(
x = df[df['MyColumnName'].isin(checklistValues)].groupby('MyColumnName').size().index,
y = df[df['MyColumnName'].isin(checklistValues)].groupby('MyColumnName').size().values,
name = 'Bar 1'
),
go.Bar(
x = df[df['MyColumnName'].isin(checklistValues)].groupby('MyColumnName')['# cores'].sum().reset_index()['MyColumnName'],
y = df[df['MyColumnName'].isin(checklistValues)].groupby('MyColumnName')['# cores'].sum().reset_index()['MyOtherColumnName'],
name = 'Bar 2'
)
],
layout=go.Layout(
title='The Title',
showlegend=True,
legend=go.layout.Legend(
x=0,
y=1.0
),
margin=go.layout.Margin(l=40, r=40, t=40, b=110)
)
)
return figure

Perl checkbox group subgroupings

I'm trying to make a checkbox group in Perl with sub-titles dividing certain checkboxes. All the checkboxes are related, but they are in subcategories that I would like to be displayed when the user is selecting their choices.
my $grocery_list = $q->checkbox_group(
-name=>'grocery_list',
-values=>\#items,
-linebreak=>'true',
-labels=>\%items,
In the above example, I might have 'Milk' and 'Cheese' be under the subcategory of "DAIRY", while 'Ham' and 'Turkey' are under the subcategory of "MEATS". I already have a checkbox group with my values but I'm struggling creating those subcategory titles (DAIRY and MEATS) in between the subgroups of checkboxes. Ideally, the subcategory titles wouldn't be checkboxes, but would just divide checkboxes. Is it possible to put these subdivisions in a single checkbox, or would I have to make multiple checkboxes and merge the checked items into a single array afterward?
Note that CGI is no longer considered to be best practice. You should read CGI::Alternatives for an explanation, as well as suggestions for alternative modules.
You need the fieldset and legend elements to do what you describe. Without any additional CSS, the former draws a box around the group of inputs that it contains, and the latter labels that box.
Unfortunately, the checkbox_group convenience method doesn't allow you to subdivide its elements between two field sets, so you will have to call it twice with the same parameters except for the values and labels. It may be better to write your own helper routine that calls checkbox directly to build appropriate grouping.
Here's the basic idea. There's nothing magical about the CGI methods -- they just generate HTML according to the parameters you pass.
my %labels = (
milk => 'Milk',
cheese => 'Cheese',
ham => 'Ham',
turkey => 'Turkey',
);
my #dairy_items = qw/ milk cheese /;
my #meat_items = qw/ ham turkey /;
my $dairy = $q->checkbox_group(
-name => 'grocery_list',
-values => \#dairy_items,
-linebreak => 'true',
-labels => \%labels,
);
my $meat = $q->checkbox_group(
-name => 'grocery_list',
-values => \#meat_items,
-linebreak => 'true',
-labels => \%labels,
);
print
$q->start_form,
$q->fieldset(
$q->legend('Dairy'),
$dairy,
),
$q->fieldset(
$q->legend('Meat'),
$meat,
),
$q->end_form;