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