-
-
-
+
-
{{ EVENT_STYLES[action]?.icon || '📊' }}
-
{{ group.labels[actionIndex] }}
-
{{ group.values[actionIndex] }} ({{ Math.round(group.percentages[actionIndex]) }}%)
+
+ {{ EVENT_STYLES[action]?.icon || '📊' }}
+ {{ group.labels[actionIndex] }}
+ {{ group.values[actionIndex] }} ({{ Math.round(group.percentages[actionIndex]) }}%)
+
+
+ {{ selectedPlayerUuid ? 'Proporciones del jugador seleccionado' : 'Proporciones globales' }}.
+ Los segmentos muestran la proporción relativa dentro de cada categoría.
+
-
- {{ selectedPlayerUuid ? 'Proporciones del jugador seleccionado' : 'Proporciones globales' }}.
- Los segmentos muestran la proporción relativa dentro de cada categoría.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ currentPie?.name || '' }}
+ {{ selectedPlayerUuid ? 'Jugador' : 'Global' }}
+
+
+
+
+
+ {{ EVENT_STYLES[action]?.icon || '📊' }}
+ {{ currentPie?.labels[idx] }}
+ {{ (currentPie?.values[idx] || 0) }} ({{ round((currentPie?.percentages[idx] || 0)) }}%)
+
+
+
Usa el carrusel para navegar por las categorías.
@@ -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
(), {
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 = {
- 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;
}
@@ -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;