Spaces:
Runtime error
Runtime error
Create pyplutchik.py
Browse files- pyplutchik.py +1336 -0
pyplutchik.py
ADDED
|
@@ -0,0 +1,1336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
"""
|
| 3 |
+
**********
|
| 4 |
+
Plutchik
|
| 5 |
+
**********
|
| 6 |
+
|
| 7 |
+
This package contains a data visualization tool for corpora annotated with emotions.
|
| 8 |
+
Given a JSON representation of the Plutchik's emotions (or dyads) in a text or in a group of texts,
|
| 9 |
+
it draws the corresponding Plutchik's flower.
|
| 10 |
+
|
| 11 |
+
See Plutchik, Robert. "A general psychoevolutionary theory of emotion." Theories of emotion. Academic press, 1980. 3-33.
|
| 12 |
+
|
| 13 |
+
--------
|
| 14 |
+
repository available at https://www.github.com/alfonsosemeraro/pyplutchik
|
| 15 |
+
@author: Alfonso Semeraro <[email protected]>
|
| 16 |
+
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import shapely.geometry as sg
|
| 20 |
+
import matplotlib.pyplot as plt
|
| 21 |
+
import descartes
|
| 22 |
+
from math import sqrt, cos, sin, radians
|
| 23 |
+
import numpy as np
|
| 24 |
+
from matplotlib import colors
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
__author__ = """Alfonso Semeraro ([email protected])"""
|
| 28 |
+
__all__ = ['emo_params',
|
| 29 |
+
'dyad_params',
|
| 30 |
+
'_rotate_point',
|
| 31 |
+
'_polar_coordinates',
|
| 32 |
+
'_neutral_central_circle',
|
| 33 |
+
'_petal_shape_emotion',
|
| 34 |
+
'_petal_shape_dyad',
|
| 35 |
+
'_petal_spine_emotion',
|
| 36 |
+
'_petal_spine_dyad',
|
| 37 |
+
'_petal_circle',
|
| 38 |
+
'_draw_emotion_petal',
|
| 39 |
+
'_draw_dyad_petal',
|
| 40 |
+
'_check_scores_kind',
|
| 41 |
+
'plutchik']
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def emo_params(emotion):
|
| 47 |
+
"""
|
| 48 |
+
Gets color and angle for drawing a petal.
|
| 49 |
+
Color and angle depend on the emotion name.
|
| 50 |
+
|
| 51 |
+
Required arguments:
|
| 52 |
+
----------
|
| 53 |
+
*emotion*:
|
| 54 |
+
Emotion's name. Possible values:
|
| 55 |
+
['joy', 'trust', 'fear', 'surprise', 'sadness', 'disgust', 'anger', 'anticipation']
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
Returns:
|
| 59 |
+
----------
|
| 60 |
+
*color*:
|
| 61 |
+
Matplotlib color for the petal. See: https://matplotlib.org/3.1.0/gallery/color/named_colors.html
|
| 62 |
+
|
| 63 |
+
*angle*:
|
| 64 |
+
Each subsequent petal is rotated 45° around the origin.
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
Notes:
|
| 68 |
+
-----
|
| 69 |
+
This function allows also 8 principal emotions, one for each Plutchik's flower petal.
|
| 70 |
+
No high or low intensity emotions are allowed (no 'ecstasy' or 'serenity', for instance).
|
| 71 |
+
"""
|
| 72 |
+
|
| 73 |
+
if emotion == 'joy':
|
| 74 |
+
color = 'gold'
|
| 75 |
+
angle = 0
|
| 76 |
+
elif emotion == 'trust':
|
| 77 |
+
color = 'olivedrab'
|
| 78 |
+
angle = -45
|
| 79 |
+
elif emotion == 'fear':
|
| 80 |
+
color = 'forestgreen'
|
| 81 |
+
angle = -90
|
| 82 |
+
elif emotion == 'surprise':
|
| 83 |
+
color = 'skyblue'
|
| 84 |
+
angle = -135
|
| 85 |
+
elif emotion == 'sadness':
|
| 86 |
+
color = 'dodgerblue'
|
| 87 |
+
angle = -180
|
| 88 |
+
elif emotion == 'disgust':
|
| 89 |
+
color = 'slateblue'
|
| 90 |
+
angle = -225
|
| 91 |
+
elif emotion == 'anger':
|
| 92 |
+
color = 'orangered'
|
| 93 |
+
angle = -270
|
| 94 |
+
elif emotion == 'anticipation':
|
| 95 |
+
color = 'darkorange'
|
| 96 |
+
angle = -315
|
| 97 |
+
else:
|
| 98 |
+
raise Exception("""Bad input: {} is not an accepted emotion.
|
| 99 |
+
Must be one of 'joy', 'trust', 'fear', 'surprise', 'sadness', 'disgust', 'anger', 'anticipation'""".format(emotion))
|
| 100 |
+
return color, angle, 0
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def dyad_params(dyad):
|
| 107 |
+
"""
|
| 108 |
+
Gets colormap and angle for drawing a dyad.
|
| 109 |
+
Colormap and angle depend on the dyad name.
|
| 110 |
+
|
| 111 |
+
Required arguments:
|
| 112 |
+
----------
|
| 113 |
+
*dyad*:
|
| 114 |
+
Dyad's name. Possible values:
|
| 115 |
+
|
| 116 |
+
{"primary": ['love', 'submission', 'alarm', 'disappointment', 'remorse', 'contempt', 'aggression', 'optimism'],
|
| 117 |
+
"secondary": ['guilt', 'curiosity', 'despair', '', 'envy', 'cynism', 'pride', 'fatalism'],
|
| 118 |
+
"tertiary": ['delight', 'sentimentality', 'shame', 'outrage', 'pessimism', 'morbidness', 'dominance', 'anxiety']}
|
| 119 |
+
|
| 120 |
+
Returns:
|
| 121 |
+
----------
|
| 122 |
+
*colormap*:
|
| 123 |
+
Matplotlib colormap for the dyad. See: https://matplotlib.org/3.1.0/gallery/color/named_colors.html
|
| 124 |
+
|
| 125 |
+
*angle*:
|
| 126 |
+
Each subsequent dyad is rotated 45° around the origin.
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
"""
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
# PRIMARY DYADS
|
| 133 |
+
if dyad == 'love':
|
| 134 |
+
cmap = ['gold', 'olivedrab']
|
| 135 |
+
angle = -45 / 2
|
| 136 |
+
emos = ['joy', 'trust']
|
| 137 |
+
level = 1
|
| 138 |
+
|
| 139 |
+
elif dyad == 'submission':
|
| 140 |
+
cmap = ['olivedrab', 'forestgreen']
|
| 141 |
+
angle = (-45 / 2) + (-45)
|
| 142 |
+
emos = ['trust', 'fear']
|
| 143 |
+
level = 1
|
| 144 |
+
|
| 145 |
+
elif dyad == 'alarm':
|
| 146 |
+
cmap = ['forestgreen', 'skyblue']
|
| 147 |
+
angle = (-45 / 2) + (-90)
|
| 148 |
+
emos = ['fear', 'surprise']
|
| 149 |
+
level = 1
|
| 150 |
+
|
| 151 |
+
elif dyad == 'disappointment':
|
| 152 |
+
cmap = ['skyblue', 'dodgerblue']
|
| 153 |
+
angle = (-45 / 2) + (-135)
|
| 154 |
+
emos = ['surprise', 'sadness']
|
| 155 |
+
level = 1
|
| 156 |
+
|
| 157 |
+
elif dyad == 'remorse':
|
| 158 |
+
cmap = ['dodgerblue', 'slateblue']
|
| 159 |
+
angle = (-45 / 2) + (-180)
|
| 160 |
+
emos = ['sadness', 'disgust']
|
| 161 |
+
level = 1
|
| 162 |
+
|
| 163 |
+
elif dyad == 'contempt':
|
| 164 |
+
cmap = ['slateblue', 'orangered']
|
| 165 |
+
angle = (-45 / 2) + (-225)
|
| 166 |
+
emos = ['disgust', 'anger']
|
| 167 |
+
level = 1
|
| 168 |
+
|
| 169 |
+
elif dyad == 'aggressiveness':
|
| 170 |
+
cmap = ['orangered', 'darkorange']
|
| 171 |
+
angle = (-45 / 2) + (-270)
|
| 172 |
+
emos = ['anger', 'anticipation']
|
| 173 |
+
level = 1
|
| 174 |
+
|
| 175 |
+
elif dyad == 'optimism':
|
| 176 |
+
cmap = ['darkorange', 'gold']
|
| 177 |
+
angle = (-45 / 2) + (-315)
|
| 178 |
+
emos = ['anticipation', 'joy']
|
| 179 |
+
level = 1
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
# SECONDARY DYADS
|
| 183 |
+
elif dyad == 'guilt':
|
| 184 |
+
cmap = ['gold', 'forestgreen']
|
| 185 |
+
angle = -45
|
| 186 |
+
emos = ['joy', 'fear']
|
| 187 |
+
level = 2
|
| 188 |
+
|
| 189 |
+
elif dyad == 'curiosity':
|
| 190 |
+
cmap = ['olivedrab', 'skyblue']
|
| 191 |
+
angle = -90
|
| 192 |
+
emos = ['trust', 'surprise']
|
| 193 |
+
level = 2
|
| 194 |
+
|
| 195 |
+
elif dyad == 'despair':
|
| 196 |
+
cmap = ['forestgreen', 'dodgerblue']
|
| 197 |
+
angle = -135
|
| 198 |
+
emos = ['fear', 'sadness']
|
| 199 |
+
level = 2
|
| 200 |
+
|
| 201 |
+
elif dyad == 'unbelief':
|
| 202 |
+
cmap = ['skyblue', 'slateblue']
|
| 203 |
+
angle = -180
|
| 204 |
+
emos = ['surprise', 'disgust']
|
| 205 |
+
level = 2
|
| 206 |
+
|
| 207 |
+
elif dyad == 'envy':
|
| 208 |
+
cmap = ['dodgerblue', 'orangered']
|
| 209 |
+
angle = -225
|
| 210 |
+
emos = ['sadness', 'anger']
|
| 211 |
+
level = 2
|
| 212 |
+
|
| 213 |
+
elif dyad == 'cynism':
|
| 214 |
+
cmap = ['slateblue', 'darkorange']
|
| 215 |
+
angle = -270
|
| 216 |
+
emos = ['disgust', 'anticipation']
|
| 217 |
+
level = 2
|
| 218 |
+
|
| 219 |
+
elif dyad == 'pride':
|
| 220 |
+
cmap = ['orangered', 'gold']
|
| 221 |
+
angle = -315
|
| 222 |
+
emos = ['anger', 'joy']
|
| 223 |
+
level = 2
|
| 224 |
+
|
| 225 |
+
elif dyad == 'hope':
|
| 226 |
+
cmap = ['darkorange', 'olivedrab']
|
| 227 |
+
angle = 0
|
| 228 |
+
emos = ['anticipation', 'trust']
|
| 229 |
+
level = 2
|
| 230 |
+
|
| 231 |
+
# TERTIARY DYADS
|
| 232 |
+
elif dyad == 'delight':
|
| 233 |
+
cmap = ['gold', 'skyblue']
|
| 234 |
+
angle = (-45 / 2) + (-45)
|
| 235 |
+
emos = ['joy', 'surprise']
|
| 236 |
+
level = 3
|
| 237 |
+
|
| 238 |
+
elif dyad == 'sentimentality':
|
| 239 |
+
cmap = ['olivedrab', 'dodgerblue']
|
| 240 |
+
angle = (-45 / 2) + (-90)
|
| 241 |
+
emos = ['trust', 'sadness']
|
| 242 |
+
level = 3
|
| 243 |
+
|
| 244 |
+
elif dyad == 'shame':
|
| 245 |
+
cmap = ['forestgreen', 'slateblue']
|
| 246 |
+
angle = (-45 / 2) + (-135)
|
| 247 |
+
emos = ['fear', 'disgust']
|
| 248 |
+
level = 3
|
| 249 |
+
|
| 250 |
+
elif dyad == 'outrage':
|
| 251 |
+
cmap = ['skyblue', 'orangered']
|
| 252 |
+
angle = (-45 / 2) + (-180)
|
| 253 |
+
emos = ['surprise', 'anger']
|
| 254 |
+
level = 3
|
| 255 |
+
|
| 256 |
+
elif dyad == 'pessimism':
|
| 257 |
+
cmap = ['dodgerblue', 'darkorange']
|
| 258 |
+
angle = (-45 / 2) + (-225)
|
| 259 |
+
emos = ['sadness', 'anticipation']
|
| 260 |
+
level = 3
|
| 261 |
+
|
| 262 |
+
elif dyad == 'morbidness':
|
| 263 |
+
cmap = ['slateblue', 'gold']
|
| 264 |
+
angle = (-45 / 2) + (-270)
|
| 265 |
+
emos = ['disgust', 'joy']
|
| 266 |
+
level = 3
|
| 267 |
+
|
| 268 |
+
elif dyad == 'dominance':
|
| 269 |
+
cmap = ['orangered', 'olivedrab']
|
| 270 |
+
angle = (-45 / 2) + (-315)
|
| 271 |
+
emos = ['anger', 'trust']
|
| 272 |
+
level = 3
|
| 273 |
+
|
| 274 |
+
elif dyad == 'anxiety':
|
| 275 |
+
cmap = ['darkorange', 'forestgreen']
|
| 276 |
+
angle = (-45 / 2)
|
| 277 |
+
emos = ['anticipation', 'fear']
|
| 278 |
+
level = 3
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
# OPPOSITES
|
| 282 |
+
elif dyad == 'bittersweetness':
|
| 283 |
+
cmap = ['gold', 'dodgerblue']
|
| 284 |
+
angle = 0
|
| 285 |
+
emos = ['joy', 'sadness']
|
| 286 |
+
level = 4
|
| 287 |
+
|
| 288 |
+
elif dyad == 'ambivalence':
|
| 289 |
+
cmap = ['olivedrab', 'slateblue']
|
| 290 |
+
angle = -45
|
| 291 |
+
emos = ['trust', 'disgust']
|
| 292 |
+
level = 4
|
| 293 |
+
|
| 294 |
+
elif dyad == 'frozenness':
|
| 295 |
+
cmap = ['forestgreen', 'orangered']
|
| 296 |
+
angle = -90
|
| 297 |
+
emos = ['fear', 'anger']
|
| 298 |
+
level = 4
|
| 299 |
+
|
| 300 |
+
elif dyad == 'confusion':
|
| 301 |
+
cmap = ['skyblue', 'darkorange']
|
| 302 |
+
angle = -135
|
| 303 |
+
emos = ['surprise', 'anticipation']
|
| 304 |
+
level = 4
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
else:
|
| 308 |
+
raise Exception("""Bad input: '{}' is not an accepted name for a dyad.
|
| 309 |
+
Must be one of:
|
| 310 |
+
'love', 'submission', 'alarm', 'disappointment', 'remorse', 'contempt', 'aggressiveness', 'optimism',
|
| 311 |
+
'guilt', 'curiosity', 'despair', 'unbelief', 'envy', 'cynism', 'pride', 'hope',
|
| 312 |
+
'delight', 'sentimentality', 'shame', 'outrage', 'pessimism', 'morbidness', 'dominance', 'anxiety',
|
| 313 |
+
'bittersweetness', 'ambivalence', 'frozenness', 'confusion'
|
| 314 |
+
""".format(dyad))
|
| 315 |
+
return emos, cmap, angle, level
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def _rotate_point(point, angle):
|
| 319 |
+
"""
|
| 320 |
+
Rotate a point counterclockwise by a given angle around a given origin.
|
| 321 |
+
|
| 322 |
+
Required arguments:
|
| 323 |
+
----------
|
| 324 |
+
*point*:
|
| 325 |
+
A two-values tuple, (x, y), of the point to rotate
|
| 326 |
+
|
| 327 |
+
*angle*:
|
| 328 |
+
The angle the point is rotated. The angle should be given in radians.
|
| 329 |
+
|
| 330 |
+
Returns:
|
| 331 |
+
----------
|
| 332 |
+
*(qx, qy)*:
|
| 333 |
+
A two-values tuple, the new coordinates of the rotated point.
|
| 334 |
+
|
| 335 |
+
"""
|
| 336 |
+
ox, oy = 0, 0
|
| 337 |
+
px, py = point
|
| 338 |
+
angle = radians(angle)
|
| 339 |
+
qx = ox + cos(angle) * (px - ox) - sin(angle) * (py - oy)
|
| 340 |
+
qy = oy + sin(angle) * (px - ox) + cos(angle) * (py - oy)
|
| 341 |
+
return (qx, qy)
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def _polar_coordinates(ax, font, fontweight, fontsize, show_ticklabels, ticklabels_angle, ticklabels_size, offset = .15):
|
| 347 |
+
"""
|
| 348 |
+
Draws polar coordinates as a background.
|
| 349 |
+
|
| 350 |
+
Required arguments:
|
| 351 |
+
----------
|
| 352 |
+
*ax*:
|
| 353 |
+
Axes to draw the coordinates.
|
| 354 |
+
|
| 355 |
+
*font*:
|
| 356 |
+
Font of text. Default is Montserrat.
|
| 357 |
+
|
| 358 |
+
*fontweight*:
|
| 359 |
+
Font weight of text. Default is light.
|
| 360 |
+
|
| 361 |
+
*fontsize*:
|
| 362 |
+
Font size of text. Default is 15.
|
| 363 |
+
|
| 364 |
+
*show_ticklabels*:
|
| 365 |
+
Boolean, wether to show tick labels under Joy petal. Default is False.
|
| 366 |
+
|
| 367 |
+
*ticklabels_angle*:
|
| 368 |
+
How much to rotate tick labels from y=0. Value should be given in radians. Default is 0.
|
| 369 |
+
|
| 370 |
+
*ticklabels_size*:
|
| 371 |
+
Size of tick labels. Default is 11.
|
| 372 |
+
|
| 373 |
+
*offset*:
|
| 374 |
+
Central neutral circle has radius = .15, and coordinates must start from there.
|
| 375 |
+
|
| 376 |
+
Returns:
|
| 377 |
+
----------
|
| 378 |
+
*ax*:
|
| 379 |
+
The input Axes modified.
|
| 380 |
+
|
| 381 |
+
"""
|
| 382 |
+
|
| 383 |
+
# Lines
|
| 384 |
+
for i in range(0, 110, 20):
|
| 385 |
+
c = plt.Circle((0, 0), offset + i/100, color = 'grey', alpha = .3, fill = False, zorder = -20)
|
| 386 |
+
ax.add_artist(c)
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
# Tick labels
|
| 390 |
+
if show_ticklabels:
|
| 391 |
+
for x in np.arange(0.2, 1.2, .2):
|
| 392 |
+
a = round(x, 1)
|
| 393 |
+
x, y = _rotate_point((0, a + offset), ticklabels_angle) #-.12
|
| 394 |
+
ax.annotate(s = str(a), xy = (x, y), fontfamily = font, size = ticklabels_size, fontweight = fontweight, zorder = 8, rotation = ticklabels_angle)
|
| 395 |
+
return ax
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
def _neutral_central_circle(ax, r = .15):
|
| 399 |
+
"""
|
| 400 |
+
Draws central neutral circle (in grey).
|
| 401 |
+
|
| 402 |
+
Required arguments:
|
| 403 |
+
----------
|
| 404 |
+
*ax*:
|
| 405 |
+
Axes to draw the coordinates.
|
| 406 |
+
|
| 407 |
+
*r*:
|
| 408 |
+
Radius of the circle. Default is .15.
|
| 409 |
+
|
| 410 |
+
Returns:
|
| 411 |
+
----------
|
| 412 |
+
*ax*:
|
| 413 |
+
The input Axes modified.
|
| 414 |
+
|
| 415 |
+
"""
|
| 416 |
+
c = sg.Point(0, 0).buffer(r)
|
| 417 |
+
ax.add_patch(descartes.PolygonPatch(c, fc='white', ec=(.5, .5, .5, .3), alpha=1, zorder = 15))
|
| 418 |
+
|
| 419 |
+
return ax
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
def _outer_border(ax, emotion_score, color, angle, highlight, offset = .15, height_width_ratio = 1, normalize = False):
|
| 423 |
+
"""
|
| 424 |
+
Draw a the outer border of a petal.
|
| 425 |
+
|
| 426 |
+
Required arguments:
|
| 427 |
+
----------
|
| 428 |
+
*ax*:
|
| 429 |
+
Axes to draw the coordinates.
|
| 430 |
+
|
| 431 |
+
*emotion_score*:
|
| 432 |
+
Score of the emotion. Values range from 0 to 1.
|
| 433 |
+
|
| 434 |
+
*color*:
|
| 435 |
+
Color of the petal. See emo_params().
|
| 436 |
+
|
| 437 |
+
*angle*:
|
| 438 |
+
Rotation angle of the petal. See emo_params().
|
| 439 |
+
|
| 440 |
+
*highlight*:
|
| 441 |
+
String. 'opaque' if the petal must be shadowed, 'regular' is default.
|
| 442 |
+
|
| 443 |
+
*offset*:
|
| 444 |
+
Central neutral circle has radius = .15, and petals must start from there.
|
| 445 |
+
|
| 446 |
+
*height_width_ratio*:
|
| 447 |
+
Ratio between height and width of the petal. Lower the ratio, thicker the petal. Default is 1.
|
| 448 |
+
|
| 449 |
+
*normalize*:
|
| 450 |
+
Either False or the highest value among emotions. If not False, must normalize all petal lengths.
|
| 451 |
+
|
| 452 |
+
"""
|
| 453 |
+
|
| 454 |
+
if normalize:
|
| 455 |
+
emotion_score /= normalize
|
| 456 |
+
|
| 457 |
+
# Computing proportions.
|
| 458 |
+
h = 1*emotion_score + offset
|
| 459 |
+
x = height_width_ratio*emotion_score
|
| 460 |
+
y = h/2
|
| 461 |
+
r = sqrt(x**2 + y**2)
|
| 462 |
+
|
| 463 |
+
# Computing rotated centers
|
| 464 |
+
x_right, y_right = _rotate_point((x, y), angle)
|
| 465 |
+
x_left, y_left = _rotate_point((-x, y), angle)
|
| 466 |
+
|
| 467 |
+
# Circles and intersection
|
| 468 |
+
right = sg.Point(x_right, y_right).buffer(r)
|
| 469 |
+
left = sg.Point(x_left, y_left).buffer(r)
|
| 470 |
+
petal = right.intersection(left)
|
| 471 |
+
|
| 472 |
+
# alpha and color
|
| 473 |
+
alpha = 1 if highlight == 'regular' else .8
|
| 474 |
+
ecol = (colors.to_rgba(color)[0], colors.to_rgba(color)[1], colors.to_rgba(color)[2], alpha)
|
| 475 |
+
|
| 476 |
+
|
| 477 |
+
ax.add_patch(descartes.PolygonPatch(petal, fc=(0, 0, 0, 0), ec = ecol, lw= 1))
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
|
| 481 |
+
def _petal_shape_emotion(ax, emotion_score, color, angle, font, fontweight, fontsize, highlight, will_circle, offset = .15, height_width_ratio = 1, normalize = False):
|
| 482 |
+
"""
|
| 483 |
+
Draw a petal.
|
| 484 |
+
A petal is the intersection area between two circles.
|
| 485 |
+
The height of the petal depends on the radius and the center of the circles.
|
| 486 |
+
Full details at http://www.github.com/alfonsosemeraro/plutchik/tutorial.ipynb
|
| 487 |
+
|
| 488 |
+
Required arguments:
|
| 489 |
+
----------
|
| 490 |
+
*ax*:
|
| 491 |
+
Axes to draw the coordinates.
|
| 492 |
+
|
| 493 |
+
*emotion_score*:
|
| 494 |
+
Score of the emotion. Values range from 0 to 1.
|
| 495 |
+
|
| 496 |
+
*color*:
|
| 497 |
+
Color of the petal. See emo_params().
|
| 498 |
+
|
| 499 |
+
*angle*:
|
| 500 |
+
Rotation angle of the petal. See emo_params().
|
| 501 |
+
|
| 502 |
+
*font*:
|
| 503 |
+
Font of text. Default is Montserrat.
|
| 504 |
+
|
| 505 |
+
*fontweight*:
|
| 506 |
+
Font weight of text. Default is light.
|
| 507 |
+
|
| 508 |
+
*fontsize*:
|
| 509 |
+
Font size of text. Default is 15.
|
| 510 |
+
|
| 511 |
+
*highlight*:
|
| 512 |
+
String. 'opaque' if the petal must be shadowed, 'regular' is default.
|
| 513 |
+
|
| 514 |
+
*will_circle*:
|
| 515 |
+
Boolean. If three intensities will be plotted, then the lower petal must be pale.
|
| 516 |
+
|
| 517 |
+
*offset*:
|
| 518 |
+
Central neutral circle has radius = .15, and petals must start from there.
|
| 519 |
+
|
| 520 |
+
*height_width_ratio*:
|
| 521 |
+
Ratio between height and width of the petal. Lower the ratio, thicker the petal. Default is 1.
|
| 522 |
+
|
| 523 |
+
*normalize*:
|
| 524 |
+
Either False or the highest value among emotions. If not False, must normalize all petal lengths.
|
| 525 |
+
|
| 526 |
+
Returns:
|
| 527 |
+
----------
|
| 528 |
+
*petal*:
|
| 529 |
+
The petal, a shapely shape.
|
| 530 |
+
|
| 531 |
+
"""
|
| 532 |
+
|
| 533 |
+
if normalize:
|
| 534 |
+
emotion_score /= normalize
|
| 535 |
+
|
| 536 |
+
# Computing proportions.
|
| 537 |
+
h = 1*emotion_score + offset
|
| 538 |
+
x = height_width_ratio*emotion_score
|
| 539 |
+
y = h/2
|
| 540 |
+
r = sqrt(x**2 + y**2)
|
| 541 |
+
|
| 542 |
+
# Computing rotated centers
|
| 543 |
+
x_right, y_right = _rotate_point((x, y), angle)
|
| 544 |
+
x_left, y_left = _rotate_point((-x, y), angle)
|
| 545 |
+
|
| 546 |
+
# Circles and intersection
|
| 547 |
+
right = sg.Point(x_right, y_right).buffer(r)
|
| 548 |
+
left = sg.Point(x_left, y_left).buffer(r)
|
| 549 |
+
petal = right.intersection(left)
|
| 550 |
+
|
| 551 |
+
# Alpha for highlighting
|
| 552 |
+
if highlight == 'regular':
|
| 553 |
+
if will_circle:
|
| 554 |
+
alpha = .3
|
| 555 |
+
else:
|
| 556 |
+
alpha = .5
|
| 557 |
+
|
| 558 |
+
elif will_circle:
|
| 559 |
+
alpha = .0
|
| 560 |
+
|
| 561 |
+
else:
|
| 562 |
+
alpha = .0
|
| 563 |
+
|
| 564 |
+
ax.add_patch(descartes.PolygonPatch(petal, fc='white', lw = 0, alpha=1, zorder = 0))
|
| 565 |
+
ax.add_patch(descartes.PolygonPatch(petal, fc=color, lw= 0, alpha=alpha, zorder = 10))
|
| 566 |
+
|
| 567 |
+
return petal
|
| 568 |
+
|
| 569 |
+
|
| 570 |
+
def _petal_shape_dyad(ax, emotion_score, colorA, colorB, angle, font, fontweight, fontsize, highlight, will_circle, offset = .15, height_width_ratio = 1, normalize = False):
|
| 571 |
+
"""
|
| 572 |
+
Draw a petal.
|
| 573 |
+
A petal is the intersection area between two circles.
|
| 574 |
+
The height of the petal depends on the radius and the center of the circles.
|
| 575 |
+
Full details at http://www.github.com/alfonsosemeraro/plutchik/tutorial.ipynb
|
| 576 |
+
|
| 577 |
+
Required arguments:
|
| 578 |
+
----------
|
| 579 |
+
*ax*:
|
| 580 |
+
Axes to draw the coordinates.
|
| 581 |
+
|
| 582 |
+
*emotion_score*:
|
| 583 |
+
Score of the emotion. Values range from 0 to 1.
|
| 584 |
+
|
| 585 |
+
*colorA*:
|
| 586 |
+
First color of the petal. See dyad_params().
|
| 587 |
+
|
| 588 |
+
*colorB*:
|
| 589 |
+
Second color of the petal. See dyad_params().
|
| 590 |
+
|
| 591 |
+
*angle*:
|
| 592 |
+
Rotation angle of the petal. See dyad_params().
|
| 593 |
+
|
| 594 |
+
*font*:
|
| 595 |
+
Font of text. Default is Montserrat.
|
| 596 |
+
|
| 597 |
+
*fontweight*:
|
| 598 |
+
Font weight of text. Default is light.
|
| 599 |
+
|
| 600 |
+
*fontsize*:
|
| 601 |
+
Font size of text. Default is 15.
|
| 602 |
+
|
| 603 |
+
*highlight*:
|
| 604 |
+
String. 'opaque' if the petal must be shadowed, 'regular' is default.
|
| 605 |
+
|
| 606 |
+
*will_circle*:
|
| 607 |
+
Boolean. If three intensities will be plotted, then the lower petal must be pale.
|
| 608 |
+
|
| 609 |
+
*offset*:
|
| 610 |
+
Central neutral circle has radius = .15, and petals must start from there.
|
| 611 |
+
|
| 612 |
+
*height_width_ratio*:
|
| 613 |
+
Ratio between height and width of the petal. Lower the ratio, thicker the petal. Default is 1.
|
| 614 |
+
|
| 615 |
+
*normalize*:
|
| 616 |
+
Either False or the highest value among emotions. If not False, must normalize all petal lengths.
|
| 617 |
+
|
| 618 |
+
Returns:
|
| 619 |
+
----------
|
| 620 |
+
*petal*:
|
| 621 |
+
The petal, a shapely shape.
|
| 622 |
+
|
| 623 |
+
"""
|
| 624 |
+
|
| 625 |
+
if emotion_score == 0:
|
| 626 |
+
return ax
|
| 627 |
+
|
| 628 |
+
if normalize:
|
| 629 |
+
emotion_score /= normalize
|
| 630 |
+
|
| 631 |
+
# Computing proportions.
|
| 632 |
+
h = 1*emotion_score + offset
|
| 633 |
+
x = height_width_ratio*emotion_score
|
| 634 |
+
y = h/2
|
| 635 |
+
r = sqrt(x**2 + y**2)
|
| 636 |
+
|
| 637 |
+
# Computing rotated centers
|
| 638 |
+
x_right, y_right = _rotate_point((x, y), angle)
|
| 639 |
+
x_left, y_left = _rotate_point((-x, y), angle)
|
| 640 |
+
|
| 641 |
+
# Circles and intersection
|
| 642 |
+
right = sg.Point(x_right, y_right).buffer(r)
|
| 643 |
+
left = sg.Point(x_left, y_left).buffer(r)
|
| 644 |
+
petal = right.intersection(left)
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
# Computing squares: left
|
| 648 |
+
A = _rotate_point((-2*x, 0), angle)
|
| 649 |
+
B = _rotate_point((-2*x, h), angle)
|
| 650 |
+
C = _rotate_point((0, h), angle)
|
| 651 |
+
D = _rotate_point((0, 0), angle)
|
| 652 |
+
square_left = sg.Polygon([A, B, C, D, A])
|
| 653 |
+
|
| 654 |
+
# Computing squares: right
|
| 655 |
+
A = _rotate_point((0, 0), angle)
|
| 656 |
+
B = _rotate_point((0, h), angle)
|
| 657 |
+
C = _rotate_point((2*x, h), angle)
|
| 658 |
+
D = _rotate_point((2*x, 0), angle)
|
| 659 |
+
square_right = sg.Polygon([A, B, C, D, A])
|
| 660 |
+
|
| 661 |
+
# Computing semipetals
|
| 662 |
+
petalA = petal.intersection(square_left)
|
| 663 |
+
petalB = petal.intersection(square_right)
|
| 664 |
+
|
| 665 |
+
|
| 666 |
+
# white petal underneath
|
| 667 |
+
ax.add_patch(descartes.PolygonPatch(petal, fc='white', lw = 0, alpha=1, zorder = 0))
|
| 668 |
+
|
| 669 |
+
# Draw each half-petal in alpha 0.7
|
| 670 |
+
alpha = .7
|
| 671 |
+
|
| 672 |
+
xs, ys = petalA.exterior.xy
|
| 673 |
+
ax.fill(xs, ys, alpha=alpha, fc= colorA, ec='none')
|
| 674 |
+
|
| 675 |
+
xs, ys = petalB.exterior.xy
|
| 676 |
+
ax.fill(xs, ys, alpha=alpha, fc=colorB, ec='none')
|
| 677 |
+
|
| 678 |
+
return ax
|
| 679 |
+
|
| 680 |
+
|
| 681 |
+
def _petal_spine_emotion(ax, emotion, emotion_score, color, angle, font, fontweight, fontsize, highlight = 'all', offset = .15):
|
| 682 |
+
"""
|
| 683 |
+
Draw the spine beneath a petal, and the annotation of emotion and emotion's value.
|
| 684 |
+
The spine is a straight line from the center, of length 1.03.
|
| 685 |
+
Full details at http://www.github.com/alfonsosemeraro/plutchik/tutorial.ipynb
|
| 686 |
+
|
| 687 |
+
Required arguments:
|
| 688 |
+
----------
|
| 689 |
+
*ax*:
|
| 690 |
+
Axes to draw the coordinates.
|
| 691 |
+
|
| 692 |
+
*emotion*:
|
| 693 |
+
Emotion's name.
|
| 694 |
+
|
| 695 |
+
*emotion_score*:
|
| 696 |
+
Score of the emotion. Values range from 0 to 1. if list, it must contain 3 values that sum up to 1.
|
| 697 |
+
|
| 698 |
+
*color*:
|
| 699 |
+
Color of the petal. See emo_params().
|
| 700 |
+
|
| 701 |
+
*angle*:
|
| 702 |
+
Rotation angle of the petal. See emo_params().
|
| 703 |
+
|
| 704 |
+
*font*:
|
| 705 |
+
Font of text. Default is Montserrat.
|
| 706 |
+
|
| 707 |
+
*fontweight*:
|
| 708 |
+
Font weight of text. Default is light.
|
| 709 |
+
|
| 710 |
+
*fontsize*:
|
| 711 |
+
Font size of text. Default is 15.
|
| 712 |
+
|
| 713 |
+
*highlight*:
|
| 714 |
+
String. 'opaque' if the petal must be shadowed, 'regular' is default.
|
| 715 |
+
|
| 716 |
+
*offset*:
|
| 717 |
+
Central neutral circle has radius = .15, and petals must start from there.
|
| 718 |
+
|
| 719 |
+
"""
|
| 720 |
+
|
| 721 |
+
# Diagonal lines and ticks
|
| 722 |
+
step = .03
|
| 723 |
+
p1 = (0, 0)
|
| 724 |
+
p2 = _rotate_point((0, 1 + step + offset), angle) # draw line until 0, 1 + step + offset
|
| 725 |
+
p3 = _rotate_point((-step, 1 + step + offset), angle) # draw tick
|
| 726 |
+
ax.plot([p1[0], p2[0]], [p1[1], p2[1]], zorder = 5, color = 'black', alpha = .3, linewidth = .75)
|
| 727 |
+
ax.plot([p2[0], p3[0]], [p2[1], p3[1]], zorder = 5, color = 'black', alpha = .3, linewidth = .75)
|
| 728 |
+
|
| 729 |
+
# Managing highlighting and transparency
|
| 730 |
+
if highlight == 'opaque':
|
| 731 |
+
alpha = .8
|
| 732 |
+
color = 'lightgrey'
|
| 733 |
+
else:
|
| 734 |
+
alpha = 1
|
| 735 |
+
|
| 736 |
+
# Checking if iterable
|
| 737 |
+
try:
|
| 738 |
+
_ = emotion_score[0]
|
| 739 |
+
iterable = True
|
| 740 |
+
except:
|
| 741 |
+
iterable = False
|
| 742 |
+
|
| 743 |
+
|
| 744 |
+
if iterable:
|
| 745 |
+
# Label
|
| 746 |
+
angle2 = angle + 180 if -110 > angle > -260 else angle
|
| 747 |
+
p4 = _rotate_point((0, 1.40 + step + offset), angle)
|
| 748 |
+
ax.annotate(s = emotion, xy = p4, rotation = angle2, ha='center', va = 'center',
|
| 749 |
+
fontfamily = font, size = fontsize, fontweight = fontweight)
|
| 750 |
+
|
| 751 |
+
# Score 1
|
| 752 |
+
p5 = _rotate_point((0, 1.07 + step + offset), angle)
|
| 753 |
+
ax.annotate(s = "{0:.2f}".format(round(emotion_score[0],2)), xy = p5, rotation = angle2, ha='center', va = 'center',
|
| 754 |
+
color = color, fontfamily = font, size = fontsize, fontweight = 'regular', alpha = alpha)
|
| 755 |
+
|
| 756 |
+
# Score 2
|
| 757 |
+
p6 = _rotate_point((0, 1.17 + step + offset), angle)
|
| 758 |
+
ax.annotate(s = "{0:.2f}".format(round(emotion_score[1],2)), xy = p6, rotation = angle2, ha='center', va = 'center',
|
| 759 |
+
color = color, fontfamily = font, size = fontsize, fontweight = 'demibold', alpha = alpha)
|
| 760 |
+
|
| 761 |
+
# Score 3
|
| 762 |
+
p7 = _rotate_point((0, 1.27 + step + offset), angle)
|
| 763 |
+
ax.annotate(s = "{0:.2f}".format(round(emotion_score[2],2)), xy = p7, rotation = angle2, ha='center', va = 'center',
|
| 764 |
+
color = color, fontfamily = font, size = fontsize, fontweight = 'regular', alpha = alpha)
|
| 765 |
+
|
| 766 |
+
else:
|
| 767 |
+
# Label
|
| 768 |
+
angle2 = angle + 180 if -110 > angle > -260 else angle
|
| 769 |
+
p4 = _rotate_point((0, 1.23 + step + offset), angle)
|
| 770 |
+
ax.annotate(s = emotion, xy = p4, rotation = angle2, ha='center', va = 'center',
|
| 771 |
+
fontfamily = font, size = fontsize, fontweight = fontweight)
|
| 772 |
+
|
| 773 |
+
# Score
|
| 774 |
+
p5 = _rotate_point((0, 1.1 + step + offset), angle)
|
| 775 |
+
ax.annotate(s = "{0:.2f}".format(round(emotion_score,2)), xy = p5, rotation = angle2, ha='center', va = 'center',
|
| 776 |
+
color = color, fontfamily = font, size = fontsize, fontweight = 'demibold', alpha = alpha)
|
| 777 |
+
|
| 778 |
+
|
| 779 |
+
|
| 780 |
+
def _petal_spine_dyad(ax, dyad, dyad_score, color, emotion_names, angle, font, fontweight, fontsize, highlight = 'all', offset = .15):
|
| 781 |
+
"""
|
| 782 |
+
Draw the spine beneath a petal, and the annotation of dyad and dyad's value.
|
| 783 |
+
The spine is a straight line from the center, of length 1.03.
|
| 784 |
+
Full details at http://www.github.com/alfonsosemeraro/plutchik/tutorial.ipynb
|
| 785 |
+
|
| 786 |
+
Required arguments:
|
| 787 |
+
----------
|
| 788 |
+
*ax*:
|
| 789 |
+
Axes to draw the coordinates.
|
| 790 |
+
|
| 791 |
+
*dyad*:
|
| 792 |
+
Dyad's name.
|
| 793 |
+
|
| 794 |
+
*dyad_score*:
|
| 795 |
+
Score of the dyad. Values range from 0 to 1. if list, it must contain 3 values that sum up to 1.
|
| 796 |
+
|
| 797 |
+
*color*:
|
| 798 |
+
Color of the two emotions of the dyad. See dyad_params().
|
| 799 |
+
|
| 800 |
+
*emotion_names*:
|
| 801 |
+
Name of the emotions the dyad is made of. See dyad_params().
|
| 802 |
+
|
| 803 |
+
*angle*:
|
| 804 |
+
Rotation angle of the petal. See dyad_params().
|
| 805 |
+
|
| 806 |
+
*font*:
|
| 807 |
+
Font of text. Default is Montserrat.
|
| 808 |
+
|
| 809 |
+
*fontweight*:
|
| 810 |
+
Font weight of text. Default is light.
|
| 811 |
+
|
| 812 |
+
*fontsize*:
|
| 813 |
+
Font size of text. Default is 15.
|
| 814 |
+
|
| 815 |
+
*highlight*:
|
| 816 |
+
String. 'opaque' if the petal must be shadowed, 'regular' is default.
|
| 817 |
+
|
| 818 |
+
*offset*:
|
| 819 |
+
Central neutral circle has radius = .15, and petals must start from there.
|
| 820 |
+
|
| 821 |
+
"""
|
| 822 |
+
|
| 823 |
+
# Diagonal lines and ticks
|
| 824 |
+
step = .03
|
| 825 |
+
p1 = (0, 0) # 0, 0 + offset
|
| 826 |
+
p2 = _rotate_point((0, 1 + step + offset), angle) # draw line until 0, 1 + step + offset
|
| 827 |
+
p3 = _rotate_point((-step, 1 + step + offset), angle) # draw tick
|
| 828 |
+
ax.plot([p1[0], p2[0]], [p1[1], p2[1]], zorder = 5, color = 'black', alpha = .3, linewidth = .75)
|
| 829 |
+
ax.plot([p2[0], p3[0]], [p2[1], p3[1]], zorder = 5, color = 'black', alpha = .3, linewidth = .75)
|
| 830 |
+
|
| 831 |
+
# Managing highlighting and opacity
|
| 832 |
+
if highlight == 'opaque':
|
| 833 |
+
alpha = .8
|
| 834 |
+
color = 'lightgrey'
|
| 835 |
+
else:
|
| 836 |
+
alpha = 1
|
| 837 |
+
|
| 838 |
+
|
| 839 |
+
## Drawing the two-colored circular arc over dyads
|
| 840 |
+
from matplotlib.patches import Arc
|
| 841 |
+
|
| 842 |
+
H = 3.2
|
| 843 |
+
|
| 844 |
+
pac1 = Arc((0, 0), width = H, height = H, angle = 90, theta2 = angle, theta1 = angle - 18, ec = color[1], linewidth = 3)
|
| 845 |
+
pac2 = Arc((0, 0), width = H, height = H, angle = 90, theta2 = angle + 18, theta1 = angle, ec = color[0], linewidth = 3)
|
| 846 |
+
ax.add_patch(pac1)
|
| 847 |
+
ax.add_patch(pac2)
|
| 848 |
+
|
| 849 |
+
|
| 850 |
+
# Labels over the arcs
|
| 851 |
+
angle2 = angle + 180 if -110 > angle > -260 else angle
|
| 852 |
+
p9 = _rotate_point((0, 1.7), angle - 9)
|
| 853 |
+
ax.annotate(s = emotion_names[1], xy = p9, rotation = angle2 - 8, ha='center', va = 'center', zorder = 30,
|
| 854 |
+
fontfamily = font, size = fontsize * .7, fontweight = 'demibold', color = color[1])
|
| 855 |
+
p10 = _rotate_point((0, 1.7), angle + 9)
|
| 856 |
+
ax.annotate(s = emotion_names[0], xy = p10, rotation = angle2 + 8, ha='center', va = 'center', zorder = 30,
|
| 857 |
+
fontfamily = font, size = fontsize * .7, fontweight = 'demibold', color = color[0])
|
| 858 |
+
|
| 859 |
+
|
| 860 |
+
# Dyad label must be grey
|
| 861 |
+
color = '#363636'
|
| 862 |
+
|
| 863 |
+
# Label
|
| 864 |
+
angle2 = angle + 180 if -110 > angle > -260 else angle
|
| 865 |
+
p4 = _rotate_point((0, 1.23 + step + offset), angle)
|
| 866 |
+
ax.annotate(s = dyad, xy = p4, rotation = angle2, ha='center', va = 'center',
|
| 867 |
+
fontfamily = font, size = fontsize, fontweight = fontweight)
|
| 868 |
+
|
| 869 |
+
# Score
|
| 870 |
+
p5 = _rotate_point((0, 1.1 + step + offset), angle)
|
| 871 |
+
ax.annotate(s = "{0:.2f}".format(round(dyad_score,2)), xy = p5, rotation = angle2, ha='center', va = 'center',
|
| 872 |
+
color = color, fontfamily = font, size = fontsize, fontweight = 'demibold', alpha = alpha)
|
| 873 |
+
|
| 874 |
+
|
| 875 |
+
|
| 876 |
+
|
| 877 |
+
def _petal_circle(ax, petal, radius, color, inner = False, highlight = 'none', offset = .15, normalize = False):
|
| 878 |
+
"""
|
| 879 |
+
Each petal may have 3 degrees of intensity.
|
| 880 |
+
Each of the three sections of a petal is the interception between
|
| 881 |
+
the petal and up to two concentric circles from the origin.
|
| 882 |
+
This function draws one section.
|
| 883 |
+
Full details at http://www.github.com/alfonsosemeraro/plutchik/tutorial.ipynb
|
| 884 |
+
|
| 885 |
+
Required arguments:
|
| 886 |
+
----------
|
| 887 |
+
*ax*:
|
| 888 |
+
Axes to draw the coordinates.
|
| 889 |
+
|
| 890 |
+
*petal*:
|
| 891 |
+
The petal shape. See petal().
|
| 892 |
+
|
| 893 |
+
*radius*:
|
| 894 |
+
Radius of the section.
|
| 895 |
+
|
| 896 |
+
*color*:
|
| 897 |
+
Color of the section. See emo_params().
|
| 898 |
+
|
| 899 |
+
*inner*:
|
| 900 |
+
Boolean. If True, a second patch is drawn with alpha = 0.3, making the inner circle darker.
|
| 901 |
+
|
| 902 |
+
*highlight*:
|
| 903 |
+
String. 'opaque' if the petal must be shadowed, 'regular' is default.
|
| 904 |
+
|
| 905 |
+
*offset*:
|
| 906 |
+
Central neutral circle has radius = .15, and petals must start from there.
|
| 907 |
+
|
| 908 |
+
*normalize*:
|
| 909 |
+
Either False or the highest value among emotions. If not False, must normalize all petal lengths.
|
| 910 |
+
|
| 911 |
+
"""
|
| 912 |
+
|
| 913 |
+
if radius:
|
| 914 |
+
|
| 915 |
+
if normalize:
|
| 916 |
+
radius /= normalize
|
| 917 |
+
|
| 918 |
+
# Define the intersection between circle c and petal
|
| 919 |
+
c = sg.Point(0, 0).buffer(radius + offset)
|
| 920 |
+
area = petal.intersection(c)
|
| 921 |
+
|
| 922 |
+
# Managing alpha and color
|
| 923 |
+
alpha0 = 1 if highlight == 'regular' else .2
|
| 924 |
+
ecol = (colors.to_rgba(color)[0], colors.to_rgba(color)[1], colors.to_rgba(color)[2], alpha0)
|
| 925 |
+
|
| 926 |
+
alpha1 = .5 if highlight == 'regular' else .0
|
| 927 |
+
|
| 928 |
+
# Drawing separately the shape and a thicker border
|
| 929 |
+
ax.add_patch(descartes.PolygonPatch(area, fc=color, ec = 'black', lw = 0, alpha=alpha1))
|
| 930 |
+
ax.add_patch(descartes.PolygonPatch(area, fc=(0, 0, 0, 0), ec = ecol, lw = 1.3))
|
| 931 |
+
|
| 932 |
+
# The innermost circle gets to be brighter because of the repeated overlap
|
| 933 |
+
# Its alpha is diminished to avoid too much bright colors
|
| 934 |
+
if inner:
|
| 935 |
+
alpha2 = .3 if highlight == 'regular' else .0
|
| 936 |
+
ax.add_patch(descartes.PolygonPatch(area, fc=color, ec = 'w', lw = 0, alpha=alpha2))
|
| 937 |
+
ax.add_patch(descartes.PolygonPatch(area, fc=(0, 0, 0, 0), ec = ecol, lw = 1.5))
|
| 938 |
+
|
| 939 |
+
|
| 940 |
+
def _draw_emotion_petal(ax, emotion, emotion_score, highlight_emotions, show_intensity_labels, font, fontweight, fontsize, show_coordinates, height_width_ratio, normalize = False):
|
| 941 |
+
"""
|
| 942 |
+
Draw the petal and its possible sections.
|
| 943 |
+
Full details at http://www.github.com/alfonsosemeraro/plutchik/tutorial.ipynb
|
| 944 |
+
|
| 945 |
+
Required arguments:
|
| 946 |
+
----------
|
| 947 |
+
*ax*:
|
| 948 |
+
Axes to draw the coordinates.
|
| 949 |
+
|
| 950 |
+
*emotion*:
|
| 951 |
+
Emotion's name.
|
| 952 |
+
|
| 953 |
+
*emotion_score*:
|
| 954 |
+
Score of the emotion. Values range from 0 to 1.
|
| 955 |
+
|
| 956 |
+
*highlight_emotions*:
|
| 957 |
+
A list of main emotions to highlight. Other emotions will be shadowed.
|
| 958 |
+
|
| 959 |
+
*show_intensity_labels*:
|
| 960 |
+
A string or a list of main emotions. It shows all three intensity scores for each emotion in the list, and for the others cumulative scores. Default is 'none'.
|
| 961 |
+
|
| 962 |
+
*font*:
|
| 963 |
+
Font of text. Default is Montserrat.
|
| 964 |
+
|
| 965 |
+
*fontweight*:
|
| 966 |
+
Font weight of text. Default is light.
|
| 967 |
+
|
| 968 |
+
*fontsize*:
|
| 969 |
+
Font size of text. Default is 15.
|
| 970 |
+
|
| 971 |
+
*offset*:
|
| 972 |
+
Central neutral circle has radius = .15, and petals must start from there.
|
| 973 |
+
|
| 974 |
+
*show_coordinates*:
|
| 975 |
+
A boolean, wether to show polar coordinates or not.
|
| 976 |
+
|
| 977 |
+
*normalize*:
|
| 978 |
+
Either False or the highest value among emotions. If not False, normalize petal length.
|
| 979 |
+
|
| 980 |
+
"""
|
| 981 |
+
color, angle, _ = emo_params(emotion)
|
| 982 |
+
|
| 983 |
+
# Check if iterable
|
| 984 |
+
try:
|
| 985 |
+
_ = emotion_score[0]
|
| 986 |
+
iterable = True
|
| 987 |
+
except:
|
| 988 |
+
iterable = False
|
| 989 |
+
|
| 990 |
+
|
| 991 |
+
# Manage highlight and opacity
|
| 992 |
+
if highlight_emotions != 'all':
|
| 993 |
+
if emotion in highlight_emotions:
|
| 994 |
+
highlight = 'regular'
|
| 995 |
+
else:
|
| 996 |
+
highlight = 'opaque'
|
| 997 |
+
else:
|
| 998 |
+
highlight = 'regular'
|
| 999 |
+
|
| 1000 |
+
|
| 1001 |
+
if not iterable:
|
| 1002 |
+
if show_coordinates:
|
| 1003 |
+
|
| 1004 |
+
# Draw the line and tick behind a petal
|
| 1005 |
+
_petal_spine_emotion(ax = ax, emotion = emotion, emotion_score = emotion_score,
|
| 1006 |
+
color = color, angle = angle,
|
| 1007 |
+
font = font, fontweight = fontweight, fontsize = fontsize,
|
| 1008 |
+
highlight = highlight,
|
| 1009 |
+
offset = .15)
|
| 1010 |
+
# Draw petal
|
| 1011 |
+
_petal_shape_emotion(ax, emotion_score, color, angle, font, fontweight, fontsize, height_width_ratio = height_width_ratio, highlight = highlight, will_circle = False, normalize = normalize)
|
| 1012 |
+
# Draw border
|
| 1013 |
+
_outer_border(ax, emotion_score, color, angle, height_width_ratio = height_width_ratio, highlight = highlight, normalize = normalize)
|
| 1014 |
+
|
| 1015 |
+
else:
|
| 1016 |
+
# Total length is the sum of the emotion score
|
| 1017 |
+
a, b, c = emotion_score
|
| 1018 |
+
length = a + b + c
|
| 1019 |
+
# Show three scores or just the cumulative one?
|
| 1020 |
+
label = emotion_score if ((show_intensity_labels == 'all') or (emotion in show_intensity_labels)) else length
|
| 1021 |
+
|
| 1022 |
+
if show_coordinates:
|
| 1023 |
+
|
| 1024 |
+
# Draw the line and tick behind a petal
|
| 1025 |
+
_petal_spine_emotion(ax = ax, emotion = emotion, emotion_score = label,
|
| 1026 |
+
color = color, angle = angle,
|
| 1027 |
+
font = font, fontweight = fontweight, fontsize = fontsize,
|
| 1028 |
+
highlight = highlight,
|
| 1029 |
+
offset = .15)
|
| 1030 |
+
|
| 1031 |
+
# Draw petal
|
| 1032 |
+
petal_shape = _petal_shape_emotion(ax, length, color, angle, font, fontweight, fontsize, height_width_ratio = height_width_ratio, highlight = highlight, will_circle = True, normalize = normalize)
|
| 1033 |
+
# Draw inner petal section
|
| 1034 |
+
_petal_circle(ax, petal_shape, a + b, color, False, highlight, normalize = normalize)
|
| 1035 |
+
# Draw middle petal section
|
| 1036 |
+
_petal_circle(ax, petal_shape, a, color, True, highlight, normalize = normalize)
|
| 1037 |
+
# Draw border
|
| 1038 |
+
_outer_border(ax, length, color, angle, height_width_ratio = height_width_ratio, highlight = highlight, normalize = normalize)
|
| 1039 |
+
|
| 1040 |
+
|
| 1041 |
+
def _draw_dyad_petal(ax, dyad, dyad_score, font, fontweight, fontsize, show_coordinates, height_width_ratio, offset = .15, normalize = False):
|
| 1042 |
+
"""
|
| 1043 |
+
Draw the petal and its possible sections.
|
| 1044 |
+
Full details at http://www.github.com/alfonsosemeraro/plutchik/tutorial.ipynb
|
| 1045 |
+
|
| 1046 |
+
Required arguments:
|
| 1047 |
+
----------
|
| 1048 |
+
*ax*:
|
| 1049 |
+
Axes to draw the coordinates.
|
| 1050 |
+
|
| 1051 |
+
*dyad*:
|
| 1052 |
+
Dyad's name.
|
| 1053 |
+
|
| 1054 |
+
*dyad_score*:
|
| 1055 |
+
Score of the dyad. Values range from 0 to 1.
|
| 1056 |
+
|
| 1057 |
+
*font*:
|
| 1058 |
+
Font of text. Default is Montserrat.
|
| 1059 |
+
|
| 1060 |
+
*fontweight*:
|
| 1061 |
+
Font weight of text. Default is light.
|
| 1062 |
+
|
| 1063 |
+
*fontsize*:
|
| 1064 |
+
Font size of text. Default is 15.
|
| 1065 |
+
|
| 1066 |
+
*offset*:
|
| 1067 |
+
Central neutral circle has radius = .15, and petals must start from there.
|
| 1068 |
+
|
| 1069 |
+
*show_coordinates*:
|
| 1070 |
+
A boolean, wether to show polar coordinates or not.
|
| 1071 |
+
|
| 1072 |
+
*normalize*:
|
| 1073 |
+
Either False or the highest value among the dyads. If not False, normalize petal length.
|
| 1074 |
+
|
| 1075 |
+
|
| 1076 |
+
"""
|
| 1077 |
+
emos, color, angle, _ = dyad_params(dyad)
|
| 1078 |
+
colorA, colorB = color
|
| 1079 |
+
|
| 1080 |
+
if show_coordinates:
|
| 1081 |
+
# Draw the line and tick behind a petal
|
| 1082 |
+
_petal_spine_dyad(ax = ax, dyad = dyad, dyad_score = dyad_score,
|
| 1083 |
+
emotion_names = emos,
|
| 1084 |
+
color = color, angle = angle,
|
| 1085 |
+
font = font, fontweight = fontweight, fontsize = fontsize,
|
| 1086 |
+
highlight = 'all',
|
| 1087 |
+
offset = .15)
|
| 1088 |
+
|
| 1089 |
+
# Draw petal (and get the modified ax)
|
| 1090 |
+
ax = _petal_shape_dyad(ax, dyad_score, colorA, colorB, angle, font, fontweight, fontsize, height_width_ratio = height_width_ratio, highlight = 'all', will_circle = False, normalize = normalize)
|
| 1091 |
+
# Draw border
|
| 1092 |
+
_outer_border(ax, dyad_score, colorA, angle, height_width_ratio = height_width_ratio, highlight = 'all', normalize = normalize)
|
| 1093 |
+
|
| 1094 |
+
|
| 1095 |
+
|
| 1096 |
+
|
| 1097 |
+
|
| 1098 |
+
|
| 1099 |
+
def _check_scores_kind(tags):
|
| 1100 |
+
"""
|
| 1101 |
+
Checks if the inputed scores are all of the same kind
|
| 1102 |
+
(emotions or primary dyads or secondary dyads or tertiary dyads or opposites).
|
| 1103 |
+
|
| 1104 |
+
No mixed kinds are allowed.
|
| 1105 |
+
|
| 1106 |
+
Required arguments:
|
| 1107 |
+
----------
|
| 1108 |
+
|
| 1109 |
+
*tags*:
|
| 1110 |
+
List of the tags provided as 'scores'.
|
| 1111 |
+
|
| 1112 |
+
|
| 1113 |
+
Returns:
|
| 1114 |
+
----------
|
| 1115 |
+
|
| 1116 |
+
A boolean, True if `scores` contains emotions, False if it contains dyads.
|
| 1117 |
+
|
| 1118 |
+
"""
|
| 1119 |
+
kinds = []
|
| 1120 |
+
for t in tags:
|
| 1121 |
+
try:
|
| 1122 |
+
kinds += [emo_params(t)[2]]
|
| 1123 |
+
except:
|
| 1124 |
+
kinds += [dyad_params(t)[3]]
|
| 1125 |
+
|
| 1126 |
+
unique_kinds = list(set(sorted(kinds)))
|
| 1127 |
+
|
| 1128 |
+
if len(unique_kinds) > 1:
|
| 1129 |
+
unique_kinds_str = ', '.join([str(a) for a in unique_kinds])
|
| 1130 |
+
unique_kinds_str = unique_kinds_str.replace('0', 'emotions')
|
| 1131 |
+
unique_kinds_str = unique_kinds_str.replace('1', 'primary dyads')
|
| 1132 |
+
unique_kinds_str = unique_kinds_str.replace('2', 'secondary dyads')
|
| 1133 |
+
unique_kinds_str = unique_kinds_str.replace('3', 'tertiary dyads')
|
| 1134 |
+
unique_kinds_str = unique_kinds_str.replace('4', 'opposite emotions')
|
| 1135 |
+
unique_kinds_str = ' and'.join(unique_kinds_str.rsplit(',', 1))
|
| 1136 |
+
|
| 1137 |
+
error_str = "Bad input: can't draw {} altogether. Please input only one of them as 'scores'.".format(unique_kinds_str)
|
| 1138 |
+
raise Exception(error_str)
|
| 1139 |
+
|
| 1140 |
+
else:
|
| 1141 |
+
|
| 1142 |
+
kind = kinds[0]
|
| 1143 |
+
|
| 1144 |
+
if kind == 0:
|
| 1145 |
+
return True
|
| 1146 |
+
else:
|
| 1147 |
+
return False
|
| 1148 |
+
|
| 1149 |
+
def random_flower():
|
| 1150 |
+
""" Draws a Plutchik's flower with random scores """
|
| 1151 |
+
|
| 1152 |
+
import random
|
| 1153 |
+
|
| 1154 |
+
emo = {'joy': random.uniform(0, 1),
|
| 1155 |
+
'trust': random.uniform(0,1),
|
| 1156 |
+
'fear': random.uniform(0,1),
|
| 1157 |
+
'surprise': random.uniform(0,1),
|
| 1158 |
+
'sadness': random.uniform(0,1),
|
| 1159 |
+
'disgust': random.uniform(0,1),
|
| 1160 |
+
'anger': random.uniform(0,1),
|
| 1161 |
+
'anticipation': random.uniform(0,1)}
|
| 1162 |
+
|
| 1163 |
+
plutchik(emo)
|
| 1164 |
+
|
| 1165 |
+
def plutchik(scores,
|
| 1166 |
+
ax = None,
|
| 1167 |
+
font = None,
|
| 1168 |
+
fontweight = 'light',
|
| 1169 |
+
fontsize = 15,
|
| 1170 |
+
show_coordinates = True,
|
| 1171 |
+
show_ticklabels = False,
|
| 1172 |
+
highlight_emotions = 'all',
|
| 1173 |
+
show_intensity_labels = 'none',
|
| 1174 |
+
ticklabels_angle = 0,
|
| 1175 |
+
ticklabels_size = 11,
|
| 1176 |
+
height_width_ratio = 1,
|
| 1177 |
+
title = None,
|
| 1178 |
+
title_size = None,
|
| 1179 |
+
normalize = False):
|
| 1180 |
+
"""
|
| 1181 |
+
Draw the petal and its possible sections.
|
| 1182 |
+
Full details at http://www.github.com/alfonsosemeraro/plutchik/tutorial.ipynb
|
| 1183 |
+
|
| 1184 |
+
Required arguments:
|
| 1185 |
+
----------
|
| 1186 |
+
|
| 1187 |
+
*scores*:
|
| 1188 |
+
A dictionary with emotions or dyads.
|
| 1189 |
+
For each entry, values accepted are a 3-values iterable (for emotions only) or a scalar value between 0 and 1.
|
| 1190 |
+
The sum of the 3-values iterable values must not exceed 1, and no value should be negative.
|
| 1191 |
+
See emo_params() and dyad_params() for accepted keys.
|
| 1192 |
+
|
| 1193 |
+
Emotions and dyads are mutually exclusive. Different kinds of dyads are mutually exclusive.
|
| 1194 |
+
|
| 1195 |
+
*ax*:
|
| 1196 |
+
Axes to draw the coordinates.
|
| 1197 |
+
|
| 1198 |
+
*font*:
|
| 1199 |
+
Font of text. Default is sans-serif.
|
| 1200 |
+
|
| 1201 |
+
*fontweight*:
|
| 1202 |
+
Font weight of text. Default is light.
|
| 1203 |
+
|
| 1204 |
+
*fontsize*:
|
| 1205 |
+
Font size of text. Default is 15.
|
| 1206 |
+
|
| 1207 |
+
*offset*:
|
| 1208 |
+
Central neutral circle has radius = .15, and petals must start from there.
|
| 1209 |
+
|
| 1210 |
+
*show_coordinates*:
|
| 1211 |
+
A boolean, wether to show polar coordinates or not.
|
| 1212 |
+
|
| 1213 |
+
*show_ticklabels*:
|
| 1214 |
+
Boolean, wether to show tick labels under Joy petal. Default is False.
|
| 1215 |
+
|
| 1216 |
+
*highlight_emotions*:
|
| 1217 |
+
A string or a list of main emotions to highlight. If a list of emotions is given, other emotions will be shadowed. Default is 'all'.
|
| 1218 |
+
|
| 1219 |
+
*show_intensity_labels*:
|
| 1220 |
+
A string or a list of main emotions. It shows all three intensity scores for each emotion in the list, and for the others cumulative scores. Default is 'none'.
|
| 1221 |
+
|
| 1222 |
+
*ticklabels_angle*:
|
| 1223 |
+
How much to rotate tick labels from y=0. Value should be given in radians. Default is 0.
|
| 1224 |
+
|
| 1225 |
+
*ticklabels_size*:
|
| 1226 |
+
Size of tick labels. Default is 11.
|
| 1227 |
+
|
| 1228 |
+
*height_width_ratio*:
|
| 1229 |
+
Ratio between height and width of the petal. Lower the ratio, thicker the petal. Default is 1.
|
| 1230 |
+
|
| 1231 |
+
*title*:
|
| 1232 |
+
Title for the plot.
|
| 1233 |
+
|
| 1234 |
+
*title_size*:
|
| 1235 |
+
Size of the title. Default is font_size.
|
| 1236 |
+
|
| 1237 |
+
Returns:
|
| 1238 |
+
----------
|
| 1239 |
+
*ax*:
|
| 1240 |
+
The input Axes modified.
|
| 1241 |
+
|
| 1242 |
+
"""
|
| 1243 |
+
|
| 1244 |
+
scores = {key.lower(): val for key, val in scores.items()}
|
| 1245 |
+
|
| 1246 |
+
# Check if dyads or emotions, and what kind of dyads
|
| 1247 |
+
score_is_emotions = _check_scores_kind(scores)
|
| 1248 |
+
if score_is_emotions:
|
| 1249 |
+
emotions, dyads = scores, None
|
| 1250 |
+
else:
|
| 1251 |
+
emotions, dyads = None, scores
|
| 1252 |
+
|
| 1253 |
+
# Create subplot if is not provided as parameter
|
| 1254 |
+
if not ax:
|
| 1255 |
+
fig, ax = plt.subplots(figsize = (8, 8))
|
| 1256 |
+
|
| 1257 |
+
# Managing fonts
|
| 1258 |
+
if not font:
|
| 1259 |
+
font = 'sans-serif'
|
| 1260 |
+
|
| 1261 |
+
|
| 1262 |
+
|
| 1263 |
+
# Draw coordinates (if needed) before any petal
|
| 1264 |
+
if show_coordinates:
|
| 1265 |
+
_polar_coordinates(ax, font, fontweight, fontsize, show_ticklabels, ticklabels_angle, ticklabels_size)
|
| 1266 |
+
|
| 1267 |
+
# Draw inner white circle
|
| 1268 |
+
_neutral_central_circle(ax)
|
| 1269 |
+
|
| 1270 |
+
|
| 1271 |
+
# Emotions and dyads are mutually exclusive
|
| 1272 |
+
if emotions:
|
| 1273 |
+
emotions = {key.lower(): val for key, val in emotions.items()}
|
| 1274 |
+
|
| 1275 |
+
|
| 1276 |
+
for emo in emotions:
|
| 1277 |
+
|
| 1278 |
+
# Check correctedness of values
|
| 1279 |
+
if hasattr(emotions[emo], '__iter__'):
|
| 1280 |
+
if sum(emotions[emo]) > 1 or any([e < 0 for e in emotions[emo]]):
|
| 1281 |
+
raise Exception("Bad input for `{}`. Emotion scores array should be between 0 and 1.".format(emo))
|
| 1282 |
+
else:
|
| 1283 |
+
if emotions[emo] > 1 or emotions[emo] < 0:
|
| 1284 |
+
raise Exception("Bad input for `{}`. Emotion scores array should sum to between 0 and 1.".format(emo))
|
| 1285 |
+
|
| 1286 |
+
# Draw emotion petal
|
| 1287 |
+
_draw_emotion_petal(ax, emotion_score = emotions[emo], emotion = emo,
|
| 1288 |
+
font = font, fontweight = fontweight, fontsize = fontsize,
|
| 1289 |
+
highlight_emotions = highlight_emotions, show_intensity_labels = show_intensity_labels,
|
| 1290 |
+
show_coordinates = show_coordinates, height_width_ratio = height_width_ratio, normalize = normalize)
|
| 1291 |
+
|
| 1292 |
+
elif dyads:
|
| 1293 |
+
|
| 1294 |
+
for dyad in dyads:
|
| 1295 |
+
|
| 1296 |
+
# Check correctedness of values
|
| 1297 |
+
if dyads[dyad] > 1 or dyads[dyad] < 0:
|
| 1298 |
+
print("Alert: {} = {}".format(dyad, dyads[dyad]))
|
| 1299 |
+
raise Exception("Bad input for `{}`. Dyads scores array should sum to between 0 and 1.".format(dyad))
|
| 1300 |
+
|
| 1301 |
+
# Draw dyad bicolor petal
|
| 1302 |
+
_draw_dyad_petal(ax, dyad_score = dyads[dyad], dyad = dyad,
|
| 1303 |
+
font = font, fontweight = fontweight, fontsize = fontsize,
|
| 1304 |
+
show_coordinates = show_coordinates, height_width_ratio = height_width_ratio,
|
| 1305 |
+
normalize = normalize)
|
| 1306 |
+
|
| 1307 |
+
|
| 1308 |
+
# Annotation inside the circle
|
| 1309 |
+
_, _, _, level = dyad_params(list(dyads.keys())[0]) # get the first dyad level (they all are the same)
|
| 1310 |
+
ll = level if level != 4 else 'opp.' # what to annotate
|
| 1311 |
+
xy = (-0.03, -0.03) if level != 4 else (-0.13, -0.03) # exact center of '1' or 'opp' is slightly different
|
| 1312 |
+
ax.annotate(s = ll, xy = xy, fontsize = fontsize, fontfamily = font, fontweight = 'bold', zorder = 30)
|
| 1313 |
+
|
| 1314 |
+
# Ghost dotted track that connects colored arcs
|
| 1315 |
+
c = plt.Circle((0, 0), 1.60, color = 'grey', alpha = .3, fill = False, zorder = -20, linestyle = 'dotted' )
|
| 1316 |
+
ax.add_artist(c)
|
| 1317 |
+
|
| 1318 |
+
|
| 1319 |
+
# Adjusting printable area size
|
| 1320 |
+
lim = 1.6 if show_coordinates else 1.2
|
| 1321 |
+
lim = lim + 0.1 if dyads else lim
|
| 1322 |
+
|
| 1323 |
+
ax.set_xlim((-lim, lim))
|
| 1324 |
+
ax.set_ylim((-lim, lim))
|
| 1325 |
+
|
| 1326 |
+
# Default is no axis
|
| 1327 |
+
ax.axis('off')
|
| 1328 |
+
|
| 1329 |
+
# Title and title size
|
| 1330 |
+
if not title_size:
|
| 1331 |
+
title_size = fontsize
|
| 1332 |
+
|
| 1333 |
+
if title:
|
| 1334 |
+
ax.set_title(title, fontfamily = font, fontsize = title_size, fontweight = 'bold', pad = 20)
|
| 1335 |
+
|
| 1336 |
+
return ax
|