Dessin et visualisation de données

Nuage de points de la qualité de l’air

Complétez le graphique de dispersion dans le scénario de la qualité de l’air : il comprend du contenu réel, des contrôles interactifs, un retour d’état clair et une mise en page adaptée aux différentes tailles.

Dessin et visualisation de donnéesDiagramme de dispersionHTMLCanvasJavaScript
Après activation de JavaScript, ce cas peut être modifié, exécuté et enregistré. Code source fixe ci-dessous :
<!doctype html>
<html lang="fr" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Nuage de points de la qualité de l’air</title>
  <style>
    :root { --ink:#17241d; --paper:#f3f0e7; --accent:#af4f45; --secondary:#9eb49f; --line:color-mix(in srgb, var(--ink) 18%, transparent); --shadow:0 24px 60px color-mix(in srgb, var(--ink) 16%, transparent); color-scheme:light; }
    * { box-sizing:border-box; }
    html { min-width:320px; background:var(--paper); }
    body { min-height:100vh; margin:0; overflow-x:hidden; color:var(--ink); font-family:Optima,"Songti SC",serif; background:radial-gradient(ellipse at top left,color-mix(in srgb,var(--secondary) 32%,transparent),transparent 42%),radial-gradient(ellipse at bottom right,color-mix(in srgb,var(--accent) 18%,transparent),transparent 45%),var(--paper); }
    button,input,select,textarea { color:inherit; font:inherit; }
    button,[role="button"],input,select,textarea { accent-color:var(--accent); }
    button { cursor:pointer; }
    button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible { outline:3px solid var(--accent); outline-offset:3px; }
    [hidden] { display:none !important; }
    @media (max-width:700px) { .mast h1 { line-height:1.08 !important; letter-spacing:-.035em !important; overflow-wrap:anywhere; } }
    @media (prefers-reduced-motion:reduce) { *,*::before,*::after { scroll-behavior:auto !important; animation-duration:.01ms !important; animation-iteration-count:1 !important; transition-duration:.01ms !important; } }
    .page{width:min(1100px,calc(100% - 34px));margin:auto;padding:68px 0}.mast{display:grid;grid-template-columns:1fr auto;align-items:center;gap:40px;margin:0 4vw 36px}.mast h1{font-family:Optima,"Songti SC",serif;font-weight:700;font-size:clamp(40px,8vw,86px);letter-spacing:-.025em;margin:0;line-height:1.02}.eyebrow{display:block;margin-bottom:12px;color:var(--accent);font-size:12px;letter-spacing:.2em}.intro{max-width:60ch;line-height:1.9}.scenario{display:grid;width:108px;height:108px;place-items:center;padding:12px;border:1px solid var(--line);border-radius:50%;text-align:center}.demo{border-radius:72px 18px 72px 18px;box-shadow:0 28px 80px color-mix(in srgb,var(--ink) 14%,transparent);position:relative;min-height:430px;overflow:hidden;padding:clamp(28px,5vw,58px);background:color-mix(in srgb,var(--paper) 84%,transparent)}@media(max-width:700px){.page{width:calc(100% - 18px);padding:24px 0}.mast{grid-template-columns:1fr;margin-inline:12px}.scenario{width:auto;height:auto;border-radius:999px}.demo{padding:18px}}
    .panel{padding:30px;border:0;border-radius:42px 12px;background:color-mix(in srgb,var(--secondary) 18%,var(--paper))}.row{display:flex;flex-wrap:wrap;gap:14px;align-items:center}.stack{display:flex;flex-direction:column;gap:20px}.action{min-height:48px;padding:12px 22px;border:0;border-radius:50% 50% 18px 18px;background:color-mix(in srgb,var(--secondary) 42%,transparent);font-weight:800}.action.primary,.action.is-active{color:var(--paper);background:var(--accent)}.muted{color:color-mix(in srgb,var(--ink) 64%,transparent);line-height:1.7}.status{min-height:25px;margin-top:16px;color:var(--accent)}.metric{font-size:clamp(32px,7vw,66px);font-weight:800}.meter{height:11px;border-radius:50%;background:color-mix(in srgb,var(--ink) 10%,transparent)}.meter>i{display:block;height:100%;border-radius:inherit;background:var(--accent)}.chip{padding:8px 12px;border-radius:50%;background:var(--paper)}
    .composition-index{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;gap:18px}.index-mark{display:grid;align-content:space-between;width:76px;padding:16px 10px;border:1px solid var(--line);color:var(--accent);background:color-mix(in srgb,var(--paper) 82%,transparent);text-align:center}.index-mark strong{font:900 34px/1 ui-monospace,monospace}.index-mark span{writing-mode:vertical-rl;font:800 10px/1.2 ui-monospace,monospace;letter-spacing:.18em}.composition-index .demo{grid-column:1;grid-row:1}.composition-index::before{content:"";position:absolute;left:-12px;top:38px;width:5px;height:45%;background:linear-gradient(var(--accent),var(--secondary))}@media(max-width:700px){.composition-index{grid-template-columns:1fr}.index-mark{grid-row:1;width:auto;grid-template-columns:auto 1fr}.index-mark span{writing-mode:horizontal-tb}.composition-index .demo{grid-row:2}}
    .canvas-shell{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(230px,.6fr);gap:16px}.canvas-shell canvas{width:100%;height:440px;border:1px solid var(--line);border-radius:18px;background:var(--ink)}.canvas-tools{display:grid;align-content:center;gap:16px}.canvas-tools label{display:grid;gap:8px;font-weight:800}@media(max-width:900px){.canvas-shell{grid-template-columns:1fr}.canvas-shell canvas{height:300px}}.drawing-pattern{display:grid;gap:14px}.scatter-controller{display:grid;grid-template-columns:1fr 180px 180px auto minmax(180px,.5fr);gap:14px;align-items:end;padding:18px;border-left:12px solid var(--accent);background:var(--paper)}.scatter-controller h3{margin:.2em 0}.scatter-controller label{display:grid;grid-template-columns:1fr auto;gap:6px}.scatter-controller label select,.scatter-controller label input{grid-column:1/-1}.scatter-controller dl{display:flex;gap:14px;margin:0}.scatter-controller dl div{display:grid}.scatter-controller dd{margin:0;font-weight:900}@media(max-width:900px){.scatter-controller{grid-template-columns:1fr 1fr}.scatter-controller header,.scatter-controller dl{grid-column:1/-1}}.workflow-dispatch{display:grid;grid-template-columns:minmax(210px,.48fr) minmax(0,1.52fr);gap:18px}.dispatch-console{display:grid;align-content:start;gap:16px;padding:22px;border:1px solid var(--line);border-radius:18px;background:color-mix(in srgb,var(--secondary) 12%,var(--paper))}.dispatch-console h2{margin:.2em 0}.dispatch-console form,.dispatch-console label{display:grid;gap:7px}.dispatch-console input,.dispatch-console select{min-width:0;width:100%;padding:10px;border:1px solid var(--line);border-radius:8px;background:var(--paper)}.dispatch-console fieldset{display:flex;gap:12px;border:1px solid var(--line);border-radius:10px}.dispatch-console fieldset label{display:flex;align-items:center}.dispatch-workbench{--dispatch-load:.22;min-width:0;padding:14px;border-left:8px solid color-mix(in srgb,var(--accent) calc(var(--dispatch-load)*100%),var(--line));background:linear-gradient(90deg,color-mix(in srgb,var(--accent) 8%,transparent),transparent 28%)}.dispatch-workbench[data-dispatch-priority="urgent"]{box-shadow:inset 0 0 0 2px var(--accent)}@media(max-width:900px){.workflow-dispatch{grid-template-columns:1fr}.dispatch-console{order:2}}
  </style>
</head>
<body data-aesthetic="organic" data-composition="field-index">
  <main class="page">
    <header class="mast"><div><span class="eyebrow">organic / nuage de points / INTERMEDIATE</span><h1>Nuage de points de la qualité de l’air</h1><p class="intro">Complétez le graphique de dispersion dans le scénario de la qualité de l’air : il comprend du contenu réel, des contrôles interactifs, un retour d’état clair et une mise en page adaptée aux différentes tailles.</p></div><span class="scenario">Qualité de l’air</span></header>
    <div class="composition-index"><section class="demo" aria-label="Zone interactive du graphique de dispersion de la qualité de l’air"><div class="workflow-dispatch"><aside class="dispatch-console"><span class="eyebrow">Qualité de l’air DISPATCH</span><h2>Tableau de commande des stations d’air</h2><form id="workflowDispatchForm"><label>Type de tâche<select id="workflowDispatchKind"><option>Station routière</option><option>Station du parc</option><option>Communauté</option></select></label><label>AQI seuil<input id="workflowDispatchAmount" type="number" min="1" max="9999" value="33"><span>AQI</span></label><fieldset><legend>Priorité de traitement</legend><label><input type="radio" name="workflowPriority" value="normal" checked>Standard</label><label><input type="radio" name="workflowPriority" value="urgent">Urgent</label></fieldset><button class="action primary" type="submit">Démarrer la surveillance</button></form><label>Tâche en cours<select id="workflowDispatchActive" aria-label="Basculer la tâche en cours"><option value="1">Station routière</option><option value="2">Station du parc</option></select></label><output id="workflowDispatchSummary">En attente de planification</output></aside><section id="workflowCore" class="workflow-core dispatch-workbench" data-dispatch-priority="normal"><!-- 11DZ_CORE_START:drawing-data-viz-scatter-plot --><div class="drawing-pattern drawing-pattern-5"><div class="canvas-shell"><canvas id="artboard" width="900" height="440" aria-label="Toile de nuage de points" aria-describedby="canvasDescription"></canvas><div class="canvas-tools panel"><span class="eyebrow">CANVAS MODE 6</span><h2>Diagramme de dispersion</h2><label>complexité <input id="canvasAmount" type="range" min="4" max="28" value="25"></label><button id="redrawCanvas" class="action primary" type="button">Redessiner</button><p id="canvasDescription" class="muted">Un graphique de dispersion sur fond sombre est tracé avec deux couleurs très contrastées ; le curseur de complexité détermine le nombre de formes ; l’ensemble est généré entièrement en local dans le navigateur.</p></div></div><section class="drawing-controller scatter-controller"><header><span class="eyebrow">CLUSTER LAB</span><h3>Regroupement en nuage de points pour la qualité de l’air</h3></header><label>Algorithme de regroupement<select id="scatterClusterMode"><option value="density">Regroupement par densité</option><option value="distance">groupement par distance</option><option value="manual">Regroupement manuel</option></select></label><label>Amplitude de l’ondulation<input id="scatterJitter" type="range" min="0" max="30" value="12" data-core-bridge="drawing-data-viz-scatter-plot"><output id="scatterJitterValue">12</output></label><button id="scatterResample" class="action" type="button">Rééchantillonner</button><dl><div><dt>Lot d’échantillons</dt><dd id="scatterBatch">1</dd></div><div><dt>Nombre de grappes</dt><dd id="scatterClusters">3</dd></div></dl></section></div><!-- 11DZ_CORE_END --></section></div><p id="status" class="status" role="status" aria-live="polite"></p></section><aside class="index-mark" aria-hidden="true"><strong>05</strong><span>Qualité de l’air FIELD INDEX</span></aside></div>
  </main>
  <script>
    'use strict';
    const statusOutput = document.querySelector('#status');
    const announce = message => { statusOutput.textContent = message; };
    /* 11DZ_CORE_SCRIPT_START:drawing-data-viz-scatter-plot */
const canvas = document.querySelector('#artboard'); const context = canvas.getContext('2d'); const amount = document.querySelector('#canvasAmount'); let drawSeed = 1046044980;
    function random(){ drawSeed = (drawSeed * 1664525 + 1013904223) >>> 0; return drawSeed / 4294967296; }
    function drawCanvas(){ const rect = canvas.getBoundingClientRect(); const ratio = Math.min(2,window.devicePixelRatio || 1); canvas.width = Math.max(320,Math.round(rect.width * ratio)); canvas.height = Math.max(240,Math.round(rect.height * ratio)); context.setTransform(ratio,0,0,ratio,0,0); const width = rect.width; const height = rect.height; context.clearRect(0,0,width,height); context.fillStyle = '#17241d'; context.fillRect(0,0,width,height); const drawingFactor=Number(canvas.dataset.drawingFactor||1); const count=Math.max(1,Math.round(Number(amount.value)*drawingFactor)); drawSeed += 1+Number(canvas.dataset.drawingShift||0); context.globalCompositeOperation=canvas.dataset.drawingBlend||'source-over';
      for(let index=0; index<count; index+=1){ const x=random()*width; const y=random()*height; const size=(10+random()*70)*Math.max(.45,drawingFactor); context.beginPath(); context.strokeStyle=index%2?'#af4f45':'#9eb49f'; context.fillStyle=context.strokeStyle+'88'; context.lineWidth=(2+random()*4)*Math.max(.6,drawingFactor);
        context.arc(x,y,3+random()*7,0,Math.PI*2); context.fill();
      } context.globalCompositeOperation='source-over'; announce('Le canevas a été généré ' + count + ' éléments graphiques'); }
    document.querySelector('#redrawCanvas').addEventListener('click',drawCanvas); amount.addEventListener('input',drawCanvas); addEventListener('resize',drawCanvas,{passive:true}); drawCanvas();
const scatterMode=document.querySelector('#scatterClusterMode');const scatterJitter=document.querySelector('#scatterJitter');const scatterModel=new Map([['batch',1],['clusters',3],['jitter',12]]);function renderScatterModel(){scatterModel.set('jitter',Number(scatterJitter.value));scatterModel.set('clusters',{density:3,distance:4,manual:2}[scatterMode.value]);document.querySelector('#scatterJitterValue').textContent=scatterModel.get('jitter');document.querySelector('#scatterBatch').textContent=scatterModel.get('batch');document.querySelector('#scatterClusters').textContent=scatterModel.get('clusters');canvas.dataset.drawingFactor=String(.65+scatterModel.get('clusters')*.15);canvas.dataset.drawingShift=String(scatterModel.get('batch')*19+scatterModel.get('jitter'));drawCanvas();}scatterMode.addEventListener('change',renderScatterModel);scatterJitter.addEventListener('input',renderScatterModel);document.querySelector('#scatterResample').addEventListener('click',()=>{scatterModel.set('batch',scatterModel.get('batch')+1);renderScatterModel();announce('Les échantillons de nuage de points ont été de nouveau collectés et regroupés en clusters');});renderScatterModel();
/* 11DZ_CORE_SCRIPT_END */
const workflowBridgeSpec={"name":"drawing-data-viz-scatter-plot","selector":"[data-core-bridge=\"drawing-data-viz-scatter-plot\"]","mode":"range","key":"ArrowRight"};const workflowBridgeCore=document.querySelector('#workflowCore');const workflowBridgeControl=workflowBridgeCore.querySelector(workflowBridgeSpec.selector);if(!workflowBridgeControl)throw new Error('Missing core bridge: '+workflowBridgeSpec.name);const workflowBridgeControlledId=workflowBridgeControl.getAttribute('aria-controls');const workflowBridgeControlled=workflowBridgeControlledId?document.getElementById(workflowBridgeControlledId):null;workflowBridgeCore.dataset.workflowBridge=workflowBridgeSpec.name;workflowBridgeCore.dataset.workflowVersion='0';function workflowBridgeSerialize(){const visualNode=workflowBridgeCore.firstElementChild||workflowBridgeCore;const visual=getComputedStyle(visualNode);const outputs=[...workflowBridgeCore.querySelectorAll('output,[role="status"],[aria-live],meter,progress')].map(node=>node.textContent.trim()+'|'+node.getAttribute('aria-valuenow')+'|'+node.getAttribute('value'));const canvas=[...workflowBridgeCore.querySelectorAll('canvas')].map(node=>{try{return node.toDataURL().slice(-160);}catch{return 'canvas-unavailable';}});return JSON.stringify({html:workflowBridgeCore.innerHTML,status:statusOutput.textContent,outputs,canvas,visual:[visual.display,visual.visibility,visual.opacity,visual.transform,visual.filter,visual.backgroundColor,visual.width,visual.height],scroll:workflowBridgeSpec.mode==='scroll'?workflowBridgeControl.scrollTop:null});}let workflowBridgeLastEffect=workflowBridgeSerialize();function workflowBridgeSummary(){const label=(workflowBridgeControl.getAttribute('aria-label')||workflowBridgeControl.textContent||workflowBridgeControl.value||workflowBridgeSpec.name).trim().replace(/\s+/g,' ').slice(0,72);return label+' · Version de base '+workflowBridgeCore.dataset.workflowVersion;}function workflowBridgeCommit(){const effect=workflowBridgeSerialize();if(effect===workflowBridgeLastEffect)return {changed:false,summary:workflowBridgeSummary()};workflowBridgeLastEffect=effect;workflowBridgeCore.dataset.workflowVersion=String(Number(workflowBridgeCore.dataset.workflowVersion||0)+1);return {changed:true,summary:workflowBridgeSummary()};}function workflowBridgeScheduleCommit(){queueMicrotask(workflowBridgeCommit);setTimeout(workflowBridgeCommit,0);}workflowBridgeControl.addEventListener("input",workflowBridgeScheduleCommit);workflowBridgeControl.addEventListener('change',workflowBridgeScheduleCommit);const workflowBridgeStateTargets=new Set(workflowBridgeCore.querySelectorAll('dialog'));if(workflowBridgeControlled&&workflowBridgeCore.contains(workflowBridgeControlled))workflowBridgeStateTargets.add(workflowBridgeControlled);workflowBridgeStateTargets.forEach(target=>['close','cancel','toggle'].forEach(eventName=>target.addEventListener(eventName,workflowBridgeScheduleCommit)));function workflowBridgeActivate(){workflowBridgeControl.focus();const step=Number(workflowBridgeControl.step)||1;const min=workflowBridgeControl.min===''?-Infinity:Number(workflowBridgeControl.min);const max=workflowBridgeControl.max===''?Infinity:Number(workflowBridgeControl.max);let next=Math.min(max,Math.max(min,(Number(workflowBridgeControl.value)||0)+step));if(next===Number(workflowBridgeControl.value))next=Math.max(min,Number(workflowBridgeControl.value)-step);workflowBridgeControl.value=String(next);workflowBridgeControl.dispatchEvent(new Event('input',{bubbles:true}));workflowBridgeControl.dispatchEvent(new Event('change',{bubbles:true}));const result=workflowBridgeCommit();setTimeout(workflowBridgeCommit,0);return result;}function workflowBridgeReset(){workflowBridgeCore.dataset.workflowVersion='0';workflowBridgeLastEffect=workflowBridgeSerialize();}
const workflowDispatchCore=document.querySelector('#workflowCore');const workflowDispatchForm=document.querySelector('#workflowDispatchForm');const workflowDispatchSelect=document.querySelector('#workflowDispatchActive');const workflowDispatchJobs=new Map([["1",{"label":"Station routière","amount":12,"priority":"normal","touches":0}],["2",{"label":"Station du parc","amount":21,"priority":"urgent","touches":0}]]);let workflowDispatchSerial=workflowDispatchJobs.size;function workflowDispatchDrive(job,activate=false){workflowDispatchCore.dataset.dispatchPriority=job.priority;workflowDispatchCore.style.setProperty('--dispatch-load',Math.min(.95,.16+job.amount/120));const bridge=activate?workflowBridgeActivate():{changed:false,summary:workflowBridgeSummary()};document.querySelector('#workflowDispatchSummary').textContent=job.label+' · '+job.amount+' AQI · '+bridge.summary;announce('Tâche actuelle du point de mesure de la qualité de l’air :'+job.label);}function workflowDispatchRender(activeId,activate=false){workflowDispatchSelect.replaceChildren(...[...workflowDispatchJobs].map(([id,job])=>{const option=document.createElement('option');option.value=id;option.textContent=job.label+(job.priority==='urgent'?' · Urgent':'');return option;}));workflowDispatchSelect.value=activeId;workflowDispatchDrive(workflowDispatchJobs.get(activeId),activate);}workflowDispatchForm.addEventListener('submit',event=>{event.preventDefault();const data=new FormData(workflowDispatchForm);const id=String(++workflowDispatchSerial);workflowDispatchJobs.set(id,{label:document.querySelector('#workflowDispatchKind').value,amount:Math.max(1,Number(document.querySelector('#workflowDispatchAmount').value)||1),priority:data.get('workflowPriority'),touches:0});workflowDispatchRender(id,true);});workflowDispatchSelect.addEventListener('change',()=>workflowDispatchDrive(workflowDispatchJobs.get(workflowDispatchSelect.value),true));workflowDispatchCore.addEventListener('focusin',()=>{const job=workflowDispatchJobs.get(workflowDispatchSelect.value);if(!job)return;job.touches+=1;document.querySelector('#workflowDispatchSummary').textContent=job.label+' · '+job.amount+' AQI · '+workflowBridgeSummary();});workflowDispatchRender('1',false);
workflowBridgeReset();
  </script>
</body>
</html>
Commentaires sur la pageVous avez découvert un problème ou avez une suggestion d’amélioration ?
Les fonctions de retour nécessitent l’utilisation de la version en ligne

Veuillez activer JavaScript dans la version en ligne avant de soumettre ; les fonctionnalités locales de l’outil ou du cas ne sont pas affectées.

Aller vers la version en ligne pour envoyer des commentaires