8
8
9
9
from .base import Renderer
10
10
from .utils import list_dict_to_dict_list
11
- from .vega_templates import BadTemplateError , LinearTemplate , get_template
11
+ from .vega_templates import BadTemplateError , LinearTemplate , Template , get_template
12
12
13
13
14
14
class VegaRenderer (Renderer ):
@@ -58,14 +58,12 @@ def __init__(self, datapoints: List, name: str, **properties):
58
58
],
59
59
"shape" : ["square" , "circle" , "triangle" , "diamond" ],
60
60
}
61
- self ._optional_anchor_values : Dict [
62
- str ,
63
- Dict [str , Dict [str , str ]],
64
- ] = defaultdict (dict )
61
+
62
+ self ._split_content : Dict [str , Any ] = {}
65
63
66
64
def get_filled_template (
67
65
self ,
68
- skip_anchors : Optional [List [str ]] = None ,
66
+ split_anchors : Optional [List [str ]] = None ,
69
67
strict : bool = True ,
70
68
as_string : bool = True ,
71
69
) -> Union [str , Dict [str , Any ]]:
@@ -74,8 +72,8 @@ def get_filled_template(
74
72
if not self .datapoints :
75
73
return {}
76
74
77
- if skip_anchors is None :
78
- skip_anchors = []
75
+ if split_anchors is None :
76
+ split_anchors = []
79
77
80
78
if strict :
81
79
if self .properties .get ("x" ):
@@ -91,15 +89,18 @@ def get_filled_template(
91
89
self .properties .setdefault ("y_label" , self .properties .get ("y" ))
92
90
self .properties .setdefault ("data" , self .datapoints )
93
91
94
- self ._process_optional_anchors (skip_anchors )
92
+ self ._process_optional_anchors (split_anchors )
95
93
96
94
names = ["title" , "x" , "y" , "x_label" , "y_label" , "data" ]
97
95
for name in names :
98
- if name in skip_anchors :
99
- continue
100
96
value = self .properties .get (name )
101
97
if value is None :
102
98
continue
99
+
100
+ if name in split_anchors :
101
+ self ._set_split_content (name , value )
102
+ continue
103
+
103
104
if name == "data" :
104
105
if not self .template .has_anchor (name ):
105
106
anchor = self .template .anchor (name )
@@ -116,6 +117,15 @@ def get_filled_template(
116
117
117
118
return self .template .content
118
119
120
+ def get_partial_filled_template (self ):
121
+ """
122
+ Returns a partially filled template along with the split out anchor content
123
+ """
124
+ content = self .get_filled_template (
125
+ split_anchors = ["data" , "color" , "stroke_dash" , "shape" ], strict = True
126
+ )
127
+ return content , self ._split_content
128
+
119
129
def partial_html (self , ** kwargs ) -> str :
120
130
return self .get_filled_template () # type: ignore
121
131
@@ -164,7 +174,7 @@ def generate_markdown(self, report_path=None) -> str:
164
174
165
175
return ""
166
176
167
- def _process_optional_anchors (self , skip_anchors : List [str ]):
177
+ def _process_optional_anchors (self , split_anchors : List [str ]):
168
178
optional_anchors = [
169
179
anchor
170
180
for anchor in [
@@ -177,79 +187,85 @@ def _process_optional_anchors(self, skip_anchors: List[str]):
177
187
]
178
188
if self .template .has_anchor (anchor )
179
189
]
180
- if optional_anchors :
181
- # split varied_keys out from _fill_optional_anchors to avoid bugs
182
- # but first.... tests
183
- varied_keys = self ._fill_optional_anchors (skip_anchors , optional_anchors )
184
- self ._update_datapoints (varied_keys )
185
-
186
- def _fill_optional_anchors (
187
- self , skip_anchors : List [str ], optional_anchors : List [str ]
188
- ) -> List [str ]:
189
- self ._fill_color (skip_anchors , optional_anchors )
190
-
191
190
if not optional_anchors :
192
- return []
191
+ return
193
192
194
193
y_defn = self .properties .get ("anchors_y_defn" , [])
194
+ is_single_source = len (y_defn ) <= 1
195
195
196
- if len (y_defn ) <= 1 :
197
- self ._fill_optional_anchor (
198
- skip_anchors , optional_anchors , "group_by" , ["rev" ]
199
- )
200
- self ._fill_optional_anchor (
201
- skip_anchors , optional_anchors , "pivot_field" , "datum.rev"
202
- )
203
- for anchor in optional_anchors :
204
- self .template .fill_anchor (anchor , {})
205
- return []
196
+ if is_single_source :
197
+ self ._process_single_source_plot (split_anchors , optional_anchors )
198
+ return
199
+
200
+ self ._process_multi_source_plot (split_anchors , optional_anchors , y_defn )
201
+
202
+ def _process_single_source_plot (
203
+ self , split_anchors : List [str ], optional_anchors : List [str ]
204
+ ):
205
+ self ._fill_color (split_anchors , optional_anchors )
206
+ self ._fill_optional_anchor (split_anchors , optional_anchors , "group_by" , ["rev" ])
207
+ self ._fill_optional_anchor (
208
+ split_anchors , optional_anchors , "pivot_field" , "datum.rev"
209
+ )
210
+ for anchor in optional_anchors :
211
+ self .template .fill_anchor (anchor , {})
212
+
213
+ self ._update_datapoints ([])
214
+
215
+ def _process_multi_source_plot (
216
+ self ,
217
+ split_anchors : List [str ],
218
+ optional_anchors : List [str ],
219
+ y_defn : List [Dict [str , str ]],
220
+ ):
221
+ varied_keys , varied_values = self ._collect_variations (y_defn )
222
+ domain = self ._get_domain (varied_keys , varied_values , y_defn )
223
+
224
+ self ._fill_optional_multi_source_anchors (
225
+ split_anchors , optional_anchors , varied_keys , domain
226
+ )
227
+ self ._update_datapoints (varied_keys )
228
+
229
+ def _fill_optional_multi_source_anchors (
230
+ self ,
231
+ split_anchors : List [str ],
232
+ optional_anchors : List [str ],
233
+ varied_keys : List [str ],
234
+ domain : List [str ],
235
+ ):
236
+ self ._fill_color (split_anchors , optional_anchors )
237
+
238
+ if not optional_anchors :
239
+ return
206
240
207
- varied_keys , variations = self ._collect_variations (y_defn )
208
241
grouped_keys = ["rev" , * varied_keys ]
209
- concat_field = "::" .join (varied_keys )
210
242
self ._fill_optional_anchor (
211
- skip_anchors , optional_anchors , "group_by" , grouped_keys
243
+ split_anchors , optional_anchors , "group_by" , grouped_keys
212
244
)
213
245
self ._fill_optional_anchor (
214
- skip_anchors ,
246
+ split_anchors ,
215
247
optional_anchors ,
216
248
"pivot_field" ,
217
249
" + '::' + " .join ([f"datum.{ key } " for key in grouped_keys ]),
218
250
)
219
- # concatenate grouped_keys together
220
- self ._fill_optional_anchor (
221
- skip_anchors , optional_anchors , "row" , {"field" : concat_field }
222
- )
223
-
224
- if not optional_anchors :
225
- return varied_keys
226
251
227
- if len (varied_keys ) == 2 :
228
- domain = ["::" .join ([d .get ("filename" ), d .get ("field" )]) for d in y_defn ]
229
- else :
230
- filenameOrField = varied_keys [0 ]
231
- domain = list (variations [filenameOrField ])
232
-
233
- domain .sort ()
234
-
235
- stroke_dash_scale = self ._set_optional_anchor_scale (
236
- optional_anchors , concat_field , "stroke_dash" , domain
237
- )
252
+ concat_field = "::" .join (varied_keys )
238
253
self ._fill_optional_anchor (
239
- skip_anchors , optional_anchors , "stroke_dash " , stroke_dash_scale
254
+ split_anchors , optional_anchors , "row " , { "field" : concat_field }
240
255
)
241
256
242
- shape_scale = self ._set_optional_anchor_scale (
243
- optional_anchors , concat_field , "shape" , domain
244
- )
245
- self ._fill_optional_anchor (skip_anchors , optional_anchors , "shape" , shape_scale )
257
+ if not optional_anchors :
258
+ return
246
259
247
- return varied_keys
260
+ for field in ["stroke_dash" , "shape" ]:
261
+ self ._fill_optional_anchor_mapping (
262
+ split_anchors , optional_anchors , concat_field , field , domain
263
+ )
248
264
249
- def _fill_color (self , skip_anchors : List [str ], optional_anchors : List [str ]):
265
+ def _fill_color (self , split_anchors : List [str ], optional_anchors : List [str ]):
250
266
all_revs = self .properties .get ("anchor_revs" , [])
251
267
self ._fill_optional_anchor (
252
- skip_anchors ,
268
+ split_anchors ,
253
269
optional_anchors ,
254
270
"color" ,
255
271
{
@@ -266,15 +282,15 @@ def _fill_color(self, skip_anchors: List[str], optional_anchors: List[str]):
266
282
def _collect_variations (
267
283
self , y_defn : List [Dict [str , str ]]
268
284
) -> Tuple [List [str ], Dict [str , set ]]:
269
- variations = defaultdict (set )
285
+ varied_values = defaultdict (set )
270
286
for defn in y_defn :
271
287
for key in ["filename" , "field" ]:
272
- variations [key ].add (defn .get (key , None ))
288
+ varied_values [key ].add (defn .get (key , None ))
273
289
274
290
values_match_variations = []
275
291
less_values_than_variations = []
276
292
277
- for filenameOrField , valueSet in variations .items ():
293
+ for filenameOrField , valueSet in varied_values .items ():
278
294
num_values = len (valueSet )
279
295
if num_values == 1 :
280
296
continue
@@ -286,14 +302,14 @@ def _collect_variations(
286
302
if values_match_variations :
287
303
values_match_variations .extend (less_values_than_variations )
288
304
values_match_variations .sort (reverse = True )
289
- return values_match_variations , variations
305
+ return values_match_variations , varied_values
290
306
291
307
less_values_than_variations .sort (reverse = True )
292
- return less_values_than_variations , variations
308
+ return less_values_than_variations , varied_values
293
309
294
310
def _fill_optional_anchor (
295
311
self ,
296
- skip_anchors : List [str ],
312
+ split_anchors : List [str ],
297
313
optional_anchors : List [str ],
298
314
name : str ,
299
315
value : Any ,
@@ -303,26 +319,63 @@ def _fill_optional_anchor(
303
319
304
320
optional_anchors .remove (name )
305
321
306
- if name in skip_anchors :
322
+ if name in split_anchors :
307
323
return
308
324
309
325
self .template .fill_anchor (name , value )
310
326
311
- def _set_optional_anchor_scale (
312
- self , optional_anchors : List [str ], field : str , name : str , domain : List [str ]
327
+ def _get_domain (
328
+ self ,
329
+ varied_keys : List [str ],
330
+ varied_values : Dict [str , set ],
331
+ y_defn : List [Dict [str , str ]],
332
+ ):
333
+ if len (varied_keys ) == 2 :
334
+ domain = [
335
+ "::" .join ([d .get ("filename" , "" ), d .get ("field" , "" )]) for d in y_defn
336
+ ]
337
+ else :
338
+ filenameOrField = varied_keys [0 ]
339
+ domain = list (varied_values [filenameOrField ])
340
+
341
+ domain .sort ()
342
+ return domain
343
+
344
+ def _fill_optional_anchor_mapping (
345
+ self ,
346
+ split_anchors : List [str ],
347
+ optional_anchors : List [str ],
348
+ field : str ,
349
+ name : str ,
350
+ domain : List [str ],
313
351
):
314
352
if name not in optional_anchors :
315
- return {"field" : field , "scale" : {"domain" : [], "range" : []}}
353
+ return
354
+
355
+ optional_anchors .remove (name )
356
+
357
+ encoding = self ._get_optional_anchor_mapping (field , name , domain )
316
358
359
+ if name in split_anchors :
360
+ self ._set_split_content (name , encoding )
361
+ return
362
+
363
+ self .template .fill_anchor (name , encoding )
364
+
365
+ def _get_optional_anchor_mapping (
366
+ self ,
367
+ field : str ,
368
+ name : str ,
369
+ domain : List [str ],
370
+ ):
317
371
full_range_values : List [Any ] = self ._optional_anchor_ranges .get (name , [])
318
372
anchor_range_values = full_range_values .copy ()
319
- anchor_range = []
320
373
321
- for domain_value in domain :
374
+ anchor_range = []
375
+ for _ in range (len (domain )):
322
376
if not anchor_range_values :
323
377
anchor_range_values = full_range_values .copy ()
324
378
range_value = anchor_range_values .pop (0 )
325
- self ._optional_anchor_values [name ][domain_value ] = range_value
326
379
anchor_range .append (range_value )
327
380
328
381
return {
@@ -347,3 +400,6 @@ def _update_datapoints(self, varied_keys: List[str]):
347
400
)
348
401
for key in to_remove :
349
402
datapoint .pop (key , None )
403
+
404
+ def _set_split_content (self , name : str , value : Any ):
405
+ self ._split_content [Template .anchor (name )] = value
0 commit comments