1use super::GraphMaker;
2use std::fmt::Write;
3
4pub struct SlopeIcon {
78 above: bool, edge_color: String, face_color: String, line_style: String, line_width: f64, length: f64, offset_v: f64, no_text: bool, fontsize: f64, precision: usize, text_h: String, text_v: String, text_color: String, text_offset_h: f64, text_offset_v: f64, buffer: String, }
95
96impl SlopeIcon {
97 pub fn new() -> Self {
99 SlopeIcon {
100 above: false,
101 edge_color: "#000000".to_string(),
102 face_color: "#f7f7f7".to_string(),
103 line_style: String::new(),
104 line_width: 0.0,
105 length: 0.1,
106 offset_v: 5.0,
107 no_text: false,
108 fontsize: 0.0,
109 precision: 0,
110 text_h: "1".to_string(),
111 text_v: String::new(),
112 text_color: "#000000".to_string(),
113 text_offset_h: 3.0,
114 text_offset_v: 2.0,
115 buffer: String::new(),
116 }
117 }
118
119 pub fn draw(&mut self, slope: f64, x_center: f64, y_center: f64) {
121 let flip = if slope < 0.0 { !self.above } else { self.above };
123
124 write!(
126 &mut self.buffer,
127 "slope,cx,cy=float({}),float({}),float({})\n\
128 if plt.gca().get_xscale() == 'log': cx=np.log10(cx)\n\
129 if plt.gca().get_yscale() == 'log': cy=np.log10(cy)\n\
130 xc,yc=data_to_axis((cx,cy))\n\
131 xa,ya=data_to_axis((cx+1.0,cy+slope))\n\
132 m,l=(ya-yc)/(xa-xc),{}\n",
133 slope,
134 x_center,
135 y_center,
136 self.length / 2.0,
137 )
138 .unwrap();
139
140 if flip {
142 self.buffer.push_str(
143 "dat=[[pth.Path.MOVETO,(xc-l,yc-m*l)],\
144 [pth.Path.LINETO,(xc-l,yc+m*l)],\
145 [pth.Path.LINETO,(xc+l,yc+m*l)],\
146 [pth.Path.CLOSEPOLY,(None,None)]]\n",
147 );
148 } else {
149 self.buffer.push_str(
150 "dat=[[pth.Path.MOVETO,(xc-l,yc-m*l)],\
151 [pth.Path.LINETO,(xc+l,yc-m*l)],\
152 [pth.Path.LINETO,(xc+l,yc+m*l)],\
153 [pth.Path.CLOSEPOLY,(None,None)]]\n",
154 );
155 }
156
157 let tf = self.transform(slope);
159 let opt = self.options();
160 write!(
161 &mut self.buffer,
162 "{}cmd,pts=zip(*dat)\n\
163 h=pth.Path(pts,cmd)\n\
164 p=pat.PathPatch(h{})\n\
165 plt.gca().add_patch(p)\n",
166 tf, opt,
167 )
168 .unwrap();
169
170 if self.no_text {
172 return;
173 }
174
175 self.buffer.push_str(
177 "xm,ym=xc-l,yc-m*l\n\
178 xp,yp=xc+l,yc+m*l\n",
179 );
180
181 let mut text = String::new();
183 if self.text_v == "" {
184 if self.precision == 0 {
185 write!(&mut text, "{}", f64::abs(slope)).unwrap();
186 } else {
187 write!(&mut text, "{:.1$}", f64::abs(slope), self.precision).unwrap();
188 }
189 } else {
190 write!(&mut text, "{}", self.text_v).unwrap();
191 }
192
193 let tf_txt = self.transform_text(slope);
195 self.buffer.push_str(&tf_txt);
196 let (opt_x, opt_y) = self.options_text();
197 if flip {
198 if slope < 0.0 {
199 write!(
200 &mut self.buffer,
201 "plt.text(xc,yp,r'{}',ha='center',va='top'{})\n",
202 self.text_h, opt_x
203 )
204 .unwrap();
205 } else {
206 write!(
207 &mut self.buffer,
208 "plt.text(xc,yp,r'{}',ha='center',va='bottom'{})\n",
209 self.text_h, opt_x
210 )
211 .unwrap();
212 }
213 write!(
214 &mut self.buffer,
215 "plt.text(xm,yc,r'{}',ha='right',va='center'{})\n",
216 text, opt_y
217 )
218 .unwrap();
219 } else {
220 if slope < 0.0 {
221 write!(
222 &mut self.buffer,
223 "plt.text(xc,ym,r'{}',ha='center',va='bottom'{})\n",
224 self.text_h, opt_x
225 )
226 .unwrap();
227 } else {
228 write!(
229 &mut self.buffer,
230 "plt.text(xc,ym,r'{}',ha='center',va='top'{})\n",
231 self.text_h, opt_x
232 )
233 .unwrap();
234 }
235 write!(
236 &mut self.buffer,
237 "plt.text(xp,yc,r'{}',ha='left',va='center'{})\n",
238 text, opt_y
239 )
240 .unwrap();
241 }
242 }
243
244 pub fn set_above(&mut self, flag: bool) -> &mut Self {
246 self.above = flag;
247 self
248 }
249
250 pub fn set_edge_color(&mut self, color: &str) -> &mut Self {
252 self.edge_color = String::from(color);
253 self
254 }
255
256 pub fn set_face_color(&mut self, color: &str) -> &mut Self {
258 self.face_color = String::from(color);
259 self
260 }
261
262 pub fn set_line_style(&mut self, style: &str) -> &mut Self {
269 self.line_style = String::from(style);
270 self
271 }
272
273 pub fn set_line_width(&mut self, width: f64) -> &mut Self {
275 self.line_width = width;
276 self
277 }
278
279 pub fn set_length(&mut self, value: f64) -> &mut Self {
281 self.length = value;
282 self
283 }
284
285 pub fn set_offset_v(&mut self, value: f64) -> &mut Self {
287 self.offset_v = value;
288 self
289 }
290
291 pub fn set_no_text(&mut self, flag: bool) -> &mut Self {
293 self.no_text = flag;
294 self
295 }
296
297 pub fn set_fontsize(&mut self, fontsize: f64) -> &mut Self {
299 self.fontsize = fontsize;
300 self
301 }
302
303 pub fn set_precision(&mut self, value: usize) -> &mut Self {
305 self.precision = value;
306 self
307 }
308
309 pub fn set_text_h(&mut self, one: &str) -> &mut Self {
311 self.text_h = String::from(one);
312 self
313 }
314
315 pub fn set_text_v(&mut self, slope: &str) -> &mut Self {
317 self.text_v = String::from(slope);
318 self
319 }
320
321 pub fn set_text_color(&mut self, color: &str) -> &mut Self {
323 self.text_color = String::from(color);
324 self
325 }
326
327 pub fn set_text_offset_h(&mut self, value: f64) -> &mut Self {
329 self.text_offset_h = value;
330 self
331 }
332
333 pub fn set_text_offset_v(&mut self, value: f64) -> &mut Self {
335 self.text_offset_v = value;
336 self
337 }
338
339 fn transform(&self, slope: f64) -> String {
341 let flip = if slope < 0.0 { !self.above } else { self.above };
342 let mut opt = String::new();
343 if self.offset_v > 0.0 {
344 let dv = if flip {
345 self.offset_v * f64::signum(slope)
346 } else {
347 -self.offset_v * f64::signum(slope)
348 };
349 write!(
350 &mut opt,
351 "tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y={},units='points')\n",
352 dv,
353 )
354 .unwrap();
355 } else {
356 opt.push_str("tf=plt.gca().transAxes\n");
357 }
358 opt
359 }
360
361 fn transform_text(&self, slope: f64) -> String {
363 let flip = if slope < 0.0 { !self.above } else { self.above };
364 let mut opt = String::new();
365 if self.offset_v > 0.0 || self.text_offset_v > 0.0 {
366 let dv = if flip {
367 (self.offset_v + self.text_offset_v) * f64::signum(slope)
368 } else {
369 -(self.offset_v + self.text_offset_v) * f64::signum(slope)
370 };
371 write!(
372 &mut opt,
373 "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y={},units='points')\n",
374 dv,
375 )
376 .unwrap();
377 } else {
378 opt.push_str("tfx=plt.gca().transAxes\n");
379 }
380 if self.offset_v > 0.0 || self.text_offset_h > 0.0 {
381 let dv = if flip {
382 self.offset_v * f64::signum(slope)
383 } else {
384 -self.offset_v * f64::signum(slope)
385 };
386 let dh = if flip { -self.text_offset_h } else { self.text_offset_h };
387 write!(
388 &mut opt,
389 "tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x={},y={},units='points')\n",
390 dh, dv,
391 )
392 .unwrap();
393 } else {
394 opt.push_str("tfy=plt.gca().transAxes\n");
395 }
396 opt
397 }
398
399 fn options(&self) -> String {
401 let mut opt = String::from(",transform=tf");
402 if self.edge_color != "" {
403 write!(&mut opt, ",edgecolor='{}'", self.edge_color).unwrap();
404 }
405 if self.face_color != "" {
406 write!(&mut opt, ",facecolor='{}'", self.face_color).unwrap();
407 }
408 if self.line_style != "" {
409 write!(&mut opt, ",linestyle='{}'", self.line_style).unwrap();
410 }
411 if self.line_width > 0.0 {
412 write!(&mut opt, ",linewidth={}", self.line_width).unwrap();
413 }
414 opt
415 }
416
417 fn options_text(&self) -> (String, String) {
419 let mut opt_x = String::from(",transform=tfx");
420 let mut opt_y = String::from(",transform=tfy");
421 if self.text_color != "" {
422 write!(&mut opt_x, ",color='{}'", self.text_color).unwrap();
423 write!(&mut opt_y, ",color='{}'", self.text_color).unwrap();
424 }
425 if self.fontsize > 0.0 {
426 write!(&mut opt_x, ",fontsize={}", self.fontsize).unwrap();
427 write!(&mut opt_y, ",fontsize={}", self.fontsize).unwrap();
428 }
429 (opt_x, opt_y)
430 }
431}
432
433impl GraphMaker for SlopeIcon {
434 fn get_buffer<'a>(&'a self) -> &'a String {
435 &self.buffer
436 }
437 fn clear_buffer(&mut self) {
438 self.buffer.clear();
439 }
440}
441
442#[cfg(test)]
445mod tests {
446 use super::SlopeIcon;
447 use crate::GraphMaker;
448
449 #[test]
450 fn new_works() {
451 let icon = SlopeIcon::new();
452 assert_eq!(icon.above, false);
453 assert_eq!(icon.edge_color.len(), 7);
454 assert_eq!(icon.line_style.len(), 0);
455 assert_eq!(icon.line_width, 0.0);
456 assert_eq!(icon.length, 0.1);
457 assert_eq!(icon.offset_v, 5.0);
458 assert_eq!(icon.no_text, false);
459 assert_eq!(icon.fontsize, 0.0);
460 assert_eq!(icon.precision, 0);
461 assert_eq!(icon.text_h.len(), 1);
462 assert_eq!(icon.text_v.len(), 0);
463 assert_eq!(icon.text_color.len(), 7);
464 assert_eq!(icon.text_offset_h, 3.0);
465 assert_eq!(icon.text_offset_v, 2.0);
466 assert_eq!(icon.buffer.len(), 0);
467 }
468
469 #[test]
470 fn transform_works() {
471 let mut icon = SlopeIcon::new();
472 icon.set_offset_v(7.0);
473 icon.set_above(false);
474 assert_eq!(
475 icon.transform(1.0),
476 "tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-7,units='points')\n"
477 );
478 icon.set_above(true);
479 assert_eq!(
480 icon.transform(1.0),
481 "tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=7,units='points')\n"
482 );
483 icon.set_above(false);
484 assert_eq!(
485 icon.transform(-1.0),
486 "tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-7,units='points')\n"
487 );
488 icon.set_above(true);
489 assert_eq!(
490 icon.transform(-1.0),
491 "tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=7,units='points')\n"
492 );
493 icon.set_offset_v(0.0);
494 icon.set_above(false);
495 assert_eq!(icon.transform(-1.0), "tf=plt.gca().transAxes\n");
496 }
497
498 #[test]
499 fn transform_text_works() {
500 let mut icon = SlopeIcon::new();
501
502 icon.set_offset_v(7.0);
503 icon.set_text_offset_h(1.0);
504 icon.set_text_offset_v(3.0);
505 icon.set_above(false);
506 assert_eq!(
507 icon.transform_text(1.0),
508 "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-10,units='points')\n\
509 tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=1,y=-7,units='points')\n"
510 );
511 icon.set_above(true);
512 assert_eq!(
513 icon.transform_text(1.0),
514 "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=10,units='points')\n\
515 tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=-1,y=7,units='points')\n"
516 );
517 icon.set_above(false);
518 assert_eq!(
519 icon.transform_text(-1.0),
520 "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-10,units='points')\n\
521 tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=-1,y=-7,units='points')\n"
522 );
523 icon.set_above(true);
524 assert_eq!(
525 icon.transform_text(-1.0),
526 "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=10,units='points')\n\
527 tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=1,y=7,units='points')\n"
528 );
529
530 icon.set_offset_v(0.0);
531 icon.set_above(false);
532 assert_eq!(
533 icon.transform_text(1.0),
534 "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-3,units='points')\n\
535 tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=1,y=-0,units='points')\n"
536 );
537 icon.set_above(true);
538 assert_eq!(
539 icon.transform_text(1.0),
540 "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=3,units='points')\n\
541 tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=-1,y=0,units='points')\n"
542 );
543 icon.set_above(false);
544 assert_eq!(
545 icon.transform_text(-1.0),
546 "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-3,units='points')\n\
547 tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=-1,y=-0,units='points')\n"
548 );
549 icon.set_above(true);
550 assert_eq!(
551 icon.transform_text(-1.0),
552 "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=3,units='points')\n\
553 tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=1,y=0,units='points')\n"
554 );
555
556 icon.set_offset_v(0.0);
557 icon.set_text_offset_v(0.0);
558 icon.set_text_offset_h(0.0);
559 icon.set_above(false);
560 assert_eq!(
561 icon.transform_text(1.0),
562 "tfx=plt.gca().transAxes\n\
563 tfy=plt.gca().transAxes\n"
564 );
565 assert_eq!(
566 icon.transform_text(-1.0),
567 "tfx=plt.gca().transAxes\n\
568 tfy=plt.gca().transAxes\n"
569 );
570 icon.set_above(true);
571 assert_eq!(
572 icon.transform_text(1.0),
573 "tfx=plt.gca().transAxes\n\
574 tfy=plt.gca().transAxes\n"
575 );
576 assert_eq!(
577 icon.transform_text(-1.0),
578 "tfx=plt.gca().transAxes\n\
579 tfy=plt.gca().transAxes\n"
580 );
581 }
582
583 #[test]
584 fn options_works() {
585 let mut icon = SlopeIcon::new();
586 icon.set_edge_color("red")
587 .set_face_color("gold")
588 .set_line_style("--")
589 .set_line_width(2.0);
590 let options = icon.options();
591 assert_eq!(
592 options,
593 ",transform=tf\
594 ,edgecolor='red'\
595 ,facecolor='gold'\
596 ,linestyle='--'\
597 ,linewidth=2"
598 );
599 }
600
601 #[test]
602 fn options_text_works() {
603 let mut icon = SlopeIcon::new();
604 icon.set_text_color("red").set_fontsize(12.0);
605 let (opt_x, opt_y) = icon.options_text();
606 assert_eq!(
607 opt_x,
608 ",transform=tfx\
609 ,color='red'\
610 ,fontsize=12"
611 );
612 assert_eq!(
613 opt_y,
614 ",transform=tfy\
615 ,color='red'\
616 ,fontsize=12"
617 );
618 }
619
620 #[test]
621 fn draw_works() {
622 let mut icon = SlopeIcon::new();
623 icon.set_above(true)
624 .set_edge_color("red")
625 .set_face_color("blue")
626 .set_line_style(":")
627 .set_line_width(1.1)
628 .set_length(0.2)
629 .set_offset_v(3.0)
630 .set_no_text(false)
631 .set_fontsize(4.0)
632 .set_precision(5)
633 .set_text_h("one")
634 .set_text_v("lambda")
635 .set_text_color("gold")
636 .set_text_offset_h(6.0)
637 .set_text_offset_v(7.0)
638 .draw(10.0, 0.5, 0.1);
639 let b: &str = "slope,cx,cy=float(10),float(0.5),float(0.1)\n\
640 if plt.gca().get_xscale() == 'log': cx=np.log10(cx)\n\
641 if plt.gca().get_yscale() == 'log': cy=np.log10(cy)\n\
642 xc,yc=data_to_axis((cx,cy))\n\
643 xa,ya=data_to_axis((cx+1.0,cy+slope))\n\
644 m,l=(ya-yc)/(xa-xc),0.1\n\
645 dat=[[pth.Path.MOVETO,(xc-l,yc-m*l)],[pth.Path.LINETO,(xc-l,yc+m*l)],[pth.Path.LINETO,(xc+l,yc+m*l)],[pth.Path.CLOSEPOLY,(None,None)]]\n\
646 tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=3,units='points')\n\
647 cmd,pts=zip(*dat)\n\
648 h=pth.Path(pts,cmd)\n\
649 p=pat.PathPatch(h,transform=tf,edgecolor='red',facecolor='blue',linestyle=':',linewidth=1.1)\n\
650 plt.gca().add_patch(p)\n\
651 xm,ym=xc-l,yc-m*l\n\
652 xp,yp=xc+l,yc+m*l\n\
653 tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=10,units='points')\n\
654 tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=-6,y=3,units='points')\n\
655 plt.text(xc,yp,r'one',ha='center',va='bottom',transform=tfx,color='gold',fontsize=4)\n\
656 plt.text(xm,yc,r'lambda',ha='right',va='center',transform=tfy,color='gold',fontsize=4)\n";
657 assert_eq!(icon.buffer, b);
658 icon.clear_buffer();
659 assert_eq!(icon.buffer, "");
660 }
661}