Bonjour,
Pour ceux qui souhaiteraient un widget souple pour visualiser graphiquement des données de colonnes différentes (texte, choix unique, choix multiple, numérique), par exemple dans le cadre d’une exploitation d’un résultat de sondage.
Ce code ne demande qu’à être optimisé mais il a le mérite d’exister !
Pour le mettre en place, cela nécessite de le lier avec la table que vous souhaitez visualiser.
Pour chaque donnée vous pouvez choisir le type de graphique le plus adapté dans l’onglet « type ».
Vous pouvez également choisir de masquer certaines colonnes que vous ne souhaitez pas voir.
Vous pouvez changer les couleurs des données visualisées (version bêta)
Il fait appel à un certain nombre de bibliothèques externes listées en en-tête de script
Pour des questions de limitations du forum le code est publié en 2 parties:
Widget:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Visualisation Graphique</title>
<script src="https://docs.getgrist.com/grist-plugin-api.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Marianne:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #2563eb;
--primary-hover: #1d4ed8;
--primary-dark: #1e40af;
--success: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--background: #ffffff;
--card-bg: #ffffff;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--grey-50: #f8fafc;
--grey-100: #f1f5f9;
--grey-200: #e2e8f0;
--grey-300: #cbd5e1;
--grey-400: #94a3b8;
--grey-500: #64748b;
--grey-600: #475569;
--grey-700: #334155;
--grey-800: #1e293b;
--grey-900: #0f172a;
--radius: 6px;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
--toggle-off: #e2e8f0;
--toggle-on: #2563eb;
}
[data-theme="dark"] {
--background: #0f172a;
--card-bg: #1e293b;
--text: #f8fafc;
--text-muted: #94a3b8;
--border: #334155;
--grey-50: #1e293b;
--grey-100: #334155;
--grey-200: #475569;
--primary: #3b82f6;
--primary-hover: #2563eb;
--toggle-off: #475569;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
transition: background 0.2s, color 0.2s, border-color 0.2s;
}
body {
font-family: "Marianne", system-ui, -apple-system, sans-serif;
background: var(--background);
color: var(--text);
padding: 1rem;
line-height: 1.5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border);
}
.title {
font-size: 1.25rem;
font-weight: 700;
color: var(--primary);
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 60%;
flex: 0 1 auto;
}
.header-actions {
display: flex;
gap: 0.5rem;
align-items: center;
}
.btn {
background: var(--grey-50);
border: 1px solid var(--border);
padding: 0.375rem 0.75rem;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
border-radius: var(--radius);
transition: all 0.2s;
color: var(--text);
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.25rem;
}
.btn:hover {
background: var(--grey-200);
}
.btn-primary {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.btn-primary:hover {
background: var(--primary-hover);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.info-text {
font-size: 0.875rem;
color: var(--text-muted);
margin-left: 0.75rem;
}
.navigation {
display: flex;
padding: 1rem 1.5rem;
gap: 1rem;
border-bottom: 1px solid var(--border);
align-items: center;
flex-wrap: wrap;
}
.nav-container {
display: flex;
align-items: center;
gap: 1rem;
flex: 1;
min-width: 0;
flex-wrap: wrap;
}
.nav-controls {
display: flex;
gap: 0.5rem;
flex-shrink: 0;
}
.column-title {
font-size: 1.125rem;
font-weight: 600;
color: var(--primary);
flex: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.controls-bar {
display: flex;
gap: 1rem;
padding: 0.75rem 1.5rem;
border-bottom: 1px solid var(--border);
align-items: center;
flex-wrap: wrap;
}
.control-group {
display: flex;
align-items: center;
gap: 0.75rem;
}
.select {
background: var(--card-bg);
border: 1px solid var(--border);
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
color: var(--text);
cursor: pointer;
border-radius: var(--radius);
min-width: 120px;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2364748b'%3e%3cpath d='M7 10l5 5 5-5z'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 0.5rem center;
background-size: 1em;
padding-right: 2rem;
}
.toggle-wrapper {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
margin-right: 1rem;
}
.toggle {
position: relative;
width: 40px;
height: 20px;
background: var(--toggle-off);
border-radius: 20px;
cursor: pointer;
appearance: none;
-webkit-appearance: none;
}
.toggle::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
background: white;
border-radius: 50%;
transition: transform 0.2s;
}
.toggle:checked {
background: var(--toggle-on);
}
.toggle:checked::after {
transform: translateX(20px);
}
.toggle-label {
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
}
.content {
padding: 1.5rem;
min-height: 500px;
}
.chart-container {
position: relative;
width: 100%;
min-height: 450px;
}
canvas {
max-height: 500px !important;
}
.multiple-choice-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
}
.choice-card {
background: var(--card-bg);
border: 1px solid var(--border);
padding: 1rem;
border-radius: var(--radius);
transition: border-color 0.2s, transform 0.1s;
}
.choice-card:hover {
border-color: var(--primary);
transform: translateY(-2px);
}
.choice-label {
font-size: 0.9rem;
font-weight: 500;
margin-bottom: 0.5rem;
color: var(--text);
}
.choice-bar-container {
background: var(--grey-100);
height: 1.5rem;
position: relative;
border-radius: 1rem;
overflow: hidden;
}
.choice-bar {
height: 100%;
background: var(--primary);
transition: width 0.6s ease;
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 0.75rem;
border-radius: 1rem;
}
.choice-count {
color: white;
font-weight: 600;
font-size: 0.8rem;
}
.choice-percentage {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
font-size: 0.8rem;
font-weight: 600;
color: white;
}
.wordcloud {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 0.75rem;
padding: 1.5rem;
min-height: 400px;
}
.word {
display: inline-block;
padding: 0.5rem 1rem;
background: var(--primary);
color: white;
font-weight: 500;
border-radius: 1rem;
transition: transform 0.2s;
}
.word:hover {
transform: scale(1.05);
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1rem;
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid var(--border);
}
.stat-card {
background: var(--primary);
color: white;
padding: 1.25rem;
text-align: center;
border-radius: var(--radius);
}
.stat-label {
font-size: 0.8rem;
opacity: 0.9;
margin-bottom: 0.5rem;
}
.stat-value {
font-size: 1.75rem;
font-weight: 700;
}
.loading, .error {
text-align: center;
padding: 2rem;
font-size: 1rem;
border-radius: var(--radius);
}
.error {
background: #fee2e2;
color: var(--danger);
border: 1px solid var(--danger);
}
.boolean-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
padding: 1.5rem;
}
.boolean-item {
text-align: center;
padding: 1.5rem;
background: var(--card-bg);
border: 2px solid var(--border);
border-radius: var(--radius);
transition: border-color 0.2s;
}
.boolean-item:hover {
border-color: var(--primary);
}
.boolean-item.true {
border-color: var(--success);
}
.boolean-item.false {
border-color: var(--danger);
}
.boolean-icon {
font-size: 2.5rem;
margin-bottom: 0.75rem;
}
.boolean-label {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.boolean-count {
font-size: 2rem;
font-weight: 700;
color: var(--text);
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.modal-content {
background: var(--card-bg);
margin: 5% auto;
padding: 1.5rem;
border: 1px solid var(--border);
width: 90%;
max-width: 500px;
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border);
}
.modal-title {
font-size: 1.125rem;
font-weight: 600;
color: var(--primary);
}
.close-modal {
font-size: 1.5rem;
cursor: pointer;
color: var(--text-muted);
}
.close-modal:hover {
color: var(--text);
}
.column-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
max-height: 400px;
overflow-y: auto;
}
.column-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0.75rem;
background: var(--grey-50);
border: 1px solid var(--border);
border-radius: var(--radius);
}
.column-label {
flex: 1;
font-size: 0.9rem;
font-weight: 500;
}
.badge {
padding: 0.25rem 0.5rem;
font-size: 0.7rem;
background: var(--grey-200);
color: var(--text-muted);
border-radius: 1rem;
}
.color-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0.75rem;
background: var(--grey-50);
border: 1px solid var(--border);
border-radius: var(--radius);
}