From da1ce1ea3057f55ea48a1940b196d82d00285461 Mon Sep 17 00:00:00 2001 From: Tasio Mendez Date: Tue, 10 Apr 2018 18:26:24 +0200 Subject: [PATCH] Colors and shapes --- TerroristNetworkModel.py | 245 ++++++++++++++++++++++++++++++++++ TerroristNetworkModel.yml | 58 ++++++++ server.py | 16 ++- templates/css/main.css | 1 + templates/img/svg/home.svg | 1 + templates/img/svg/person.svg | 1 + templates/img/svg/plus.svg | 1 + templates/img/svg/target.svg | 1 + templates/img/svg/time.svg | 1 + templates/index.html | 2 +- templates/js/socket.js | 27 +++- templates/js/template.js | 4 +- templates/js/visualization.js | 145 +++++++++++++++++--- visualization.py | 12 +- 14 files changed, 485 insertions(+), 30 deletions(-) create mode 100644 TerroristNetworkModel.py create mode 100644 TerroristNetworkModel.yml create mode 100644 templates/img/svg/home.svg create mode 100644 templates/img/svg/person.svg create mode 100644 templates/img/svg/plus.svg create mode 100644 templates/img/svg/target.svg create mode 100644 templates/img/svg/time.svg diff --git a/TerroristNetworkModel.py b/TerroristNetworkModel.py new file mode 100644 index 0000000..58575b4 --- /dev/null +++ b/TerroristNetworkModel.py @@ -0,0 +1,245 @@ +import random +import networkx as nx +from soil.agents import BaseAgent +from scipy.spatial import cKDTree as KDTree + +global betweenness_centrality_global +global degree_centrality_global + +betweenness_centrality_global = None +degree_centrality_global = None + +class TerroristSpreadModel(BaseAgent): + """ + Settings: + information_spread_intensity + + terrorist_additional_influence + + min_vulnerability (optional else zero) + + max_vulnerability + + prob_interaction + """ + + def __init__(self, environment=None, agent_id=0, state=()): + super().__init__(environment=environment, agent_id=agent_id, state=state) + + global betweenness_centrality_global + global degree_centrality_global + + if betweenness_centrality_global == None: + betweenness_centrality_global = nx.betweenness_centrality(self.global_topology) + if degree_centrality_global == None: + degree_centrality_global = nx.degree_centrality(self.global_topology) + + self.information_spread_intensity = environment.environment_params['information_spread_intensity'] + self.terrorist_additional_influence = environment.environment_params['terrorist_additional_influence'] + self.prob_interaction = environment.environment_params['prob_interaction'] + + if self.state['id'] == 0: # Civilian + self.initial_belief = random.uniform(0.00, 0.5) + elif self.state['id'] == 1: # Terrorist + self.initial_belief = random.uniform(0.8, 1.00) + elif self.state['id'] == 2: # Leader + self.initial_belief = 1.00 + + if 'min_vulnerability' in environment.environment_params: + self.vulnerability = random.uniform( environment.environment_params['min_vulnerability'], environment.environment_params['max_vulnerability'] ) + else : + self.vulnerability = random.uniform( 0, environment.environment_params['max_vulnerability'] ) + + self.mean_belief = self.initial_belief + self.betweenness_centrality = betweenness_centrality_global[self.id] + self.degree_centrality = degree_centrality_global[self.id] + + # self.state['radicalism'] = self.mean_belief + + def count_neighboring_agents(self, state_id=None): + if isinstance(state_id, list): + return len(self.get_neighboring_agents(state_id)) + else: + return len(super().get_agents(state_id, limit_neighbors=True)) + + def get_neighboring_agents(self, state_id=None): + if isinstance(state_id, list): + _list = [] + for i in state_id: + _list += super().get_agents(i, limit_neighbors=True) + return [ neighbour for neighbour in _list if isinstance(neighbour, TerroristSpreadModel) ] + else: + _list = super().get_agents(state_id, limit_neighbors=True) + return [ neighbour for neighbour in _list if isinstance(neighbour, TerroristSpreadModel) ] + + def step(self): + if self.state['id'] == 0: # Civilian + self.civilian_behaviour() + elif self.state['id'] == 1: # Terrorist + self.terrorist_behaviour() + elif self.state['id'] == 2: # Leader + self.leader_behaviour() + + def civilian_behaviour(self): + if self.count_neighboring_agents() > 0: + neighbours = [] + for neighbour in self.get_neighboring_agents(): + if random.random() < self.prob_interaction: + neighbours.append(neighbour) + influence = sum( neighbour.degree_centrality for neighbour in neighbours ) + mean_belief = sum( neighbour.mean_belief * neighbour.degree_centrality / influence for neighbour in neighbours ) + self.initial_belief = self.mean_belief + mean_belief = mean_belief * self.information_spread_intensity + self.initial_belief * ( 1 - self.information_spread_intensity ) + self.mean_belief = mean_belief * self.vulnerability + self.initial_belief * ( 1 - self.vulnerability ) + + if self.mean_belief >= 0.8: + self.state['id'] = 1 + + # self.state['radicalism'] = self.mean_belief + + def leader_behaviour(self): + self.mean_belief = self.mean_belief ** ( 1 - self.terrorist_additional_influence ) + if self.count_neighboring_agents(state_id=[1,2]) > 0: + for neighbour in self.get_neighboring_agents(state_id=[1,2]): + if neighbour.betweenness_centrality > self.betweenness_centrality: + self.state['id'] = 1 + + # self.state['radicalism'] = self.mean_belief + + def terrorist_behaviour(self): + if self.count_neighboring_agents(state_id=[1,2]) > 0: + neighbours = self.get_neighboring_agents(state_id=[1,2]) + influence = sum( neighbour.degree_centrality for neighbour in neighbours ) + mean_belief = sum( neighbour.mean_belief * neighbour.degree_centrality / influence for neighbour in neighbours ) + self.initial_belief = self.mean_belief + self.mean_belief = mean_belief * self.vulnerability + self.initial_belief * ( 1 - self.vulnerability ) + self.mean_belief = self.mean_belief ** ( 1 - self.terrorist_additional_influence ) + + if self.count_neighboring_agents(state_id=2) == 0 and self.count_neighboring_agents(state_id=1) > 0: + max_betweenness_centrality = self + for neighbour in self.get_neighboring_agents(state_id=1): + if neighbour.betweenness_centrality > max_betweenness_centrality.betweenness_centrality: + max_betweenness_centrality = neighbour + if max_betweenness_centrality == self: + self.state['id'] = 2 + + # self.state['radicalism'] = self.mean_belief + + def add_edge(self, G, source, target): + G.add_edge(source.id, target.id, start=self.env._now) + + def link_search(self, G, node, radius): + pos = nx.get_node_attributes(G, 'pos') + nodes, coords = list(zip(*pos.items())) + kdtree = KDTree(coords) # Cannot provide generator. + edge_indexes = kdtree.query_pairs(radius, 2) + _list = [ edge[int(not edge.index(node))] for edge in edge_indexes if node in edge ] + return [ G.nodes()[index]['agent'] for index in _list ] + + def social_search(self, G, node, steps): + nodes = list(nx.ego_graph(G, node, radius=steps).nodes()) + nodes.remove(node) + return [ G.nodes()[index]['agent'] for index in nodes ] + + +class TrainingAreaModel(BaseAgent): + """ + Settings: + training_influence + + min_vulnerability + + Requires TerroristSpreadModel. + """ + + def __init__(self, environment=None, agent_id=0, state=()): + super().__init__(environment=environment, agent_id=agent_id, state=state) + self.training_influence = environment.environment_params['training_influence'] + if 'min_vulnerability' in environment.environment_params: + self.min_vulnerability = environment.environment_params['min_vulnerability'] + else: self.min_vulnerability = 0 + + def step(self): + for neighbour in self.get_neighboring_agents(): + if isinstance(neighbour, TerroristSpreadModel) and neighbour.vulnerability > self.min_vulnerability: + neighbour.vulnerability = neighbour.vulnerability * ( 1 - self.training_influence ) + + +class HavenModel(BaseAgent): + """ + Settings: + haven_influence + + max_vulnerability + + Requires TerroristSpreadModel. + """ + + def __init__(self, environment=None, agent_id=0, state=()): + super().__init__(environment=environment, agent_id=agent_id, state=state) + self.haven_influence = environment.environment_params['haven_influence'] + self.max_vulnerability = environment.environment_params['max_vulnerability'] + + def step(self): + if self.count_neighboring_agents(state_id=0) == 0: + self.state['id'] = 1 # Terrorism Haven + else: + self.state['id'] = 0 # Civilian Haven + + for neighbour in self.get_neighboring_agents(): + if isinstance(neighbour, TerroristSpreadModel) and neighbour.vulnerability < self.max_vulnerability: + neighbour.vulnerability = neighbour.vulnerability ** ( 1 - self.haven_influence ) + + +class TerroristNetworkModel(TerroristSpreadModel): + """ + Settings: + sphere_influence + + vision_range + + weight_social_distance + + weight_link_distance + """ + + def __init__(self, environment=None, agent_id=0, state=()): + super().__init__(environment=environment, agent_id=agent_id, state=state) + + self.vision_range = environment.environment_params['vision_range'] + self.sphere_influence = environment.environment_params['sphere_influence'] + self.weight_social_distance = environment.environment_params['weight_social_distance'] + self.weight_link_distance = environment.environment_params['weight_link_distance'] + + def step(self): + if self.state['id'] == 1 or self.state['id'] == 2: + self.update_relationships() + super().step() + + def update_relationships(self): + if self.count_neighboring_agents(state_id=0) == 0: + close_ups = self.link_search(self.global_topology, self.id, self.vision_range) + step_neighbours = self.social_search(self.global_topology, self.id, self.sphere_influence) + search = list(set(close_ups).union(step_neighbours)) + neighbours = self.get_neighboring_agents() + search = [item for item in search if not item in neighbours and isinstance(item, TerroristNetworkModel)] + for agent in search: + social_distance = 1 / self.shortest_path_length(self.global_topology, self.id, agent.id) + spatial_proximity = ( 1 - self.get_distance(self.global_topology, self.id, agent.id) ) + prob_new_interaction = self.weight_social_distance * social_distance + self.weight_link_distance * spatial_proximity + if random.random() < prob_new_interaction: + self.add_edge(self.global_topology, self, agent) + break + + def get_distance(self, G, source, target): + source_x, source_y = nx.get_node_attributes(G, 'pos')[source] + target_x, target_y = nx.get_node_attributes(G, 'pos')[target] + dx = abs( source_x - target_x ) + dy = abs( source_y - target_y ) + return ( dx ** 2 + dy ** 2 ) ** ( 1 / 2 ) + + def shortest_path_length(self, G, source, target): + try: + return nx.shortest_path_length(G, source, target) + except nx.NetworkXNoPath: + return float('inf') diff --git a/TerroristNetworkModel.yml b/TerroristNetworkModel.yml new file mode 100644 index 0000000..1cb0e29 --- /dev/null +++ b/TerroristNetworkModel.yml @@ -0,0 +1,58 @@ +name: TerroristNetworkModel_sim +load_module: TerroristNetworkModel +max_time: 100 +num_trials: 1 +network_params: + generator: random_geometric_graph + radius: 0.2 + # generator: geographical_threshold_graph + # theta: 20 + n: 50 +network_agents: + - agent_type: TerroristNetworkModel + weight: 0.8 + state: + id: 0 # Civilians + - agent_type: TerroristNetworkModel + weight: 0.1 + state: + id: 2 # Leaders + - agent_type: TrainingAreaModel + weight: 0.05 + state: + id: 2 # Terrorism + - agent_type: HavenModel + weight: 0.05 + state: + id: 0 # Civilian + +environment_params: + # TerroristSpreadModel + information_spread_intensity: 0.7 + terrorist_additional_influence: 0.035 + max_vulnerability: 0.5 + prob_interaction: 0.5 + + # TrainingAreaModel and HavenModel + training_influence: 0.20 + haven_influence: 0.20 + + # TerroristNetworkModel + vision_range: 0.50 + sphere_influence: 2 + weight_social_distance: 0.035 + weight_link_distance: 0.035 + +visualization_params: + # Icons downloaded from https://www.iconfinder.com/ + shape_property: agent + shapes: + TrainingAreaModel: target + HavenModel: home + colors: + - attr_id: 0 + color: green + - attr_id: 1 + color: red + - attr_id: 2 + color: '#c16a6a' diff --git a/server.py b/server.py index 2ac20cc..2f5f23b 100644 --- a/server.py +++ b/server.py @@ -86,6 +86,9 @@ class SocketHandler(tornado.websocket.WebSocketHandler): self.config = self.config[0] self.send_log('INFO.soil', 'Using config: {name}'.format(name=self.config['name'])) + if 'visualization_params' in self.config: + self.write_message({'type': 'visualization_params', + 'data': self.config['visualization_params']}) self.name = self.config['name'] self.run_simulation() @@ -93,6 +96,8 @@ class SocketHandler(tornado.websocket.WebSocketHandler): for key in self.config['environment_params']: if type(self.config['environment_params'][key]) == float: setting_type = 'number' + elif type(self.config['environment_params'][key]) == int: + setting_type = 'number' elif type(self.config['environment_params'][key]) == bool: setting_type = 'boolean' else: @@ -149,8 +154,15 @@ class SocketHandler(tornado.websocket.WebSocketHandler): def run_simulation(self): # Run simulation and capture logs + if 'visualization_params' in self.config: + del self.config['visualization_params'] with self.logging(self.application.model.name): - self.simulation = self.application.model.run(self.config) + try: + self.simulation = self.application.model.run(self.config) + except: + error = 'Something went wrong. Please, try again.' + self.write_message({'type': 'error', + 'error': error}) trials = [] for i in range(self.config['num_trials']): @@ -232,5 +244,5 @@ class ModularServer(tornado.web.Application): url = 'http://127.0.0.1:{PORT}'.format(PORT=self.port) print('Interface starting at {url}'.format(url=url)) self.listen(self.port) - webbrowser.open(url) + # webbrowser.open(url) tornado.ioloop.IOLoop.instance().start() diff --git a/templates/css/main.css b/templates/css/main.css index 6168a65..ff1e823 100644 --- a/templates/css/main.css +++ b/templates/css/main.css @@ -183,6 +183,7 @@ hr { z-index: 5; height: 35px; padding: .5rem 1rem; + overflow: hidden; line-height: 1.5; color: #464a4c; pointer-events: none; diff --git a/templates/img/svg/home.svg b/templates/img/svg/home.svg new file mode 100644 index 0000000..197c39c --- /dev/null +++ b/templates/img/svg/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/img/svg/person.svg b/templates/img/svg/person.svg new file mode 100644 index 0000000..fa8f8ad --- /dev/null +++ b/templates/img/svg/person.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/img/svg/plus.svg b/templates/img/svg/plus.svg new file mode 100644 index 0000000..9dd7d5d --- /dev/null +++ b/templates/img/svg/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/img/svg/target.svg b/templates/img/svg/target.svg new file mode 100644 index 0000000..43c0c16 --- /dev/null +++ b/templates/img/svg/target.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/img/svg/time.svg b/templates/img/svg/time.svg new file mode 100644 index 0000000..103dd57 --- /dev/null +++ b/templates/img/svg/time.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index f51c8eb..ebe917d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -104,7 +104,7 @@ $('#simulation_modal .btn-success').click(function() { if ( !jQuery.isEmptyObject(run_simulation()) ) { self.GraphVisualization.reset(); - $('#load').show().addClass('loader');; + $('#load').show().addClass('loader'); _socket.send(run_simulation(), 'run_simulation'); $('.console').append('
'); } diff --git a/templates/js/socket.js b/templates/js/socket.js index c482042..c05a788 100755 --- a/templates/js/socket.js +++ b/templates/js/socket.js @@ -15,15 +15,15 @@ ws.onmessage = function(message) { switch(msg['type']) { case 'trials': - $('#load').removeClass('loader'); reset_trials(); set_trials(msg['data']); + // $('#load').removeClass('loader'); break; case 'get_trial': console.log(msg['data']); - GraphVisualization.import(convertJSON(msg['data']), function() { - $('#load').hide(); + + self.GraphVisualization.import(convertJSON(msg['data']), function() { reset_configuration(); set_configuration(); $('#home_menu').click(function() { @@ -34,6 +34,7 @@ ws.onmessage = function(message) { }); reset_timeline(); set_timeline(msg['data']); + $('#load').hide(); }); $('#charts .chart').removeClass('no-data'); set_chart_nodes(msg['data'], chart_nodes) @@ -61,6 +62,11 @@ ws.onmessage = function(message) { $('.console').animate({ scrollTop: $('.console')[0].scrollHeight }, 'fast'); break; + case 'visualization_params': + console.log(msg['data']); + self.GraphVisualization.set_params(msg['data']['shape_property'], msg['data']['shapes'], msg['data']['colors']); + break; + default: console.log('Unexpected message!') } @@ -103,6 +109,16 @@ var reset_trials = function() { } var convertJSON = function(json) { + // For NetworkX Geometric Graphs get positions + json.nodes.forEach(function(node) { + var scx = d3.scale.linear().domain([0, 1]).range([0, height]); + var scy = d3.scale.linear().domain([0, 1]).range([height, 0]); + if ( node.pos ) { + node.scx = scx(node.pos[0]); + node.scy = scy(node.pos[1]); + } + delete node.pos; + }); json.links.forEach(function(link) { link.source = json.nodes[link.source] link.target = json.nodes[link.target] @@ -136,7 +152,7 @@ var update_statistics_table = function() { statisticsSorted[i].split('.').pop().split('\'')[0] : statisticsSorted[i]; $('').addClass('col-sm-12').appendTo('#percentTable > tbody'); - $('').css('background-color', self.GraphVisualization.color(statisticsSorted[i])).addClass('col-sm-1').appendTo(appendTo); + $('').css('background-color', self.GraphVisualization.color($('.config-item #properties').val(), statisticsSorted[i])).addClass('col-sm-1').appendTo(appendTo); $('').addClass('text-left col-sm-4').text(self.GraphVisualization.statistics[statisticsSorted[i]] + ' %').appendTo(appendTo); $('').addClass('text-right col-sm-6 property-name').text(propertyName).appendTo(appendTo); } @@ -211,6 +227,9 @@ var set_timeline = function(graph) { // Draw graph for the first time self.GraphVisualization.update_graph($('.config-item #properties').val(), maxUnix, function() { update_statistics_table(); + setTimeout(function() { + self.GraphVisualization.fit(); + }, 100); }); // 'Speed' slider diff --git a/templates/js/template.js b/templates/js/template.js index 15eb1df..c772f9f 100644 --- a/templates/js/template.js +++ b/templates/js/template.js @@ -70,7 +70,7 @@ var initGUI = function(model_params) { }; for (var option in model_params) { - + var type = typeof(model_params[option]); var param_str = String(option); @@ -82,7 +82,7 @@ var initGUI = function(model_params) { addSliderInput(model_params[option]['label'], model_params[option]['value']); break; default: - console.log(model_params[option]['type'] + ' not defined!'); + console.log(model_params[option]['label'] + ' not defined!'); break; } } diff --git a/templates/js/visualization.js b/templates/js/visualization.js index c3b9021..aa75fe0 100644 --- a/templates/js/visualization.js +++ b/templates/js/visualization.js @@ -14,7 +14,7 @@ // Private constants var focus_opacity = 0.1, radius = 8, - required_node = ['id', 'index', 'label', 'px', 'py', 'spells', 'weight', 'x', 'y']; + required_node = ['id', 'index', 'label', 'px', 'py', 'spells', 'weight', 'x', 'y', 'pos', 'scx', 'scy']; // Private variables var width, @@ -35,7 +35,10 @@ data_link, // Actual link data for the graph link, // Line svgs - node; // Circles for the nodes + node, // Circles for the nodes + shape_property, // Property to whom the shape will be applied + shapes, // Dictionary of shapes for nodes + colors; // Dictionary of colors for nodes Number.prototype.between = function(min, max) { var min = (min || min === 0) ? min : Math.max(), @@ -60,15 +63,29 @@ .attr('r', radius) .style('fill', function (d) { if ( Array.isArray(d[property]) ) { - var color_node = color(d[property][0][0]); + var color_node = _helpers.set_color(property, d[property][0][0]); d[property].forEach(function(p) { - if ( time.between(p[1], p[2]) ) color_node = color(p[0]); + if ( time.between(p[1], p[2]) ) color_node = _helpers.set_color(property, p[0]); }); return color_node; } else { - return color(d[property]); + return _helpers.set_color(property, d[property]); } }) + .style('stroke', function(d) { + if (_helpers.set_shape(d[shape_property]) !== (-1)) + if ( Array.isArray(d[property]) ) { + var color_node = _helpers.set_color(property, d[property][0][0]); + d[property].forEach(function(p) { + if ( time.between(p[1], p[2]) ) color_node = _helpers.set_color(property, p[0]); + }); + return color_node; + } else { + return _helpers.set_color(property, d[property]); + } + else + return '#ffffff'; + }) // Cancel zoom movement so you can move the node .on('mousedown', function(d) { d3.event.stopPropagation(); @@ -93,16 +110,33 @@ node.attr('class', 'node') .attr('r', radius) .style('fill', function (d) { + if (_helpers.set_shape(d[shape_property]) !== (-1)) { + return 'url(#' + _helpers.set_shape(d[shape_property]) + ')'; + } if ( Array.isArray(d[property]) ) { - var color_node = color(d[property][0][0]); + var color_node = _helpers.set_color(property, d[property][0][0]); d[property].forEach(function(p) { - if ( time.between(p[1], p[2]) ) color_node = color(p[0]); + if ( time.between(p[1], p[2]) ) color_node = _helpers.set_color(property, p[0]); }); return color_node; } else { - return color(d[property]); + return _helpers.set_color(property, d[property]); } }) + .style('stroke', function(d) { + if (_helpers.set_shape(d[shape_property]) !== (-1)) + if ( Array.isArray(d[property]) ) { + var color_node = _helpers.set_color(property, d[property][0][0]); + d[property].forEach(function(p) { + if ( time.between(p[1], p[2]) ) color_node = _helpers.set_color(property, p[0]); + }); + return color_node; + } else { + return _helpers.set_color(property, d[property]); + } + else + return '#ffffff'; + }) .on('dblclick', function(d) { d3.event.stopPropagation(); if (d === lastFocusNode) { @@ -139,12 +173,33 @@ }); }, push_once: function(array, item, key) { - for (var i in array) { - if ( array[i][key] == item[key] ) return false; + for (var i in array) { + if ( array[i][key] == item[key] ) return false; + } + array.push(item); + return true; + }, + set_color: function(property, value) { + if ( colors instanceof Array ) { + for ( var c in colors ) { + if ( colors[c][property] == value ) { return colors[c]['color']; } + } + return color(value); + } else { + return color(value); + } + }, + set_shape: function(value) { + if ( shapes instanceof Object && shape_property ) { + for ( var s in shapes ) { + var str_value = (value.includes('class')) ? value.split('.').pop().split('\'')[0] : value; + if ( str_value == s ) return shapes[s]; + } + return (-1); + } else { + return (-1); + } } - array.push(item); - return true; - } } @@ -170,6 +225,29 @@ glinks = groot.append('g') .attr('id', 'links'); gnodes = groot.append('g') .attr('id', 'nodes'); + // Add patterns for shapes + var defs = []; + for ( var i in shapes ) + if (!defs.includes(shapes[i])) defs.push(shapes[i]) + + svg.append('defs') + .selectAll('pattern') + .data(defs) + .enter() + .append('pattern') + .attr('id', function(d, i) { + return d; + }) + .attr('patternUnits', 'objectBoundingBox') + .attr('width', 1) + .attr('height', 1) + .append('image') + .attr('href', function(d) { + return window.location.protocol + '//' + window.location.host + '/img/svg/' + d + '.svg'; + }) + .attr('width', 16) + .attr('height', 16); + // Zoom zoom = d3.behavior .zoom() @@ -200,17 +278,22 @@ force.on('tick', function () { link.attr('x1', function (d) { - return d.source.x; + if ( d.source.scx ) return d.source.scx; + else return d.source.x; }).attr('y1', function (d) { - return d.source.y; + if ( d.source.scy ) return d.source.scy; + else return d.source.y; }).attr('x2', function (d) { - return d.target.x; + if ( d.target.scx ) return d.target.scx; + else return d.target.x; }).attr('y2', function (d) { - return d.target.y; + if ( d.target.scy ) return d.target.scy; + else return d.target.y; }); node.attr('transform', function translate(d) { - return 'translate(' + d.x + ',' + d.y + ')'; + if ( d.scx || d.scy ) return 'translate(' + d.scx + ',' + d.scy + ')'; + else return 'translate(' + d.x + ',' + d.y + ')'; }); }); @@ -384,6 +467,25 @@ } } + /** + * Set shapes and color of graph. + * A function that set the shapes and colors of the nodes depending on their status. + * + * @param {object} set_shapes Shapes for nodes. + * @param {object} set_colors Colors for nodes. + * @param {object} callback A function called at the end. + */ + function set_params(set_shape_property, set_shapes, set_colors, callback) { + shape_property = set_shape_property; + shapes = set_shapes; + colors = set_colors; + + self.GraphVisualization.shapes = shapes; + self.GraphVisualization.colors = colors; + + if (callback) { callback(); } + } + /** * Adjust the graph to the whole area. * A function that adjust the graph to the svg item. @@ -437,9 +539,9 @@ * @param {object} value Value. * @return {object} color The color in hexadecimal. */ - function color(value) { + function color(property, value) { if (graph) { - return color(value); + return _helpers.set_color(property, value); } } @@ -519,6 +621,7 @@ create: create, import: importJSON, update_graph: update_graph, + set_params: set_params, set_link_distance: set_link_distance, fit: zoom_to_fit, reset: reset, @@ -530,6 +633,8 @@ // Getters color: color, + shapes: shapes, + colors: colors, get_attributes: get_attributes, get_nodes: get_nodes, diff --git a/visualization.py b/visualization.py index 7aa165c..57d2c2c 100644 --- a/visualization.py +++ b/visualization.py @@ -1,4 +1,5 @@ import os +import networkx as nx from server import VisualizationElement from soil.simulation import SoilSimulation from xml.etree import ElementTree @@ -22,7 +23,16 @@ class Model(): print('Dumping results to {} : {}'.format(sim.dir_path, sim.dump)) - return sim.run_simulation() + simulation_results = sim.run_simulation() + + G = simulation_results[0].history_to_graph() + for node in G.nodes(): + if 'pos' in G.node[node]: + G.node[node]['viz'] = {"position": {"x": G.node[node]['pos'][0], "y": G.node[node]['pos'][1], "z": 0.0}} + del (G.node[node]['pos']) + nx.write_gexf(G, 'test.gexf', version='1.2draft') + + return simulation_results def reset(self): pass