Skip to content
Open
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
22 changes: 13 additions & 9 deletions dataweaver/apps/web/src/components/elements/card/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import type { TLShapeId } from 'tldraw';
import { Button } from '~/components/elements/button';
import { CARD_VARIANT_MAX } from '~/components/scopes/atlas/config';
import type { CardVariant } from '~/components/scopes/atlas/helpers';
import { useCachedResizeValues } from '~/hooks/use_cached_resize_values';
import s from './base.module.scss';
import { useCardAutoHeight } from './use_card_auto_height';
import { useCardClearTextSelection } from './use_card_clear_text_selection';
Expand Down Expand Up @@ -68,10 +67,6 @@ export const CardBase = ({

const startDragging = useCardDragHandle(id);

const getCachedCanScroll = useCachedResizeValues((element: HTMLElement) => {
return element.scrollHeight > element.clientHeight;
});

return (
<article
className={s.container}
Expand Down Expand Up @@ -99,11 +94,20 @@ export const CardBase = ({
<div
ref={childrenContainerRef}
className={s['children-container']}
// TLDraw captures all wheel events; this ensures that cards can be
// scrolled when children here can scroll
// TLDraw captures all wheel events; walk from the event target up to
// this container — if any element in the chain is scrollable, reserve
// the wheel event for it instead of letting tldraw zoom/pan.
onWheelCapture={(event) => {
if (getCachedCanScroll(false, event.currentTarget)) {
event.stopPropagation();
let el = event.target as HTMLElement | null;
while (el && el !== event.currentTarget.parentElement) {
const style = window.getComputedStyle(el);
if (style.overflowY === 'auto' || style.overflowY === 'scroll') {
if (el.scrollHeight > el.clientHeight) {
event.stopPropagation();
return;
}
}
el = el.parentElement;
}
}}
Comment thread
sagrimson marked this conversation as resolved.
// Once the card is the single selection, reserve dragging for the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
.container {
display: flex;
flex-shrink: 0;
flex: 1;
flex-direction: column;
padding: 28px;
min-height: 0;
padding: 28px 28px 0;
}

.header-container {
display: flex;
flex-shrink: 0;
flex-direction: column;
row-gap: 8px;
margin-bottom: 16px;
Expand Down
23 changes: 10 additions & 13 deletions dataweaver/apps/web/src/components/elements/card/chart/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { useQueryActions } from '~/components/scopes/atlas/query_provider';
import type { FacetInfo } from '~/server/types';
import s from './chart.module.scss';
import { ConditionalTabs } from './conditional_tabs';
import { DataChartBarHorizontal } from './data_chart_bar_horizontal';
import { DataChartBarVertical } from './data_chart_bar_vertical';
import { DataChartLine } from './data_chart_line';
import { DataTable } from './data_table';
import { FacetSelector } from './facet_selector';
Expand All @@ -29,10 +31,6 @@ export interface ChartDatum {
value: number;
}

// TODO: Get dynamically instead of hard coding here
const CHART_WIDTH = 356;
const CHART_HEIGHT = 200;

export interface CardChartProps extends CardState {
id: TLShapeId;
title?: string;
Expand Down Expand Up @@ -60,7 +58,6 @@ export const CardChart = ({
const { open: openExport } = useExportActions();
const { runPrompt } = useQueryActions();

// TODO: Support the different chart styles (for now we always show bar chart)
const [selectedStyle, setSelectedStyle] =
useState<ChartStyle>('bar-vertical');
const [isStyleMenuOpen, setIsStyleMenuOpen] = useState(false);
Expand All @@ -71,7 +68,6 @@ export const CardChart = ({
// Derive chart data from selected facet if facets are available
const currentFacet = facets?.find((f) => f.facetId === selectedFacetId);
const chartData = currentFacet?.observations ?? data;

return (
<Card.Base
id={id}
Expand Down Expand Up @@ -127,13 +123,14 @@ export const CardChart = ({
{
icon: IconLineGraphSingle,
label: 'Chart',
children: (
<DataChartLine
data={chartData}
width={CHART_WIDTH}
height={CHART_HEIGHT}
/>
),
children:
selectedStyle === 'bar-vertical' ? (
<DataChartBarVertical data={chartData} />
) : selectedStyle === 'bar-horizontal' ? (
<DataChartBarHorizontal data={chartData} />
) : (
<DataChartLine data={chartData} />
),
},
{
icon: IconTable,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.tabs-container {
display: flex;
margin-bottom: 16px;
flex-shrink: 0;
border-bottom: 1px solid rgb(var(--color-card-divider));
}

Expand Down Expand Up @@ -37,3 +37,17 @@
width: 24px;
height: 24px;
}

.panel {
font-weight: 500;

&:has(table) {
overflow-y: auto;

thead {
position: sticky;
top: 0;
z-index: $z-index-above-content;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const ConditionalTabs = ({ tabs }: ConditionalTabsProps) => {
<m.div
key={activeTab.label}
role="tabpanel"
className={s.panel}
id={panelId(activeIndex)}
aria-labelledby={tabId(activeIndex)}
initial={{ opacity: 0 }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.tooltip {
@include type-label-large;

padding: 10px 12px;
color: rgb(var(--color-card-content));
background-color: rgb(var(--color-card-surface));
border-radius: 8px;
box-shadow: var(--shadow-elevated);

p {
margin: 0;
}

.label {
@include type-label-small;

margin-bottom: 2px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import styles from './custom_tooltip.module.scss';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've been going with the principle of category-first naming: tooltip_custom (along with this component).


const tooltipFormatter = new Intl.NumberFormat(undefined, {
maximumFractionDigits: 2,
notation: 'standard',
});

export const CustomTooltip = ({
active,
payload,
label,
}: {
active?: boolean;
payload?: { value: number }[];
label?: string;
}) => {
if (active && payload && payload.length) {
const value = tooltipFormatter.format(Number(payload?.[0]?.value));
return (
<div className={styles.tooltip}>
<p className={styles.label}>{`${label}`}</p>
<p className={styles.value}>{`${value}`}</p>
</div>
);
}
return null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use client';

import { COLORS } from '@package/tokens/ts';
import {
Bar,
BarChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';

import type { ChartDatum } from './chart';
import { CustomTooltip } from './custom_tooltip';

const BAR_COLOR = `rgb(${COLORS['card-surface-selected']})`;
const GRID_COLOR = `rgb(${COLORS['card-chart-grid']})`;
const AXIS_COLOR = `rgb(${COLORS['card-content-muted']})`;
Comment on lines +17 to +19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reckon all chart colours should probably just be directly tokenized for ease. I.e:

Suggested change
const BAR_COLOR = `rgb(${COLORS['card-surface-selected']})`;
const GRID_COLOR = `rgb(${COLORS['card-chart-grid']})`;
const AXIS_COLOR = `rgb(${COLORS['card-content-muted']})`;
const BAR_COLOR = `rgb(${COLORS['card-chart-bar']})`;
const GRID_COLOR = `rgb(${COLORS['card-chart-grid']})`;
const AXIS_COLOR = `rgb(${COLORS['card-chart-axis']})`;


interface ChartProps {
data: ChartDatum[];
}

const compactFormatter = new Intl.NumberFormat(undefined, {
notation: 'compact',
});

export const DataChartBarHorizontal = ({ data }: ChartProps) => {
return (
<ResponsiveContainer
width="100%"
aspect={0.75}
style={{ paddingBottom: '28px' }}
>
<BarChart
data={data}
layout="vertical"
margin={{ top: 32, right: 12, bottom: 0, left: 0 }}
>
<CartesianGrid stroke={GRID_COLOR} horizontal={false} />
<XAxis
type="number"
tickLine={{ stroke: AXIS_COLOR }}
axisLine={{ stroke: AXIS_COLOR }}
tick={{ fontSize: 10, fill: AXIS_COLOR }}
tickFormatter={(value) => compactFormatter.format(value)}
tickMargin={6}
/>
<YAxis
type="category"
dataKey="date"
width={'auto'}
tickLine={false}
axisLine={{ stroke: AXIS_COLOR }}
tick={{ fontSize: 10, fill: AXIS_COLOR }}
/>
<Tooltip
cursor={{ fill: GRID_COLOR, opacity: 0.4 }}
content={<CustomTooltip />}
/>
<Bar dataKey="value" fill={BAR_COLOR} radius={[0, 2, 2, 0]} />
</BarChart>
</ResponsiveContainer>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
'use client';

import { COLORS } from '@package/tokens/ts';
import {
Bar,
BarChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
usePlotArea,
XAxis,
YAxis,
} from 'recharts';

import type { ChartDatum } from './chart';
import { CustomTooltip } from './custom_tooltip';

const BAR_COLOR = `rgb(${COLORS['card-surface-selected']})`;
const GRID_COLOR = `rgb(${COLORS['card-chart-grid']})`;
const AXIS_COLOR = `rgb(${COLORS['card-content-muted']})`;

interface ChartProps {
data: ChartDatum[];
}

const compactFormatter = new Intl.NumberFormat(undefined, {
notation: 'compact',
});

const CustomCursor = (props: {
x?: number;
y?: number;
width?: number;
height?: number;
top?: number;
}) => {
const { x = 0, width = 0, height = 0, top = 0 } = props;
return (
<rect
x={x}
y={0}
width={width}
height={height + top}
fill={GRID_COLOR}
opacity={0.4}
/>
);
};
Comment thread
sagrimson marked this conversation as resolved.

const FullWidthAxisLine = () => {
const plotArea = usePlotArea();
if (!plotArea) return null;
const y = plotArea.y + plotArea.height;
return (
<line
x1={0}
y1={y}
x2={plotArea.x + plotArea.width}
y2={y}
stroke={AXIS_COLOR}
strokeWidth={1}
/>
);
};

export const DataChartBarVertical = ({ data }: ChartProps) => {
return (
<ResponsiveContainer
width="100%"
aspect={1.78}
style={{ paddingBottom: '28px' }}
>
<BarChart
data={data}
margin={{ top: 32, right: 0, bottom: 0, left: 0 }}
style={{ overflow: 'hidden' }}
>
<CartesianGrid
stroke={GRID_COLOR}
vertical={false}
x={0}
width={9999}
/>
<XAxis
dataKey="date"
tickLine={{ stroke: AXIS_COLOR }}
axisLine={false}
tick={{ fontSize: 10, fill: AXIS_COLOR }}
tickMargin={6}
padding={{ left: 0, right: 4 }}
/>
<YAxis
width="auto"
tickLine={false}
axisLine={false}
tick={{ fontSize: 10, dy: -7, textAnchor: 'end' }}
tickFormatter={(value) => compactFormatter.format(Number(value))}
/>
<FullWidthAxisLine />
<Tooltip cursor={<CustomCursor />} content={<CustomTooltip />} />
<Bar dataKey="value" fill={BAR_COLOR} radius={[2, 2, 0, 0]} />
</BarChart>
</ResponsiveContainer>
);
};
Loading