1use plotly_derive::FieldSetter;
8use serde::ser::{SerializeSeq, Serializer};
9use serde::Serialize;
10
11use crate::{Layout, Traces};
12
13#[serde_with::skip_serializing_none]
16#[derive(Serialize, Clone, FieldSetter)]
17pub struct Frame {
18 group: Option<String>,
21 name: Option<String>,
23 traces: Option<Vec<usize>>,
26 baseframe: Option<String>,
31 data: Option<Traces>,
34 layout: Option<Layout>,
37}
38
39impl Frame {
40 pub fn new() -> Self {
41 Default::default()
42 }
43}
44
45#[derive(Clone, Debug)]
48pub struct Animation {
49 frames: FrameListMode,
51 options: AnimationOptions,
53}
54
55impl Default for Animation {
56 fn default() -> Self {
57 Self {
58 frames: FrameListMode::All,
59 options: AnimationOptions::default(),
60 }
61 }
62}
63
64impl Animation {
65 pub fn new() -> Self {
68 Self::default()
69 }
70
71 pub fn all_frames() -> Self {
73 Self::new()
74 }
75
76 pub fn pause() -> Self {
78 Self {
79 frames: FrameListMode::Pause,
80 options: AnimationOptions::new()
81 .mode(AnimationMode::Immediate)
82 .frame(FrameSettings::new().duration(0).redraw(false))
83 .transition(TransitionSettings::new().duration(0)),
84 }
85 }
86
87 pub fn frames(frames: Vec<String>) -> Self {
89 Self {
90 frames: FrameListMode::Frames(frames),
91 ..Default::default()
92 }
93 }
94
95 pub fn options(mut self, options: AnimationOptions) -> Self {
97 self.options = options;
98 self
99 }
100}
101
102impl Serialize for Animation {
103 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
104 where
105 S: Serializer,
106 {
107 let mut seq = serializer.serialize_seq(Some(2))?;
108 seq.serialize_element(&self.frames)?;
109 seq.serialize_element(&self.options)?;
110 seq.end()
111 }
112}
113
114#[derive(Clone, Debug)]
116pub enum FrameListMode {
117 All,
119 Frames(Vec<String>),
121 Pause,
123}
124
125impl Serialize for FrameListMode {
126 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
127 where
128 S: serde::Serializer,
129 {
130 match self {
131 FrameListMode::All => serializer.serialize_unit(),
132 FrameListMode::Pause => {
133 let arr = vec![serde_json::Value::Null];
134 arr.serialize(serializer)
135 }
136 FrameListMode::Frames(frames) => frames.serialize(serializer),
137 }
138 }
139}
140
141#[serde_with::skip_serializing_none]
144#[derive(Serialize, Clone, Debug, FieldSetter)]
145pub struct AnimationOptions {
146 frame: Option<FrameSettings>,
148 transition: Option<TransitionSettings>,
150 mode: Option<AnimationMode>,
152 direction: Option<AnimationDirection>,
154 fromcurrent: Option<bool>,
156}
157
158impl AnimationOptions {
159 pub fn new() -> Self {
160 Default::default()
161 }
162}
163
164#[serde_with::skip_serializing_none]
166#[derive(Serialize, Clone, Debug, FieldSetter)]
167pub struct FrameSettings {
168 duration: Option<usize>,
170 redraw: Option<bool>,
172}
173
174impl FrameSettings {
175 pub fn new() -> Self {
176 Default::default()
177 }
178}
179
180#[serde_with::skip_serializing_none]
182#[derive(Serialize, Clone, Debug, FieldSetter)]
183pub struct TransitionSettings {
184 duration: Option<usize>,
186 easing: Option<AnimationEasing>,
188 ordering: Option<TransitionOrdering>,
190}
191
192impl TransitionSettings {
193 pub fn new() -> Self {
194 Default::default()
195 }
196}
197
198#[derive(Serialize, Debug, Clone, Copy, PartialEq)]
200#[serde(rename_all = "lowercase")]
201pub enum AnimationMode {
202 Immediate,
203 Next,
204 AfterAll,
205}
206
207#[derive(Serialize, Debug, Clone, Copy, PartialEq)]
209#[serde(rename_all = "lowercase")]
210pub enum AnimationDirection {
211 Forward,
212 Reverse,
213}
214
215#[derive(Serialize, Debug, Clone, Copy, PartialEq)]
217#[serde(rename_all = "lowercase")]
218pub enum TransitionOrdering {
219 #[serde(rename = "layout first")]
220 LayoutFirst,
221 #[serde(rename = "traces first")]
222 TracesFirst,
223}
224
225#[derive(Serialize, Debug, Clone, Copy, PartialEq)]
227#[serde(rename_all = "lowercase")]
228pub enum AnimationEasing {
229 Linear,
230 Quad,
231 Cubic,
232 Sin,
233 Exp,
234 Circle,
235 Elastic,
236 Back,
237 Bounce,
238 #[serde(rename = "linear-in")]
239 LinearIn,
240 #[serde(rename = "quad-in")]
241 QuadIn,
242 #[serde(rename = "cubic-in")]
243 CubicIn,
244 #[serde(rename = "sin-in")]
245 SinIn,
246 #[serde(rename = "exp-in")]
247 ExpIn,
248 #[serde(rename = "circle-in")]
249 CircleIn,
250 #[serde(rename = "elastic-in")]
251 ElasticIn,
252 #[serde(rename = "back-in")]
253 BackIn,
254 #[serde(rename = "bounce-in")]
255 BounceIn,
256 #[serde(rename = "linear-out")]
257 LinearOut,
258 #[serde(rename = "quad-out")]
259 QuadOut,
260 #[serde(rename = "cubic-out")]
261 CubicOut,
262 #[serde(rename = "sin-out")]
263 SinOut,
264 #[serde(rename = "exp-out")]
265 ExpOut,
266 #[serde(rename = "circle-out")]
267 CircleOut,
268 #[serde(rename = "elastic-out")]
269 ElasticOut,
270 #[serde(rename = "back-out")]
271 BackOut,
272 #[serde(rename = "bounce-out")]
273 BounceOut,
274 #[serde(rename = "linear-in-out")]
275 LinearInOut,
276 #[serde(rename = "quad-in-out")]
277 QuadInOut,
278 #[serde(rename = "cubic-in-out")]
279 CubicInOut,
280 #[serde(rename = "sin-in-out")]
281 SinInOut,
282 #[serde(rename = "exp-in-out")]
283 ExpInOut,
284 #[serde(rename = "circle-in-out")]
285 CircleInOut,
286 #[serde(rename = "elastic-in-out")]
287 ElasticInOut,
288 #[serde(rename = "back-in-out")]
289 BackInOut,
290 #[serde(rename = "bounce-in-out")]
291 BounceInOut,
292}
293
294#[cfg(test)]
295mod tests {
296 use serde_json::{json, to_value};
297
298 use super::*;
299 use crate::Scatter;
300
301 #[test]
302 fn serialize_animation_easing() {
303 let test_cases = [
304 (AnimationEasing::Linear, "linear"),
305 (AnimationEasing::Cubic, "cubic"),
306 (AnimationEasing::CubicInOut, "cubic-in-out"),
307 (AnimationEasing::ElasticInOut, "elastic-in-out"),
308 ];
309
310 for (easing, expected) in test_cases {
311 assert_eq!(
312 to_value(easing).unwrap(),
313 json!(expected),
314 "Failed for {:?}",
315 easing
316 );
317 }
318 }
319
320 #[test]
321 fn serialize_animation_mode() {
322 let test_cases = [
323 (AnimationMode::Immediate, "immediate"),
324 (AnimationMode::Next, "next"),
325 (AnimationMode::AfterAll, "afterall"),
326 ];
327
328 for (mode, expected) in test_cases {
329 assert_eq!(
330 to_value(mode).unwrap(),
331 json!(expected),
332 "Failed for {:?}",
333 mode
334 );
335 }
336 }
337
338 #[test]
339 fn serialize_animation_direction() {
340 let test_cases = [
341 (AnimationDirection::Forward, "forward"),
342 (AnimationDirection::Reverse, "reverse"),
343 ];
344
345 for (direction, expected) in test_cases {
346 assert_eq!(
347 to_value(direction).unwrap(),
348 json!(expected),
349 "Failed for {:?}",
350 direction
351 );
352 }
353 }
354
355 #[test]
356 fn serialize_transition_ordering() {
357 let test_cases = [
358 (TransitionOrdering::LayoutFirst, "layout first"),
359 (TransitionOrdering::TracesFirst, "traces first"),
360 ];
361
362 for (ordering, expected) in test_cases {
363 assert_eq!(
364 to_value(ordering).unwrap(),
365 json!(expected),
366 "Failed for {:?}",
367 ordering
368 );
369 }
370 }
371
372 #[test]
373 fn serialize_frame() {
374 let frame = Frame::new()
375 .name("test_frame")
376 .group("test_group")
377 .baseframe("base_frame");
378
379 let expected = json!({
380 "name": "test_frame",
381 "group": "test_group",
382 "baseframe": "base_frame"
383 });
384
385 assert_eq!(to_value(frame).unwrap(), expected);
386 }
387
388 #[test]
389 fn serialize_frame_with_data() {
390 let trace = Scatter::new(vec![1, 2, 3], vec![1, 2, 3]);
391 let mut traces = Traces::new();
392 traces.push(trace);
393
394 let frame = Frame::new().name("frame_with_data").data(traces);
395
396 let expected = json!({
397 "name": "frame_with_data",
398 "data": [
399 {
400 "type": "scatter",
401 "x": [1, 2, 3],
402 "y": [1, 2, 3]
403 }
404 ]
405 });
406
407 assert_eq!(to_value(frame).unwrap(), expected);
408 }
409
410 #[test]
411 fn serialize_animation() {
412 let test_cases = [
413 (
414 Animation::all_frames(),
415 json!(null),
416 "all frames should serialize to null",
417 ),
418 (
419 Animation::pause(),
420 json!([null]),
421 "pause should serialize to [null]",
422 ),
423 (
424 Animation::frames(vec!["frame1".to_string(), "frame2".to_string()]),
425 json!(["frame1", "frame2"]),
426 "specific frames should serialize to frame names array",
427 ),
428 ];
429
430 for (animation, expected_frames, description) in test_cases {
431 let json = to_value(animation).unwrap();
432 assert_eq!(json[0], expected_frames, "{}", description);
433 assert!(json[1].is_object(), "Second element should be an object");
434 }
435 }
436
437 #[test]
438 fn serialize_animation_options_defaults() {
439 let options = AnimationOptions::new();
440 assert_eq!(to_value(options).unwrap(), json!({}));
441 }
442
443 #[test]
444 fn serialize_animation_options() {
445 let options = AnimationOptions::new()
446 .mode(AnimationMode::Immediate)
447 .direction(AnimationDirection::Forward)
448 .fromcurrent(false)
449 .frame(FrameSettings::new().duration(500).redraw(true))
450 .transition(
451 TransitionSettings::new()
452 .duration(300)
453 .easing(AnimationEasing::CubicInOut)
454 .ordering(TransitionOrdering::LayoutFirst),
455 );
456
457 let expected = json!({
458 "mode": "immediate",
459 "direction": "forward",
460 "fromcurrent": false,
461 "frame": {
462 "duration": 500,
463 "redraw": true
464 },
465 "transition": {
466 "duration": 300,
467 "easing": "cubic-in-out",
468 "ordering": "layout first"
469 }
470 });
471
472 assert_eq!(to_value(options).unwrap(), expected);
473 }
474}