1Minimal OTel trace from URL

The simplest possible usage: point data-url at a pre-built OTLP JSON file. The component fetches it, parses the OTel structure, and renders the waterfall. full-width stretches it to fill the container.

HTML
<trace-visualizer
  data-url="./otel-trace.json"
  full-width>
</trace-visualizer>

<script type="module" src="./dist/log2trace.min.js"></script>
▶ Live result

2Direct traceData setter

Instead of a URL fetch, assign a TraceData object directly to the .traceData property. Useful when your data is already in memory or comes from a non-HTTP source. The OTel structure uses nanosecond timestamps as strings.

JavaScript
const el = document.querySelector('#ex2');

el.traceData = {
  resourceSpans: [
    {
      resource: { attributes: [{ key: 'service.name', value: { stringValue: 'frontend' } }] },
      scopeSpans: [{
        scope: { name: 'my-tracer' },
        spans: [
          {
            traceId: 'aaaa', spanId: 's1', name: 'HTTP GET /home',
            kind: 2, // Server
            startTimeUnixNano: '1700000000000000000',
            endTimeUnixNano:   '1700000000250000000',
            status: { code: 1 }
          }
        ]
      }]
    },
    {
      resource: { attributes: [{ key: 'service.name', value: { stringValue: 'backend' } }] },
      scopeSpans: [{
        scope: { name: 'my-tracer' },
        spans: [
          {
            traceId: 'aaaa', spanId: 's2', parentSpanId: 's1', name: 'db.query users',
            kind: 3, // Client
            startTimeUnixNano: '1700000000050000000',
            endTimeUnixNano:   '1700000000210000000',
            status: { code: 1 }
          },
          {
            traceId: 'aaaa', spanId: 's3', parentSpanId: 's1', name: 'cache.get session',
            kind: 1, // Internal
            startTimeUnixNano: '1700000000010000000',
            endTimeUnixNano:   '1700000000040000000',
            status: { code: 1 }
          }
        ]
      }]
    }
  ]
};
▶ Live result

3Log transformation — HTML attributes

Your logs can stay in their native format. Declare field mappings as HTML attributes and the component applies transformLogs() internally. Dot-path notation (text.action) traverses nested objects.

Required transform attributes: trace-id-field, span-name-field, service-name-field, timestamp-field. All others are optional enhancements.
HTML + JavaScript
<trace-visualizer id="ex3" full-width
  trace-id-field="requestId"
  span-name-field="action"
  service-name-field="service"
  timestamp-field="timestamp">
</trace-visualizer>
JavaScript — assign logs array
// Logs are arbitrary objects — no schema required
document.querySelector('#ex3').logData = {
  logs: SIMPLE_LOGS,           // defined at page level (see data section below)
  config: {
    traceIdField:    'requestId',
    spanNameField:   'action',
    serviceNameField:'service',
    timestampField:  'timestamp',
  }
};
▶ Live result

4Log transformation — JS .logData setter

The .logData setter accepts { logs, config } directly — no HTML attributes needed. The config object mirrors all TraceVisualizerConfig transform fields. Use this when building dynamic UIs or when config comes from an API.

This example also demonstrates spanGroupFields — grouping multiple log entries into a single span when they share the same service + action combination.

JavaScript
document.querySelector('#ex4').logData = {
  logs: SIMPLE_LOGS,
  config: {
    traceIdField:     'requestId',
    spanGroupFields:  ['service', 'action'],  // group logs by service + action
    spanNameField:    'action',
    serviceNameField: 'service',
    timestampField:   'timestamp',
    endTimeField:     'endTimestamp',         // optional explicit end time
    statusCodeField:  'statusCode',           // non-zero → error status
  }
};
▶ Live result

5Display options

Control visual appearance: row height, padding, background color, legend visibility, and the detail panel width. All can be set as HTML attributes or via the .config setter.

HTML
<trace-visualizer
  full-width
  show-legend
  span-height="40"
  span-padding="8"
  background-color="#f8f9ff"
  detail-panel-width="35%">
</trace-visualizer>
▶ Live result

6Custom color scheme

Map each SpanKind numeric value to a CSS hex color via the color-scheme attribute (JSON string) or the colorScheme config property. Keys correspond to: 0 Unspecified, 1 Internal, 2 Server, 3 Client, 4 Producer, 5 Consumer.

HTML
<trace-visualizer
  full-width show-legend
  color-scheme='{
    "0": "#b0bec5",
    "1": "#7e57c2",
    "2": "#26a69a",
    "3": "#ef5350",
    "4": "#ff7043",
    "5": "#ab47bc"
  }'>
</trace-visualizer>
▶ Live result

7Explicit width & height sizing

By default the component fits its content (width=0, height=0). Set explicit pixel dimensions to constrain it, or use full-width to stretch horizontally. When height is set, the timeline area becomes scrollable.

HTML — fixed 700×250 px
<trace-visualizer width="700" height="250"></trace-visualizer>
▶ Live result — fixed 700×250 px

HTML — full-width, height auto
<trace-visualizer full-width></trace-visualizer>
▶ Live result — full-width

8Text filter (local)

Add a <trace-filter type="text" source="local"> child element to render a text input above the timeline. source="local" means filtering is applied client-side — no server round-trip. Try typing a service or action name.

HTML
<trace-visualizer full-width>
  <trace-filter
    field="spanName"
    label="Operation"
    type="text"
    source="local"
    placeholder="Filter by operation name..."
    width="280"
    debounce="300">
  </trace-filter>
</trace-visualizer>
▶ Live result — try filtering

9Dropdown filter — auto & static options

options-source="auto" derives dropdown choices from the loaded data automatically. Alternatively, provide a static JSON array via the options attribute — either plain strings or {"value","label"} objects.

HTML
<trace-visualizer full-width>
  <!-- Auto-populated from data -->
  <trace-filter
    field="serviceName"
    label="Service"
    type="dropdown"
    source="local"
    options-source="auto"
    placeholder="All services">
  </trace-filter>

  <!-- Static options with value/label pairs -->
  <trace-filter
    field="spanKind"
    label="Span Kind"
    type="dropdown"
    source="local"
    options='[
      {"value":"1","label":"Internal"},
      {"value":"2","label":"Server"},
      {"value":"3","label":"Client"}
    ]'
    placeholder="All kinds">
  </trace-filter>
</trace-visualizer>
▶ Live result

10Checkbox filter — errors only

The special field "hasError" filters to spans whose status code is Error (2). Use it with type="checkbox" to give users a one-click "show errors only" toggle.

HTML
<trace-visualizer full-width>
  <trace-filter
    field="hasError"
    label="Errors Only"
    type="checkbox"
    source="local">
  </trace-filter>
</trace-visualizer>
▶ Live result — check the box to filter to errors

11Datetime-range filter

Renders two datetime inputs (From / To). The auto-range attribute pre-fills min/max from the loaded data. The filter value is an object { from?: string, to?: string } with ISO 8601 strings.

HTML
<trace-visualizer full-width>
  <trace-filter
    field="startTime"
    label="Time Range"
    type="datetime-range"
    source="local"
    auto-range>
  </trace-filter>
</trace-visualizer>
▶ Live result — adjust date range to filter spans

12Multiselect filter

Like dropdown but allows selecting multiple values simultaneously. The filter value is a string[]. Use options-source="auto" or provide a static options array.

HTML
<trace-visualizer full-width>
  <trace-filter
    field="serviceName"
    label="Services"
    type="multiselect"
    source="local"
    options-source="auto"
    width="220">
  </trace-filter>
</trace-visualizer>
▶ Live result — select one or more services

13Wildcard search field="*"

Setting field="*" searches across all span fields: name, service, span ID, all attributes, all event names and event attributes. Great as a global search box.

HTML
<trace-visualizer full-width>
  <trace-filter
    field="*"
    label="Search"
    type="text"
    source="local"
    placeholder="Search all fields..."
    width="320">
  </trace-filter>
</trace-visualizer>
▶ Live result — type anything to search all fields

14All filter types combined

Multiple <trace-filter> elements render a filter bar. Local filters use AND logic — a span must satisfy every active filter simultaneously. The target attribute controls whether the filter matches span-level fields ("span", default) or individual log/event attributes ("log").

HTML
<trace-visualizer full-width show-legend>

  <!-- Wildcard global search -->
  <trace-filter field="*" label="Search" type="text" source="local"
    placeholder="Search all..." width="220"></trace-filter>

  <!-- Span name substring -->
  <trace-filter field="spanName" label="Operation" type="text" source="local"
    placeholder="e.g. query" width="200"></trace-filter>

  <!-- Service dropdown (auto) -->
  <trace-filter field="serviceName" label="Service" type="dropdown" source="local"
    options-source="auto" placeholder="All"></trace-filter>

  <!-- Errors only -->
  <trace-filter field="hasError" label="Errors Only" type="checkbox"
    source="local"></trace-filter>

  <!-- Datetime range -->
  <trace-filter field="startTime" label="Time Range" type="datetime-range"
    source="local" auto-range></trace-filter>

  <!-- Multi-service select -->
  <trace-filter field="serviceName" label="Services" type="multiselect" source="local"
    options-source="auto" width="180"></trace-filter>

</trace-visualizer>
▶ Live result — all filter types active

15External filter & custom fetchCallback

source="external" filters are not applied client-side — their values are forwarded to the server via query parameters (or to your fetchCallback). required gates the initial fetch until the field is filled. auto-fetch="false" shows a Search button instead of auto-triggering on change. The fetchCallback receives the URL and all active external filter values.

This example mocks the server with a 600 ms delay so you can see the loading state.

HTML
<trace-visualizer id="ex15" full-width auto-fetch="false">

  <trace-filter
    field="requestId"
    label="Request ID"
    type="dropdown"
    source="external"
    required
    options='["req-001","req-002"]'
    placeholder="Select a trace...">
  </trace-filter>

  <trace-filter
    field="service"
    label="Service"
    type="multiselect"
    source="external"
    options='["api","auth","db"]'
    width="160">
  </trace-filter>

</trace-visualizer>
JavaScript — mock fetchCallback
document.querySelector('#ex15').fetchCallback = async (url, filters) => {
  console.log('Fetching with filters:', filters);
  // Simulate a 600 ms server round-trip
  await new Promise(r => setTimeout(r, 600));
  // Return data filtered by the selected requestId
  const id = filters.requestId;
  const logs = SIMPLE_LOGS.filter(l => !id || l.requestId === id);
  return logs; // component will call transformLogs() if transform config is set
};
▶ Live result — select a Request ID, then click Search

16Span-kind classification rules

<span-kind-rule> children classify spans based on log field values. Rules are checked in order; the first match wins. Use match-field + match-value for a single-field check, or the match JSON attribute for a multi-field AND check. Unmatched spans fall back to default-span-kind.

HTML
<trace-visualizer full-width show-legend default-span-kind="Internal">

  <!-- Single-field shorthand -->
  <span-kind-rule kind="Server"   match-field="service" match-value="api"></span-kind-rule>
  <span-kind-rule kind="Consumer" match-field="service" match-value="auth"></span-kind-rule>

  <!-- Multi-field JSON match (all conditions must match) -->
  <span-kind-rule kind="Client"
    match='{"service":"db","action":"db.query"}'>
  </span-kind-rule>

</trace-visualizer>
▶ Live result — notice the different colors per span kind

17span-selected event

Fired when the user clicks a span bar. event.detail.span is the full Span OTel object. Use this to drive custom side panels, analytics, or cross-component navigation.

JavaScript
document.querySelector('#ex17').addEventListener('span-selected', (e) => {
  const { span } = e.detail;
  document.querySelector('#ex17-output').textContent =
    JSON.stringify({
      name:      span.name,
      kind:      span.kind,
      spanId:    span.spanId,
      parentId:  span.parentSpanId,
      status:    span.status,
      startNs:   span.startTimeUnixNano,
      endNs:     span.endTimeUnixNano,
      attributes: span.attributes,
    }, null, 2);
});
▶ Live result — click any span bar
Selected span (JSON):
// click a span above to see its data here

18filter-changed event

Fired whenever any filter value changes. The event detail contains { field, source, value, allFilters }. Use this for analytics, URL state sync, or custom logic that runs alongside the built-in filtering.

JavaScript
document.querySelector('#ex18').addEventListener('filter-changed', (e) => {
  const log = document.querySelector('#ex18-output');
  const entry = `[${new Date().toLocaleTimeString()}] field="${e.detail.field}" `
    + `source="${e.detail.source}" value=${JSON.stringify(e.detail.value)}`;
  log.textContent = entry + '\n' + log.textContent;
});
▶ Live result — change any filter
Event log:
// change a filter above to see events here

19Keyboard navigation

Click anywhere on the visualizer to focus it, then use the keyboard to navigate spans and control the timeline. No configuration required — this is built in.

KeyAction
/ Select previous / next span
/ Pan timeline left / right
+ / =Zoom in
- / _Zoom out
0 / HomeReset zoom to 100 %
Enter / SpaceOpen detail panel for selected span
EscapeClose detail panel
Mouse interactions: scroll wheel to zoom, drag to pan, double-click to reset zoom, click a span to open its detail panel.
▶ Live result — click the timeline, then use arrow keys

20loadTraceData() & loadAndTransform() methods

These async methods let you trigger a fetch imperatively — for example, from a button click or a WebSocket message. Both return a Promise<void> that resolves when rendering is complete.

JavaScript
const el = document.querySelector('#ex20');

// Load a pre-built OTel trace from a URL
async function loadOtel() {
  await el.loadTraceData('./otel-trace.json');
}

// Fetch raw logs and transform them
async function loadLogs() {
  await el.loadAndTransform('./logs.json', {
    traceIdField:     'text.BTMID',
    spanNameField:    'text.Action',
    serviceNameField: 'text.Application',
    timestampField:   'text.CreateDate',
  });
}
▶ Live result

21Empty & error states

The component renders a built-in placeholder when there is no data, and an error message when a fetch fails. An empty TraceData object produces the "no spans" state.

HTML — no data (empty state)
<!-- No data-url and no programmatic data → empty state -->
<trace-visualizer full-width></trace-visualizer>
▶ Empty state

HTML — bad URL (error state)
<trace-visualizer data-url="./does-not-exist.json" full-width></trace-visualizer>
▶ Error state (404)

JavaScript — empty TraceData
document.querySelector('#ex21c').traceData = { resourceSpans: [] };
▶ Empty TraceData (zero spans)

22transformLogs() standalone utility

Import transformLogs directly to convert log arrays to OTel TraceData without rendering anything. Useful for pre-processing, serialization, or feeding data to other OTel-compatible tools.

JavaScript
import { transformLogs } from './dist/log2trace.min.js';

const traceData = transformLogs(SIMPLE_LOGS, {
  traceIdField:     'requestId',
  spanGroupFields:  ['service', 'action'],
  spanNameField:    'action',
  serviceNameField: 'service',
  timestampField:   'timestamp',
  statusCodeField:  'statusCode',
});

// traceData is a standard OTLP TraceData object
console.log(JSON.stringify(traceData, null, 2));
▶ Output — TraceData JSON
// click the button above to run the transformation

23Full advanced setup

Everything combined: log transformation with parentSpanLookupFields, span-kind classification rules, all filter types, a custom color scheme, a span-selected event listener, and a mock fetchCallback. This is the pattern for a production integration.

HTML
<trace-visualizer id="ex23" full-width show-legend
  auto-fetch="false"
  trace-id-field="requestId"
  span-group-fields="service,action"
  span-name-field="action"
  service-name-field="service"
  timestamp-field="timestamp"
  end-time-field="endTimestamp"
  status-code-field="statusCode"
  parent-span-lookup-fields="parentService"
  default-span-kind="Internal"
  span-height="32"
  span-padding="6"
  detail-panel-width="38%"
  color-scheme='{"0":"#90a4ae","1":"#7e57c2","2":"#26a69a","3":"#ef5350","4":"#ff7043","5":"#ab47bc"}'>

  <!-- Span-kind rules -->
  <span-kind-rule kind="Server"   match-field="service" match-value="api"></span-kind-rule>
  <span-kind-rule kind="Consumer" match-field="service" match-value="auth"></span-kind-rule>
  <span-kind-rule kind="Client"   match='{"service":"db","action":"db.query"}'></span-kind-rule>

  <!-- Filters -->
  <trace-filter field="requestId" label="Trace" type="dropdown"
    source="external" required options='["req-001","req-002"]'
    placeholder="Select trace..."></trace-filter>

  <trace-filter field="*" label="Search" type="text" source="local"
    placeholder="Search all fields..." width="220"></trace-filter>

  <trace-filter field="serviceName" label="Services" type="multiselect"
    source="local" options-source="auto" width="180"></trace-filter>

  <trace-filter field="hasError" label="Errors Only"
    type="checkbox" source="local"></trace-filter>

  <trace-filter field="startTime" label="Time Range"
    type="datetime-range" source="local" auto-range></trace-filter>

</trace-visualizer>
JavaScript
const el23 = document.querySelector('#ex23');

// Mock server: filter by requestId, simulate 500 ms latency
el23.fetchCallback = async (url, filters) => {
  await new Promise(r => setTimeout(r, 500));
  const id = filters.requestId;
  return id ? SIMPLE_LOGS.filter(l => l.requestId === id) : SIMPLE_LOGS;
};

// React to span selection
el23.addEventListener('span-selected', (e) => {
  document.querySelector('#ex23-output').textContent =
    `Selected: "${e.detail.span.name}" (${e.detail.span.spanId})`;
});
▶ Live result — select a Trace, then use filters & click spans
// click a span above to see its name here