Bibliotecas / Timeline

Timeline

Uma biblioteca baseada em React para construir visualizações de linha do tempo interativas com renderização em canvas.

@gravity-ui/timeline npm package Release storybook

Versão em Português

Uma biblioteca baseada em React para construir visualizações interativas de linha do tempo com renderização em canvas.

Documentação

Para detalhes, consulte Documentação.

Prévia

Linha do tempo básica com eventos e eixos:

Linha do tempo básica com eventos

Renderização personalizada com eventos aninhados expansíveis (NestedEvents exemplo):

Linha do tempo com eventos aninhados

Funcionalidades

  • Renderização baseada em canvas para alta performance
  • Linha do tempo interativa com capacidades de zoom e pan
  • Interações flexíveis com roda e touchpad, incluindo passagem de scroll vertical
  • Suporte para eventos, marcadores, seções, eixos e grade
  • Seções de fundo para organização visual e destaque de períodos de tempo
  • Agrupamento inteligente de marcadores com zoom automático para o grupo - Clique em marcadores agrupados para dar zoom em seus componentes individuais
  • Renderização virtualizada para melhor performance com grandes conjuntos de dados (ativa apenas quando o conteúdo da linha do tempo excede a viewport)
  • Aparência e comportamento personalizáveis
  • Suporte a TypeScript com definições de tipo completas
  • Integração com React com hooks personalizados

Instalação

npm install @gravity-ui/timeline

Uso

O componente de linha do tempo pode ser usado em aplicações React com a seguinte configuração básica:

import { TimelineCanvas, useTimeline } from '@gravity-ui/timeline/react';

const MyTimelineComponent = () => {
  const { timeline, api, start, stop } = useTimeline({
    settings: {
      start: Date.now(),
      end: Date.now() + 3600000, // 1 hora a partir de agora
      axes: [],
      events: [],
      markers: [],
      sections: []
    },
    viewConfiguration: {
      // Configuração de visualização opcional
    }
  });

  // timeline - Instância da linha do tempo
  // api - Instância do CanvasApi (o mesmo que timeline.api)
  // start - função para inicializar a linha do tempo com o canvas
  // stop - função para destruir a linha do tempo

  return (
    <div style={{ width: '100%', height: '100%' }}>
      <TimelineCanvas timeline={timeline} />
    </div>
  );
};

Estrutura do Eixo

Cada eixo possui a seguinte estrutura:

type TimelineAxis = {
  id: string;          // Identificador único do eixo
  tracksCount: number; // Número de trilhas no eixo
  top: number;         // Posição vertical (px)
  height: number;      // Altura por trilha (px)
};

Linhas Horizontais do Eixo

Configure a posição das linhas horizontais através de viewConfiguration.axes.linePosition:

  • "center" (padrão) desenha uma linha no centro de cada trilha.
  • "between" desenha uma linha após cada trilha, em sua borda inferior. Isso é útil para linhas no estilo de tabela com barras de eventos centralizadas.
viewConfiguration: {
  axes: {
    linePosition: 'between'
  }
}

Interações Flexíveis da Câmera

ZoomMode fornece predefinições de interação familiares, enquanto camera.interactions permite sobrescrever um gesto individual. Isso é útil quando uma linha do tempo está dentro de uma página com scroll vertical: mantenha o pan horizontal e o zoom do touchpad, mas permita que o scroll normal da roda alcance o contêiner pai.

import {ZoomMode} from '@gravity-ui/timeline';

const {timeline} = useTimeline({
  settings: { /* ... */ },
  viewConfiguration: {
    camera: {
      zoom: ZoomMode.DEFAULT,
      interactions: {
        verticalWheel: 'pass-through',
        horizontalWheel: 'pan',
        pinch: 'zoom',
      },
      zoomSensitivity: {
        in: 0.5,
        out: 0.5,
      },
      minRange: 5_000,
      maxRange: 1000 * 60 * 60 * 24 * 365,
    },
  },
});

Cada interação aceita 'zoom', 'pan' ou 'pass-through'. pinch representa um gesto de Ctrl+roda do touchpad do navegador. zoomSensitivity.in e zoomSensitivity.out multiplicam independentemente a velocidade de zoom-in e zoom-out: 1 é o padrão, valores menores são mais suaves e 0 desativa o zoom nessa direção. Pequenos deltas do touchpad são suavizados automaticamente. minRange e maxRange são durações em milissegundos; o mínimo é de 5 segundos por padrão e o máximo é irrestrito, a menos que configurado, então defina maxRange para limitar o quão longe os usuários podem dar zoom para fora. Veja o exemplo interativo Camera interactions Storybook.

Estrutura da Seção

Cada seção requer a seguinte estrutura:

type TimelineSection = {
  id: string;               // Identificador único da seção
  from: number;             // Timestamp de início
  to?: number;              // Timestamp de fim opcional (padrão para o fim da linha do tempo)
  color: string;            // Cor de fundo da seção
  hoverColor?: string;      // Cor opcional quando a seção está em hover
  renderer?: AbstractSectionRenderer; // Renderizador personalizado opcional (exportado do pacote)
};

As seções fornecem coloração de fundo para períodos de tempo e ajudam a organizar o conteúdo da linha do tempo visualmente:

const MyTimelineComponent = () => {
  const { timeline } = useTimeline({
    settings: {
      start: Date.now(),
      end: Date.now() + 3600000,
      axes: [],
      events: [],
      markers: [],
      sections: [
        {
          id: 'morning',
          from: Date.now(),
          to: Date.now() + 1800000, // 30 minutos
          color: 'rgba(255, 235, 59, 0.3)', // Amarelo semitransparente
          hoverColor: 'rgba(255, 235, 59, 0.4)'
        },
        {
          id: 'afternoon',
          from: Date.now() + 1800000,
          // 'to' não especificado - estende até o fim da linha do tempo
          color: 'rgba(76, 175, 80, 0.2)', // Verde semitransparente
          hoverColor: 'rgba(76, 175, 80, 0.3)'
        }
      ]
    },
    viewConfiguration: {
      sections: {
        hitboxPadding: 2 // Preenchimento para detecção de hover
      }
    }
  });

  return <TimelineCanvas timeline={timeline} />;
};

Estrutura de Marcadores

Cada marcador requer a seguinte estrutura:

type TimelineMarker = {
  time: number;           // Timestamp para a posição do marcador
  color: string;          // Cor da linha do marcador
  activeColor: string;    // Cor quando o marcador está selecionado (obrigatório)
  hoverColor: string;     // Cor quando o marcador está em hover (obrigatório)
  lineWidth?: number;     // Largura opcional da linha do marcador
  label?: string;         // Texto opcional do rótulo
  labelColor?: string;    // Cor opcional do rótulo
  renderer?: AbstractMarkerRenderer; // Renderizador customizado opcional
  nonSelectable?: boolean;// Se o marcador pode ser selecionado
  group?: boolean;        // Se o marcador representa um grupo
};

Agrupamento e Zoom de Marcadores

A linha do tempo agrupa automaticamente marcadores que estão próximos e oferece funcionalidade de zoom:

const MyTimelineComponent = () => {
  const { timeline } = useTimeline({
    settings: {
      start: Date.now(),
      end: Date.now() + 3600000,
      axes: [],
      events: [],
      markers: [
        // Estes marcadores serão agrupados
        { time: Date.now(), color: '#ff0000', activeColor: '#ff5252', hoverColor: '#ff1744', label: 'Evento 1' },
        { time: Date.now() + 1000, color: '#ff0000', activeColor: '#ff5252', hoverColor: '#ff1744', label: 'Evento 2' },
        { time: Date.now() + 2000, color: '#ff0000', activeColor: '#ff5252', hoverColor: '#ff1744', label: 'Evento 3' },
      ]
    },
    viewConfiguration: {
      markers: {
        collapseMinDistance: 8,        // Agrupa marcadores em até 8 pixels
        groupZoomEnabled: true,        // Habilita zoom ao clicar no grupo
        groupZoomPadding: 0.3,        // 30% de preenchimento ao redor do grupo
        groupZoomMaxFactor: 0.3,      // Fator máximo de zoom
      }
    }
  });

  // Escuta eventos de zoom de grupo
  useTimelineEvent(timeline, 'on-group-marker-click', (data) => {
    console.log('Grupo com zoom:', data);
  });

  return <TimelineCanvas timeline={timeline} />;
};

Como Funciona

O componente de linha do tempo é construído usando React e oferece uma maneira flexível de criar visualizações de linha do tempo interativas. Veja como funciona:

Arquitetura do Componente

A linha do tempo é implementada como um componente React que pode ser configurado através de dois objetos principais:

  1. TimelineSettings: Controla o comportamento e a aparência principal da linha do tempo

    • start: Hora de início da linha do tempo
    • end: Hora de término da linha do tempo
    • axes: Array de configurações de eixos (veja a estrutura abaixo)
    • events: Array de configurações de eventos
    • markers: Array de configurações de marcadores
    • sections: Array de configurações de seções
  2. ViewConfiguration: Gerencia a representação visual e as configurações de interação

    • Controla a aparência, níveis de zoom e comportamento de interação
    • Pode ser personalizado ou usar valores padrão

Tratamento de Eventos

O componente de linha do tempo suporta vários eventos interativos:

  • on-click: Disparado ao clicar na linha do tempo
  • on-context-click: Disparado ao clicar com o botão direito/menu de contexto
  • on-select-change: Disparado quando a seleção muda
  • on-hover: Disparado ao passar o mouse sobre elementos da linha do tempo
  • on-leave: Disparado quando o mouse sai de elementos da linha do tempo

Exemplo de tratamento de eventos:

import { useTimelineEvent } from '@gravity-ui/timeline/react';

const MyTimelineComponent = () => {
  const { timeline } = useTimeline({ /* ... */ });

  useTimelineEvent(timeline, 'on-click', (data) => {
    console.log('Linha do tempo clicada:', data);
  });

  useTimelineEvent(timeline, 'on-select-change', (data) => {
    console.log('Seleção alterada:', data);
  });

  return <TimelineCanvas timeline={timeline} />;
};

Integração com React

O componente usa hooks customizados para gerenciamento da linha do tempo:

  • useTimeline: Gerencia a instância da linha do tempo e seu ciclo de vida

    • Cria e inicializa a linha do tempo
    • Lida com a limpeza ao desmontar o componente
    • Fornece acesso à instância da linha do tempo
  • useTimelineEvent: Lida com a assinatura de eventos e limpeza

    • Gerencia o ciclo de vida do ouvinte de eventos
    • Limpa automaticamente os ouvintes ao desmontar

O componente lida automaticamente com a limpeza e destruição da instância da linha do tempo quando desmontado.

Instale @gravity-ui/uikit e seus estilos para exibir detalhes do evento sem precisar assinar eventos de hover ou calcular coordenadas:

import '@gravity-ui/uikit/styles/fonts.css';
import '@gravity-ui/uikit/styles/styles.css';
import {EventPopup} from '@gravity-ui/timeline/react/uikit';

<>
  <TimelineCanvas timeline={timeline} />
  <EventPopup
    timeline={timeline}
    content={(event) => <EventDetails event={event} />}
  />
</>

O EventPopup abre após 150 ms e fecha 200 ms após o ponteiro sair do evento. Defina openDelay, closeDelay, placement, offset, className ou aria-label quando necessário. O popup permanece aberto enquanto seu conteúdo tiver ponteiro ou foco, fecha ao pressionar Escape ou clicar fora, e usa o último evento na ordem dos dados quando os eventos se sobrepõem. hoverColor e isHovered controlam o desenho do evento; o EventPopup controla sua UI de detalhes.

Estrutura de Evento

Eventos na linha do tempo seguem esta estrutura:

type TimelineEvent = {
  id: string;             // Identificador único
  from: number;           // Timestamp de início
  to?: number;            // Timestamp de fim (opcional para eventos pontuais)
  axisId: string;         // ID do eixo ao qual este evento pertence
  trackIndex: number;     // Índice na trilha do eixo
  renderer?: AbstractEventRenderer; // Renderizador customizado opcional
  color?: string;         // Cor opcional do evento
  hoverColor?: string;    // Cor opcional para o estado de hover
  selectedColor?: string; // Cor opcional para o estado selecionado
  cursor?: string;        // Cursor CSS opcional ao passar o mouse sobre o evento
};

Defina cursor: 'pointer' em eventos que realizam uma ação ao serem clicados. O cursor é aplicado apenas enquanto o ponteiro estiver sobre o evento; quando eventos se sobrepõem, o último evento na ordem dos dados determina o cursor.

Cores do Gravity UI

O Canvas não consegue resolver propriedades CSS customizadas por si só. O Timeline resolve um valor completo var(--token) em relação ao seu elemento canvas, portanto, os tokens semânticos do Gravity UI funcionam para eventos, marcadores, seções, eixos, grid e régua integrados.

import '@gravity-ui/uikit/styles/fonts.css';
import '@gravity-ui/uikit/styles/styles.css';
import {ThemeProvider} from '@gravity-ui/uikit';
import {useTimeline} from '@gravity-ui/timeline/react';
import {GravityTimelineCanvas} from '@gravity-ui/timeline/react/uikit';

<ThemeProvider theme="light">
  <GravityTimelineCanvas timeline={timeline} />
</ThemeProvider>

Passe tokens diretamente em qualquer campo de cor, por exemplo color: 'var(--g-color-base-positive-medium)'. O GravityTimelineCanvas redesenha automaticamente quando o tema efetivo do Gravity UI muda. Para um token ausente, use um fallback CSS como var(--app-event-color, transparent) ou chame timeline.api.resolveColor(color, fallback) de um renderizador customizado.

Para eventos, color é usado normalmente, hoverColor ao passar o mouse, e selectedColor após a seleção:

const events = [
  {
    id: 'deploy',
    from: start,
    to: end,
    axisId: 'main',
    trackIndex: 0,
    color: 'var(--g-color-base-positive-medium)',
    hoverColor: 'var(--g-color-base-positive-medium-hover)',
    selectedColor: 'var(--g-color-base-positive-heavy)',
  },
];

Renderizadores de eventos customizados recebem resolveColor como seu último argumento opcional; renderizadores de marcadores e seções customizados o recebem em seus dados de renderização.

Fontes do Canvas

Defina viewConfiguration.font uma vez para configurar a fonte padrão para a régua, eventos e marcadores. Um ruler.font, events.font ou markers.font específico do componente tem precedência. O padrão permanece 10px sans-serif.

O Canvas não pode usar variáveis CSS ou inherit diretamente em ctx.font, então o Timeline resolve tokens de valor completo no contexto CSS do canvas:

viewConfiguration: {
  font: 'var(--g-text-caption-2-font)',
}

Use font: 'inherit' para usar a fonte computada do elemento canvas. Renderizadores customizados recebem resolveFont junto com resolveColor, ou podem chamar timeline.api.resolveFont(font). Após uma fonte web carregar dinamicamente, chame timeline.api.rerender() para redesenhar o texto do canvas com ela.

Uso Direto de TypeScript

A classe Timeline pode ser usada diretamente em TypeScript sem React. Isso é útil para integrar com outros frameworks ou aplicações JavaScript vanilla:

import { Timeline } from '@gravity-ui/timeline';

const timestamp = Date.now();

// Cria uma instância do timeline
const timeline = new Timeline({
  settings: {
    start: timestamp,
    end: timestamp + 3600000, // 1 hora a partir de agora
    axes: [
      {
        id: 'main',
        tracksCount: 3,
        top: 0,
        height: 100
      }
    ],
    events: [
      {
        id: 'event1',
        from: timestamp + 1800000, // 30 minutos a partir de agora
        to: timestamp + 2400000,   // 40 minutos a partir de agora
        label: 'Evento de Exemplo',
        axisId: 'main'
      }
    ],
    markers: [
      {
        id: 'marker1',
        time: timestamp + 1200000, // 20 minutos a partir de agora
        label: 'Ponto Importante',
        color: '#ff0000',
        activeColor: '#ff5252',
        hoverColor: '#ff1744'
      }
    ],
    sections: [
      {
        id: 'section1',
        from: timestamp,
        to: timestamp + 1800000, // Primeiros 30 minutos
        color: 'rgba(33, 150, 243, 0.2)', // Fundo azul claro
        hoverColor: 'rgba(33, 150, 243, 0.3)'
      }
    ]
  },
  viewConfiguration: {
    // Opcional: personalizar configurações de visualização
    zoomLevels: [1, 2, 4, 8, 16],
    hideRuler: false,
    showGrid: true
  }
});

// Inicializa com um elemento canvas
const canvas = document.querySelector('canvas');
if (canvas instanceof HTMLCanvasElement) {
  timeline.init(canvas);
}

// Adiciona ouvintes de eventos
timeline.on('on-click', (detail) => {
  console.log('Timeline clicado:', detail);
});

timeline.on('on-select-change', (detail) => {
  console.log('Seleção alterada:', detail);
});

// Limpa quando terminar
timeline.destroy();

A classe Timeline fornece uma API rica para gerenciar o timeline:

  • Gerenciamento de Eventos:

    // Adiciona um ouvinte de evento
    timeline.on('eventClick', (detail) => {
      console.log('Evento clicado:', detail);
    });
    
    // Remove um ouvinte de evento
    const handler = (detail) => console.log(detail);
    timeline.on('eventClick', handler);
    timeline.off('eventClick', handler);
    
    // Emite eventos customizados
    timeline.emit('customEvent', { data: 'dados customizados' });
    
  • Controle do Timeline:

    // Atualiza os dados do timeline
    timeline.api.setEvents([
      {
        id: 'novoEvento',
        from: Date.now(),
        to: Date.now() + 3600000,
        label: 'Novo Evento',
        axisId: 'main',
        trackIndex: 0
      }
    ]);
    
    // Atualiza os eixos
    timeline.api.setAxes([
      {
        id: 'novoEixo',
        tracksCount: 2,
        top: 0,
        height: 80
      }
    ]);
    

```javascript
  // Atualiza marcadores
  timeline.api.setMarkers([
    {
      id: 'newMarker',
      time: Date.now(),
      label: 'Novo Marcador',
      color: '#00ff00',
      activeColor: '#4caf50',
      hoverColor: '#2e7d32'
    }
  ]);

  // Atualiza seções
  timeline.api.setSections([
    {
      id: 'newSection',
      from: Date.now(),
      to: Date.now() + 1800000,
      color: 'rgba(255, 193, 7, 0.2)', // Fundo âmbar claro
      hoverColor: 'rgba(255, 193, 7, 0.3)'
    }
  ]);

  // Atualiza configuração de visualização (mescla com a configuração atual)
  timeline.api.setViewConfiguration({ hideRuler: true });

Exemplos ao Vivo

Explore exemplos interativos em nosso Storybook:

Desenvolvimento

Storybook

Este projeto inclui o Storybook para desenvolvimento e documentação de componentes.

Para executar o Storybook:

npm run storybook

Isso iniciará o servidor de desenvolvimento do Storybook na porta 6006. Você pode acessá-lo em http://localhost:6006.

Para compilar uma versão estática do Storybook para implantação:

npm run build-storybook

Licença

MIT

Sobre a biblioteca
Apoie a biblioteca com uma estrela
Versão
1.32.2
Última atualização
22.09.2026
Repositório
github.com/gravity-ui/timeline
Licença
MIT License
Mantenedores
Colaboradores