aboutsummaryrefslogtreecommitdiffstats
path: root/plot/style.py
diff options
context:
space:
mode:
authorJonathan Herman <hermanjl@cs.unc.edu>2013-05-03 16:30:10 -0400
committerJonathan Herman <hermanjl@cs.unc.edu>2013-05-03 16:30:10 -0400
commitec4c208196a190a8b771bef7e1533b642b59f8df (patch)
tree1b7146871a8c93dc1c425acbd6b770fefddbd945 /plot/style.py
parenta530b618fb32321e61b2a90001caa4d6b1b2b192 (diff)
Improved flexibility of plot_exps.py script.wip-color-mc
* No longer needs an X connection to render. This also vastly increases performance. * If too many configuration values are plotted, a key with color=column1, line=column2, marker=column3 etc is not created. Instead, each combination of values is given its own line/color/marker style and plotted, and each line has an entry in the key. Ugly, but better than nothing.
Diffstat (limited to 'plot/style.py')
-rw-r--r--plot/style.py190
1 files changed, 162 insertions, 28 deletions
diff --git a/plot/style.py b/plot/style.py
index 5c2d661..ad29dd3 100644
--- a/plot/style.py
+++ b/plot/style.py
@@ -1,22 +1,100 @@
1from common import log_once
1from collections import namedtuple 2from collections import namedtuple
3from parse.tuple_table import TupleTable
4
5import itertools
2import matplotlib.pyplot as plot 6import matplotlib.pyplot as plot
3 7
8class ExcessVarietyException(Exception):
9 '''Too many fields or field values to use field style'''
10 pass
11
4class Style(namedtuple('SS', ['marker', 'color', 'line'])): 12class Style(namedtuple('SS', ['marker', 'color', 'line'])):
5 def fmt(self): 13 def fmt(self):
6 return self.marker + self.line + self.color 14 return self.marker + self.line + self.color
7 15
16
17def make_styler(col_map):
18 try:
19 return FieldStyle(col_map.get_values())
20 except ExcessVarietyException:
21 # Fallback, don't style by field values, instead create
22 # a unique style for every combination of field values possible
23 # This is significantly harder to visually parse
24 log_once("Too many columns and/or column values to create pretty "
25 "and simple graphs!\nGiving each combination of properties "
26 "its own line.")
27 return CombinationStyle(col_map)
28
8class StyleMap(object): 29class StyleMap(object):
9 '''Maps configs (dicts) to specific line styles.''' 30 # The base style, a solid black line
31 # The values of columns are used to change this line
10 DEFAULT = Style(marker='', line= '-', color='k') 32 DEFAULT = Style(marker='', line= '-', color='k')
33
34 def __init__(self, col_values):
35 raise NotImplementedError()
36
37 def _all_styles(self):
38 '''A dict holding all possible values for style each property.'''
39 return Style(marker=list('.,ov^<>1234sp*hH+xDd|_'),
40 line=['-', ':', '--'],
41 color=list('kbgrcmy'))._asdict()
42
43 def get_style(self, kv):
44 '''Translate column values to unique line style.'''
45 raise NotImplementedError()
46
47 def get_key(self):
48 '''A visual description of this StyleMap.'''
49 raise NotImplementedError()
50
51
52class FieldStyle(StyleMap):
53 '''Changes properties of a line style by the values of each field.'''
54
11 ORDER = [ str, bool, float, int ] 55 ORDER = [ str, bool, float, int ]
12 56
13 def __init__(self, col_list, col_values): 57 def __init__(self, col_values):
14 '''Assign (some) columns in @col_list to fields in @Style to vary, and 58 '''Assign (some) columns in @col_list to fields in @Style to vary, and
15 assign values for these columns to specific field values.''' 59 assign values for these columns to specific field values.'''
60 # column->map(column_value->field_value)
16 self.value_map = {} 61 self.value_map = {}
62 # column->style_field
17 self.field_map = {} 63 self.field_map = {}
18 64
19 # Prioritize non-numbers 65 if len(col_values.keys()) > len(FieldStyle.DEFAULT):
66 raise ExcessVarietyException("Too many columns to style!")
67
68 col_list = self.__get_sorted_columns(col_values)
69 field_list = self.__get_sorted_fields()
70 field_dict = self._all_styles()
71
72 while len(col_list) < len(field_list):
73 curr_col = col_list[-1]
74 check_field = field_list[-2]
75 if len(col_values[curr_col]) <= len(field_dict[check_field]):
76 field_list.pop()
77 elif len(col_values[curr_col]) > len(field_dict[field_list[-1]]):
78 raise ExcessVarietyException("Too many values to style!")
79 else:
80 field_list.pop(0)
81
82 # Pair each column with a style field
83 for i in xrange(len(col_list)):
84 column = col_list[i]
85 field = field_list[i]
86 field_values = field_dict[field]
87
88 # Give each unique value of column a matching unique value of field
89 value_dict = {}
90 for value in sorted(col_values[column]):
91 value_dict[value] = field_values.pop(0)
92
93 self.value_map[column] = value_dict
94 self.field_map[column] = field
95
96 def __get_sorted_columns(self, col_values):
97 # Break ties using the type of the column
20 def type_priority(column): 98 def type_priority(column):
21 value = col_values[column].pop() 99 value = col_values[column].pop()
22 col_values[column].add(value) 100 col_values[column].add(value)
@@ -24,35 +102,23 @@ class StyleMap(object):
24 t = float if float(value) % 1.0 else int 102 t = float if float(value) % 1.0 else int
25 except: 103 except:
26 t = bool if value in ['True','False'] else str 104 t = bool if value in ['True','False'] else str
27 # return StyleMap.ORDER.index(t) 105 return StyleMap.ORDER.index(t)
28 return len(col_values[column])
29 col_list = sorted(col_list, key=type_priority, reverse=True)
30
31 # TODO: undo this, switch to popping mechanism
32 for field, values in [x for x in self.__get_all()._asdict().iteritems()]:
33 if not col_list:
34 break
35
36 next_column = col_list.pop(0)
37 value_dict = {}
38 106
39 for value in sorted(col_values[next_column]): 107 def column_compare(cola, colb):
40 try: 108 lena = len(col_values[cola])
41 value_dict[value] = values.pop(0) 109 lenb = len(col_values[colb])
42 except Exception as e: 110 if lena == lenb:
43 raise e 111 return type_priority(cola) - type_priority(colb)
112 else:
113 return lena - lenb
44 114
45 self.value_map[next_column] = value_dict 115 return sorted(col_values.keys(), cmp=column_compare)
46 self.field_map[next_column] = field
47 116
48 def __get_all(self): 117 def __get_sorted_fields(self):
49 '''A Style holding all possible values for each property.''' 118 fields = self._all_styles()
50 return Style(marker=list('.,ov^<>1234sp*hH+xDd|_'), 119 return sorted(fields.keys(), key=lambda x: len(fields[x]))
51 line=['-', ':', '--', '_'],
52 color=list('bgrcmyk'))
53 120
54 def get_style(self, kv): 121 def get_style(self, kv):
55 '''Translate column values to unique line style.'''
56 style_fields = {} 122 style_fields = {}
57 123
58 for column, values in self.value_map.iteritems(): 124 for column, values in self.value_map.iteritems():
@@ -64,7 +130,6 @@ class StyleMap(object):
64 return StyleMap.DEFAULT._replace(**style_fields) 130 return StyleMap.DEFAULT._replace(**style_fields)
65 131
66 def get_key(self): 132 def get_key(self):
67 '''A visual description of this StyleMap.'''
68 key = [] 133 key = []
69 134
70 for column, values in self.value_map.iteritems(): 135 for column, values in self.value_map.iteritems():
@@ -79,3 +144,72 @@ class StyleMap(object):
79 144
80 return sorted(key, key=lambda x:x[1]) 145 return sorted(key, key=lambda x:x[1])
81 146
147class CombinationStyle(StyleMap):
148 def __init__(self, col_map):
149 self.col_map = col_map
150 self.kv_styles = TupleTable(col_map)
151 self.kv_seen = TupleTable(col_map, lambda:False)
152
153 all_styles = self._all_styles()
154 styles_order = sorted(all_styles.keys(),
155 key=lambda x: len(all_styles[x]),
156 reverse = True)
157
158 # Add a 'None' option in case some lines are plotted without
159 # any value specified for this kv
160 column_values = col_map.get_values()
161 for key in column_values.keys():
162 column_values[key].add(None)
163
164 styles_iter = self.__dict_combinations(all_styles, styles_order)
165 kv_iter = self.__dict_combinations(column_values)
166
167 # Cycle in case there are more kv combinations than styles
168 # This will be really, really ugly..
169 styles_iter = itertools.cycle(styles_iter)
170
171 for kv, style in zip(kv_iter, styles_iter):
172 self.kv_styles[kv] = Style(**style)
173
174 for kv_tup, style in self.kv_styles:
175 kv = self.col_map.get_kv(kv_tup)
176 if not self.kv_styles[kv]:
177 raise Exception("Didn't initialize %s" % kv)
178
179 def __dict_combinations(self, list_dict, column_order = None):
180 def helper(set_columns, remaining_columns):
181 if not remaining_columns:
182 yield set_columns
183 return
184
185 next_column = remaining_columns.pop(0)
186
187 for v in list_dict[next_column]:
188 set_columns[next_column] = v
189 for vals in helper(dict(set_columns), list(remaining_columns)):
190 yield vals
191
192 if not column_order:
193 # Just use the random order returned by the dict
194 column_order = list_dict.keys()
195
196 return helper({}, column_order)
197
198 def get_style(self, kv):
199 self.kv_seen[kv] = True
200 return self.kv_styles[kv]
201
202 def get_key(self):
203 key = []
204
205 for kv_tup, style in self.kv_styles:
206 kv = self.col_map.get_kv(kv_tup)
207 if not self.kv_seen[kv]:
208 continue
209
210 styled_line = plot.plot([], [], style.fmt())[0]
211 description = self.col_map.encode(kv, minimum=True)
212
213 key += [(styled_line, description)]
214
215 return sorted(key, key=lambda x:x[1])