Initial commit — Diabetix V2
Application Laravel 12 + Inertia + Vue 3 + Tailwind. Fonctionnalités : dashboard glycémique, saisie de mesures, courbe SVG, statistiques (jour/semaine/mois/trimestre), défis & badges, chat coach IA (Gemini), paramètres profil avec palette de couleurs, pages auth redessinées, emails transactionnels via Resend avec thème Diabetix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
53
app/resources/js/Components/Diabetix/GlucoseChart.vue
Normal file
53
app/resources/js/Components/Diabetix/GlucoseChart.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Array, default: () => [] },
|
||||
tok: { type: Object, required: true },
|
||||
width: { type: Number, default: 300 },
|
||||
height: { type: Number, default: 110 },
|
||||
targetMin: { type: Number, default: 70 },
|
||||
targetMax: { type: Number, default: 180 },
|
||||
});
|
||||
|
||||
const PAD = { t: 10, r: 18, b: 24, l: 28 };
|
||||
|
||||
const computed_ = computed(() => {
|
||||
const cW = props.width - PAD.l - PAD.r;
|
||||
const cH = props.height - PAD.t - PAD.b;
|
||||
const data = props.data.length ? props.data : [{ l: '–', v: props.targetMin }];
|
||||
const values = data.map(d => d.v);
|
||||
// Sensible mg/dL window with auto-extension when values exceed bounds.
|
||||
const MIN_V = Math.min(50, Math.floor((Math.min(...values) - 10) / 10) * 10);
|
||||
const MAX_V = Math.max(220, Math.ceil((Math.max(...values) + 10) / 10) * 10);
|
||||
const yS = v => cH - ((v - MIN_V) / (MAX_V - MIN_V)) * cH;
|
||||
const xS = i => (i / Math.max(data.length - 1, 1)) * cW;
|
||||
const pts = data.map((d, i) => ({ x: xS(i), y: yS(d.v), v: d.v, l: d.l }));
|
||||
|
||||
let line = `M ${pts[0].x} ${pts[0].y}`;
|
||||
for (let i = 1; i < pts.length; i++) {
|
||||
const cx = (pts[i - 1].x + pts[i].x) / 2;
|
||||
line += ` C ${cx} ${pts[i - 1].y} ${cx} ${pts[i].y} ${pts[i].x} ${pts[i].y}`;
|
||||
}
|
||||
const area = `${line} L ${pts[pts.length - 1].x} ${cH} L ${pts[0].x} ${cH} Z`;
|
||||
return { cW, cH, pts, line, area, ty1: yS(props.targetMax), ty2: yS(props.targetMin), yS };
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg :width="width" :height="height" style="display:block;overflow:visible">
|
||||
<g :transform="`translate(${PAD.l},${PAD.t})`">
|
||||
<line v-for="v in [70,120,180,240]" :key="v" :x1="0" :y1="computed_.yS(v)" :x2="computed_.cW" :y2="computed_.yS(v)" :stroke="tok.border" stroke-width="0.8" stroke-dasharray="3,3" />
|
||||
<rect :x="0" :y="computed_.ty1" :width="computed_.cW" :height="computed_.ty2 - computed_.ty1" :fill="tok.primary" opacity="0.13" rx="3" />
|
||||
<text :x="computed_.cW + 3" :y="computed_.ty1 + 4" font-size="7" :fill="tok.primary">{{ targetMax }}</text>
|
||||
<text :x="computed_.cW + 3" :y="computed_.ty2 + 4" font-size="7" :fill="tok.primary">{{ targetMin }}</text>
|
||||
<path :d="computed_.area" :fill="tok.primary" opacity="0.09" />
|
||||
<path :d="computed_.line" fill="none" :stroke="tok.primary" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<circle v-for="(p, i) in computed_.pts" :key="'c'+i" :cx="p.x" :cy="p.y" r="3.5"
|
||||
:fill="p.v >= targetMin && p.v <= targetMax ? tok.primary : tok.amber"
|
||||
:stroke="tok.white" stroke-width="1.5" />
|
||||
<text v-for="(p, i) in computed_.pts" :key="'l'+i" :x="p.x" :y="computed_.cH + 14" text-anchor="middle" font-size="8" :fill="tok.muted">{{ p.l }}</text>
|
||||
<text v-for="v in [70,180]" :key="'y'+v" :x="-4" :y="computed_.yS(v) + 3" text-anchor="end" font-size="8" :fill="tok.muted">{{ v }}</text>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
Reference in New Issue
Block a user