···2233import dataclasses
44from collections import defaultdict
55+from numbers import Number
5667from .. import elements, types
78from . import playback_control_ui, playback_control_js
···1516 end_delay: float = 0
1617 repeat_count: Union[int, str] = 'indefinite'
1718 fill: str = 'freeze'
1919+ freeze_frame_at: Optional[float] = None
18201921 # Playback controls
2022 show_playback_progress: bool = False
···9395 self, controls_width=width, controls_x=x, controls_center_y=y,
9496 controls_js=js)
95979898+ def override_args(self, args, lcontext):
9999+ if (self.freeze_frame_at is not None
100100+ and hasattr(lcontext.element, 'animation_data')):
101101+ args = dict(args)
102102+ data = lcontext.element.animation_data
103103+ args.update(data.interpolate_at_time(self.freeze_frame_at))
104104+ return args
105105+9610697107@dataclasses.dataclass
98108class AnimatedAttributeTimeline:
···119129 raise ValueError('out-of-order key frame times')
120130 self.times.extend(times)
121131 self.values.extend(values)
132132+133133+ def interpolate_at_time(self, at_time):
134134+ return linear_interpolate_value(self.times, self.values, at_time)
122135123136 def as_animate_element(self, config: Optional[SyncedAnimationConfig]=None):
124137 if config is not None:
···180193 self.attr_timelines[attr] = timeline
181194 timeline.extend(times, values)
182195196196+ def interpolate_at_time(self, at_time):
197197+ r = {
198198+ name: timeline.interpolate_at_time(at_time)
199199+ for name, timeline in self.attr_timelines.items()
200200+ }
201201+ print(r)
202202+ return r
203203+183204 def _timelines_adjusted_for_context(self, lcontext=None):
184205 all_timelines = dict(self.attr_timelines)
185206 if lcontext is not None and lcontext.context.invert_y:
···212233 yvalues = [lcontext.element.args.get('y', 0)]
213234 if y_timeline is not None or height_timeline is not None:
214235 ytimes, yvalues = _merge_timeline_inverted_y_values(
215215- ytimes, yvalues, htimes, hvalues)
236236+ ytimes, yvalues, htimes, hvalues,
237237+ linear_interpolate_value, linear_interpolate_value)
216238 if ytimes is not None:
217239 y_timeline = AnimatedAttributeTimeline(
218240 'y', y_attrs, ytimes, yvalues)
···220242 return all_timelines
221243222244 def children_with_context(self, lcontext=None):
245245+ if (lcontext is not None
246246+ and lcontext.context.animation_config is not None
247247+ and lcontext.context.animation_config.freeze_frame_at
248248+ is not None):
249249+ return [] # Don't animate if frame is frozen
223250 all_timelines = self._timelines_adjusted_for_context(lcontext)
224251 return [
225252 timeline.as_animate_element(lcontext.context.animation_config)
···227254 ]
228255229256230230-def _merge_timeline_inverted_y_values(ytimes, yvalues, htimes, hvalues):
257257+def linear_interpolate_value(times, values, at_time):
258258+ print(times, values, at_time)
259259+ if len(times) == 0:
260260+ return 0
261261+ idx = sum(t <= at_time for t in times)
262262+ if idx >= len(times):
263263+ return values[-1]
264264+ elif idx <= 0:
265265+ return values[0]
266266+ elif at_time == times[idx-1]:
267267+ return values[idx-1]
268268+ elif isinstance(values[idx], Number) and isinstance(values[idx-1], Number):
269269+ fraction = (at_time-times[idx-1]) / (times[idx]-times[idx-1])
270270+ return values[idx-1] * (1-fraction) + (values[idx] * fraction)
271271+ else:
272272+ return values[idx-1]
273273+274274+def _merge_timeline_inverted_y_values(ytimes, yvalues, htimes, hvalues,
275275+ yinterpolate, hinterpolate):
231276 if len(yvalues) == 1:
232277 try:
233278 return htimes, [-yvalues[0]-h for h in hvalues]
···243288 return ytimes, [-y-h for y, h in zip(yvalues, hvalues)]
244289 except TypeError:
245290 return None, None
246246- def interpolate(times, values, at_time):
247247- if len(times) == 0:
248248- return 0
249249- idx = sum(t <= at_time for t in times)
250250- if idx >= len(times):
251251- return values[-1]
252252- elif idx <= 0:
253253- return values[0]
254254- elif at_time == times[idx-1]:
255255- return values[idx-1]
256256- else:
257257- fraction = (at_time-times[idx-1]) / (times[idx]-times[idx-1])
258258- return values[idx-1] * (1-fraction)+ (values[idx] * fraction)
259291 try:
260292 # Offset y-value by height if invert_y
261293 # Merge key_times for y and height animations
···267299 yt = ytimes[0] if len(ytimes) else inf
268300 while ht < inf and yt < inf:
269301 if yt < ht:
270270- h_val = interpolate(htimes, hvalues, yt)
302302+ h_val = hinterpolate(htimes, hvalues, yt)
271303 new_times.append(yt)
272304 new_values.append(-yvalues[yi] - h_val)
273305 yi += 1
274306 elif ht < yt:
275275- y_val = interpolate(ytimes, yvalues, ht)
307307+ y_val = yinterpolate(ytimes, yvalues, ht)
276308 new_times.append(ht)
277309 new_values.append(-y_val - hvalues[hi])
278310 hi += 1
+2
drawsvg/types.py
···146146147147 def write_tag_args(self, args, output_file, id_map=None):
148148 '''Called by an element during SVG output of its tag.'''
149149+ if self.context.animation_config is not None:
150150+ args = self.context.animation_config.override_args(args, self)
149151 self.context._write_tag_args(
150152 self.context.override_args(args), output_file, id_map=id_map)
151153