1 звезда на GitHub = 1 новый компонент.

Поставить звезду
Графики и статистикаchartsvgzero-dependencyhistogramdistributionstatistics

Histogram Chart

Разбивает значения на равные интервалы и показывает распределение с отметками среднего и медианы.

Живое превью

Page load time

240 sessions, seconds

1.19smedian
02550751000.61.42.33.13.94.75.66.4
View data
BucketCount
0.6–1.0s81
1.0–1.4s78
1.4–1.8s38
1.8–2.3s25
2.3–2.7s14
2.7–3.1s1
3.1–3.5s0
3.5–3.9s1
3.9–4.3s1
4.3–4.7s0
4.7–5.1s0
5.1–5.6s0
5.6–6.0s0
6.0–6.4s1

Использование

Скопируйте и вставьте этот код в ваш React-компонент:

tsx
import { HistogramChart } from 'react-component-kit-ui/charts';

<HistogramChart values={loadTimes} bins={12} unit="s" showStats />

Ручная установка

Если вы не используете пакет npm, создайте файл и вставьте следующий код: src/components/HistogramChart.tsx + HistogramChart.css:

HistogramChart.tsx
'use client';

import ChartFrame from './ChartFrame';
import { scaleLinear, niceTicks, quantile, sum } from './chart-utils';

// Deterministic sample: a right-skewed page-load distribution.
const sample = Array.from({ length: 240 }, (_, i) => {
  const a = Math.sin(i * 12.9898) * 43758.5453;
  const b = Math.sin(i * 78.233) * 12345.678;
  const u = a - Math.floor(a);
  const v = b - Math.floor(b);
  return Math.round((0.6 + Math.abs(Math.sqrt(-2 * Math.log(u + 1e-9)) * Math.cos(2 * Math.PI * v)) * 0.9) * 100) / 100;
});

/**
 * HistogramChart — Bins raw values into equal-width buckets and draws the
 * distribution, with optional mean and median markers.
 *
 * @param {number[]} values - Raw observations.
 * @param {number} bins - Number of buckets.
 * @param {boolean} showStats - Draw mean & median reference lines.
 * @param {string} unit - Unit suffix for bucket edges.
 * @param {string} color - Bar color.
 * @param {string} variant - Card style (glass|solid|ghost).
 * @param {string} size - Density (sm|md|lg).
 */
export interface HistogramChartProps {
  title?: string;
  subtitle?: string;
  values?: number[];
  bins?: number;
  showStats?: boolean;
  unit?: string;
  height?: number;
  color?: string;
  variant?: 'glass' | 'solid' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

export function HistogramChart({
  title = 'Page load time',
  subtitle = '240 sessions, seconds',
  values = sample,
  bins = 14,
  showStats = true,
  unit = 's',
  height = 260,
  color = 'var(--chart-1, #3987e5)',
  variant = 'glass',
  size = 'md',
  className = '',
}: HistogramChartProps) {
  const W = 640;
  const H = height;
  const m = { t: 22, r: 12, b: 30, l: 40 };
  const sorted = [...values].sort((a, b) => a - b);
  const lo = sorted[0];
  const hi = sorted[sorted.length - 1];
  const width = (hi - lo) / bins || 1;
  const counts = Array.from({ length: bins }, () => 0);
  values.forEach((v) => {
    counts[Math.min(bins - 1, Math.floor((v - lo) / width))] += 1;
  });
  const yTicks = niceTicks(0, Math.max(...counts), 4);
  const y = scaleLinear(0, yTicks[yTicks.length - 1], H - m.b, m.t);
  const x = scaleLinear(lo, hi, m.l, W - m.r);
  const mean = sum(values) / values.length;
  const median = quantile(sorted, 0.5);
  const edge = (i: number) => (lo + i * width).toFixed(1);
  return (
    <>
      <ChartFrame title={title} subtitle={subtitle} value={`${median.toFixed(2)}${unit}`} delta="median" variant={variant} size={size} className={className} table={{ headers: ['Bucket', 'Count'], rows: counts.map((c, i) => [`${edge(i)}–${edge(i + 1)}${unit}`, c]) }}>
        <svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${title}: histogram of ${values.length} values, median ${median.toFixed(2)}${unit}`}>
          {yTicks.map((t, __i: number) => (
            <g key={__i}>
              <line className={t === 0 ? 'cf-axis' : 'cf-grid'} x1={m.l} x2={W - m.r} y1={y(t)} y2={y(t)} />
              <text className="cf-label" x={m.l - 8} y={y(t) + 4} textAnchor="end">{t}</text>
            </g>
          ))}
          {counts.map((c, i) => {
            const bx = x(lo + i * width) + 1;
            const bw = x(lo + (i + 1) * width) - x(lo + i * width) - 2;
            return (
              <rect className="cf-mark cf-dim" x={bx} y={y(c)} width={Math.max(1, bw)} height={Math.max(0, y(0) - y(c))} rx="2" fill={color} tabIndex={0} role="img" aria-label={`${edge(i)} to ${edge(i + 1)}${unit}: ${c} values`} data-tip-title={`${edge(i)}–${edge(i + 1)}${unit}`} data-tip={`${color}::${c} values · ${((c / values.length) * 100).toFixed(1)}%`} />
            );
          })}
          {Array.from({ length: bins + 1 }, (_, i) => i).filter((i) => i % 2 === 0).map((i, __i: number) => (
            <text key={__i} className="cf-label" x={x(lo + i * width)} y={H - 10} textAnchor="middle">{edge(i)}</text>
          ))}
          {showStats && (
            <g aria-hidden="true">
              <line x1={x(median)} x2={x(median)} y1={m.t - 6} y2={H - m.b} stroke="var(--c-text-1, #fff)" strokeWidth="1.5" />
              <text className="cf-label cf-label--strong" x={x(median) + 5} y={m.t}>Median</text>
              <line x1={x(mean)} x2={x(mean)} y1={m.t + 10} y2={H - m.b} stroke="var(--chart-2, #d95926)" strokeWidth="1.5" />
              <text className="cf-label" x={x(mean) + 5} y={m.t + 16}>Mean {mean.toFixed(2)}{unit}</text>
            </g>
          )}
        </svg>
      </ChartFrame>
    </>
  );
}

export default HistogramChart;

Краткая информация

Категория
Графики и статистика
Имя файла
HistogramChart.tsx
Зависимости
Нет — чистый React + CSS
Теги
chartsvgzero-dependencyhistogramdistributionstatistics