Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion components/charts/bar-chart/plot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export function BarChartPlot({
setActive,
open,
setOpen,
touching,
setTouching,
plotRef,
tooltipId,
reduce,
Expand Down Expand Up @@ -162,7 +164,11 @@ export function BarChartPlot({
y: vertical ? PAD.top : Math.max(activeRank, 0) * slot,
opacity: open && activeRank >= 0 ? 0.05 : 0,
}}
transition={{ default: spring, opacity: { duration: 0.1, ease: EASE_OUT } }}
// The band tracks a finger directly; any glide under touch reads as lag.
transition={{
default: touching ? { duration: 0 } : spring,
opacity: { duration: 0.1, ease: EASE_OUT },
}}
/>
<g clipPath={`url(#${clipId})`}>
{order.map((categoryIndex, rank) => {
Expand Down Expand Up @@ -317,6 +323,7 @@ export function BarChartPlot({
orientation={vertical ? "horizontal" : "vertical"}
open={open}
setOpen={setOpen}
onTouchChange={setTouching}
tooltipId={tooltipId}
canHover={canHover}
className="inset-0 size-full"
Expand Down
15 changes: 10 additions & 5 deletions components/charts/composition-chart/plot.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"use client";

import { useId, useRef, useState, type PointerEvent } from "react";
import { Tooltip } from "@/components/motion/tooltip";
import { CompositionTooltipContent } from "./tooltip-content";
import { motion } from "motion/react";
import { cn } from "@/lib/utils";
import { type PointerEvent, useId, useRef, useState } from "react";
import { Tooltip } from "@/components/motion/tooltip";
import { SPRING_LAYOUT } from "@/lib/ease";
import { cn } from "@/lib/utils";
import { useCompositionChart } from "./context";
import { compositionArea } from "./model";
import { CompositionTooltipContent } from "./tooltip-content";

export function CompositionChartPlot({ className }: { className?: string }) {
const { columns, rows, column, index, select, view, highlight, reduce, canHover } =
Expand All @@ -16,6 +16,8 @@ export function CompositionChartPlot({ className }: { className?: string }) {
const tooltipId = useId();
const pointerDriven = useRef(false);
const [tooltipOpen, setTooltipOpen] = useState(false);
// Under a finger the crosshair tracks the touch directly; a glide reads as lag.
const [touching, setTouching] = useState(false);
const inspectPointer = (event: PointerEvent<HTMLDivElement>) => {
const bounds = event.currentTarget.getBoundingClientRect();
if (!bounds.width || !bounds.height || !columns.length) return;
Expand Down Expand Up @@ -54,14 +56,17 @@ export function CompositionChartPlot({ className }: { className?: string }) {
}}
onPointerDown={(event) => {
pointerDriven.current = true;
if (event.pointerType === "touch") setTouching(true);
inspectPointer(event);
setTooltipOpen(true);
}}
onPointerUp={() => {
pointerDriven.current = false;
setTouching(false);
}}
onPointerCancel={() => {
pointerDriven.current = false;
setTouching(false);
setTooltipOpen(false);
}}
onPointerMove={(event) => {
Expand Down Expand Up @@ -125,7 +130,7 @@ export function CompositionChartPlot({ className }: { className?: string }) {
<motion.line
initial={false}
animate={{ x: ((index + 0.5) / columns.length) * 100 }}
transition={reduce ? { duration: 0 } : SPRING_LAYOUT}
transition={reduce || touching ? { duration: 0 } : SPRING_LAYOUT}
x1="0"
x2="0"
y1="0"
Expand Down
7 changes: 6 additions & 1 deletion components/charts/line-chart/plot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export function LineChartPlot({
setActive,
open,
setOpen,
touching,
setTouching,
setOverlay,
setGeometry,
tooltipId,
Expand Down Expand Up @@ -157,7 +159,9 @@ export function LineChartPlot({
});
const stride = labelStride(n, plotWidth, 64);
const baselinePoint = points.get(BASELINE);
const spring: Transition = reduce ? { duration: 0 } : SPRING_LAYOUT;
// Inspection marks glide between samples for a mouse or keys, but under a
// finger any glide reads as lag, so they track the touch directly.
const spring: Transition = reduce || touching ? { duration: 0 } : SPRING_LAYOUT;
const revealed = (inView || reduce) && !loading;
const fade = plotWidth > 0 ? Math.min(32, plotWidth * 0.08) / plotWidth : 0;
const crossX = active === null ? AXIS : x(active);
Expand Down Expand Up @@ -382,6 +386,7 @@ export function LineChartPlot({
mode="point"
open={open}
setOpen={setOpen}
onTouchChange={setTouching}
tooltipId={tooltipId}
canHover={canHover}
className="inset-0 size-full"
Expand Down
4 changes: 3 additions & 1 deletion components/charts/line-chart/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function LineChartTooltip({ children, className, side = "auto" }: LineCha
tooltipId,
formatValue,
reduce,
touching,
} = useLineChart();
const panelRef = useRef<HTMLDivElement>(null);
// Kept between openings: the panel's size barely changes from one point to the next.
Expand Down Expand Up @@ -121,7 +122,8 @@ export function LineChartTooltip({ children, className, side = "auto" }: LineCha
className="pointer-events-none absolute top-0 left-0 z-10 w-max"
initial={position}
animate={position}
transition={reduce ? { duration: 0 } : FOLLOW}
// The trailing follow suits a mouse; under a finger it reads as lag.
transition={reduce || touching ? { duration: 0 } : FOLLOW}
>
<TooltipSurface
id={tooltipId}
Expand Down
5 changes: 4 additions & 1 deletion components/charts/sparkline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export function Sparkline({
const { ref, width } = useElementWidth<HTMLDivElement>();
const [active, setActive] = useState<number | null>(null);
const [open, setOpen] = useState(false);
// Under a finger the readout dot tracks the touch directly instead of gliding.
const [touching, setTouching] = useState(false);
const values = data.map((value) =>
typeof value === "number" && Number.isFinite(value) ? value : null,
);
Expand Down Expand Up @@ -167,7 +169,7 @@ export function Sparkline({
strokeWidth={inspecting ? 2 : 0}
initial={false}
animate={{ x: x(dotIndex), y: y(dotValue) }}
transition={reduce ? { duration: 0 } : SPRING_LAYOUT}
transition={reduce || touching ? { duration: 0 } : SPRING_LAYOUT}
/>
) : null}
</svg>
Expand All @@ -185,6 +187,7 @@ export function Sparkline({
mode="point"
open={open}
setOpen={setOpen}
onTouchChange={setTouching}
tooltipId={tooltipId}
canHover={canHover}
className="inset-0 size-full"
Expand Down
12 changes: 10 additions & 2 deletions components/charts/xy/inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface XYInspectorProps {
setOpen: (open: boolean) => void;
tooltipId?: string;
canHover: boolean;
/** Reports a touch drag starting and ending, so marks can track the finger 1:1. */
onTouchChange?: (touching: boolean) => void;
className?: string;
style?: CSSProperties;
}
Expand All @@ -38,6 +40,7 @@ export function XYInspector({
setOpen,
tooltipId,
canHover,
onTouchChange,
className,
style,
}: XYInspectorProps) {
Expand Down Expand Up @@ -107,12 +110,17 @@ export function XYInspector({
}}
onPointerDown={(event) => {
pointerDriven.current = true;
if (event.pointerType !== "touch") mouseFocus.current = true;
if (event.pointerType === "touch") onTouchChange?.(true);
else mouseFocus.current = true;
last.current = null;
inspectPointer(event);
}}
onPointerCancel={() => {
onPointerUp={(event) => {
if (event.pointerType === "touch") onTouchChange?.(false);
}}
onPointerCancel={(event) => {
pointerDriven.current = false;
if (event.pointerType === "touch") onTouchChange?.(false);
setOpen(false);
}}
onClick={() => {
Expand Down
4 changes: 4 additions & 0 deletions components/charts/xy/use-inspection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export function useInspection({
if (activeId === undefined && requested !== null && !ids.includes(requested)) setInternal(null);
const activeIndex = requested === null ? -1 : ids.indexOf(requested);
const [open, setOpen] = useState(false);
// A finger on the plot: inspection marks follow it directly instead of gliding.
const [touching, setTouching] = useState(false);
const plotRef = useRef<HTMLDivElement>(null);
const tooltipId = useId();
return {
Expand All @@ -50,6 +52,8 @@ export function useInspection({
setActive,
open,
setOpen,
touching,
setTouching,
plotRef,
tooltipId,
};
Expand Down
5 changes: 5 additions & 0 deletions lib/changelog/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export const changelogEntries: ChangelogEntry[] = [
"text": "Sparkline adds a compact trend to stat cards and tables, with an optional change badge and a hover or keyboard readout for individual samples.",
"component": "sparkline"
},
{
"type": "fixed",
"text": "Composition Chart's crosshair follows your finger directly when you drag across the chart on a touch screen, instead of gliding behind it.",
"component": "composition-chart"
},
{
"type": "fixed",
"text": "Card Dealer cards stay inside their table and no longer draw over a sticky page header when you scroll.",
Expand Down
2 changes: 1 addition & 1 deletion lib/component-dates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const COMPONENT_DATES = {
"motion/shader-background": { publishedAt: "2026-07-02", updatedAt: "2026-10-18" },
"motion/cylinder-carousel": { publishedAt: "2026-07-04", updatedAt: "2026-10-18" },
"motion/loader": { publishedAt: "2026-07-04", updatedAt: "2026-10-18" },
"charts/composition-chart": { publishedAt: "2026-09-25", updatedAt: "2026-10-18" },
"charts/composition-chart": { publishedAt: "2026-09-25", updatedAt: "2026-10-27" },
"charts/funnel-chart": { publishedAt: "2026-09-20", updatedAt: "2026-10-18" },
"charts/liquidity-heatmap": { publishedAt: "2026-09-20", updatedAt: "2026-10-18" },
"charts/bump-chart": { publishedAt: "2026-09-20", updatedAt: "2026-10-18" },
Expand Down
Loading