Skip to content

Vues alternatives de résultat

L'exécution d'un SQL renvoie un jeu de résultats ; par défaut on voit la grille (voir Grille de résultats). Mais souvent la grille n'est pas la meilleure forme — pour cent lignes (month, revenue), un graphique ligne est dix mille fois plus parlant qu'un tableau. SkylerX intègre dans la barre d'outils de résultat un ensemble de vues alternatives : les données ne sont pas re-requêtées, elles changent juste de forme en mémoire.

Cette page clarifie : quand changer de vue, comment chaque vue calcule, quelle forme de données elle attend, ce qui peut être sauvegardé.

Quand changer de vue est plus parlant que la grille

Forme de donnéesVue recommandéeCas typique
Une colonne catégorie + une numériqueBarres / Camembert / AnneauVentes par ville, erreurs par endpoint
Une colonne temps + une numérique (continue)Ligne / AireTendance DAU, charge CPU
Deux colonnes numériques (corrélation)Nuage de pointsActivité utilisateur vs rétention
Trois colonnes catégorie / numériqueTableau croiséCanal × Mois = Revenu
(lat, lng)Nuage géographiqueDistribution magasins, carte utilisateurs
Une colonne temps + une labelFrise chronologiqueÉvénements de déploiement, cycle de vie commande
(id, parent_id, ...)Arbre FK auto-référencéCommentaires imbriqués, organigramme
Multiples versions d'une même ligneHistorique de ligneAudit log retracement

Déclencheur dans la barre du bas (packages/ui/src/components/ResultGrid.vue:1202-1215) :

vue
<button :disabled="!result?.rows.length" @click="chartOpen = true">📊 Graphique</button>
<div class="menu-box">
  <button @click="showViewMenu = !showViewMenu">📐 Vues</button>
  <!-- Menu déroulant -->
  <button @click="altView = 'pivot'">⊞ Croisé</button>
  <button @click="altView = 'tree'">🌳 Arbre</button>
  <button @click="altView = 'geo'">🗺 Carte</button>
  <button @click="altView = 'timeline'">⏱ Chronologie</button>
</div>

Toutes ces vues s'ouvrent en modal, ferment → retour à la grille — elles sont la "loupe" de la grille, pas son remplacement.

1. Vue Graphique (barres / ligne / camembert + 4 extensions)

packages/ui/src/components/ChartDialog.vue, 630 lignes, bouton : 📊 Graphique.

Choix de conception

Le commentaire du code est franc :

Pas d'ECharts, on dessine du SVG à la main (barres / ligne + camembert ~ une centaine de lignes chacun), pourquoi :

  • Taille du bundle desktop sensible ; les graphiques sont juste un "petit outil" du result grid, pas la scène principale
  • 3 types couvrent 90% des cas de visualisation ponctuelle ; pour plus fancy, ECharts plus tard
  • SVG facile à exporter en PNG (toDataURL via <canvas>)

7 types de graphiques, tous en SVG manuel :

TypeConvientLimiteNote
📊 BarresComparaison catégorie / numérique50 premières lignesLimite Y auto-arrondie
📈 LigneTendance / time series200 premières lignesChemin M / L
🥧 CamembertProportions50 premières lignesÉtiquettes % auto
⛰ AireTendance + magnitude200 premières lignesLigne fermée vers baseline
·· Nuage de pointsPoints dispersés200 premières lignesPoints ronds
⭕ AnneauVariante proportions50 premières lignesExterne r * 1.0, interne r * 0.55
📡 RadarMulti-dimensions50 premières lignes, ≥ 3 pointsUn axe par ligne

Sélection de colonnes

3 sélecteurs en haut : Label (toute colonne, .toString()), Value (auto-détection numérique, colonnes non-numériques marquées (?)), Type. isNumericColumn prend les 20 premières lignes pour Number.isFinite(Number(v)), Y par défaut = première colonne numérique. Au changement de résultat, watch réinitialise la sélection.

Règles de données : lignes Number(v) → NaN sont skippées, limite atteinte → tronquage (barres / cam. 50, ligne / aire / nuage 200, radar 50).

Axe Y

Pour des graduations "rondes", la borne sup utilise Math.ceil(m / 10^floor(log10(m))) * 10^floor(log10(m)). Format des nombres : B / M / k (au-dessus de 1e9 / 1e6 / 1e4).

Sortie : export PNG

Bouton ⬇ Exporter PNG en haut à droite → XMLSerializer sérialise le SVG → dessin <canvas> HiDPI 2× (fond sombre #1d1e22) → canvas.toBlob('image/png')SaveFileDialog personnalisé. Nom de fichier chart-{kind}-{ts}.png, résolution 1440×720, parfait pour Feishu / Slack.

2. Tableau croisé (PivotDialog)

packages/ui/src/components/PivotDialog.vue, 162 lignes. Déclencheur : 📐 Vues → ⊞ Croisé.

Rôle : pivot en mémoire sur le résultat courant, pas de re-requête. Algorithme simple — lignes groupées par (rowFields...) → bucketing par colField → agrégation agg par bucket.

3 axes + 1 fonction d'agrégation

ContrôleAction
Lignes (chips multi)Groupage par ces colonnes, clé '|'
Colonnes (dropdown)Toutes les valeurs distinctes de cette colonne deviennent les en-têtes (ordre lexicographique)
Valeur + agrégationPour chaque (row, col), agrégation sur cette colonne
Dropdown agrégationCOUNT / SUM / AVG / MIN / MAX

Algorithme

Nested Map<rowKey, Map<colKey, number[]>> : parcours unique de result.rows, rowKey = champs rowFields joints par |, colKey = valeur string de colField, Number(row[valueField]) poussé dans le tableau. NULL uniformément 'NULL' (regroupement). COUNT par length, autres par agrégation numérique.

Limites

Le commentaire du code est direct :

Non supportés : plusieurs value fields, ordre custom des colonnes (lexicographique), filtre ; à compléter prochainement.

Donc : tri par mois "1-12 et non 10, 11, 12, 1, 2..." pas possible pour l'instant — il faut zero-padder en SQL d'abord ('01' / '02' / ...).

Sortie

Juste une vue tableau temporaire, pas d'export direct. Pour persister :

  • Fermer le croisé → retour grille → clic droit copier → CSV / Markdown pour coller dans Excel / Notion
  • Réécrire la logique pivot en SQL : MySQL GROUP BY x WITH ROLLUP / PG crosstab()

3. Nuage de points géographique (GeoMapDialog)

packages/ui/src/components/GeoMapDialog.vue, 138 lignes. Déclencheur : 📐 Vues → 🗺 Carte.

Pas de leaflet / pas de fond de carte, juste un nuage SVG (lng, lat). Commentaire du code :

Projection : équidistante équirectangulaire (Mercator faible distorsion visuelle, pour données locales le tracé direct en lat/lng suffit, pas de SR complexe). Pas fait : fond de carte (pas de tiles), clustering (points denses se chevauchent mais zoom/pan résout).

Détection auto des colonnes

ts
latCol = cols.find(c => /^(lat|latitude|y)$/i.test(c)) ?? cols[0]
lngCol = cols.find(c => /^(lng|lon|long|longitude|x)$/i.test(c)) ?? cols[1]
labelCol = cols.find(c => /^(name|title|label|id)$/i.test(c)) ?? ''

Filtre de validité numérique (anti-déchets) :

ts
if (!Number.isFinite(lat) || !Number.isFinite(lng)) continue
if (lat < -90 || lat > 90 || lng < -180 || lng > 180) continue

Cadrage auto

Pas de planisphère, le bounds calcule "juste englobant tous les points + 5% de marge" :

ts
const dx = Math.max(0.001, (maxX - minX) * 0.05)
return { minX: minX - dx, maxX: maxX + dx, ... }

Les coordonnées des 4 coins s'affichent sur les bords du SVG, le survol d'un point montre lat=... lng=....

Sortie

Visuel seul, pas d'export PNG (à venir). Pour visualisation persistante, sortez une colonne catégorie en SQL et utilisez la vue Graphique (Nuage).

Forme de données attendue

Noms de colonnes compatiblesExemple
lat, latitude, ylatitude FLOAT
lng, lon, long, longitude, xlng DECIMAL(9,6)
name, title, label, id (label, optionnel)store_name VARCHAR

Si les noms sont hors standard, sélection manuelle dans le dropdown, tant que les valeurs sont numériques et dans la plage.

4. Frise chronologique (TimelineDialog)

packages/ui/src/components/TimelineDialog.vue, 171 lignes. Déclencheur : 📐 Vues → ⏱ Chronologie.

Détection auto des colonnes

ts
timeCol = cols.find(c => /at$|_time$|date|time|created|updated/i.test(c)) ?? cols[0]
labelCol = cols.find(c => /^(name|title|label|id|user|action)$/i.test(c)) ?? ''
colorCol = ''   // optionnel : coloration par cette colonne

Couvre created_at / updated_at / event_time / order_date / login_time etc.

Parsing temporel (toMs)

Quatre formats acceptés :

ts
function toMs(v: unknown): number | null {
  if (v instanceof Date) return v.getTime()
  if (typeof v === 'number') return v > 1e12 ? v : v * 1000   // heuristique ms ou s
  const ms = Date.parse(String(v))  // ISO / "YYYY-MM-DD HH:MM:SS"
  return Number.isNaN(ms) ? null : ms
}

Numérique < 1e12 (année 2001) traité comme Unix secondes × 1000 ; au-dessus comme ms. Suffisant pour le métier, rares timestamps pré-1969 mal classés — convertir en chaîne via to_char(...) en SQL si nécessaire.

Rendu

Frise horizontale, points alternés verticalement pour éviter le chevauchement (i % 2 === 0 ? -16 : +16), axe X 5 graduations dates.

Si la colonne color est spécifiée, palette de 8 couleurs cyclique (#7c6cff / #4caf50 / #e0a020 / #e04050 / #3aa1ff / #b48cff / #67c23a / #ff9966), légende en bas. Survol d'un point : barre d'info bas temps · label.

Forme de données attendue

Au moins une colonne temporelle (Date / ISO / Unix s ou ms). Label / Color optionnels.

5. Arbre FK auto-référencé (TreeViewDialog)

packages/ui/src/components/TreeViewDialog.vue, 130 lignes. Déclencheur : 📐 Vues → 🌳 Arbre.

Adapté aux FK auto-référencées ou hiérarchies : commentaires imbriqués (comments.parent_id → comments.id), organigramme (departments.parent_dept_id → id), régions (regions.parent_id).

Trois axes

SélecteurRègle de déduction
idPriorité /^id$/i, sinon première colonne
parentMatch /parent[_-]?id|pid/i, vide par défaut
labelMatch /^(name|title|label)$/i, sinon fallback id

Algorithme

Deux passes : 1ère pour indexer par id (byId: Map<id, node>), 2ème pour rattacher les enfants. Parent id non indexé (NULL inclus) = racine. parent === self = racine (anti-WHERE id=1 AND parent_id=1).

Détection de cycle

walk(n, depth) DFS avec Set<string> des visités ; re-rencontre = n.cycle = true + stop. Le nœud affiche un jaune, survol = "Cycle". Courant après modification erronée par un opérateur.

Rendu

Flatten avec indentation depth * 18px, chaque nœud ▸ <label> #<id>. Survol du label : title="{json}" pour voir la ligne complète (vérif visuelle rapide).

Forme de données attendue

Au moins id + parent ; SELECT id, parent_id, name FROM comments WHERE post_id = 1234 ramène tout l'arbre en une fois, la vue rend la hiérarchie automatiquement.

6. Historique de ligne (RowHistoryDialog)

packages/ui/src/components/RowHistoryDialog.vue, 123 lignes.

Rôle : traçabilité d'une ligne — pour une PK donnée d'une table, trouver toutes les versions dans la table shadow audit / *_history / *_log.

Détection auto de la table shadow

À l'ouverture, SELECT table_name FROM information_schema.tables WHERE table_name LIKE '{base}_%' OR table_name = 'audit_{base}' OR table_name = '{base}_history', candidats dans <datalist>, sélection ou saisie manuelle.

Requête historique

Une fois la shadow choisie, requête par PK : SELECT * FROM {shadow} WHERE {pk}=... ORDER BY changed_at, updated_at, created_at, version, revision DESC LIMIT 200. ORDER BY liste 5 candidats, la DB utilise la colonne présente (MySQL tolérant / PG strict, les audit courants ont au moins une).

Forme de données attendue

Table métier + table shadow *_history / *_audit / *_log (PK + colonnes métier répliquées + changed_at / version). Les triggers d'audit courants respectent ce contrat.

Note d'implémentation : ce dialogue est codé dans le repo (Workspace.vue a rowHistOpen et le mount modal), mais sans entrée directe depuis la grille pour l'instant — réservé à un futur menu contextuel.

7. Data lineage (LineageDialog) — version heuristique

packages/ui/src/components/LineageDialog.vue, 98 lignes.

Le commentaire du code est explicite :

Lineage de colonnes (version heuristique) : pas encore de vrai SQL parser, juste la plus simple heuristique — match « {table}.{column} » ou bare {column} (sous condition que {table} soit dans FROM) dans l'historique SQL. Précision limitée : ratés (alias / sous-requêtes), faux positifs (colonnes homonymes). Clairement annoncé "heuristic" en attendant un parser SQL.

Algorithme

Charge les 500 dernières requêtes SQL de la connexion courante, match \b{table}\b + \b{column}\b (deux regex word-boundary) sur chaque. Selon le préfixe : INSERT / UPDATE → sinks (écriture), SELECT / WITH → sources (lecture).

Rendu

Deux colonnes :

  • ← Sinks — SQL qui écrit cette colonne (INSERT / UPDATE)
  • → Sources — SQL qui lit cette colonne (SELECT / WITH)

Chaque ligne : timestamp + 120 premiers caractères du SQL. Bandeau jaune en haut : "résultat heuristique, ne pas utiliser comme preuve d'audit".

Forme de données attendue

Dépend de l'historique de requêtes (client.connections.history). Sans historique pertinent dans SkylerX, la fenêtre affichera "No hits".

Note d'implémentation : comme RowHistoryDialog, monté dans Workspace.vue et déclenché de l'extérieur (lineageOpen.value = {...}), sans entrée UI dédiée pour l'instant, API réservée.

Matrice de support

VueDétection autoLimite donnéesExport statiqueRe-requête SQLConvient à
Graphique (7 types)Détection numérique50 / 200 lignesPNG (HiDPI 2×)NonMagnitude / tendance / proportions
Tableau croisé1ère/2ème/3ème colonneLimité par RAM navigateurCopier en CSVNonAgrégation cross 2 axes
Nuage géoAlias lat / lng / x / yIllimitéNonNonTracé direct lat/lng
ChronologieSuffixes at$ / time / date / createdIllimitéNonNonFlux d'événements + couleurs
Arbreid / parent_id / nameIllimitéNonNonHiérarchie FK auto-référencée
Hist ligneNom table *_history / *_audit200 lignes (SQL LIMIT)Non✓ (audit table)Traçabilité d'une ligne
Lineage500 dernières du logNonNonRelation lecture/écriture par colonne (heuristique)

Déclencheurs

VueEntréeNote
GraphiqueBarre 📊 GraphiqueOuvre directement barres par défaut
Croisé / Arbre / Carte / ChronologieBarre 📐 Vues → menuMême modal, état altView partagé
Hist ligneVia rowHistOpen.value = { conn, table, pk }Réservé, en attente de menu contextuel
LineageVia lineageOpen.value = { conn, table, column }Réservé, en attente de menu contextuel

Toutes les modals ferment et reviennent à la grille originale, sans perte de pagination / tri — elles ne sont qu'une "loupe" sur la grille, sans remplacer le résultat.

Petit arbre de décision pour choisir une vue

Vous voulez magnitude / classement / tendance / proportions ? → Graphique

  • Magnitude vs temps → Ligne / Aire
  • Classement par catégorie → Barres
  • Proportions → Camembert / Anneau
  • Multi-dimensions → Radar

Croisement 2 axes (ex. "canal × mois") → Croisé

Données contiennent (lat, lng) → Carte

Données avec colonne temporelle :

  • Valeurs continues (DAU jour par jour) → Ligne
  • Événements discrets (déploiements, alertes) → Chronologie

Données auto-référencées par FK → Arbre

Voir historique de modifications d'une ligne → Hist ligne

Voir qui lit / écrit cette colonne → Lineage (heuristique, à utiliser avec prudence)

Toutes les vues alternatives au niveau résultat sont couvertes. Si votre forme de données n'entre dans aucune, 90% du temps une réécriture SQL la rendra exploitable par une de ces vues — sinon, retour grille et copie vers Excel / Numbers / Notion.

Pour voir l'exécution du SQL lui-même (slow log, Explain, recommandation d'index), voir Avancé et ingénierie ; pour import/export, voir Migration de données.

Apache License 2.0 · Wuhan Skyler Network Technology Co., Ltd.