Convert to using a custom historgram based approach using direct SVG rendering
diff --git a/ui/src/components/details/thread_slice_details_tab.ts b/ui/src/components/details/thread_slice_details_tab.ts
index 8b268b9..51a9757 100644
--- a/ui/src/components/details/thread_slice_details_tab.ts
+++ b/ui/src/components/details/thread_slice_details_tab.ts
@@ -506,6 +506,8 @@
       {title: 'Statistics'},
       m(Histogram, {
         data,
+        height: 150,
+        logScale: true,
         selection: bucket ? {start: bucket.start, end: bucket.end} : undefined,
         formatXValue: (value) => Duration.format(BigInt(Math.round(value))),
       } satisfies HistogramAttrs),
diff --git a/ui/src/components/widgets/charts/histogram.ts b/ui/src/components/widgets/charts/histogram.ts
index 0553582..b0fc326 100644
--- a/ui/src/components/widgets/charts/histogram.ts
+++ b/ui/src/components/widgets/charts/histogram.ts
@@ -13,28 +13,31 @@
 // limitations under the License.
 
 import m from 'mithril';
-import type {EChartsCoreOption} from 'echarts/core';
-import {extractBrushRange, formatNumber} from './chart_utils';
+import {formatNumber} from './chart_utils';
 import {
   HistogramBucket,
   HistogramData,
   HistogramConfig,
   computeHistogram,
 } from './histogram_loader';
-import {EChartView, EChartEventHandler} from './echart_view';
-import {buildChartOption, SELECTION_COLOR} from './chart_option_builder';
+import {SELECTION_COLOR} from './chart_option_builder';
+import {getChartThemeColors} from './chart_theme';
 
 // Re-export data types for convenience
 export {HistogramBucket, HistogramData, HistogramConfig, computeHistogram};
 
-// Color for the selection highlight background.
 const SELECTION_BG_COLOR = 'rgba(0, 120, 212, 0.08)';
+const DEFAULT_HEIGHT = 200;
+
+// Margins around the plot area (space for axis labels/ticks).
+const MARGIN = {top: 10, right: 10, bottom: 30, left: 50};
 
 export interface HistogramAttrs {
   /**
    * Histogram data to display, or undefined if loading.
    * When undefined, a loading spinner is shown.
-   * Use the computeHistogram() utility function to compute this from raw values.
+   * Use the computeHistogram() utility function to compute this from raw
+   * values.
    */
   readonly data: HistogramData | undefined;
 
@@ -110,192 +113,362 @@
   readonly integerDimension?: boolean;
 }
 
-export class Histogram implements m.ClassComponent<HistogramAttrs> {
-  view({attrs}: m.Vnode<HistogramAttrs>) {
-    const {data, height, fillParent, className, onBrush} = attrs;
-
-    const nullCount = data?.nullCount ?? 0;
-    const hasData =
-      data !== undefined && (data.buckets.length > 0 || nullCount > 0);
-    const option = hasData ? buildOption(attrs, data) : undefined;
-
-    return m(EChartView, {
-      option,
-      height,
-      fillParent,
-      className,
-      empty: data !== undefined && !hasData,
-      eventHandlers: buildEventHandlers(attrs, data),
-      activeBrushType: onBrush !== undefined ? 'lineX' : undefined,
-    });
+// Compute ~5 nice tick values for an axis range.
+function computeTicks(min: number, max: number, logScale: boolean): number[] {
+  if (logScale) {
+    if (max <= 0) return [1];
+    const minLog = 0; // Always start at 10^0 = 1
+    const maxLog = Math.ceil(Math.log10(max));
+    const ticks: number[] = [];
+    for (let i = minLog; i <= maxLog; i++) {
+      ticks.push(Math.pow(10, i));
+    }
+    return ticks;
   }
+
+  const range = max - min;
+  if (range === 0) return [min];
+  const rawStep = range / 5;
+  const mag = Math.pow(10, Math.floor(Math.log10(rawStep)));
+  const ratio = rawStep / mag;
+  let step: number;
+  if (ratio < 1.5) step = mag;
+  else if (ratio < 3.5) step = 2 * mag;
+  else if (ratio < 7.5) step = 5 * mag;
+  else step = 10 * mag;
+
+  const ticks: number[] = [];
+  const start = Math.ceil(min / step) * step;
+  for (let v = start; v <= max + step * 0.01; v += step) {
+    ticks.push(v);
+  }
+  return ticks;
 }
 
-function buildOption(
-  attrs: HistogramAttrs,
-  data: HistogramData,
-): EChartsCoreOption {
-  const {
-    xAxisLabel,
-    yAxisLabel = 'Count',
-    formatXValue = (v: number) => formatNumber(v),
-    formatYValue,
-    barColor,
-    barHoverColor,
-    logScale = false,
-  } = attrs;
-  const fmtY = formatYValue ?? formatNumber;
-  const nullCount = data.nullCount ?? 0;
-  const totalWithNull = data.totalCount + nullCount;
-  const buckets = data.buckets;
-  const sel = attrs.selection;
+export class Histogram implements m.ClassComponent<HistogramAttrs> {
+  private hoverIdx = -1;
+  private tooltip?: {x: number; y: number; html: string};
+  private containerWidth = 0;
+  private resizeObs?: ResizeObserver;
+  private dom?: Element;
 
-  // Build dataset: each row is [start, end, count, isSelected].
-  const source = buckets.map((b) => {
-    const eps = (b.end - b.start) * 0.01;
-    const inSel =
-      sel !== undefined &&
-      b.end > sel.start + eps &&
-      b.start < sel.end - eps;
-    return [b.start, b.end, b.count, inSel ? 1 : 0];
-  });
+  oncreate({dom}: m.CVnodeDOM<HistogramAttrs>) {
+    this.dom = dom;
+    this.resizeObs = new ResizeObserver(() => {
+      const w = (dom as HTMLElement).clientWidth;
+      if (w !== this.containerWidth) {
+        this.containerWidth = w;
+        m.redraw();
+      }
+    });
+    this.resizeObs.observe(dom as HTMLElement);
+    this.containerWidth = (dom as HTMLElement).clientWidth;
+  }
 
-  // Compute x-axis range from bucket extents.
-  const xMin = buckets.length > 0 ? buckets[0].start : 0;
-  const xMax = buckets.length > 0 ? buckets[buckets.length - 1].end : 1;
+  onremove() {
+    this.resizeObs?.disconnect();
+  }
 
-  const option = buildChartOption({
-    grid: {bottom: xAxisLabel ? 40 : 25},
-    xAxis: {
-      type: 'value',
-      name: xAxisLabel,
-      min: xMin,
-      max: xMax,
-      formatter: (v) => formatXValue(v as number),
-    },
-    yAxis: {
-      type: logScale ? 'log' : 'value',
-      name: yAxisLabel,
-      formatter:
-        formatYValue !== undefined
-          ? (v) => formatYValue(v as number)
-          : undefined,
-      ...(logScale ? {} : {minInterval: 1}),
-    },
-    tooltip: {
-      trigger: 'item' as const,
-      formatter: (params: {dataIndex?: number}) => {
-        const idx = params?.dataIndex;
-        if (idx === undefined || idx < 0 || idx >= buckets.length) {
-          return '';
-        }
-        const bucket = buckets[idx];
-        const pct =
-          totalWithNull > 0
-            ? ((bucket.count / totalWithNull) * 100).toFixed(1)
-            : '0';
-        return [
-          `Range: ${formatXValue(bucket.start)} \u2013 ${formatXValue(bucket.end)}`,
-          `Count: ${fmtY(bucket.count)}`,
-          `${pct}%`,
-        ].join('<br>');
-      },
-    },
-    brush: attrs.onBrush ? {xAxisIndex: 0, brushType: 'lineX'} : undefined,
-  });
+  view({attrs}: m.Vnode<HistogramAttrs>) {
+    const {data, height = DEFAULT_HEIGHT, className, fillParent} = attrs;
+    const formatXValue = attrs.formatXValue ?? formatNumber;
+    const formatYValue = attrs.formatYValue ?? formatNumber;
+    const logScale = attrs.logScale ?? false;
+    const sel = attrs.selection;
 
-  const optionAny = option as Record<string, unknown>;
+    const hasData =
+      data !== undefined &&
+      (data.buckets.length > 0 || (data.nullCount ?? 0) > 0);
 
-  // Custom series: renderItem draws each bar as a rect from bucket.start
-  // to bucket.end on a value axis, giving precise pixel alignment.
-  // We pass bucket data directly rather than using dataset/encode to avoid
-  // ECharts' dimension mapping complexities.
-  const renderItem = (
-    params: {dataIndex: number; coordSys: {x: number; y: number; width: number; height: number}},
-    api: {
-      coord: (point: [number, number]) => [number, number];
-      style: (extra?: Record<string, unknown>) => Record<string, unknown>;
-    },
-  ) => {
-    const idx = params.dataIndex;
-    const bucket = source[idx];
-    const start = bucket[0];
-    const end = bucket[1];
-    const count = bucket[2];
-    const selected = bucket[3];
-
-    const topLeft = api.coord([start, count]);
-    const bottomRight = api.coord([end, logScale ? 1 : 0]);
-    const x = topLeft[0];
-    const y = topLeft[1];
-    const width = bottomRight[0] - topLeft[0];
-    const height = bottomRight[1] - topLeft[1];
-
-    const children: Array<Record<string, unknown>> = [];
-
-    // Selection background — full height of the chart area.
-    if (selected) {
-      const coordSys = params.coordSys;
-      children.push({
-        type: 'rect',
-        shape: {x, y: coordSys.y, width, height: coordSys.height},
-        style: {fill: SELECTION_BG_COLOR},
-        silent: true,
-        z2: 0,
-      });
+    if (data === undefined) {
+      return m(
+        'div',
+        {
+          class: className,
+          style: {
+            height: `${height}px`,
+            display: 'flex',
+            alignItems: 'center',
+            justifyContent: 'center',
+            ...(fillParent ? {width: '100%'} : {}),
+          },
+        },
+        'Loading...',
+      );
     }
 
-    // The bar itself. api.style() returns the theme-applied default style
-    // (including fill color from the series color palette).
-    const defaultStyle = api.style();
-    const fill = selected
-      ? SELECTION_COLOR
-      : barColor ?? defaultStyle.fill;
-    children.push({
-      type: 'rect',
-      shape: {x, y, width, height},
-      style: {...defaultStyle, fill},
-      emphasis: barHoverColor !== undefined
-        ? {style: {fill: barHoverColor}}
-        : undefined,
-      z2: 1,
-    });
+    if (!hasData) {
+      return m(
+        'div',
+        {
+          class: className,
+          style: {
+            height: `${height}px`,
+            display: 'flex',
+            alignItems: 'center',
+            justifyContent: 'center',
+            ...(fillParent ? {width: '100%'} : {}),
+          },
+        },
+        'No data',
+      );
+    }
 
-    return {type: 'group', children};
-  };
+    // Read theme colors from DOM if available.
+    const colors =
+      this.dom !== undefined ? getChartThemeColors(this.dom) : undefined;
+    const barColor = attrs.barColor ?? colors?.chartColors[0] ?? '#6e93d6';
+    const textColor = colors?.textColor ?? '#999';
 
-  optionAny.series = [
-    {
-      type: 'custom',
-      renderItem,
-      encode: {x: [0, 1], y: 2},
-      data: source,
-      animation: false,
-    },
-  ];
+    const svgWidth = fillParent ? this.containerWidth || 400 : 400;
+    const svgHeight = height;
+    const plotW = svgWidth - MARGIN.left - MARGIN.right;
+    const plotH = svgHeight - MARGIN.top - MARGIN.bottom;
 
-  return option;
-}
+    const buckets = data.buckets;
+    const xMin = buckets[0].start;
+    const xMax = buckets[buckets.length - 1].end;
+    const xRange = xMax - xMin || 1;
+    const maxCount = Math.max(...buckets.map((b) => b.count), 1);
 
-function buildEventHandlers(
-  attrs: HistogramAttrs,
-  data: HistogramData | undefined,
-): ReadonlyArray<EChartEventHandler> {
-  if (!attrs.onBrush || data === undefined || data.buckets.length === 0) {
-    return [];
-  }
-  const onBrush = attrs.onBrush;
+    // Scale functions: data -> pixel
+    const xScale = (v: number) => MARGIN.left + ((v - xMin) / xRange) * plotW;
+    // For log scale, use 0.5 as the floor so bars with count=1 are visible.
+    const logMin = 0.5;
+    const yScale = (v: number): number => {
+      if (logScale) {
+        if (v <= logMin) return MARGIN.top + plotH;
+        const logRange = Math.log10(maxCount) - Math.log10(logMin);
+        const logV = Math.log10(v) - Math.log10(logMin);
+        return MARGIN.top + plotH - (logV / logRange) * plotH;
+      }
+      return MARGIN.top + plotH - (v / maxCount) * plotH;
+    };
 
-  return [
-    {
-      eventName: 'brushEnd',
-      handler: (params) => {
-        // On a value axis, coordRange returns actual values.
-        const range = extractBrushRange(params);
-        if (range !== undefined) {
-          onBrush({start: range[0], end: range[1]});
-        }
+    const totalWithNull = data.totalCount + (data.nullCount ?? 0);
+
+    // Build bar rects
+    const bars: m.Children[] = [];
+    for (let i = 0; i < buckets.length; i++) {
+      const b = buckets[i];
+      const x = Math.floor(xScale(b.start));
+      const x2 = Math.floor(xScale(b.end));
+      const w = x2 - x;
+      const y = yScale(b.count);
+      const h = yScale(0) - y;
+
+      // Selection highlight
+      const eps = (b.end - b.start) * 0.01;
+      const inSelection =
+        sel !== undefined && b.end > sel.start + eps && b.start < sel.end - eps;
+
+      if (inSelection) {
+        bars.push(
+          m('rect', {
+            x,
+            y: MARGIN.top,
+            width: w,
+            height: plotH,
+            fill: SELECTION_BG_COLOR,
+          }),
+        );
+      }
+
+      const fill = inSelection
+        ? SELECTION_COLOR
+        : this.hoverIdx === i
+          ? attrs.barHoverColor ?? barColor
+          : barColor;
+
+      bars.push(
+        m('rect', {
+          x,
+          y: b.count === 0 ? yScale(0) : y,
+          width: w,
+          height: b.count === 0 ? 0 : Math.max(h, 0),
+          fill,
+          onmouseenter: (e: MouseEvent) => {
+            this.hoverIdx = i;
+            const pct =
+              totalWithNull > 0
+                ? ((b.count / totalWithNull) * 100).toFixed(1)
+                : '0';
+            this.tooltip = {
+              x: e.offsetX,
+              y: e.offsetY,
+              html: [
+                `Range: ${formatXValue(b.start)} \u2013 ${formatXValue(b.end)}`,
+                `Count: ${formatYValue(b.count)}`,
+                `${pct}%`,
+              ].join('\n'),
+            };
+            m.redraw();
+          },
+          onmouseleave: () => {
+            this.hoverIdx = -1;
+            this.tooltip = undefined;
+            m.redraw();
+          },
+        }),
+      );
+    }
+
+    // Axes
+    const xTicks = computeTicks(xMin, xMax, false);
+    const yTicks = computeTicks(0, maxCount, logScale);
+
+    const xAxisEls: m.Children[] = [];
+    // Axis line
+    xAxisEls.push(
+      m('line', {
+        'x1': MARGIN.left,
+        'y1': MARGIN.top + plotH,
+        'x2': MARGIN.left + plotW,
+        'y2': MARGIN.top + plotH,
+        'stroke': textColor,
+        'stroke-width': 1,
+      }),
+    );
+    for (const v of xTicks) {
+      const x = xScale(v);
+      if (x < MARGIN.left - 1 || x > MARGIN.left + plotW + 1) continue;
+      xAxisEls.push(
+        m('line', {
+          'x1': x,
+          'y1': MARGIN.top + plotH,
+          'x2': x,
+          'y2': MARGIN.top + plotH + 4,
+          'stroke': textColor,
+          'stroke-width': 1,
+        }),
+      );
+      xAxisEls.push(
+        m(
+          'text',
+          {
+            x,
+            'y': MARGIN.top + plotH + 16,
+            'text-anchor': 'middle',
+            'fill': textColor,
+            'font-size': 10,
+          },
+          formatXValue(v),
+        ),
+      );
+    }
+
+    const yAxisEls: m.Children[] = [];
+    yAxisEls.push(
+      m('line', {
+        'x1': MARGIN.left,
+        'y1': MARGIN.top,
+        'x2': MARGIN.left,
+        'y2': MARGIN.top + plotH,
+        'stroke': textColor,
+        'stroke-width': 1,
+      }),
+    );
+    for (const v of yTicks) {
+      const y = yScale(v);
+      if (y < MARGIN.top - 1 || y > MARGIN.top + plotH + 1) continue;
+      yAxisEls.push(
+        m('line', {
+          'x1': MARGIN.left - 4,
+          'y1': y,
+          'x2': MARGIN.left,
+          'y2': y,
+          'stroke': textColor,
+          'stroke-width': 1,
+        }),
+      );
+      yAxisEls.push(
+        m(
+          'text',
+          {
+            'x': MARGIN.left - 6,
+            'y': y + 3,
+            'text-anchor': 'end',
+            'fill': textColor,
+            'font-size': 10,
+          },
+          formatYValue(v),
+        ),
+      );
+    }
+
+    // Axis labels
+    if (attrs.yAxisLabel) {
+      yAxisEls.push(
+        m(
+          'text',
+          {
+            'x': 12,
+            'y': MARGIN.top - 2,
+            'fill': textColor,
+            'font-size': 11,
+            'text-anchor': 'start',
+          },
+          attrs.yAxisLabel ?? 'Count',
+        ),
+      );
+    }
+    if (attrs.xAxisLabel) {
+      xAxisEls.push(
+        m(
+          'text',
+          {
+            'x': MARGIN.left + plotW / 2,
+            'y': svgHeight - 2,
+            'fill': textColor,
+            'font-size': 11,
+            'text-anchor': 'middle',
+          },
+          attrs.xAxisLabel,
+        ),
+      );
+    }
+
+    return m(
+      'div',
+      {
+        class: className,
+        style: {
+          position: 'relative',
+          ...(fillParent ? {width: '100%'} : {}),
+        },
       },
-    },
-  ];
+      m(
+        'svg',
+        {
+          width: svgWidth,
+          height: svgHeight,
+          style: {display: 'block'},
+        },
+        ...bars,
+        ...xAxisEls,
+        ...yAxisEls,
+      ),
+      this.tooltip
+        ? m(
+            'div',
+            {
+              style: {
+                position: 'absolute',
+                left: `${this.tooltip.x + 10}px`,
+                top: `${this.tooltip.y - 10}px`,
+                background: 'rgba(0, 0, 0, 0.8)',
+                color: '#fff',
+                padding: '4px 8px',
+                borderRadius: '4px',
+                fontSize: '11px',
+                pointerEvents: 'none',
+                whiteSpace: 'pre',
+                zIndex: 10,
+              },
+            },
+            this.tooltip.html,
+          )
+        : null,
+    );
+  }
 }