705 lines
18 KiB
HTML
705 lines
18 KiB
HTML
|
|
{% extends "base.html" %}
|
||
|
|
|
||
|
|
{% block title %}Toxicity Analysis Dashboard{% endblock %}
|
||
|
|
|
||
|
|
{% block extra_css %}
|
||
|
|
<style>
|
||
|
|
/* Color scheme */
|
||
|
|
:root {
|
||
|
|
--bg-primary: #1a1a2e;
|
||
|
|
--bg-secondary: #16213e;
|
||
|
|
--nav-bg: #0f3460;
|
||
|
|
--text-primary: #e0e0e0;
|
||
|
|
--text-secondary: #b0b0b0;
|
||
|
|
--accent: #00b4d8;
|
||
|
|
--danger: #e74c3c;
|
||
|
|
--warning: #f39c12;
|
||
|
|
--success: #27ae60;
|
||
|
|
--category-1: #00b4d8;
|
||
|
|
--category-2: #e67e22;
|
||
|
|
--category-3: #9b59b6;
|
||
|
|
--category-4: #1abc9c;
|
||
|
|
--category-5: #e74c3c;
|
||
|
|
--category-6: #f39c12;
|
||
|
|
--category-7: #3498db;
|
||
|
|
--category-8: #2ecc71;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Layout */
|
||
|
|
.page-header {
|
||
|
|
margin-bottom: 2rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.page-header h1 {
|
||
|
|
font-size: 2.5rem;
|
||
|
|
font-weight: 700;
|
||
|
|
color: var(--text-primary);
|
||
|
|
margin-bottom: 0.5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.page-header .subtitle {
|
||
|
|
font-size: 1rem;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Grid layout */
|
||
|
|
.stats-grid {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||
|
|
gap: 1.5rem;
|
||
|
|
margin-bottom: 2rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Stat cards */
|
||
|
|
.stat-card {
|
||
|
|
background-color: var(--bg-secondary);
|
||
|
|
border-radius: 8px;
|
||
|
|
padding: 1.5rem;
|
||
|
|
border-left: 4px solid var(--accent);
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
justify-content: space-between;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stat-card.danger {
|
||
|
|
border-left-color: var(--danger);
|
||
|
|
}
|
||
|
|
|
||
|
|
.stat-card.warning {
|
||
|
|
border-left-color: var(--warning);
|
||
|
|
}
|
||
|
|
|
||
|
|
.stat-card-label {
|
||
|
|
font-size: 0.875rem;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
text-transform: uppercase;
|
||
|
|
letter-spacing: 0.5px;
|
||
|
|
margin-bottom: 0.75rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stat-card-value {
|
||
|
|
font-size: 1.75rem;
|
||
|
|
font-weight: 700;
|
||
|
|
color: var(--text-primary);
|
||
|
|
margin-bottom: 0.5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stat-card-detail {
|
||
|
|
font-size: 0.875rem;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Percentage bar */
|
||
|
|
.percentage-bar {
|
||
|
|
width: 100%;
|
||
|
|
height: 8px;
|
||
|
|
background-color: rgba(224, 224, 224, 0.1);
|
||
|
|
border-radius: 4px;
|
||
|
|
overflow: hidden;
|
||
|
|
margin-top: 0.75rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.percentage-bar-fill {
|
||
|
|
height: 100%;
|
||
|
|
background-color: var(--accent);
|
||
|
|
border-radius: 4px;
|
||
|
|
transition: width 0.3s ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stat-card.danger .percentage-bar-fill {
|
||
|
|
background-color: var(--danger);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Cards */
|
||
|
|
.card {
|
||
|
|
background-color: var(--bg-secondary);
|
||
|
|
border-radius: 8px;
|
||
|
|
padding: 1.5rem;
|
||
|
|
margin-bottom: 2rem;
|
||
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-title {
|
||
|
|
font-size: 1.25rem;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--text-primary);
|
||
|
|
margin-bottom: 1.5rem;
|
||
|
|
border-bottom: 2px solid rgba(0, 180, 216, 0.2);
|
||
|
|
padding-bottom: 0.75rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Chart containers */
|
||
|
|
.chart-container {
|
||
|
|
position: relative;
|
||
|
|
height: 400px;
|
||
|
|
width: 100%;
|
||
|
|
}
|
||
|
|
|
||
|
|
.chart-container.horizontal {
|
||
|
|
height: 300px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Links section */
|
||
|
|
.quick-links {
|
||
|
|
display: flex;
|
||
|
|
gap: 1rem;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
}
|
||
|
|
|
||
|
|
.quick-link {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
padding: 0.75rem 1.25rem;
|
||
|
|
background-color: rgba(0, 180, 216, 0.1);
|
||
|
|
border: 1px solid var(--accent);
|
||
|
|
border-radius: 6px;
|
||
|
|
color: var(--accent);
|
||
|
|
text-decoration: none;
|
||
|
|
font-weight: 500;
|
||
|
|
transition: all 0.3s ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.quick-link:hover {
|
||
|
|
background-color: var(--accent);
|
||
|
|
color: var(--bg-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.quick-link::after {
|
||
|
|
content: " →";
|
||
|
|
margin-left: 0.5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Runs table */
|
||
|
|
.runs-table {
|
||
|
|
width: 100%;
|
||
|
|
border-collapse: collapse;
|
||
|
|
}
|
||
|
|
|
||
|
|
.runs-table thead {
|
||
|
|
background-color: rgba(0, 180, 216, 0.1);
|
||
|
|
border-bottom: 2px solid var(--accent);
|
||
|
|
}
|
||
|
|
|
||
|
|
.runs-table th {
|
||
|
|
padding: 1rem;
|
||
|
|
text-align: left;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--text-primary);
|
||
|
|
font-size: 0.875rem;
|
||
|
|
text-transform: uppercase;
|
||
|
|
letter-spacing: 0.5px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.runs-table td {
|
||
|
|
padding: 0.75rem 1rem;
|
||
|
|
border-bottom: 1px solid rgba(224, 224, 224, 0.1);
|
||
|
|
color: var(--text-secondary);
|
||
|
|
font-size: 0.875rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.runs-table tbody tr:hover {
|
||
|
|
background-color: rgba(0, 180, 216, 0.05);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Status badge */
|
||
|
|
.status-badge {
|
||
|
|
display: inline-block;
|
||
|
|
padding: 0.35rem 0.75rem;
|
||
|
|
border-radius: 4px;
|
||
|
|
font-size: 0.75rem;
|
||
|
|
font-weight: 600;
|
||
|
|
text-transform: uppercase;
|
||
|
|
letter-spacing: 0.5px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-badge.completed {
|
||
|
|
background-color: rgba(39, 174, 96, 0.2);
|
||
|
|
color: var(--success);
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-badge.in-progress {
|
||
|
|
background-color: rgba(243, 156, 18, 0.2);
|
||
|
|
color: var(--warning);
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-badge.failed {
|
||
|
|
background-color: rgba(231, 76, 60, 0.2);
|
||
|
|
color: var(--danger);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Responsive */
|
||
|
|
@media (max-width: 768px) {
|
||
|
|
.page-header h1 {
|
||
|
|
font-size: 1.75rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stats-grid {
|
||
|
|
grid-template-columns: 1fr;
|
||
|
|
}
|
||
|
|
|
||
|
|
.chart-container {
|
||
|
|
height: 300px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.quick-links {
|
||
|
|
flex-direction: column;
|
||
|
|
}
|
||
|
|
|
||
|
|
.quick-link {
|
||
|
|
width: 100%;
|
||
|
|
justify-content: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.runs-table th,
|
||
|
|
.runs-table td {
|
||
|
|
padding: 0.5rem 0.75rem;
|
||
|
|
font-size: 0.75rem;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Empty state */
|
||
|
|
.empty-state {
|
||
|
|
text-align: center;
|
||
|
|
padding: 2rem;
|
||
|
|
color: var(--text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.empty-state p {
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Number formatting */
|
||
|
|
.number-highlight {
|
||
|
|
color: var(--accent);
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
.percentage-highlight {
|
||
|
|
color: var(--warning);
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
.percentage-highlight.high {
|
||
|
|
color: var(--danger);
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
{% endblock %}
|
||
|
|
|
||
|
|
{% block content %}
|
||
|
|
<div class="page-header">
|
||
|
|
<h1>Toxicity Analysis</h1>
|
||
|
|
<p class="subtitle">
|
||
|
|
{{ stats.total_scored_posts | format_number }} / {{ stats.total_posts | format_number }} posts scored
|
||
|
|
<span style="margin: 0 0.5rem;">•</span>
|
||
|
|
{{ stats.total_scored_mentions | format_number }} / {{ stats.total_mentions | format_number }} mentions scored
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Stats Grid -->
|
||
|
|
<div class="stats-grid">
|
||
|
|
<!-- Total Scored Card -->
|
||
|
|
<div class="stat-card">
|
||
|
|
<div>
|
||
|
|
<div class="stat-card-label">Total Scored</div>
|
||
|
|
<div class="stat-card-value">{{ (stats.total_scored_posts + stats.total_scored_mentions) | format_number }}</div>
|
||
|
|
</div>
|
||
|
|
<div class="stat-card-detail">
|
||
|
|
{{ stats.total_scored_posts | format_number }} posts
|
||
|
|
<span style="color: var(--text-secondary); margin: 0 0.25rem;">+</span>
|
||
|
|
{{ stats.total_scored_mentions | format_number }} mentions
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Flagged Posts Card -->
|
||
|
|
<div class="stat-card {% if stats.flagged_posts > 0 and (stats.flagged_posts / (stats.total_scored_posts or 1)) > 0.05 %}danger{% endif %}">
|
||
|
|
<div>
|
||
|
|
<div class="stat-card-label">Flagged Posts</div>
|
||
|
|
<div class="stat-card-value">{{ stats.flagged_posts | format_number }}</div>
|
||
|
|
</div>
|
||
|
|
<div class="stat-card-detail">
|
||
|
|
<span class="{% if stats.flagged_posts > 0 and (stats.flagged_posts / (stats.total_scored_posts or 1)) > 0.05 %}percentage-highlight high{% else %}percentage-highlight{% endif %}">
|
||
|
|
{{ "%.2f" | format(100.0 * stats.flagged_posts / (stats.total_scored_posts or 1)) }}%
|
||
|
|
</span>
|
||
|
|
of scored posts
|
||
|
|
</div>
|
||
|
|
<div class="percentage-bar">
|
||
|
|
<div class="percentage-bar-fill" style="width: {{ 100.0 * stats.flagged_posts / (stats.total_scored_posts or 1) }}%"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Flagged Mentions Card -->
|
||
|
|
<div class="stat-card">
|
||
|
|
<div>
|
||
|
|
<div class="stat-card-label">Flagged Mentions</div>
|
||
|
|
<div class="stat-card-value">{{ stats.flagged_mentions | format_number }}</div>
|
||
|
|
</div>
|
||
|
|
<div class="stat-card-detail">
|
||
|
|
<span class="percentage-highlight">
|
||
|
|
{{ "%.2f" | format(100.0 * stats.flagged_mentions / (stats.total_scored_mentions or 1)) }}%
|
||
|
|
</span>
|
||
|
|
of scored mentions
|
||
|
|
</div>
|
||
|
|
<div class="percentage-bar">
|
||
|
|
<div class="percentage-bar-fill" style="width: {{ 100.0 * stats.flagged_mentions / (stats.total_scored_mentions or 1) }}%"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Avg Toxicity Card -->
|
||
|
|
<div class="stat-card">
|
||
|
|
<div>
|
||
|
|
<div class="stat-card-label">Average Toxicity</div>
|
||
|
|
<div class="stat-card-value">{{ "%.1f" | format(100.0 * ((stats.avg_toxicity_posts + stats.avg_toxicity_mentions) / 2.0)) }}%</div>
|
||
|
|
</div>
|
||
|
|
<div class="stat-card-detail">
|
||
|
|
Posts: {{ "%.2f" | format(100.0 * stats.avg_toxicity_posts) }}%
|
||
|
|
<span style="color: var(--text-secondary); margin: 0 0.25rem;">•</span>
|
||
|
|
Mentions: {{ "%.2f" | format(100.0 * stats.avg_toxicity_mentions) }}%
|
||
|
|
</div>
|
||
|
|
<div class="percentage-bar">
|
||
|
|
<div class="percentage-bar-fill" style="width: {{ 100.0 * ((stats.avg_toxicity_posts + stats.avg_toxicity_mentions) / 2.0) }}%"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Trend Chart -->
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-title">Toxicity Trends Over Time</div>
|
||
|
|
<div class="chart-container">
|
||
|
|
<canvas id="trendChart"></canvas>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Category Breakdown -->
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-title">Toxicity by Category</div>
|
||
|
|
<div class="chart-container horizontal">
|
||
|
|
<canvas id="categoriesChart"></canvas>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Recent Analysis Runs -->
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-title">Recent Analysis Runs</div>
|
||
|
|
{% if runs %}
|
||
|
|
<div style="overflow-x: auto;">
|
||
|
|
<table class="runs-table">
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Started</th>
|
||
|
|
<th>Duration</th>
|
||
|
|
<th>Posts Scored</th>
|
||
|
|
<th>Mentions Scored</th>
|
||
|
|
<th>Errors</th>
|
||
|
|
<th>Cost</th>
|
||
|
|
<th>Status</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
{% for run in runs[:5] %}
|
||
|
|
<tr>
|
||
|
|
<td>{{ run.started_at | time_ago }}</td>
|
||
|
|
<td>{% if run.duration_secs is not none %}{{ "%.0f" | format(run.duration_secs | float) }}s{% else %}—{% endif %}</td>
|
||
|
|
<td>{{ run.posts_scored | format_number }}</td>
|
||
|
|
<td>{{ run.mentions_scored | format_number }}</td>
|
||
|
|
<td>{{ run.errors }}</td>
|
||
|
|
<td>${{ "%.4f" | format(run.cost_usd | default(0) | float) }}</td>
|
||
|
|
<td>
|
||
|
|
<span class="status-badge {% if run.status == 'completed' %}completed{% elif run.status == 'in_progress' %}in-progress{% elif run.status == 'failed' %}failed{% endif %}">
|
||
|
|
{{ run.status }}
|
||
|
|
</span>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
{% endfor %}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
{% else %}
|
||
|
|
<div class="empty-state">
|
||
|
|
<p>No analysis runs yet. Start a new analysis to see results here.</p>
|
||
|
|
</div>
|
||
|
|
{% endif %}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Quick Links -->
|
||
|
|
<div style="margin-top: 2rem;">
|
||
|
|
<div class="quick-links">
|
||
|
|
<a href="{{ url_for('analysis.flagged') }}" class="quick-link">View Flagged Content</a>
|
||
|
|
<a href="{{ url_for('analysis.accounts') }}" class="quick-link">Account Breakdown</a>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Chart.js Script -->
|
||
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.7/chart.umd.min.js"></script>
|
||
|
|
<script>
|
||
|
|
// Chart color scheme
|
||
|
|
const chartColors = {
|
||
|
|
accent: '#00b4d8',
|
||
|
|
orange: '#e67e22',
|
||
|
|
gridLine: '#2a2a3e',
|
||
|
|
text: '#e0e0e0',
|
||
|
|
categories: [
|
||
|
|
'#00b4d8', // 1
|
||
|
|
'#e67e22', // 2
|
||
|
|
'#9b59b6', // 3
|
||
|
|
'#1abc9c', // 4
|
||
|
|
'#e74c3c', // 5
|
||
|
|
'#f39c12', // 6
|
||
|
|
'#3498db', // 7
|
||
|
|
'#2ecc71' // 8
|
||
|
|
]
|
||
|
|
};
|
||
|
|
|
||
|
|
// Trend Chart
|
||
|
|
{% if trend_json %}
|
||
|
|
const trendData = {{ trend_json | safe }};
|
||
|
|
|
||
|
|
const trendCtx = document.getElementById('trendChart').getContext('2d');
|
||
|
|
const trendChart = new Chart(trendCtx, {
|
||
|
|
type: 'line',
|
||
|
|
data: {
|
||
|
|
labels: trendData.map(d => d.week),
|
||
|
|
datasets: [
|
||
|
|
{
|
||
|
|
label: 'Avg Post Toxicity',
|
||
|
|
data: trendData.map(d => d.avg_post_toxicity),
|
||
|
|
borderColor: chartColors.accent,
|
||
|
|
backgroundColor: 'rgba(0, 180, 216, 0.05)',
|
||
|
|
borderWidth: 2,
|
||
|
|
tension: 0.4,
|
||
|
|
fill: true,
|
||
|
|
yAxisID: 'y',
|
||
|
|
pointBackgroundColor: chartColors.accent,
|
||
|
|
pointBorderColor: '#1a1a2e',
|
||
|
|
pointBorderWidth: 2,
|
||
|
|
pointRadius: 5,
|
||
|
|
pointHoverRadius: 7
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: 'Avg Mention Toxicity',
|
||
|
|
data: trendData.map(d => d.avg_mention_toxicity),
|
||
|
|
borderColor: chartColors.orange,
|
||
|
|
backgroundColor: 'rgba(230, 126, 34, 0.05)',
|
||
|
|
borderWidth: 2,
|
||
|
|
tension: 0.4,
|
||
|
|
fill: true,
|
||
|
|
yAxisID: 'y',
|
||
|
|
pointBackgroundColor: chartColors.orange,
|
||
|
|
pointBorderColor: '#1a1a2e',
|
||
|
|
pointBorderWidth: 2,
|
||
|
|
pointRadius: 5,
|
||
|
|
pointHoverRadius: 7
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: 'Flagged Posts',
|
||
|
|
data: trendData.map(d => d.flagged_posts),
|
||
|
|
type: 'bar',
|
||
|
|
borderColor: 'rgba(231, 76, 60, 0.5)',
|
||
|
|
backgroundColor: 'rgba(231, 76, 60, 0.2)',
|
||
|
|
yAxisID: 'y1',
|
||
|
|
borderWidth: 1,
|
||
|
|
barThickness: 8,
|
||
|
|
categoryPercentage: 0.8,
|
||
|
|
maxBarThickness: 15
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: 'Flagged Mentions',
|
||
|
|
data: trendData.map(d => d.flagged_mentions),
|
||
|
|
type: 'bar',
|
||
|
|
borderColor: 'rgba(243, 156, 18, 0.5)',
|
||
|
|
backgroundColor: 'rgba(243, 156, 18, 0.2)',
|
||
|
|
yAxisID: 'y1',
|
||
|
|
borderWidth: 1,
|
||
|
|
barThickness: 8,
|
||
|
|
categoryPercentage: 0.8,
|
||
|
|
maxBarThickness: 15
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
options: {
|
||
|
|
responsive: true,
|
||
|
|
maintainAspectRatio: false,
|
||
|
|
interaction: {
|
||
|
|
mode: 'index',
|
||
|
|
intersect: false
|
||
|
|
},
|
||
|
|
plugins: {
|
||
|
|
legend: {
|
||
|
|
display: true,
|
||
|
|
labels: {
|
||
|
|
color: chartColors.text,
|
||
|
|
usePointStyle: true,
|
||
|
|
padding: 15,
|
||
|
|
font: {
|
||
|
|
size: 12
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
tooltip: {
|
||
|
|
backgroundColor: '#0f3460',
|
||
|
|
titleColor: chartColors.text,
|
||
|
|
bodyColor: chartColors.text,
|
||
|
|
borderColor: chartColors.accent,
|
||
|
|
borderWidth: 1,
|
||
|
|
padding: 10,
|
||
|
|
displayColors: true
|
||
|
|
}
|
||
|
|
},
|
||
|
|
scales: {
|
||
|
|
x: {
|
||
|
|
grid: {
|
||
|
|
color: chartColors.gridLine,
|
||
|
|
drawBorder: false
|
||
|
|
},
|
||
|
|
ticks: {
|
||
|
|
color: chartColors.text,
|
||
|
|
font: {
|
||
|
|
size: 11
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
y: {
|
||
|
|
type: 'linear',
|
||
|
|
display: true,
|
||
|
|
position: 'left',
|
||
|
|
min: 0,
|
||
|
|
max: 1,
|
||
|
|
ticks: {
|
||
|
|
color: chartColors.text,
|
||
|
|
font: {
|
||
|
|
size: 11
|
||
|
|
},
|
||
|
|
callback: function(value) {
|
||
|
|
return (value * 100).toFixed(0) + '%';
|
||
|
|
}
|
||
|
|
},
|
||
|
|
grid: {
|
||
|
|
color: chartColors.gridLine,
|
||
|
|
drawBorder: false
|
||
|
|
},
|
||
|
|
title: {
|
||
|
|
display: true,
|
||
|
|
text: 'Toxicity Score',
|
||
|
|
color: chartColors.text,
|
||
|
|
font: {
|
||
|
|
size: 12,
|
||
|
|
weight: 'bold'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
y1: {
|
||
|
|
type: 'linear',
|
||
|
|
display: true,
|
||
|
|
position: 'right',
|
||
|
|
grid: {
|
||
|
|
drawOnChartArea: false
|
||
|
|
},
|
||
|
|
ticks: {
|
||
|
|
color: chartColors.text,
|
||
|
|
font: {
|
||
|
|
size: 11
|
||
|
|
}
|
||
|
|
},
|
||
|
|
title: {
|
||
|
|
display: true,
|
||
|
|
text: 'Flagged Count',
|
||
|
|
color: chartColors.text,
|
||
|
|
font: {
|
||
|
|
size: 12,
|
||
|
|
weight: 'bold'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
// Category Chart
|
||
|
|
{% if categories_json %}
|
||
|
|
const categoriesData = {{ categories_json | safe }};
|
||
|
|
const categoryNames = {{ categories | tojson | safe }};
|
||
|
|
|
||
|
|
const categoriesCtx = document.getElementById('categoriesChart').getContext('2d');
|
||
|
|
const categoriesChart = new Chart(categoriesCtx, {
|
||
|
|
type: 'bar',
|
||
|
|
data: {
|
||
|
|
labels: categoryNames,
|
||
|
|
datasets: [
|
||
|
|
{
|
||
|
|
label: 'Average Toxicity Score',
|
||
|
|
data: categoryNames.map(cat => categoriesData[cat] || 0),
|
||
|
|
backgroundColor: chartColors.categories,
|
||
|
|
borderColor: chartColors.categories.map(c => c.replace('0.', '1.')),
|
||
|
|
borderWidth: 1.5,
|
||
|
|
borderRadius: 4
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
options: {
|
||
|
|
indexAxis: 'y',
|
||
|
|
responsive: true,
|
||
|
|
maintainAspectRatio: false,
|
||
|
|
plugins: {
|
||
|
|
legend: {
|
||
|
|
display: false
|
||
|
|
},
|
||
|
|
tooltip: {
|
||
|
|
backgroundColor: '#0f3460',
|
||
|
|
titleColor: chartColors.text,
|
||
|
|
bodyColor: chartColors.text,
|
||
|
|
borderColor: chartColors.accent,
|
||
|
|
borderWidth: 1,
|
||
|
|
padding: 10,
|
||
|
|
callbacks: {
|
||
|
|
label: function(context) {
|
||
|
|
return 'Score: ' + (context.parsed.x * 100).toFixed(2) + '%';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
scales: {
|
||
|
|
x: {
|
||
|
|
min: 0,
|
||
|
|
max: 1,
|
||
|
|
grid: {
|
||
|
|
color: chartColors.gridLine,
|
||
|
|
drawBorder: false
|
||
|
|
},
|
||
|
|
ticks: {
|
||
|
|
color: chartColors.text,
|
||
|
|
font: {
|
||
|
|
size: 11
|
||
|
|
},
|
||
|
|
callback: function(value) {
|
||
|
|
return (value * 100).toFixed(0) + '%';
|
||
|
|
}
|
||
|
|
},
|
||
|
|
title: {
|
||
|
|
display: true,
|
||
|
|
text: 'Average Toxicity',
|
||
|
|
color: chartColors.text,
|
||
|
|
font: {
|
||
|
|
size: 12,
|
||
|
|
weight: 'bold'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
y: {
|
||
|
|
grid: {
|
||
|
|
drawOnChartArea: false,
|
||
|
|
drawBorder: false
|
||
|
|
},
|
||
|
|
ticks: {
|
||
|
|
color: chartColors.text,
|
||
|
|
font: {
|
||
|
|
size: 11
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
{% endif %}
|
||
|
|
</script>
|
||
|
|
{% endblock %}
|