| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- <script>
- let { data = [], onPointSelect = () => {}, selectedPeriod = null } = $props();
- const chartWidth = 760;
- const chartHeight = 320;
- const padding = { top: 30, right: 20, bottom: 50, left: 50 };
- const innerWidth = chartWidth - padding.left - padding.right;
- const innerHeight = chartHeight - padding.top - padding.bottom;
- const maxValue = $derived(Math.max(1, ...data.map((item) => Math.max(item.gains, item.losses))));
- const points = $derived(
- data.map((item, index) => {
- const xStep = data.length > 1 ? innerWidth / (data.length - 1) : 0;
- const x = padding.left + index * xStep;
- const gainsY = padding.top + (1 - item.gains / maxValue) * innerHeight;
- const lossesY = padding.top + (1 - item.losses / maxValue) * innerHeight;
- return { ...item, x, gainsY, lossesY };
- })
- );
- const gainsPath = $derived(points.map((point) => `${point.x},${point.gainsY}`).join(' '));
- const lossesPath = $derived(points.map((point) => `${point.x},${point.lossesY}`).join(' '));
- const yAxisTicks = $derived([0, 0.25, 0.5, 0.75, 1].map((tick) => Math.round(maxValue * tick)));
- </script>
- <section class="h-[460px] flex flex-col rounded-xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-[#1e293b] p-5 shadow-sm transition-colors duration-200">
- <div class="mb-6 flex items-center justify-between border-b border-slate-100 dark:border-slate-800/50 pb-3 shrink-0">
- <h2 class="text-sm font-bold tracking-wider text-slate-500 dark:text-slate-400 uppercase">
- Opinião do Público
- </h2>
- <div class="flex items-center gap-4 text-xs font-bold">
- <span class="flex items-center gap-1.5 text-emerald-600 dark:text-emerald-400">
- <i class="h-2.5 w-2.5 rounded-full bg-emerald-500"></i>Positivo
- </span>
- <span class="flex items-center gap-1.5 text-red-600 dark:text-red-400">
- <i class="h-2.5 w-2.5 rounded-full bg-red-500"></i>Negativo
- </span>
- </div>
- </div>
- <div class="flex-1 w-full flex items-center justify-center pt-2">
- <svg viewBox={`0 0 ${chartWidth} ${chartHeight}`} class="h-full w-full overflow-visible">
- <!-- Background Grid -->
- {#each yAxisTicks as tick}
- {@const y = padding.top + (1 - tick / maxValue) * innerHeight}
- <line
- x1={padding.left}
- y1={y}
- x2={chartWidth - padding.right}
- y2={y}
- class="stroke-slate-200 dark:stroke-slate-700/80"
- stroke-dasharray="4 4"
- />
- <text x={padding.left - 12} y={y + 4} text-anchor="end" class="fill-slate-500 dark:fill-slate-400 text-xs font-medium">
- {tick}
- </text>
- {/each}
- <!-- Lines -->
- <polyline fill="none" points={gainsPath} stroke="#10b981" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="drop-shadow-sm" />
- <polyline fill="none" points={lossesPath} stroke="#ef4444" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="drop-shadow-sm" />
- <!-- Points -->
- {#each points as point}
- <g
- role="button"
- tabindex="0"
- aria-label={`Selecionar periodo ${point.period}`}
- onclick={() => onPointSelect(point)}
- onkeydown={(event) => (event.key === 'Enter' || event.key === ' ') && onPointSelect(point)}
- class="cursor-pointer group outline-none"
- >
- <!-- Gain Point -->
- <circle
- cx={point.x}
- cy={point.gainsY}
- r={selectedPeriod === point.period ? 6 : 4}
- class="fill-emerald-500 stroke-white dark:stroke-[#1e293b] stroke-2 transition-all duration-200 group-hover:r-6"
- />
- <!-- Loss Point -->
- <circle
- cx={point.x}
- cy={point.lossesY}
- r={selectedPeriod === point.period ? 6 : 4}
- class="fill-red-500 stroke-white dark:stroke-[#1e293b] stroke-2 transition-all duration-200 group-hover:r-6"
- />
- </g>
- <text x={point.x} y={chartHeight - 10} text-anchor="middle" class="fill-slate-500 dark:fill-slate-400 text-xs font-semibold">
- {point.period}
- </text>
- {/each}
- </svg>
- </div>
- </section>
|