summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--odoo/addons/base/models/ir_ui_view.py75
-rw-r--r--odoo/tools/__init__.py1
-rwxr-xr-xodoo/tools/graph.py755
3 files changed, 1 insertions, 830 deletions
diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py
index 2eabcdc69ec..613ed6e0be1 100644
--- a/odoo/addons/base/models/ir_ui_view.py
+++ b/odoo/addons/base/models/ir_ui_view.py
@@ -23,7 +23,7 @@ from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError
from odoo.http import request
from odoo.modules.module import get_resource_from_path, get_resource_path
-from odoo.tools import config, graph, ConstantMapping, pycompat, apply_inheritance_specs, locate_node
+from odoo.tools import config, ConstantMapping, pycompat, apply_inheritance_specs, locate_node
from odoo.tools.convert import _fix_multiple_roots
from odoo.tools.json import scriptsafe as json_scriptsafe
from odoo.tools.safe_eval import safe_eval
@@ -1160,79 +1160,6 @@ actual arch.
return self.env['ir.translation'].translate_fields('ir.ui.view', self.id, 'arch_db')
@api.model
- def graph_get(self, id, model, node_obj, conn_obj, src_node, des_node, label, scale):
- def rec_name(rec):
- return (rec.name if 'name' in rec else
- rec.x_name if 'x_name' in rec else
- None)
-
- nodes = []
- nodes_name = []
- transitions = []
- start = []
- tres = {}
- labels = {}
- no_ancester = []
- blank_nodes = []
-
- Model = self.env[model]
- Node = self.env[node_obj]
-
- for model_key, model_value in Model._fields.items():
- if model_value.type == 'one2many':
- if model_value.comodel_name == node_obj:
- _Node_Field = model_key
- _Model_Field = model_value.inverse_name
- for node_key, node_value in Node._fields.items():
- if node_value.type == 'one2many':
- if node_value.comodel_name == conn_obj:
- # _Source_Field = "Incoming Arrows" (connected via des_node)
- if node_value.inverse_name == des_node:
- _Source_Field = node_key
- # _Destination_Field = "Outgoing Arrows" (connected via src_node)
- if node_value.inverse_name == src_node:
- _Destination_Field = node_key
-
- record = Model.browse(id)
- for line in record[_Node_Field]:
- if line[_Source_Field] or line[_Destination_Field]:
- nodes_name.append((line.id, rec_name(line)))
- nodes.append(line.id)
- else:
- blank_nodes.append({'id': line.id, 'name': rec_name(line)})
-
- if 'flow_start' in line and line.flow_start:
- start.append(line.id)
- elif not line[_Source_Field]:
- no_ancester.append(line.id)
-
- for t in line[_Destination_Field]:
- transitions.append((line.id, t[des_node].id))
- tres[str(t['id'])] = (line.id, t[des_node].id)
- label_string = ""
- if label:
- for lbl in safe_eval(label):
- if tools.ustr(lbl) in t and tools.ustr(t[lbl]) == 'False':
- label_string += ' '
- else:
- label_string = label_string + " " + tools.ustr(t[lbl])
- labels[str(t['id'])] = (line.id, label_string)
-
- g = graph(nodes, transitions, no_ancester)
- g.process(start)
- g.scale(*scale)
- result = g.result_get()
- results = {}
- for node_id, node_name in nodes_name:
- results[str(node_id)] = result[node_id]
- results[str(node_id)]['name'] = node_name
- return {'nodes': results,
- 'transitions': tres,
- 'label': labels,
- 'blank_nodes': blank_nodes,
- 'node_parent_field': _Model_Field}
-
- @api.model
def _validate_custom_views(self, model):
"""Validate architecture of custom views (= without xml id) for a given model.
This method is called at the end of registry update.
diff --git a/odoo/tools/__init__.py b/odoo/tools/__init__.py
index 4ac0af8ddfe..d2666bd1cbf 100644
--- a/odoo/tools/__init__.py
+++ b/odoo/tools/__init__.py
@@ -8,7 +8,6 @@ from . import pdf
from .config import config
from .misc import *
from .translate import *
-from .graph import graph
from .image import *
from .sql import *
from .float_utils import *
diff --git a/odoo/tools/graph.py b/odoo/tools/graph.py
deleted file mode 100755
index 894b7f3bd2b..00000000000
--- a/odoo/tools/graph.py
+++ /dev/null
@@ -1,755 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import operator
-import math
-
-
-class graph(object):
- def __init__(self, nodes, transitions, no_ancester=None):
- """Initialize graph's object
-
- @param nodes list of ids of nodes in the graph
- @param transitions list of edges in the graph in the form (source_node, destination_node)
- @param no_ancester list of nodes with no incoming edges
- """
-
- self.nodes = nodes or []
- self.edges = transitions or []
- self.no_ancester = no_ancester or {}
- trans = {}
-
- for t in transitions:
- trans.setdefault(t[0], [])
- trans[t[0]].append(t[1])
- self.transitions = trans
- self.result = {}
-
-
- def init_rank(self):
- """Computes rank of the nodes of the graph by finding initial feasible tree
- """
- self.edge_wt = {}
- for link in self.links:
- self.edge_wt[link] = self.result[link[1]]['x'] - self.result[link[0]]['x']
-
- tot_node = len(self.partial_order)
- #do until all the nodes in the component are searched
- while self.tight_tree()<tot_node:
- list_node = []
- list_edge = []
-
- for node in self.nodes:
- if node not in self.reachable_nodes:
- list_node.append(node)
-
- for edge in self.edge_wt:
- if edge not in self.tree_edges:
- list_edge.append(edge)
-
- slack = 100
-
- for edge in list_edge:
- if ((edge[0] in self.reachable_nodes and edge[1] not in self.reachable_nodes) or
- (edge[1] in self.reachable_nodes and edge[0] not in self.reachable_nodes)):
- if slack > self.edge_wt[edge]-1:
- slack = self.edge_wt[edge]-1
- new_edge = edge
-
- if new_edge[0] not in self.reachable_nodes:
- delta = -(self.edge_wt[new_edge]-1)
- else:
- delta = self.edge_wt[new_edge]-1
-
- for node in self.result:
- if node in self.reachable_nodes:
- self.result[node]['x'] += delta
-
- for edge in self.edge_wt:
- self.edge_wt[edge] = self.result[edge[1]]['x'] - self.result[edge[0]]['x']
-
- self.init_cutvalues()
-
-
- def tight_tree(self):
- self.reachable_nodes = []
- self.tree_edges = []
- self.reachable_node(self.start)
- return len(self.reachable_nodes)
-
-
- def reachable_node(self, node):
- """Find the nodes of the graph which are only 1 rank apart from each other
- """
-
- if node not in self.reachable_nodes:
- self.reachable_nodes.append(node)
- for edge in self.edge_wt:
- if edge[0]==node:
- if self.edge_wt[edge]==1:
- self.tree_edges.append(edge)
- if edge[1] not in self.reachable_nodes:
- self.reachable_nodes.append(edge[1])
- self.reachable_node(edge[1])
-
-
- def init_cutvalues(self):
- """Initialize cut values of edges of the feasible tree.
- Edges with negative cut-values are removed from the tree to optimize rank assignment
- """
- self.cut_edges = {}
- self.head_nodes = []
- i=0
-
- for edge in self.tree_edges:
- self.head_nodes = []
- rest_edges = []
- rest_edges += self.tree_edges
- del rest_edges[i]
- self.head_component(self.start, rest_edges)
- i+=1
- positive = 0
- negative = 0
- for source_node in self.transitions:
- if source_node in self.head_nodes:
- for dest_node in self.transitions[source_node]:
- if dest_node not in self.head_nodes:
- negative+=1
- else:
- for dest_node in self.transitions[source_node]:
- if dest_node in self.head_nodes:
- positive+=1
-
- self.cut_edges[edge] = positive - negative
-
-
- def head_component(self, node, rest_edges):
- """Find nodes which are reachable from the starting node, after removing an edge
- """
- if node not in self.head_nodes:
- self.head_nodes.append(node)
-
- for edge in rest_edges:
- if edge[0]==node:
- self.head_component(edge[1],rest_edges)
-
-
- def process_ranking(self, node, level=0):
- """Computes initial feasible ranking after making graph acyclic with depth-first search
- """
-
- if node not in self.result:
- self.result[node] = {'y': None, 'x':level, 'mark':0}
- else:
- if level > self.result[node]['x']:
- self.result[node]['x'] = level
-
- if self.result[node]['mark']==0:
- self.result[node]['mark'] = 1
- for sec_end in self.transitions.get(node, []):
- self.process_ranking(sec_end, level+1)
-
-
- def make_acyclic(self, parent, node, level, tree):
- """Computes Partial-order of the nodes with depth-first search
- """
-
- if node not in self.partial_order:
- self.partial_order[node] = {'level':level, 'mark':0}
- if parent:
- tree.append((parent, node))
-
- if self.partial_order[node]['mark']==0:
- self.partial_order[node]['mark'] = 1
- for sec_end in self.transitions.get(node, []):
- self.links.append((node, sec_end))
- self.make_acyclic(node, sec_end, level+1, tree)
-
- return tree
-
-
- def rev_edges(self, tree):
- """reverse the direction of the edges whose source-node-partail_order> destination-node-partail_order
- to make the graph acyclic
- """
- Is_Cyclic = False
- i=0
- for link in self.links:
- src = link[0]
- des = link[1]
- edge_len = self.partial_order[des]['level'] - self.partial_order[src]['level']
- if edge_len < 0:
- del self.links[i]
- self.links.insert(i, (des, src))
- self.transitions[src].remove(des)
- self.transitions.setdefault(des, []).append(src)
- Is_Cyclic = True
- elif math.fabs(edge_len) > 1:
- Is_Cyclic = True
- i += 1
-
- return Is_Cyclic
-
- def exchange(self, e, f):
- """Exchange edges to make feasible-tree optimized
- :param e: edge with negative cut-value
- :param f: new edge with minimum slack-value
- """
- del self.tree_edges[self.tree_edges.index(e)]
- self.tree_edges.append(f)
- self.init_cutvalues()
-
-
- def enter_edge(self, edge):
- """Finds a new_edge with minimum slack value to replace an edge with negative cut-value
-
- @param edge edge with negative cut-value
- """
-
- self.head_nodes = []
- rest_edges = []
- rest_edges += self.tree_edges
- del rest_edges[rest_edges.index(edge)]
- self.head_component(self.start, rest_edges)
-
- if edge[1] in self.head_nodes:
- l = []
- for node in self.result:
- if node not in self.head_nodes:
- l.append(node)
- self.head_nodes = l
-
- slack = 100
- new_edge = edge
- for source_node in self.transitions:
- if source_node in self.head_nodes:
- for dest_node in self.transitions[source_node]:
- if dest_node not in self.head_nodes:
- if slack>(self.edge_wt[edge]-1):
- slack = self.edge_wt[edge]-1
- new_edge = (source_node, dest_node)
-
- return new_edge
-
-
- def leave_edge(self):
- """Returns the edge with negative cut_value(if exists)
- """
- if self.critical_edges:
- for edge in self.critical_edges:
- self.cut_edges[edge] = 0
-
- for edge in self.cut_edges:
- if self.cut_edges[edge]<0:
- return edge
-
- return None
-
-
- def finalize_rank(self, node, level):
- self.result[node]['x'] = level
- for destination in self.optimal_edges.get(node, []):
- self.finalize_rank(destination, level+1)
-
-
- def normalize(self):
- """The ranks are normalized by setting the least rank to zero.
- """
-
- least_rank = min(x['x'] for x in self.result.values())
-
- if least_rank!=0:
- for node in self.result:
- self.result[node]['x']-=least_rank
-
-
- def make_chain(self):
- """Edges between nodes more than one rank apart are replaced by chains of unit
- length edges between temporary nodes.
- """
-
- for edge in self.edge_wt:
- if self.edge_wt[edge]>1:
- self.transitions[edge[0]].remove(edge[1])
- start = self.result[edge[0]]['x']
- end = self.result[edge[1]]['x']
-
- for rank in range(start+1, end):
- if not self.result.get((rank, 'temp'), False):
- self.result[(rank, 'temp')] = {'y': None, 'x': rank, 'mark': 0}
-
- for rank in range(start, end):
- if start==rank:
- self.transitions[edge[0]].append((rank+1, 'temp'))
- elif rank==end-1:
- self.transitions.setdefault((rank, 'temp'), []).append(edge[1])
- else:
- self.transitions.setdefault((rank, 'temp'), []).append((rank+1, 'temp'))
-
-
- def init_order(self, node, level):
- """Initialize orders the nodes in each rank with depth-first search
- """
- if not self.result[node]['y']:
- self.result[node]['y'] = self.order[level]
- self.order[level] += 1
-
- for sec_end in self.transitions.get(node, []):
- if node!=sec_end:
- self.init_order(sec_end, self.result[sec_end]['x'])
-
-
- def order_heuristic(self):
- for i in range(12):
- self.wmedian()
-
-
- def wmedian(self):
- """Applies median heuristic to find optimized order of the nodes with in their ranks
- """
- for level in self.levels:
-
- node_median = []
- nodes = self.levels[level]
- for node in nodes:
- node_median.append((node, self.median_value(node, level-1)))
-
- sort_list = sorted(node_median, key=operator.itemgetter(1))
-
- new_list = [tuple[0] for tuple in sort_list]
-
- self.levels[level] = new_list
- order = 0
- for node in nodes:
- self.result[node]['y'] = order
- order +=1
-
-
- def median_value(self, node, adj_rank):
- """Returns median value of a vertex , defined as the median position of the adjacent vertices
-
- @param node node to process
- @param adj_rank rank 1 less than the node's rank
- """
- adj_nodes = self.adj_position(node, adj_rank)
- l = len(adj_nodes)
- m = l // 2
-
- if l==0:
- return -1.0
- elif l%2 == 1:
- return adj_nodes[m]#median of the middle element
- elif l==2:
- return (adj_nodes[0]+adj_nodes[1])/2
- else:
- left = adj_nodes[m-1] - adj_nodes[0]
- right = adj_nodes[l-1] - adj_nodes[m]
- return ((adj_nodes[m-1]*right) + (adj_nodes[m]*left))/(left+right)
-
-
- def adj_position(self, node, adj_rank):
- """Returns list of the present positions of the nodes adjacent to node in the given adjacent rank.
-
- @param node node to process
- @param adj_rank rank 1 less than the node's rank
- """
-
- pre_level_nodes = self.levels.get(adj_rank, [])
- adj_nodes = []
-
- if pre_level_nodes:
- for src in pre_level_nodes:
- if self.transitions.get(src) and node in self.transitions[src]:
- adj_nodes.append(self.result[src]['y'])
-
- return adj_nodes
-
-
- def preprocess_order(self):
- levels = {}
-
- for r in self.partial_order:
- l = self.result[r]['x']
- levels.setdefault(l,[])
- levels[l].append(r)
-
- self.levels = levels
-
-
- def graph_order(self):
- """Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component
- """
- mid_pos = 0.0
- max_level = max(len(x) for x in self.levels.values())
-
- for level in self.levels:
- if level:
- no = len(self.levels[level])
- factor = (max_level - no) * 0.10
- list = self.levels[level]
- list.reverse()
-
- if no%2==0:
- first_half = list[no // 2:]
- factor = -factor
- else:
- first_half = list[no // 2 + 1:]
- if max_level==1:#for the case when horizontal graph is there
- self.result[list[no // 2]]['y'] = mid_pos + (self.result[list[no // 2]]['x']%2 * 0.5)
- else:
- self.result[list[no // 2]]['y'] = mid_pos + factor
-
- last_half = list[:no // 2]
-
- i=1
- for node in first_half:
- self.result[node]['y'] = mid_pos - (i + factor)
- i += 1
-
- i=1
- for node in last_half:
- self.result[node]['y'] = mid_pos + (i + factor)
- i += 1
- else:
- self.max_order += max_level+1
- mid_pos = self.result[self.start]['y']
-
-
- def tree_order(self, node, last=0):
- mid_pos = self.result[node]['y']
- l = self.transitions.get(node, [])
- l.reverse()
- no = len(l)
-
- rest = no%2
- first_half = l[no // 2 + rest:]
- last_half = l[:no // 2]
-
- for i, child in enumerate(first_half):
- self.result[child]['y'] = mid_pos - (i+1 - (0 if rest else 0.5))
-
- if self.transitions.get(child, False):
- if last:
- self.result[child]['y'] = last + len(self.transitions[child])/2 + 1
- last = self.tree_order(child, last)
-
- if rest:
- mid_node = l[no // 2]
- self.result[mid_node]['y'] = mid_pos
-
- if self.transitions.get(mid_node, False):
- if last:
- self.result[mid_node]['y'] = last + len(self.transitions[mid_node])/2 + 1
- if node!=mid_node:
- last = self.tree_order(mid_node)
- else:
- if last:
- self.result[mid_node]['y'] = last + 1
- self.result[node]['y'] = self.result[mid_node]['y']
- mid_pos = self.result[node]['y']
-
- i=1
- last_child = None
- for child in last_half:
- self.result[child]['y'] = mid_pos + (i - (0 if rest else 0.5))
- last_child = child
- i += 1
- if self.transitions.get(child, False):
- if last:
- self.result[child]['y'] = last + len(self.transitions[child])/2 + 1
- if node!=child:
- last = self.tree_order(child, last)
-
- if last_child:
- last = self.result[last_child]['y']
-
- return last
-
-
- def process_order(self):
- """Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component
- """
-
- if self.Is_Cyclic:
- max_level = max(len(x) for x in self.levels.values())
-
- if max_level%2:
- self.result[self.start]['y'] = (max_level+1)/2 + self.max_order + (self.max_order and 1)
- else:
- self.result[self.start]['y'] = max_level /2 + self.max_order + (self.max_order and 1)
-
- self.graph_order()
-
- else:
- self.result[self.start]['y'] = 0
- self.tree_order(self.start, 0)
- min_order = math.fabs(min(x['y'] for x in self.result.values()))
-
- index = self.start_nodes.index(self.start)
- same = False
-
- roots = []
- if index>0:
- for start in self.start_nodes[:index]:
- same = True
- for edge in self.tree_list[start][1:]:
- if edge in self.tree_list[self.start]:
- continue
- else:
- same = False
- break
- if same:
- roots.append(start)
-
- if roots:
- min_order += self.max_order
- else:
- min_order += self.max_order + 1
-
- for level in self.levels:
- for node in self.levels[level]:
- self.result[node]['y'] += min_order
-
- if roots:
- roots.append(self.start)
- one_level_el = self.tree_list[self.start][0][1]
- base = self.result[one_level_el]['y']# * 2 / (index + 2)
-
-
- no = len(roots)
- first_half = roots[:no // 2]
-
- if no%2==0:
- last_half = roots[no // 2:]
- else:
- last_half = roots[no // 2 + 1:]
-
- factor = -math.floor(no // 2)
- for start in first_half:
- self.result[start]['y'] = base + factor
- factor += 1
-
- if no%2:
- self.result[roots[no // 2]]['y'] = base + factor
- factor +=1
-
- for start in last_half:
- self.result[start]['y'] = base + factor
- factor += 1
-
- self.max_order = max(x['y'] for x in self.result.values())
-
- def find_starts(self):
- """Finds other start nodes of the graph in the case when graph is disconnected
- """
- rem_nodes = []
- for node in self.nodes:
- if not self.partial_order.get(node):
- rem_nodes.append(node)
- cnt = 0
- while True:
- if len(rem_nodes)==1:
- self.start_nodes.append(rem_nodes[0])
- break
- else:
- count = 0
- new_start = rem_nodes[0]
- largest_tree = []
-
- for node in rem_nodes:
- self.partial_order = {}
- tree = self.make_acyclic(None, node, 0, [])
- if len(tree)+1 > count:
- count = len(tree) + 1
- new_start = node
- largest_tree = tree
- else:
- if not largest_tree:
- new_start = rem_nodes[0]
- rem_nodes.remove(new_start)
-
- self.start_nodes.append(new_start)
-
-
- for edge in largest_tree:
- if edge[0] in rem_nodes:
- rem_nodes.remove(edge[0])
- if edge[1] in rem_nodes:
- rem_nodes.remove(edge[1])
-
- if not rem_nodes:
- break
-
-
- def rank(self):
- """Finds the optimized rank of the nodes using Network-simplex algorithm
- """
- self.levels = {}
- self.critical_edges = []
- self.partial_order = {}
- self.links = []
- self.Is_Cyclic = False
-
- self.tree_list[self.start] = self.make_acyclic(None, self.start, 0, [])
- self.Is_Cyclic = self.rev_edges(self.tree_list[self.start])
- self.process_ranking(self.start)
- self.init_rank()
-
- #make cut values of all tree edges to 0 to optimize feasible tree
- e = self.leave_edge()
-
- while e :
- f = self.enter_edge(e)
- if e==f:
- self.critical_edges.append(e)
- else:
- self.exchange(e,f)
- e = self.leave_edge()
-
- #finalize rank using optimum feasible tree
-# self.optimal_edges = {}
-# for edge in self.tree_edges:
-# source = self.optimal_edges.setdefault(edge[0], [])
-# source.append(edge[1])
-
-# self.finalize_rank(self.start, 0)
-
- #normalization
- self.normalize()
- for edge in self.edge_wt:
- self.edge_wt[edge] = self.result[edge[1]]['x'] - self.result[edge[0]]['x']
-
- def order_in_rank(self):
- """Finds optimized order of the nodes within their ranks using median heuristic
- """
-
- self.make_chain()
- self.preprocess_order()
- self.order = {}
- max_rank = max(x for x in self.levels)
-
- for i in range(max_rank+1):
- self.order[i] = 0
-
- self.init_order(self.start, self.result[self.start]['x'])
-
- for level in self.levels:
- self.levels[level].sort(key=lambda x: self.result[x]['y'])
-
- self.order_heuristic()
- self.process_order()
-
- def process(self, starting_node):
- """Process the graph to find ranks and order of the nodes
-
- @param starting_node node from where to start the graph search
- """
-
- self.start_nodes = starting_node or []
- self.partial_order = {}
- self.links = []
- self.tree_list = {}
-
- if self.nodes:
- if self.start_nodes:
- #add dummy edges to the nodes which does not have any incoming edges
- tree = self.make_acyclic(None, self.start_nodes[0], 0, [])
-
- for node in self.no_ancester:
- for sec_node in self.transitions.get(node, []):
- if sec_node in self.partial_order:
- self.transitions[self.start_nodes[0]].append(node)
- break
-
- self.partial_order = {}
- tree = self.make_acyclic(None, self.start_nodes[0], 0, [])
-
-
- # if graph is disconnected or no start-node is given
- #than to find starting_node for each component of the node
- if len(self.nodes) > len(self.partial_order):
- self.find_starts()
-
- self.max_order = 0
- #for each component of the graph find ranks and order of the nodes
- for s in self.start_nodes:
- self.start = s
- self.rank() # First step:Netwoek simplex algorithm
- self.order_in_rank() #Second step: ordering nodes within ranks
-
-
- def __str__(self):
- result = ''
- for l in self.levels:
- result += 'PosY: ' + str(l) + '\n'
- for node in self.levels[l]:
- result += '\tPosX: '+ str(self.result[node]['y']) + ' - Node:' + str(node) + "\n"
- return result
-
-
- def scale(self, maxx, maxy, nwidth=0, nheight=0, margin=20):
- """Computes actual co-ordiantes of the nodes
- """
-
- #for flat edges ie. source an destination nodes are on the same rank
- for src in self.transitions:
- for des in self.transitions[src]:
- if self.result[des]['x'] - self.result[src]['x'] == 0:
- self.result[src]['x'] += 0.08
- self.result[des]['x'] -= 0.08
-
- factorX = maxx + nheight
- factorY = maxy + nwidth
-
- for node in self.result:
- self.result[node]['y'] = (self.result[node]['y']) * factorX + margin
- self.result[node]['x'] = (self.result[node]['x']) * factorY + margin
-
-
- def result_get(self):
- return self.result
-
-if __name__=='__main__':
- starting_node = ['profile'] # put here nodes with flow_start=True
- nodes = ['project','account','hr','base','product','mrp','test','profile']
- transitions = [
- ('profile','mrp'),
- ('mrp','project'),
- ('project','product'),
- ('mrp','hr'),
- ('mrp','test'),
- ('project','account'),
- ('project','hr'),
- ('product','base'),
- ('account','product'),
- ('account','test'),
- ('account','base'),
- ('hr','base'),
- ('test','base')
- ]
-
- radius = 20
- g = graph(nodes, transitions)
- g.process(starting_node)
- g.scale(radius*3,radius*3, radius, radius)
-
- from PIL import Image
- from PIL import ImageDraw
- img = Image.new("RGB", (800, 600), "#ffffff")
- draw = ImageDraw.Draw(img)
-
- result = g.result_get()
- node_res = {}
- for node in nodes:
- node_res[node] = result[node]
-
- for name, node in node_res.items():
-
- draw.arc( (int(node['y']-radius), int(node['x']-radius),int(node['y']+radius), int(node['x']+radius) ), 0, 360, (128,128,128))
- draw.text( (int(node['y']), int(node['x'])), str(name), (128,128,128))
-
-
- for t in transitions:
- draw.line( (int(node_res[t[0]]['y']), int(node_res[t[0]]['x']),int(node_res[t[1]]['y']),int(node_res[t[1]]['x'])),(128,128,128) )
- img.save("graph.png", "PNG")