avichr commited on
Commit
ee0a609
1 Parent(s): b6f956e

Create pyplutchik.py

Browse files
Files changed (1) hide show
  1. 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 <alfonso.semeraro@gmail.com>
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 (alfonso.semeraro@gmail.com)"""
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