rocky250 commited on
Commit
214dad1
·
verified ·
1 Parent(s): 2b86ee0

Update charts.py

Browse files
Files changed (1) hide show
  1. charts.py +133 -141
charts.py CHANGED
@@ -1,28 +1,21 @@
1
- """
2
- charts.py — All Plotly chart builders. Pure functions, no Streamlit imports.
3
- """
4
-
5
  from typing import Dict, List, Tuple, Optional
6
  import plotly.graph_objects as go
7
  import plotly.express as px
8
  import pandas as pd
9
  import numpy as np
10
 
11
- # Shared theme — Stormy Morning & Ink Wash palette
12
- DARK_BG = "#FFFFE3" # Ink Wash Cream — used as bar marker-line for "floating" effect
13
- CARD_BG = "#FFFFFF" # white card base
14
- BORDER = "#BDDDFC" # Stormy Light
15
- TEXT_MAIN = "#4A4A4A" # Ink Wash Dark
16
- TEXT_DIM = "#7b7b7b" # muted text
17
- PRIMARY = "#269ccc" # Optimism Blue
18
- CYAN = "#269ccc" # alias for PRIMARY
19
-
20
- # Sentiment palette — Stormy Morning
21
- POS_COLOR = "#88BDF2" # Stormy Sky — Positively Engagement
22
- NEG_COLOR = "#6A89A7" # Stormy Slate — Negatively Engagement
23
- NEU_COLOR = "#CBCBCB" # Ink Wash Grey — Neutral
24
-
25
- # Legacy / other chart colours
26
  ALGAE = "#9ed2c5"
27
  GREEN = "#16a34a"
28
  RED = "#dc2626"
@@ -30,18 +23,20 @@ AMBER = "#d97706"
30
  PURPLE = "#7c3aed"
31
  BLUE = "#2563eb"
32
 
 
 
 
 
 
33
  PLOTLY_LAYOUT = dict(
34
- paper_bgcolor="rgba(255,255,227,0)", # transparent over cream bg
35
- plot_bgcolor="rgba(255,255,227,0)", # transparent — card provides bg
36
- font=dict(family="'DM Sans', 'Nunito', sans-serif", color=TEXT_MAIN, size=12),
37
- margin=dict(l=20, r=20, t=40, b=20),
38
  )
39
 
40
 
41
- # Misinformation Gauge
42
-
43
  def misinfo_gauge(score: float, label: str) -> go.Figure:
44
- """Gauge chart for misinformation confidence score (0–1)."""
45
  pct = score * 100
46
  if score < 0.35:
47
  bar_color = GREEN
@@ -55,13 +50,13 @@ def misinfo_gauge(score: float, label: str) -> go.Figure:
55
  value=pct,
56
  number={"suffix": "%", "font": {"size": 32, "color": bar_color, "family": "'Nunito', sans-serif"}},
57
  delta={"reference": 50, "increasing": {"color": RED}, "decreasing": {"color": GREEN}},
58
- title={"text": label, "font": {"size": 13, "color": TEXT_DIM}},
59
  gauge={
60
  "axis": {
61
  "range": [0, 100],
62
  "tickwidth": 1,
63
  "tickcolor": BORDER,
64
- "tickfont": {"color": TEXT_DIM, "size": 10},
65
  },
66
  "bar": {"color": bar_color, "thickness": 0.3},
67
  "bgcolor": CARD_BG,
@@ -82,10 +77,7 @@ def misinfo_gauge(score: float, label: str) -> go.Figure:
82
  return fig
83
 
84
 
85
- # Sentiment Donut ─
86
-
87
  def sentiment_donut(summary: Dict) -> go.Figure:
88
- """Donut chart: Positively Engagement / Negatively Engagement / Neutral breakdown."""
89
  labels = ["Positively Engagement", "Neutral", "Negatively Engagement"]
90
  values = [summary["Positively Engagement"], summary["Neutral"], summary["Negatively Engagement"]]
91
  colors = [POS_COLOR, NEU_COLOR, NEG_COLOR]
@@ -101,7 +93,6 @@ def sentiment_donut(summary: Dict) -> go.Figure:
101
  rotation=90,
102
  ))
103
 
104
- # Centre annotation
105
  avg = summary.get("avg_compound", 0)
106
  overall = "😊 Positively Engaged" if avg > 0.05 else ("😟 Negatively Engaged" if avg < -0.05 else "😐 Mixed")
107
  fig.add_annotation(
@@ -111,14 +102,15 @@ def sentiment_donut(summary: Dict) -> go.Figure:
111
  font=dict(size=13, color=TEXT_MAIN, family="'DM Sans', sans-serif"),
112
  align="center",
113
  )
114
- fig.update_layout(**PLOTLY_LAYOUT, height=300,
115
- legend=dict(orientation="h", y=-0.08, font=dict(size=11)),
116
- title=dict(text="Sentiment Breakdown", font=dict(size=13, color=TEXT_DIM), x=0))
 
 
 
117
  return fig
118
 
119
 
120
- # Keyword Bar Chart ─
121
-
122
  def keyword_bar(
123
  keywords: List[Tuple[str, float]],
124
  title: str = "Top Keywords",
@@ -128,7 +120,6 @@ def keyword_bar(
128
  return _empty_fig(title)
129
 
130
  words, weights = zip(*keywords[:15])
131
- # Normalize to 0-100
132
  max_w = max(weights) or 1
133
  norm = [w / max_w * 100 for w in weights]
134
 
@@ -139,7 +130,7 @@ def keyword_bar(
139
  marker=dict(
140
  color=norm,
141
  colorscale=[[0, f"{PRIMARY}33"], [1, PRIMARY]],
142
- line=dict(color=DARK_BG, width=1.5), # floating effect — matches bg
143
  ),
144
  text=[f"{w:.0f}" for w in weights],
145
  textposition="inside",
@@ -148,21 +139,28 @@ def keyword_bar(
148
  ))
149
  fig.update_layout(
150
  **PLOTLY_LAYOUT,
151
- title=dict(text=title, font=dict(size=13, color=TEXT_DIM), x=0),
152
  height=380,
153
- yaxis=dict(autorange="reversed", tickfont=dict(size=11), gridcolor=BORDER,
154
- showgrid=True, gridwidth=1),
155
- xaxis=dict(showticklabels=False, gridcolor=BORDER, showgrid=False),
 
 
 
 
 
 
 
 
 
 
156
  bargap=0.3,
157
- plot_bgcolor="rgba(189,221,252,0.08)",
158
  )
159
  return fig
160
 
161
 
162
- # Stream Trust Bars ─
163
-
164
  def stream_trust_bars(stream_details: Dict) -> go.Figure:
165
- """Horizontal bar chart for per-stream misinfo scores."""
166
  labels = list(stream_details.keys())
167
  values = [round(v * 100, 1) for v in stream_details.values()]
168
  colors = [RED if v > 50 else (AMBER if v > 30 else GREEN) for v in values]
@@ -179,33 +177,16 @@ def stream_trust_bars(stream_details: Dict) -> go.Figure:
179
  ))
180
  fig.update_layout(
181
  **PLOTLY_LAYOUT,
182
- title=dict(text="Per-Stream Analysis", font=dict(size=13, color=TEXT_DIM), x=0),
183
  height=220,
184
- xaxis=dict(range=[0, 110], showticklabels=False, gridcolor=BORDER),
185
- yaxis=dict(tickfont=dict(size=11)),
186
  bargap=0.4,
187
  )
188
  return fig
189
 
190
 
191
- # Modality Misinformation Distribution ─
192
-
193
  def modality_misinfo_distribution(modality_analysis: Dict) -> go.Figure:
194
- """
195
- Grouped bar chart — Misinformation Score vs Not-Misinformation Score per modality.
196
-
197
- Bars are derived directly from the model's per-stream softmax probabilities
198
- (values in ``modality_analysis[modality]["misinfo_pct"]`` /
199
- ``modality_analysis[modality]["credible_pct"]``).
200
- Each pair of bars sums to exactly 100 % because they are complementary
201
- softmax outputs from the same binary classification head.
202
-
203
- Parameters
204
- ----------
205
- modality_analysis : dict
206
- Mapping {"text": {...}, "audio": {...}, "video": {...}} as returned by
207
- ``analyzer._compute_modality_analysis()`` — one sub-dict per stream.
208
- """
209
  MODALITIES = ["Text", "Audio", "Video"]
210
  KEYS = ["text", "audio", "video"]
211
 
@@ -226,7 +207,7 @@ def modality_misinfo_distribution(modality_analysis: Dict) -> go.Figure:
226
  marker=dict(
227
  color=[RED, RED, RED],
228
  opacity=0.88,
229
- line=dict(color=DARK_BG, width=1.5), # floating bar lines
230
  ),
231
  text=[f"{v:.1f}%" for v in misinfo_pcts],
232
  textposition="outside",
@@ -246,7 +227,7 @@ def modality_misinfo_distribution(modality_analysis: Dict) -> go.Figure:
246
  marker=dict(
247
  color=[GREEN, GREEN, GREEN],
248
  opacity=0.88,
249
- line=dict(color=DARK_BG, width=1.5), # floating bar lines
250
  ),
251
  text=[f"{v:.1f}%" for v in credible_pcts],
252
  textposition="outside",
@@ -263,18 +244,21 @@ def modality_misinfo_distribution(modality_analysis: Dict) -> go.Figure:
263
  **PLOTLY_LAYOUT,
264
  title=dict(
265
  text="Modality Misinformation Distribution",
266
- font=dict(size=13, color=TEXT_DIM),
267
  x=0,
268
  ),
269
  barmode="group",
270
  height=280,
271
  xaxis=dict(
272
  title="Modality",
273
- tickfont=dict(size=12),
 
274
  gridcolor=BORDER,
275
  ),
276
  yaxis=dict(
277
  title="Softmax Score (%)",
 
 
278
  range=[0, 115],
279
  gridcolor=BORDER,
280
  ticksuffix="%",
@@ -282,30 +266,17 @@ def modality_misinfo_distribution(modality_analysis: Dict) -> go.Figure:
282
  legend=dict(
283
  orientation="h",
284
  y=1.12,
285
- font=dict(size=11),
286
  bgcolor="rgba(255,255,227,0)",
287
  ),
288
  bargap=0.22,
289
  bargroupgap=0.06,
290
- plot_bgcolor="rgba(189,221,252,0.08)",
291
  )
292
  return fig
293
 
294
 
295
- # Trust Score by Modality ─
296
-
297
  def trust_score_by_modality(modality_analysis: Dict) -> go.Figure:
298
- """
299
- Vertical bar chart — model's reliability/trustworthiness coefficient per stream.
300
-
301
- Trust is computed as a linear combination of model confidence (1 – Shannon entropy)
302
- and content-richness, both derived from the actual inference pass, never fixed.
303
-
304
- Parameters
305
- ----------
306
- modality_analysis : dict
307
- Same structure as ``modality_misinfo_distribution``.
308
- """
309
  MODALITIES = ["Text", "Audio", "Video"]
310
  KEYS = ["text", "audio", "video"]
311
 
@@ -321,7 +292,7 @@ def trust_score_by_modality(modality_analysis: Dict) -> go.Figure:
321
  marker=dict(
322
  color=bar_colors,
323
  opacity=0.88,
324
- line=dict(color=DARK_BG, width=1.5), # floating bar lines
325
  ),
326
  text=[f"{v:.1f}%" for v in trust_vals],
327
  textposition="outside",
@@ -334,7 +305,6 @@ def trust_score_by_modality(modality_analysis: Dict) -> go.Figure:
334
  ),
335
  ))
336
 
337
- # Reference lines
338
  for level, label, color in [(80, "High Trust", GREEN), (50, "Threshold", AMBER)]:
339
  fig.add_hline(
340
  y=level,
@@ -348,49 +318,37 @@ def trust_score_by_modality(modality_analysis: Dict) -> go.Figure:
348
  **PLOTLY_LAYOUT,
349
  title=dict(
350
  text="Trust Score by Modality",
351
- font=dict(size=13, color=TEXT_DIM),
352
  x=0,
353
  ),
354
  height=280,
355
  xaxis=dict(
356
  title="Modality",
357
- tickfont=dict(size=12),
 
358
  gridcolor=BORDER,
359
  ),
360
  yaxis=dict(
361
  title="Trust Level (%)",
 
 
362
  range=[0, 115],
363
  gridcolor=BORDER,
364
  ticksuffix="%",
365
  ),
366
  bargap=0.38,
367
- plot_bgcolor="rgba(189,221,252,0.08)",
368
  )
369
  return fig
370
 
371
 
372
- # Uncertainty Analysis
373
-
374
  def uncertainty_analysis(modality_analysis: Dict) -> go.Figure:
375
- """
376
- Vertical bar chart — Shannon entropy of the model's softmax distribution per stream.
377
-
378
- High entropy ( → 100 %) means the model is maximally unsure (uniform distribution).
379
- Low entropy ( → 0 %) means the model is highly confident in its prediction.
380
- Values come directly from H = –Σ p·log₂(p) over the two softmax outputs.
381
-
382
- Parameters
383
- ----------
384
- modality_analysis : dict
385
- Same structure as ``modality_misinfo_distribution``.
386
- """
387
  MODALITIES = ["Text", "Audio", "Video"]
388
  KEYS = ["text", "audio", "video"]
389
 
390
  uncertainty_vals = [modality_analysis.get(k, {}).get("uncertainty", 100.0) for k in KEYS]
391
  misinfo_pcts = [modality_analysis.get(k, {}).get("misinfo_pct", 50.0) for k in KEYS]
392
 
393
- # Colour encodes confidence direction: red = uncertain, green = confident
394
  bar_colors = [
395
  (GREEN if v <= 35 else (AMBER if v <= 65 else RED))
396
  for v in uncertainty_vals
@@ -402,7 +360,7 @@ def uncertainty_analysis(modality_analysis: Dict) -> go.Figure:
402
  marker=dict(
403
  color=bar_colors,
404
  opacity=0.88,
405
- line=dict(color=DARK_BG, width=1.5), # floating bar lines
406
  ),
407
  text=[f"{v:.1f}%" for v in uncertainty_vals],
408
  textposition="outside",
@@ -417,7 +375,6 @@ def uncertainty_analysis(modality_analysis: Dict) -> go.Figure:
417
  ),
418
  ))
419
 
420
- # Max-entropy reference
421
  fig.add_hline(
422
  y=100,
423
  line=dict(color=RED, width=1, dash="dot"),
@@ -437,31 +394,31 @@ def uncertainty_analysis(modality_analysis: Dict) -> go.Figure:
437
  **PLOTLY_LAYOUT,
438
  title=dict(
439
  text="Uncertainty Analysis (Shannon Entropy)",
440
- font=dict(size=13, color=TEXT_DIM),
441
  x=0,
442
  ),
443
  height=280,
444
  xaxis=dict(
445
  title="Modality",
446
- tickfont=dict(size=12),
 
447
  gridcolor=BORDER,
448
  ),
449
  yaxis=dict(
450
  title="Uncertainty (%)",
 
 
451
  range=[0, 120],
452
  gridcolor=BORDER,
453
  ticksuffix="%",
454
  ),
455
  bargap=0.38,
456
- plot_bgcolor="rgba(189,221,252,0.08)",
457
  )
458
  return fig
459
 
460
 
461
- # Comment Sentiment Timeline
462
-
463
  def sentiment_timeline(comments_df: pd.DataFrame, sentiments: List[Dict]) -> go.Figure:
464
- """Scatter: comment likes vs. sentiment compound score."""
465
  if comments_df.empty:
466
  return _empty_fig("Comment Sentiment Distribution")
467
 
@@ -502,25 +459,41 @@ def sentiment_timeline(comments_df: pd.DataFrame, sentiments: List[Dict]) -> go.
502
  fig.add_hline(y=0, line=dict(color=BORDER, width=1, dash="dot"))
503
  fig.update_layout(
504
  **PLOTLY_LAYOUT,
505
- title=dict(text="Comment Sentiment (size = likes)", font=dict(size=13, color=TEXT_DIM), x=0),
506
  height=320,
507
- xaxis=dict(title="Comment index", gridcolor=BORDER, showgrid=False),
508
- yaxis=dict(title="Compound score", gridcolor=BORDER, range=[-1.1, 1.1],
509
- showgrid=True, gridwidth=1),
510
- legend=dict(orientation="h", y=1.12, font=dict(size=11),
511
- bgcolor="rgba(255,255,227,0.8)", bordercolor=BORDER, borderwidth=1),
512
- plot_bgcolor="rgba(189,221,252,0.08)",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  )
514
  return fig
515
 
516
 
517
- # Positively Engagement vs Negatively Engagement Keyword Comparison ─
518
-
519
  def keyword_comparison(
520
  pos_kw: List[Tuple[str, float]],
521
  neg_kw: List[Tuple[str, float]],
522
  ) -> go.Figure:
523
- """Diverging bar chart: Positive keywords right, negative left."""
524
  if not pos_kw and not neg_kw:
525
  return _empty_fig("Sentiment Keywords")
526
 
@@ -540,11 +513,11 @@ def keyword_comparison(
540
  orientation="h",
541
  marker=dict(
542
  color=POS_COLOR,
543
- line=dict(color=DARK_BG, width=1.5), # floating bar lines
544
  ),
545
  text=[f"{v/max_p*100:.0f}" for v in pv],
546
  textposition="outside",
547
- textfont=dict(size=10, color=TEXT_DIM),
548
  hovertemplate="<b>%{y}</b><br>Score: %{x:.1f}<extra></extra>",
549
  ))
550
 
@@ -558,35 +531,54 @@ def keyword_comparison(
558
  orientation="h",
559
  marker=dict(
560
  color=NEG_COLOR,
561
- line=dict(color=DARK_BG, width=1.5), # floating bar lines
562
  ),
563
  text=[f"{v/max_n*100:.0f}" for v in nv],
564
  textposition="outside",
565
- textfont=dict(size=10, color=TEXT_DIM),
566
  hovertemplate="<b>%{y}</b><br>Score: %{x:.1f}<extra></extra>",
567
  ))
568
 
569
  fig.update_layout(
570
  **PLOTLY_LAYOUT,
571
- title=dict(text="Sentiment-Weighted Keywords", font=dict(size=13, color=TEXT_DIM), x=0),
572
  height=360,
573
  barmode="overlay",
574
- xaxis=dict(title="← Negatively Engagement | Positively Engagement →", gridcolor=BORDER,
575
- zeroline=True, zerolinecolor=BORDER, zerolinewidth=2),
576
- yaxis=dict(tickfont=dict(size=10)),
577
- legend=dict(orientation="h", y=1.1, bgcolor="rgba(255,255,227,0.8)",
578
- bordercolor=BORDER, borderwidth=1),
579
- plot_bgcolor="rgba(189,221,252,0.08)",
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  )
581
  return fig
582
 
583
 
584
- # Helpers ─
585
-
586
  def _empty_fig(title: str) -> go.Figure:
587
  fig = go.Figure()
588
- fig.add_annotation(text="No data available", x=0.5, y=0.5, showarrow=False,
589
- font=dict(size=14, color=TEXT_DIM))
590
- fig.update_layout(**PLOTLY_LAYOUT, title=dict(text=title, x=0), height=250,
591
- plot_bgcolor="rgba(189,221,252,0.08)")
 
 
 
 
 
 
 
 
592
  return fig
 
 
 
 
 
1
  from typing import Dict, List, Tuple, Optional
2
  import plotly.graph_objects as go
3
  import plotly.express as px
4
  import pandas as pd
5
  import numpy as np
6
 
7
+ DARK_BG = "#FFFFE3"
8
+ CARD_BG = "#FFFFFF"
9
+ BORDER = "#BDDDFC"
10
+ TEXT_MAIN = "#2d2d2d"
11
+ TEXT_DIM = "#555555"
12
+ PRIMARY = "#269ccc"
13
+ CYAN = "#269ccc"
14
+
15
+ POS_COLOR = "#88BDF2"
16
+ NEG_COLOR = "#6A89A7"
17
+ NEU_COLOR = "#CBCBCB"
18
+
 
 
 
19
  ALGAE = "#9ed2c5"
20
  GREEN = "#16a34a"
21
  RED = "#dc2626"
 
23
  PURPLE = "#7c3aed"
24
  BLUE = "#2563eb"
25
 
26
+ _FONT = dict(family="'DM Sans', 'Nunito', sans-serif", color=TEXT_MAIN, size=12)
27
+ _TITLE_FONT = dict(family="'DM Sans', sans-serif", color=TEXT_MAIN, size=13)
28
+ _TICK_FONT = dict(family="'DM Sans', sans-serif", color=TEXT_MAIN, size=11)
29
+ _LEGEND_FONT= dict(family="'DM Sans', sans-serif", color=TEXT_MAIN, size=11)
30
+
31
  PLOTLY_LAYOUT = dict(
32
+ paper_bgcolor="rgba(255,255,227,0)",
33
+ plot_bgcolor="rgba(189,221,252,0.13)",
34
+ font=_FONT,
35
+ margin=dict(l=20, r=20, t=44, b=20),
36
  )
37
 
38
 
 
 
39
  def misinfo_gauge(score: float, label: str) -> go.Figure:
 
40
  pct = score * 100
41
  if score < 0.35:
42
  bar_color = GREEN
 
50
  value=pct,
51
  number={"suffix": "%", "font": {"size": 32, "color": bar_color, "family": "'Nunito', sans-serif"}},
52
  delta={"reference": 50, "increasing": {"color": RED}, "decreasing": {"color": GREEN}},
53
+ title={"text": label, "font": {"size": 13, "color": TEXT_MAIN}},
54
  gauge={
55
  "axis": {
56
  "range": [0, 100],
57
  "tickwidth": 1,
58
  "tickcolor": BORDER,
59
+ "tickfont": {"color": TEXT_MAIN, "size": 10},
60
  },
61
  "bar": {"color": bar_color, "thickness": 0.3},
62
  "bgcolor": CARD_BG,
 
77
  return fig
78
 
79
 
 
 
80
  def sentiment_donut(summary: Dict) -> go.Figure:
 
81
  labels = ["Positively Engagement", "Neutral", "Negatively Engagement"]
82
  values = [summary["Positively Engagement"], summary["Neutral"], summary["Negatively Engagement"]]
83
  colors = [POS_COLOR, NEU_COLOR, NEG_COLOR]
 
93
  rotation=90,
94
  ))
95
 
 
96
  avg = summary.get("avg_compound", 0)
97
  overall = "😊 Positively Engaged" if avg > 0.05 else ("😟 Negatively Engaged" if avg < -0.05 else "😐 Mixed")
98
  fig.add_annotation(
 
102
  font=dict(size=13, color=TEXT_MAIN, family="'DM Sans', sans-serif"),
103
  align="center",
104
  )
105
+ fig.update_layout(
106
+ **PLOTLY_LAYOUT,
107
+ height=300,
108
+ legend=dict(orientation="h", y=-0.08, font=_LEGEND_FONT),
109
+ title=dict(text="Sentiment Breakdown", font=_TITLE_FONT, x=0),
110
+ )
111
  return fig
112
 
113
 
 
 
114
  def keyword_bar(
115
  keywords: List[Tuple[str, float]],
116
  title: str = "Top Keywords",
 
120
  return _empty_fig(title)
121
 
122
  words, weights = zip(*keywords[:15])
 
123
  max_w = max(weights) or 1
124
  norm = [w / max_w * 100 for w in weights]
125
 
 
130
  marker=dict(
131
  color=norm,
132
  colorscale=[[0, f"{PRIMARY}33"], [1, PRIMARY]],
133
+ line=dict(color=DARK_BG, width=1.5),
134
  ),
135
  text=[f"{w:.0f}" for w in weights],
136
  textposition="inside",
 
139
  ))
140
  fig.update_layout(
141
  **PLOTLY_LAYOUT,
142
+ title=dict(text=title, font=_TITLE_FONT, x=0),
143
  height=380,
144
+ yaxis=dict(
145
+ autorange="reversed",
146
+ tickfont=_TICK_FONT,
147
+ title_font=_TITLE_FONT,
148
+ gridcolor=BORDER,
149
+ showgrid=True,
150
+ gridwidth=1,
151
+ ),
152
+ xaxis=dict(
153
+ showticklabels=False,
154
+ gridcolor=BORDER,
155
+ showgrid=False,
156
+ ),
157
  bargap=0.3,
158
+ plot_bgcolor="rgba(189,221,252,0.13)",
159
  )
160
  return fig
161
 
162
 
 
 
163
  def stream_trust_bars(stream_details: Dict) -> go.Figure:
 
164
  labels = list(stream_details.keys())
165
  values = [round(v * 100, 1) for v in stream_details.values()]
166
  colors = [RED if v > 50 else (AMBER if v > 30 else GREEN) for v in values]
 
177
  ))
178
  fig.update_layout(
179
  **PLOTLY_LAYOUT,
180
+ title=dict(text="Per-Stream Analysis", font=_TITLE_FONT, x=0),
181
  height=220,
182
+ xaxis=dict(range=[0, 110], showticklabels=False, gridcolor=BORDER, tickfont=_TICK_FONT),
183
+ yaxis=dict(tickfont=_TICK_FONT),
184
  bargap=0.4,
185
  )
186
  return fig
187
 
188
 
 
 
189
  def modality_misinfo_distribution(modality_analysis: Dict) -> go.Figure:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  MODALITIES = ["Text", "Audio", "Video"]
191
  KEYS = ["text", "audio", "video"]
192
 
 
207
  marker=dict(
208
  color=[RED, RED, RED],
209
  opacity=0.88,
210
+ line=dict(color=DARK_BG, width=1.5),
211
  ),
212
  text=[f"{v:.1f}%" for v in misinfo_pcts],
213
  textposition="outside",
 
227
  marker=dict(
228
  color=[GREEN, GREEN, GREEN],
229
  opacity=0.88,
230
+ line=dict(color=DARK_BG, width=1.5),
231
  ),
232
  text=[f"{v:.1f}%" for v in credible_pcts],
233
  textposition="outside",
 
244
  **PLOTLY_LAYOUT,
245
  title=dict(
246
  text="Modality Misinformation Distribution",
247
+ font=_TITLE_FONT,
248
  x=0,
249
  ),
250
  barmode="group",
251
  height=280,
252
  xaxis=dict(
253
  title="Modality",
254
+ title_font=_TITLE_FONT,
255
+ tickfont=_TICK_FONT,
256
  gridcolor=BORDER,
257
  ),
258
  yaxis=dict(
259
  title="Softmax Score (%)",
260
+ title_font=_TITLE_FONT,
261
+ tickfont=_TICK_FONT,
262
  range=[0, 115],
263
  gridcolor=BORDER,
264
  ticksuffix="%",
 
266
  legend=dict(
267
  orientation="h",
268
  y=1.12,
269
+ font=_LEGEND_FONT,
270
  bgcolor="rgba(255,255,227,0)",
271
  ),
272
  bargap=0.22,
273
  bargroupgap=0.06,
274
+ plot_bgcolor="rgba(189,221,252,0.13)",
275
  )
276
  return fig
277
 
278
 
 
 
279
  def trust_score_by_modality(modality_analysis: Dict) -> go.Figure:
 
 
 
 
 
 
 
 
 
 
 
280
  MODALITIES = ["Text", "Audio", "Video"]
281
  KEYS = ["text", "audio", "video"]
282
 
 
292
  marker=dict(
293
  color=bar_colors,
294
  opacity=0.88,
295
+ line=dict(color=DARK_BG, width=1.5),
296
  ),
297
  text=[f"{v:.1f}%" for v in trust_vals],
298
  textposition="outside",
 
305
  ),
306
  ))
307
 
 
308
  for level, label, color in [(80, "High Trust", GREEN), (50, "Threshold", AMBER)]:
309
  fig.add_hline(
310
  y=level,
 
318
  **PLOTLY_LAYOUT,
319
  title=dict(
320
  text="Trust Score by Modality",
321
+ font=_TITLE_FONT,
322
  x=0,
323
  ),
324
  height=280,
325
  xaxis=dict(
326
  title="Modality",
327
+ title_font=_TITLE_FONT,
328
+ tickfont=_TICK_FONT,
329
  gridcolor=BORDER,
330
  ),
331
  yaxis=dict(
332
  title="Trust Level (%)",
333
+ title_font=_TITLE_FONT,
334
+ tickfont=_TICK_FONT,
335
  range=[0, 115],
336
  gridcolor=BORDER,
337
  ticksuffix="%",
338
  ),
339
  bargap=0.38,
340
+ plot_bgcolor="rgba(189,221,252,0.13)",
341
  )
342
  return fig
343
 
344
 
 
 
345
  def uncertainty_analysis(modality_analysis: Dict) -> go.Figure:
 
 
 
 
 
 
 
 
 
 
 
 
346
  MODALITIES = ["Text", "Audio", "Video"]
347
  KEYS = ["text", "audio", "video"]
348
 
349
  uncertainty_vals = [modality_analysis.get(k, {}).get("uncertainty", 100.0) for k in KEYS]
350
  misinfo_pcts = [modality_analysis.get(k, {}).get("misinfo_pct", 50.0) for k in KEYS]
351
 
 
352
  bar_colors = [
353
  (GREEN if v <= 35 else (AMBER if v <= 65 else RED))
354
  for v in uncertainty_vals
 
360
  marker=dict(
361
  color=bar_colors,
362
  opacity=0.88,
363
+ line=dict(color=DARK_BG, width=1.5),
364
  ),
365
  text=[f"{v:.1f}%" for v in uncertainty_vals],
366
  textposition="outside",
 
375
  ),
376
  ))
377
 
 
378
  fig.add_hline(
379
  y=100,
380
  line=dict(color=RED, width=1, dash="dot"),
 
394
  **PLOTLY_LAYOUT,
395
  title=dict(
396
  text="Uncertainty Analysis (Shannon Entropy)",
397
+ font=_TITLE_FONT,
398
  x=0,
399
  ),
400
  height=280,
401
  xaxis=dict(
402
  title="Modality",
403
+ title_font=_TITLE_FONT,
404
+ tickfont=_TICK_FONT,
405
  gridcolor=BORDER,
406
  ),
407
  yaxis=dict(
408
  title="Uncertainty (%)",
409
+ title_font=_TITLE_FONT,
410
+ tickfont=_TICK_FONT,
411
  range=[0, 120],
412
  gridcolor=BORDER,
413
  ticksuffix="%",
414
  ),
415
  bargap=0.38,
416
+ plot_bgcolor="rgba(189,221,252,0.13)",
417
  )
418
  return fig
419
 
420
 
 
 
421
  def sentiment_timeline(comments_df: pd.DataFrame, sentiments: List[Dict]) -> go.Figure:
 
422
  if comments_df.empty:
423
  return _empty_fig("Comment Sentiment Distribution")
424
 
 
459
  fig.add_hline(y=0, line=dict(color=BORDER, width=1, dash="dot"))
460
  fig.update_layout(
461
  **PLOTLY_LAYOUT,
462
+ title=dict(text="Comment Sentiment (size = likes)", font=_TITLE_FONT, x=0),
463
  height=320,
464
+ xaxis=dict(
465
+ title="Comment index",
466
+ title_font=_TITLE_FONT,
467
+ tickfont=_TICK_FONT,
468
+ gridcolor=BORDER,
469
+ showgrid=False,
470
+ ),
471
+ yaxis=dict(
472
+ title="Compound score",
473
+ title_font=_TITLE_FONT,
474
+ tickfont=_TICK_FONT,
475
+ gridcolor=BORDER,
476
+ range=[-1.1, 1.1],
477
+ showgrid=True,
478
+ gridwidth=1,
479
+ ),
480
+ legend=dict(
481
+ orientation="h",
482
+ y=1.12,
483
+ font=_LEGEND_FONT,
484
+ bgcolor="rgba(255,255,255,0.85)",
485
+ bordercolor=BORDER,
486
+ borderwidth=1,
487
+ ),
488
+ plot_bgcolor="rgba(189,221,252,0.13)",
489
  )
490
  return fig
491
 
492
 
 
 
493
  def keyword_comparison(
494
  pos_kw: List[Tuple[str, float]],
495
  neg_kw: List[Tuple[str, float]],
496
  ) -> go.Figure:
 
497
  if not pos_kw and not neg_kw:
498
  return _empty_fig("Sentiment Keywords")
499
 
 
513
  orientation="h",
514
  marker=dict(
515
  color=POS_COLOR,
516
+ line=dict(color=DARK_BG, width=1.5),
517
  ),
518
  text=[f"{v/max_p*100:.0f}" for v in pv],
519
  textposition="outside",
520
+ textfont=dict(size=10, color=TEXT_MAIN),
521
  hovertemplate="<b>%{y}</b><br>Score: %{x:.1f}<extra></extra>",
522
  ))
523
 
 
531
  orientation="h",
532
  marker=dict(
533
  color=NEG_COLOR,
534
+ line=dict(color=DARK_BG, width=1.5),
535
  ),
536
  text=[f"{v/max_n*100:.0f}" for v in nv],
537
  textposition="outside",
538
+ textfont=dict(size=10, color=TEXT_MAIN),
539
  hovertemplate="<b>%{y}</b><br>Score: %{x:.1f}<extra></extra>",
540
  ))
541
 
542
  fig.update_layout(
543
  **PLOTLY_LAYOUT,
544
+ title=dict(text="Sentiment-Weighted Keywords", font=_TITLE_FONT, x=0),
545
  height=360,
546
  barmode="overlay",
547
+ xaxis=dict(
548
+ title="← Negatively Engagement | Positively Engagement →",
549
+ title_font=_TITLE_FONT,
550
+ tickfont=_TICK_FONT,
551
+ gridcolor=BORDER,
552
+ zeroline=True,
553
+ zerolinecolor=BORDER,
554
+ zerolinewidth=2,
555
+ ),
556
+ yaxis=dict(tickfont=_TICK_FONT),
557
+ legend=dict(
558
+ orientation="h",
559
+ y=1.1,
560
+ font=_LEGEND_FONT,
561
+ bgcolor="rgba(255,255,255,0.85)",
562
+ bordercolor=BORDER,
563
+ borderwidth=1,
564
+ ),
565
+ plot_bgcolor="rgba(189,221,252,0.13)",
566
  )
567
  return fig
568
 
569
 
 
 
570
  def _empty_fig(title: str) -> go.Figure:
571
  fig = go.Figure()
572
+ fig.add_annotation(
573
+ text="No data available",
574
+ x=0.5, y=0.5,
575
+ showarrow=False,
576
+ font=dict(size=14, color=TEXT_MAIN),
577
+ )
578
+ fig.update_layout(
579
+ **PLOTLY_LAYOUT,
580
+ title=dict(text=title, font=_TITLE_FONT, x=0),
581
+ height=250,
582
+ plot_bgcolor="rgba(189,221,252,0.13)",
583
+ )
584
  return fig