Skip to content

Commit 647635d

Browse files
committed
Adjust columns to fit the space
1 parent 49d3a72 commit 647635d

File tree

8 files changed

+102
-82
lines changed

8 files changed

+102
-82
lines changed

site/src/modules/workspaces/WorkspaceTiming/Chart/Bar.tsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Interpolation, Theme } from "@emotion/react";
22
import { type ButtonHTMLAttributes, forwardRef, type HTMLProps } from "react";
3+
import { CSSVars } from "./constants";
34

45
export type BarColors = {
56
stroke: string;
@@ -8,9 +9,10 @@ export type BarColors = {
89

910
type BaseBarProps<T> = Omit<T, "size" | "color"> & {
1011
/**
11-
* The width of the bar component.
12+
* Scale used to determine the width based on the given value.
1213
*/
13-
size: number;
14+
scale: number;
15+
value: number;
1416
/**
1517
* The X position of the bar component.
1618
*/
@@ -25,36 +27,45 @@ type BaseBarProps<T> = Omit<T, "size" | "color"> & {
2527
type BarProps = BaseBarProps<HTMLProps<HTMLDivElement>>;
2628

2729
export const Bar = forwardRef<HTMLDivElement, BarProps>(
28-
({ colors, size, children, offset, ...htmlProps }, ref) => {
30+
({ colors, scale, value, offset, ...htmlProps }, ref) => {
2931
return (
30-
<div css={barCss({ colors, size, offset })} {...htmlProps} ref={ref} />
32+
<div
33+
css={barCss({ colors, scale, value, offset })}
34+
{...htmlProps}
35+
ref={ref}
36+
/>
3137
);
3238
},
3339
);
3440

3541
type ClickableBarProps = BaseBarProps<ButtonHTMLAttributes<HTMLButtonElement>>;
3642

3743
export const ClickableBar = forwardRef<HTMLButtonElement, ClickableBarProps>(
38-
({ colors, size, offset, ...htmlProps }, ref) => {
44+
({ colors, scale, value, offset, ...htmlProps }, ref) => {
3945
return (
4046
<button
4147
type="button"
42-
css={[...barCss({ colors, size, offset }), styles.clickable]}
48+
css={[...barCss({ colors, scale, value, offset }), styles.clickable]}
4349
{...htmlProps}
4450
ref={ref}
4551
/>
4652
);
4753
},
4854
);
4955

50-
export const barCss = ({ size, colors, offset }: BaseBarProps<unknown>) => {
56+
export const barCss = ({
57+
scale,
58+
value,
59+
colors,
60+
offset,
61+
}: BaseBarProps<unknown>) => {
5162
return [
5263
styles.bar,
5364
{
54-
width: size,
65+
width: `calc((var(${CSSVars.xAxisWidth}) * ${value}) / ${scale})`,
5566
backgroundColor: colors?.fill,
5667
borderColor: colors?.stroke,
57-
marginLeft: offset,
68+
marginLeft: `calc((var(${CSSVars.xAxisWidth}) * ${offset}) / ${scale})`,
5869
},
5970
];
6071
};

site/src/modules/workspaces/WorkspaceTiming/Chart/BarBlocks.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Interpolation, Theme } from "@emotion/react";
22
import { MoreHorizOutlined } from "@mui/icons-material";
3-
import type { FC } from "react";
3+
import { useLayoutEffect, useRef, useState, type FC } from "react";
44

55
const sidePadding = 8;
66
const spaceBetweenBlocks = 4;
@@ -9,21 +9,35 @@ const blockSize = 20;
99

1010
type BarBlocksProps = {
1111
count: number;
12-
barSize: number;
1312
};
1413

15-
export const BarBlocks: FC<BarBlocksProps> = ({ count, barSize }) => {
14+
export const BarBlocks: FC<BarBlocksProps> = ({ count }) => {
15+
const [parentWidth, setParentWidth] = useState<number>();
16+
const blocksRef = useRef<HTMLDivElement>(null);
17+
18+
useLayoutEffect(() => {
19+
if (!blocksRef.current || parentWidth) {
20+
return;
21+
}
22+
const parentEl = blocksRef.current.parentElement;
23+
if (!parentEl) {
24+
throw new Error("BarBlocks must be a child of a Bar");
25+
}
26+
setParentWidth(parentEl.clientWidth);
27+
}, [parentWidth]);
28+
1629
const totalSpaceBetweenBlocks = (count - 1) * spaceBetweenBlocks;
17-
const freeSize = barSize - sidePadding * 2;
30+
const freeSize = parentWidth ? parentWidth - sidePadding * 2 : 0;
1831
const necessarySize = blockSize * count + totalSpaceBetweenBlocks;
1932
const hasSpacing = necessarySize <= freeSize;
2033
const nOfPossibleBlocks = Math.floor(
21-
(freeSize - moreIconSize - totalSpaceBetweenBlocks) / blockSize,
34+
(freeSize - moreIconSize) / (blockSize + spaceBetweenBlocks),
2235
);
2336
const nOfBlocks = hasSpacing ? count : nOfPossibleBlocks;
37+
console.log("POSSIBLE ->", count, nOfPossibleBlocks);
2438

2539
return (
26-
<div css={styles.blocks}>
40+
<div ref={blocksRef} css={styles.blocks}>
2741
{Array.from({ length: nOfBlocks }).map((_, i) => (
2842
// biome-ignore lint/suspicious/noArrayIndexKey: we are using the index as a key here because the blocks are not expected to be reordered
2943
<div key={i} css={styles.block} style={{ minWidth: blockSize }} />

site/src/modules/workspaces/WorkspaceTiming/Chart/Chart.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import React from "react";
99
import type { BarColors } from "./Bar";
1010
import { YAxisSidePadding, YAxisWidth } from "./YAxis";
1111

12-
export const ChartContent: FC<HTMLProps<HTMLDivElement>> = (props) => {
13-
return <div css={styles.content} {...props} />;
14-
};
15-
1612
export const Chart = (props: HTMLProps<HTMLDivElement>) => {
1713
return <div css={styles.chart} {...props} />;
1814
};
1915

16+
export const ChartContent: FC<HTMLProps<HTMLDivElement>> = (props) => {
17+
return <div css={styles.content} {...props} />;
18+
};
19+
2020
export const ChartToolbar = (props: HTMLProps<HTMLDivElement>) => {
2121
return <div css={styles.toolbar} {...props} />;
2222
};

site/src/modules/workspaces/WorkspaceTiming/Chart/XAxis.tsx

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import type { FC, HTMLProps } from "react";
1+
import {
2+
useLayoutEffect,
3+
useRef,
4+
useState,
5+
type FC,
6+
type HTMLProps,
7+
} from "react";
28
import type { Interpolation, Theme } from "@emotion/react";
39
import { YAxisCaptionHeight } from "./YAxis";
410
import { formatTime } from "./utils";
5-
import { XAxisLabelsHeight, XAxisRowsGap } from "./constants";
11+
import { CSSVars, XAxisLabelsHeight, XAxisRowsGap } from "./constants";
612

7-
export const XAxisWidth = 130;
13+
export const XAxisMinWidth = 130;
814
export const XAxisSidePadding = 16;
915

1016
type XAxisProps = HTMLProps<HTMLDivElement> & {
@@ -13,13 +19,28 @@ type XAxisProps = HTMLProps<HTMLDivElement> & {
1319
};
1420

1521
export const XAxis: FC<XAxisProps> = ({ ticks, scale, ...htmlProps }) => {
22+
const rootRef = useRef<HTMLDivElement>(null);
23+
24+
// The X axis should occupy all available space. If there is extra space,
25+
// increase the column width accordingly. Use a CSS variable to propagate the
26+
// value to the child components.
27+
useLayoutEffect(() => {
28+
const rootEl = rootRef.current;
29+
if (!rootEl) {
30+
return;
31+
}
32+
// We always add one extra column to the grid to ensure that the last column
33+
// is fully visible.
34+
const avgWidth = rootEl.clientWidth / (ticks.length + 1);
35+
avgWidth > XAxisMinWidth ? avgWidth : XAxisMinWidth;
36+
rootEl.style.setProperty(CSSVars.xAxisWidth, `${avgWidth}px`);
37+
}, [ticks]);
38+
1639
return (
17-
<div css={styles.root} {...htmlProps}>
40+
<div css={styles.root} {...htmlProps} ref={rootRef}>
1841
<XAxisLabels>
1942
{ticks.map((tick) => (
20-
<XAxisLabel key={tick} width={XAxisWidth}>
21-
{formatTime(tick, scale)}
22-
</XAxisLabel>
43+
<XAxisLabel key={tick}>{formatTime(tick, scale)}</XAxisLabel>
2344
))}
2445
</XAxisLabels>
2546
{htmlProps.children}
@@ -32,11 +53,7 @@ export const XAxisLabels: FC<HTMLProps<HTMLUListElement>> = (props) => {
3253
return <ul css={styles.labels} {...props} />;
3354
};
3455

35-
type XAxisLabelProps = HTMLProps<HTMLLIElement> & {
36-
width: number;
37-
};
38-
39-
export const XAxisLabel: FC<XAxisLabelProps> = ({ width, ...htmlProps }) => {
56+
export const XAxisLabel: FC<HTMLProps<HTMLLIElement>> = (props) => {
4057
return (
4158
<li
4259
css={[
@@ -47,13 +64,13 @@ export const XAxisLabel: FC<XAxisLabelProps> = ({ width, ...htmlProps }) => {
4764
// 2. Shift the label to the left by half of the column width.
4865
// Note: This adjustment is not applied to the first element,
4966
// as the 0 label/value is not displayed in the chart.
50-
width: width * 2,
67+
width: `calc(var(${CSSVars.xAxisWidth}) * 2)`,
5168
"&:not(:first-child)": {
52-
marginLeft: -width,
69+
marginLeft: `calc(-1 * var(${CSSVars.xAxisWidth}))`,
5370
},
5471
},
5572
]}
56-
{...htmlProps}
73+
{...props}
5774
/>
5875
);
5976
};
@@ -108,7 +125,10 @@ export const XGrid: FC<XGridProps> = ({ columns, ...htmlProps }) => {
108125
return (
109126
<div css={styles.grid} role="presentation" {...htmlProps}>
110127
{[...Array(columns).keys()].map((key) => (
111-
<div key={key} css={[styles.column, { width: XAxisWidth }]} />
128+
<div
129+
key={key}
130+
css={[styles.column, { width: `var(${CSSVars.xAxisWidth})` }]}
131+
/>
112132
))}
113133
</div>
114134
);
@@ -138,7 +158,7 @@ const styles = {
138158
alignItems: "center",
139159
borderBottom: `1px solid ${theme.palette.divider}`,
140160
height: XAxisLabelsHeight,
141-
padding: `0px ${XAxisSidePadding}px`,
161+
padding: 0,
142162
minWidth: "100%",
143163
flexShrink: 0,
144164
position: "sticky",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
// Constants that are used across the Chart components
22
export const XAxisLabelsHeight = 40;
33
export const XAxisRowsGap = 20;
4+
export const CSSVars = {
5+
xAxisWidth: "--x-axis-width",
6+
};

site/src/modules/workspaces/WorkspaceTiming/Chart/utils.ts

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -60,30 +60,9 @@ export const formatTime = (time: number, scale: number): string => {
6060
})}s`;
6161
};
6262

63-
// Helper function to convert the tick spacing into pixel size. This is used
64-
// for setting the bar width and offset.
65-
export const calcSize = (
66-
time: number,
67-
scale: number,
68-
columnWidth: number,
69-
): number => {
70-
return (columnWidth * time) / scale;
71-
};
72-
73-
export const calcBarSizeAndOffset = (
63+
export const calcOffset = (
7464
timing: BaseTiming,
7565
generalTiming: BaseTiming,
76-
scale: number,
77-
columnWidth: number,
78-
) => {
79-
const offset = calcSize(
80-
timing.startedAt.getTime() - generalTiming.startedAt.getTime(),
81-
scale,
82-
columnWidth,
83-
);
84-
const size = calcSize(calcDuration(timing), scale, columnWidth);
85-
return {
86-
size,
87-
offset,
88-
};
66+
): number => {
67+
return timing.startedAt.getTime() - generalTiming.startedAt.getTime();
8968
};

site/src/modules/workspaces/WorkspaceTiming/ResourcesChart.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
XAxisRow,
44
XAxisRows,
55
XAxisSections,
6-
XAxisWidth,
6+
XAxisMinWidth,
77
} from "./Chart/XAxis";
88
import { useState, type FC } from "react";
99
import {
@@ -15,8 +15,8 @@ import {
1515
} from "./Chart/YAxis";
1616
import { Bar } from "./Chart/Bar";
1717
import {
18-
calcBarSizeAndOffset,
1918
calcDuration,
19+
calcOffset,
2020
combineTimings,
2121
formatTime,
2222
makeTicks,
@@ -137,12 +137,9 @@ export const ResourcesChart: FC<ResourcesChartProps> = ({
137137
<XAxisRow key={t.name} yAxisLabelId={t.name}>
138138
<ResourceTooltip timing={t}>
139139
<Bar
140-
{...calcBarSizeAndOffset(
141-
t,
142-
generalTiming,
143-
scale,
144-
XAxisWidth,
145-
)}
140+
value={calcDuration(t)}
141+
offset={calcOffset(t, generalTiming)}
142+
scale={scale}
146143
colors={legendsByAction[t.action].colors}
147144
/>
148145
</ResourceTooltip>

site/src/modules/workspaces/WorkspaceTiming/StagesChart.tsx

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
XAxisRow,
44
XAxisRows,
55
XAxisSections,
6-
XAxisWidth,
6+
XAxisMinWidth,
77
} from "./Chart/XAxis";
88
import type { FC } from "react";
99
import {
@@ -15,8 +15,8 @@ import {
1515
} from "./Chart/YAxis";
1616
import { Bar, ClickableBar } from "./Chart/Bar";
1717
import {
18-
calcBarSizeAndOffset,
1918
calcDuration,
19+
calcOffset,
2020
combineTimings,
2121
formatTime,
2222
makeTicks,
@@ -112,29 +112,25 @@ export const StagesChart: FC<StagesChartProps> = ({
112112
return (
113113
<XAxisRows key={category}>
114114
{timingsInCategory.map((t) => {
115-
const barSizeAndOffset = calcBarSizeAndOffset(
116-
t,
117-
generalTiming,
118-
scale,
119-
XAxisWidth,
120-
);
115+
const value = calcDuration(t);
116+
const offset = calcOffset(t, generalTiming);
117+
121118
return (
122119
<XAxisRow key={t.name} yAxisLabelId={t.name}>
123120
{/** We only want to expand stages with more than one resource */}
124121
{t.resources > 1 ? (
125122
<ClickableBar
126-
{...barSizeAndOffset}
123+
scale={scale}
124+
value={value}
125+
offset={offset}
127126
onClick={() => {
128127
onSelectStage(t, category);
129128
}}
130129
>
131-
<BarBlocks
132-
count={t.resources}
133-
barSize={barSizeAndOffset.size}
134-
/>
130+
<BarBlocks count={t.resources} />
135131
</ClickableBar>
136132
) : (
137-
<Bar {...barSizeAndOffset} />
133+
<Bar scale={scale} value={value} offset={offset} />
138134
)}
139135
{formatTime(calcDuration(t), scale)}
140136
</XAxisRow>

0 commit comments

Comments
 (0)