graficos de pie chart
This commit is contained in:
@@ -7,8 +7,8 @@
|
||||
<div class="placeholder">Cargando datos…</div>
|
||||
</div>
|
||||
|
||||
<!-- Count/percent view in a single card -->
|
||||
<div v-else-if="viewMode !== 'ratio'" class="card glass">
|
||||
<!-- Single unified view with carousel: first slide shows all proportion bars; next slides are pies per group -->
|
||||
<div v-else class="card glass">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">Eventos y comparación</h2>
|
||||
<div v-if="filtersCollapsed && (activeFilters?.hasFilters || selectedPlayerUuid)" class="active-filters-summary">
|
||||
@@ -29,92 +29,98 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bars big">
|
||||
|
||||
<div class="carousel-header">
|
||||
<button class="carousel-btn" @click="prevSlide()" :disabled="!hasPrevSlide">⟨</button>
|
||||
<div class="carousel-title">
|
||||
<span class="title">{{ slideTitle }}</span>
|
||||
<span class="slide-count">{{ currentSlide + 1 }} / {{ totalSlides }}</span>
|
||||
</div>
|
||||
<button class="carousel-btn" @click="nextSlide()" :disabled="!hasNextSlide">⟩</button>
|
||||
</div>
|
||||
|
||||
<!-- Slide 0: All ratio bars together -->
|
||||
<div v-if="currentSlide === 0" class="ratio-cards">
|
||||
<div
|
||||
v-for="eventType in eventTypes"
|
||||
:key="eventType"
|
||||
class="bar-row"
|
||||
:class="{ highlight: highlighted === eventType }"
|
||||
@mouseenter="highlighted = eventType"
|
||||
v-for="group in ratioData"
|
||||
:key="group.name"
|
||||
v-show="group.total > 0"
|
||||
class="card glass ratio-card"
|
||||
:class="{ highlight: highlighted === group.name }"
|
||||
@mouseenter="highlighted = group.name"
|
||||
@mouseleave="highlighted = ''"
|
||||
>
|
||||
<div class="bar">
|
||||
<div
|
||||
class="bar-fill global shimmer"
|
||||
:style="{
|
||||
width: globalBarWidth(eventType) + '%',
|
||||
background: EVENT_STYLES[eventType]?.gradient || 'linear-gradient(90deg, #94a3b8, #64748b)'
|
||||
}"
|
||||
></div>
|
||||
<div
|
||||
v-if="selectedPlayerUuid"
|
||||
class="bar-fill player"
|
||||
:style="{
|
||||
width: playerBarWidth(eventType) + '%',
|
||||
background: playerBarGradient
|
||||
}"
|
||||
></div>
|
||||
<div
|
||||
class="bar-chip"
|
||||
:style="{
|
||||
background: getEventChipBg(eventType),
|
||||
borderColor: getEventBorderColor(eventType)
|
||||
}"
|
||||
>
|
||||
<span class="event-icon">{{ EVENT_STYLES[eventType]?.icon || '📊' }}</span>
|
||||
<span class="chip-label">{{ friendlyEventName(eventType) }}</span>
|
||||
<span class="chip-count global">{{ globalValueLabel(eventType) }}</span>
|
||||
<span v-if="selectedPlayerUuid" class="chip-count player">{{ playerValueLabel(eventType) }}</span>
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{{ group.name }}</h3>
|
||||
<span class="group-total">{{ group.total }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint small">Basado en mensajes disponibles por sala. Haz clic en un jugador para comparar.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ratio view: one card per group -->
|
||||
<div v-else class="ratio-cards">
|
||||
<div
|
||||
v-for="group in ratioData"
|
||||
:key="group.name"
|
||||
v-show="group.total > 0"
|
||||
class="card glass ratio-card"
|
||||
:class="{ highlight: highlighted === group.name }"
|
||||
@mouseenter="highlighted = group.name"
|
||||
@mouseleave="highlighted = ''"
|
||||
>
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{{ group.name }}</h3>
|
||||
<span class="group-total">{{ group.total }}</span>
|
||||
</div>
|
||||
<div class="ratio-bar">
|
||||
<div
|
||||
v-for="(action, actionIndex) in group.actions"
|
||||
:key="action"
|
||||
class="ratio-segment"
|
||||
:style="{
|
||||
width: group.percentages[actionIndex] + '%',
|
||||
background: EVENT_STYLES[action]?.gradient || 'linear-gradient(90deg, #94a3b8, #64748b)'
|
||||
}"
|
||||
>
|
||||
<div class="ratio-bar">
|
||||
<div
|
||||
class="ratio-event-chip"
|
||||
v-if="group.percentages[actionIndex] > 5"
|
||||
v-for="(action, actionIndex) in group.actions"
|
||||
:key="action"
|
||||
class="ratio-segment"
|
||||
:style="{
|
||||
background: getEventChipBg(action),
|
||||
borderColor: getEventBorderColor(action)
|
||||
width: group.percentages[actionIndex] + '%',
|
||||
background: EVENT_STYLES[action]?.gradient || 'linear-gradient(90deg, #94a3b8, #64748b)'
|
||||
}"
|
||||
>
|
||||
<span class="ratio-icon">{{ EVENT_STYLES[action]?.icon || '📊' }}</span>
|
||||
<span class="ratio-label">{{ group.labels[actionIndex] }}</span>
|
||||
<span class="ratio-count">{{ group.values[actionIndex] }} ({{ Math.round(group.percentages[actionIndex]) }}%)</span>
|
||||
<div
|
||||
class="ratio-event-chip"
|
||||
v-if="group.percentages[actionIndex] > 5"
|
||||
:style="{
|
||||
background: getEventChipBg(action),
|
||||
borderColor: getEventBorderColor(action)
|
||||
}"
|
||||
>
|
||||
<span class="ratio-icon">{{ EVENT_STYLES[action]?.icon || '📊' }}</span>
|
||||
<span class="ratio-label">{{ group.labels[actionIndex] }}</span>
|
||||
<span class="ratio-count">{{ group.values[actionIndex] }} ({{ Math.round(group.percentages[actionIndex]) }}%)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint small">
|
||||
{{ selectedPlayerUuid ? 'Proporciones del jugador seleccionado' : 'Proporciones globales' }}.
|
||||
Los segmentos muestran la proporción relativa dentro de cada categoría.
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint small">
|
||||
{{ selectedPlayerUuid ? 'Proporciones del jugador seleccionado' : 'Proporciones globales' }}.
|
||||
Los segmentos muestran la proporción relativa dentro de cada categoría.
|
||||
|
||||
<!-- Slides 1..N: Pie per group -->
|
||||
<div v-else class="pie-wrapper">
|
||||
<div class="pie-canvas">
|
||||
<svg viewBox="0 0 400 400" preserveAspectRatio="xMidYMid meet" class="pie-svg">
|
||||
<defs>
|
||||
<filter id="pieShadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="6" stdDeviation="6" flood-color="rgba(0,0,0,0.15)" />
|
||||
</filter>
|
||||
</defs>
|
||||
<circle :cx="center" :cy="center" :r="outerR" class="pie-base" />
|
||||
<g filter="url(#pieShadow)">
|
||||
<path v-for="(seg, i) in currentPieSegments" :key="i"
|
||||
:d="describeArc(center, center, sliceR, seg.startAngle, seg.endAngle)"
|
||||
:fill="EVENT_STYLES[seg.action]?.color || '#94a3b8'"
|
||||
class="pie-slice" />
|
||||
</g>
|
||||
<circle :cx="center" :cy="center" :r="innerR" class="pie-hole" />
|
||||
<g class="pie-center">
|
||||
<text :x="center" :y="center - 4" text-anchor="middle" class="pie-center-title">{{ currentPie?.name || '' }}</text>
|
||||
<text :x="center" :y="center + 16" text-anchor="middle" class="pie-center-sub">{{ selectedPlayerUuid ? 'Jugador' : 'Global' }}</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="pie-legend">
|
||||
<div
|
||||
v-for="(action, idx) in currentPie?.actions || []"
|
||||
:key="action"
|
||||
class="legend-chip"
|
||||
:style="getLegendChipStyle(action)"
|
||||
>
|
||||
<span class="legend-icon">{{ EVENT_STYLES[action]?.icon || '📊' }}</span>
|
||||
<span class="legend-label">{{ currentPie?.labels[idx] }}</span>
|
||||
<span class="legend-count">{{ (currentPie?.values[idx] || 0) }} ({{ round((currentPie?.percentages[idx] || 0)) }}%)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint small">Usa el carrusel para navegar por las categorías.</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -131,7 +137,8 @@ interface Props {
|
||||
sourceData: string;
|
||||
};
|
||||
selectedPlayerUuid?: string;
|
||||
viewMode: 'count' | 'percent' | 'ratio';
|
||||
// viewMode no longer used; kept for backward compat
|
||||
viewMode?: 'count' | 'percent' | 'ratio';
|
||||
loading?: boolean;
|
||||
filtersCollapsed?: boolean;
|
||||
activeFilters?: {
|
||||
@@ -149,6 +156,9 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
loading: false
|
||||
});
|
||||
|
||||
// Carousel state: slide 0 = all ratio bars; slides 1..N = pies per group
|
||||
const currentSlide = ref(0);
|
||||
|
||||
// Event types and styles
|
||||
const EVENTS = [
|
||||
'p1_propose', 'p1_no_offer',
|
||||
@@ -238,8 +248,6 @@ const playerEventCounts = computed(() => {
|
||||
return playerCounts;
|
||||
});
|
||||
|
||||
const playerBarGradient = computed(() => '#8b5cf6');
|
||||
|
||||
// Group totals computation
|
||||
const groupTotals = computed(() => {
|
||||
const counts = globalEventCounts.value;
|
||||
@@ -348,47 +356,51 @@ const ratioData = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
// Global calculations
|
||||
const globalMax = computed(() => {
|
||||
const vals = eventTypes.value.map(k => globalEventCounts.value[k] || 0);
|
||||
const m = Math.max(0, ...vals);
|
||||
return m || 1;
|
||||
// Carousel + pies
|
||||
const pieGroups = computed(() => ratioData.value.filter(g => (g.total as number) > 0));
|
||||
const totalSlides = computed(() => 1 + pieGroups.value.length);
|
||||
const hasPrevSlide = computed(() => currentSlide.value > 0);
|
||||
const hasNextSlide = computed(() => currentSlide.value < totalSlides.value - 1);
|
||||
function prevSlide() { if (hasPrevSlide.value) currentSlide.value--; }
|
||||
function nextSlide() { if (hasNextSlide.value) currentSlide.value++; }
|
||||
const currentPie = computed(() => pieGroups.value[currentSlide.value - 1]);
|
||||
const slideTitle = computed(() => currentSlide.value === 0 ? 'Proporciones' : (currentPie.value?.name || ''));
|
||||
|
||||
// Convert group percentages into pie segments
|
||||
const currentPieSegments = computed(() => {
|
||||
const segments: { startAngle: number; endAngle: number; action: string }[] = [];
|
||||
if (!currentPie.value) return segments;
|
||||
let angle = -90; // start at 12 o'clock
|
||||
(currentPie.value.percentages as number[]).forEach((pct, idx) => {
|
||||
const span = (pct / 100) * 360;
|
||||
const seg = { startAngle: angle, endAngle: angle + span, action: currentPie.value!.actions[idx] };
|
||||
segments.push(seg);
|
||||
angle += span;
|
||||
});
|
||||
return segments;
|
||||
});
|
||||
|
||||
const globalTotal = computed(() =>
|
||||
eventTypes.value.reduce((acc, k) => acc + (globalEventCounts.value[k] || 0), 0) || 1
|
||||
);
|
||||
// Pie geometry based on fixed viewBox (400x400); SVG scales with CSS
|
||||
const center = 200;
|
||||
const outerR = 192;
|
||||
const sliceR = 186;
|
||||
const innerR = 102;
|
||||
|
||||
function globalBarWidth(eventType: string) {
|
||||
const v = globalEventCounts.value[eventType] || 0;
|
||||
return Math.round((v / (props.viewMode === 'percent' ? globalTotal.value : globalMax.value)) * 100);
|
||||
function polarToCartesian(cx: number, cy: number, r: number, angleDeg: number) {
|
||||
const rad = (angleDeg * Math.PI) / 180.0;
|
||||
return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) };
|
||||
}
|
||||
|
||||
function globalValueLabel(eventType: string) {
|
||||
const v = globalEventCounts.value[eventType] || 0;
|
||||
return props.viewMode === 'percent' ? `${Math.round((v / globalTotal.value) * 100)}%` : String(v);
|
||||
function describeArc(cx: number, cy: number, r: number, startAngle: number, endAngle: number) {
|
||||
const start = polarToCartesian(cx, cy, r, endAngle);
|
||||
const end = polarToCartesian(cx, cy, r, startAngle);
|
||||
const largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1';
|
||||
return `M ${cx} ${cy} L ${start.x} ${start.y} A ${r} ${r} 0 ${largeArcFlag} 0 ${end.x} ${end.y} Z`;
|
||||
}
|
||||
|
||||
// Player calculations
|
||||
const playerMax = computed(() => {
|
||||
const vals = eventTypes.value.map(k => playerEventCounts.value[k] || 0);
|
||||
const m = Math.max(0, ...vals);
|
||||
return m || 1;
|
||||
});
|
||||
function round(n: number) { return Math.round(n); }
|
||||
|
||||
const playerTotal = computed(() =>
|
||||
eventTypes.value.reduce((acc, k) => acc + (playerEventCounts.value[k] || 0), 0) || 1
|
||||
);
|
||||
|
||||
function playerBarWidth(eventType: string) {
|
||||
const v = playerEventCounts.value[eventType] || 0;
|
||||
return Math.round((v / (props.viewMode === 'percent' ? playerTotal.value : playerMax.value)) * 100);
|
||||
}
|
||||
|
||||
function playerValueLabel(eventType: string) {
|
||||
const v = playerEventCounts.value[eventType] || 0;
|
||||
return props.viewMode === 'percent' ? `${Math.round((v / playerTotal.value) * 100)}%` : String(v);
|
||||
}
|
||||
// (Removed legacy bar calculations)
|
||||
|
||||
// Styling helpers
|
||||
function getEventChipBg(eventType: string): string {
|
||||
@@ -403,21 +415,49 @@ function getEventBorderColor(eventType: string): string {
|
||||
return `${style.color}40`;
|
||||
}
|
||||
|
||||
function friendlyEventName(eventType: string): string {
|
||||
const friendlyNames: Record<string, string> = {
|
||||
p1_propose: 'Ofrecer',
|
||||
p1_no_offer: 'No Ofrecer',
|
||||
p2_snatch: 'Robar',
|
||||
p2_accept: 'Aceptar Oferta',
|
||||
p2_force: 'Forzar Oferta',
|
||||
p2_no_force: 'No Forzar Oferta',
|
||||
p2_reject: 'Rechazar Oferta',
|
||||
p1_shame: 'Asignar Vergüenza',
|
||||
p1_no_shame: 'No Asignar Vergüenza',
|
||||
p1_report: 'Denunciar',
|
||||
p1_no_report: 'No Denunciar',
|
||||
};
|
||||
return friendlyNames[eventType] || eventType;
|
||||
/* (Legacy friendlyEventName removed) */
|
||||
|
||||
// Legend chip coloring to closely match pie segments
|
||||
function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
|
||||
const h = hex.replace('#', '').trim();
|
||||
if (h.length === 3) {
|
||||
const r = parseInt(h[0] + h[0], 16);
|
||||
const g = parseInt(h[1] + h[1], 16);
|
||||
const b = parseInt(h[2] + h[2], 16);
|
||||
return { r, g, b };
|
||||
}
|
||||
if (h.length === 6) {
|
||||
const r = parseInt(h.slice(0, 2), 16);
|
||||
const g = parseInt(h.slice(2, 4), 16);
|
||||
const b = parseInt(h.slice(4, 6), 16);
|
||||
return { r, g, b };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function luminance(hex: string): number {
|
||||
const rgb = hexToRgb(hex);
|
||||
if (!rgb) return 1; // default to light
|
||||
const srgb = [rgb.r, rgb.g, rgb.b].map(v => {
|
||||
const c = v / 255;
|
||||
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
||||
});
|
||||
return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
|
||||
}
|
||||
|
||||
function rgba(hex: string, alpha: number): string {
|
||||
const rgb = hexToRgb(hex);
|
||||
if (!rgb) return `rgba(0,0,0,${alpha})`;
|
||||
return `rgba(${rgb.r},${rgb.g},${rgb.b},${alpha})`;
|
||||
}
|
||||
|
||||
function getLegendChipStyle(action: string) {
|
||||
const style = EVENT_STYLES[action];
|
||||
const base = style?.color || '#94a3b8';
|
||||
return {
|
||||
background: base,
|
||||
borderColor: rgba(base, 0.6)
|
||||
} as Record<string, string>;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -452,6 +492,24 @@ function friendlyEventName(eventType: string): string {
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
/* Carousel header */
|
||||
.carousel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.carousel-title { display: flex; align-items: center; gap: 10px; }
|
||||
.slide-count {
|
||||
font-weight: 800;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
background: rgba(99,102,241,0.06);
|
||||
border: 1px solid rgba(99,102,241,0.18);
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.ratio-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -606,69 +664,14 @@ function friendlyEventName(eventType: string): string {
|
||||
border: 1px solid rgba(99,102,241,0.3);
|
||||
}
|
||||
|
||||
/* Shimmer on global bars for subtle movement */
|
||||
.shimmer::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(110deg, transparent 30%, rgba(255,255,255,0.25) 50%, transparent 70%);
|
||||
transform: translateX(-100%);
|
||||
animation: shimmer 3.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { transform: translateX(-100%); }
|
||||
45% { transform: translateX(110%); }
|
||||
100% { transform: translateX(110%); }
|
||||
}
|
||||
/* (Legacy shimmer removed) */
|
||||
|
||||
.hint.small {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* Ratio bars styles */
|
||||
.ratio-bars {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.ratio-group {
|
||||
flex: 0 0 auto;
|
||||
min-height: 120px;
|
||||
margin-bottom: 8px;
|
||||
transition: transform .18s ease;
|
||||
}
|
||||
|
||||
.ratio-group.highlight {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
/* Group header styles */
|
||||
.ratio-group-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border: 1px solid rgba(229, 231, 235, 0.4);
|
||||
border-radius: 10px;
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.group-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
color: #1e293b;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
/* Ratio group styles */
|
||||
.group-total {
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
@@ -702,6 +705,97 @@ function friendlyEventName(eventType: string): string {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Pie styles */
|
||||
.pie-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.pie-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.pie-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.carousel-btn {
|
||||
appearance: none;
|
||||
border: 1px solid rgba(209, 213, 219, 0.9);
|
||||
background: white;
|
||||
color: #334155;
|
||||
font-weight: 900;
|
||||
font-size: 16px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.carousel-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
.pie-canvas {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: min(100%, 70vh);
|
||||
max-height: 70vh;
|
||||
aspect-ratio: 1;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.pie-svg { width: 100%; height: 100%; }
|
||||
.pie-base {
|
||||
fill: rgba(238,242,255,0.6);
|
||||
stroke: rgba(199,210,254,0.6);
|
||||
stroke-width: 1;
|
||||
}
|
||||
.pie-slice { stroke: rgba(255,255,255,0.9); stroke-width: 1; }
|
||||
.pie-hole {
|
||||
fill: rgba(255,255,255,0.9);
|
||||
stroke: rgba(229,231,235,0.8);
|
||||
stroke-width: 1;
|
||||
}
|
||||
.pie-center-title { font-weight: 900; font-size: 14px; fill: #1f2937; }
|
||||
.pie-center-sub { font-weight: 700; font-size: 11px; fill: #64748b; }
|
||||
.pie-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: clamp(6px, 1.2vw, 12px);
|
||||
}
|
||||
.legend-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: clamp(6px, 1.5vw, 18px);
|
||||
padding: clamp(4px, 1vw, 12px) clamp(8px, 2vw, 24px);
|
||||
border: 1px solid;
|
||||
border-radius: clamp(10px, 1.2vw, 14px);
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.35);
|
||||
}
|
||||
.legend-icon { font-size: clamp(14px, 3vw, 42px); }
|
||||
.legend-label, .legend-count {
|
||||
color: #111827; /* black-ish interior */
|
||||
font-weight: 900;
|
||||
/* white outline for legibility */
|
||||
text-shadow:
|
||||
0 1px 0 #ffffff,
|
||||
1px 0 0 #ffffff,
|
||||
0 -1px 0 #ffffff,
|
||||
-1px 0 0 #ffffff,
|
||||
1px 1px 0 #ffffff,
|
||||
-1px 1px 0 #ffffff,
|
||||
1px -1px 0 #ffffff,
|
||||
-1px -1px 0 #ffffff;
|
||||
}
|
||||
.legend-label { font-size: clamp(12px, 2.4vw, 36px); }
|
||||
.legend-count {
|
||||
padding: clamp(2px, 0.6vw, 6px) clamp(6px, 1.8vw, 18px);
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.75);
|
||||
border: 1px solid rgba(229,231,235,0.6);
|
||||
font-size: clamp(11px, 2.2vw, 33px);
|
||||
}
|
||||
|
||||
.ratio-segment {
|
||||
height: 100%;
|
||||
transition: all 0.6s cubic-bezier(.2,.7,.1,1);
|
||||
@@ -766,75 +860,12 @@ function friendlyEventName(eventType: string): string {
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
/* Responsive chip sizing */
|
||||
@media (min-width: 1200px) {
|
||||
.bar-chip {
|
||||
min-width: 200px;
|
||||
padding: 6px 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.event-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chip-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.chip-count {
|
||||
padding: 3px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1199px) {
|
||||
.bar-chip {
|
||||
min-width: 140px;
|
||||
padding: 5px 10px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.event-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.chip-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.chip-count {
|
||||
padding: 2px 6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.card { padding: 10px 12px; }
|
||||
.card-header { margin-bottom: 8px; }
|
||||
.card-title { font-size: 16px; }
|
||||
.bars.big .bar-row { min-height: 28px; }
|
||||
.ratio-group { min-height: 90px; }
|
||||
.ratio-bar { height: 42px; }
|
||||
.bar-chip {
|
||||
min-width: 120px;
|
||||
padding: 4px 8px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.event-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.chip-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.chip-count {
|
||||
padding: 2px 4px;
|
||||
font-size: 9px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
@@ -852,7 +883,6 @@ function friendlyEventName(eventType: string): string {
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
.bars.big .bar-row { min-height: 24px; }
|
||||
|
||||
.group-total {
|
||||
font-size: 12px;
|
||||
|
||||
Reference in New Issue
Block a user