Skip to content

Conversation

@MatthewKhouzam
Copy link
Contributor

@MatthewKhouzam MatthewKhouzam commented Oct 23, 2025

What it does

Add an x axis for density views.

  • Add bottom time axis using D3.js scales and axes
  • Support timestamp, range, and category sampling from tsp-typescript-client
  • Display duration labels (s/ms/μs/ns) for range sampling
  • Include gridlines extending through chart area
  • Add CSS styling with VSCode theme integration
  • Increase bottom margin to accommodate axis labels
  • Have minor and major ticks where the major ticks are labeled

How to test

Open a function density view.

image image

Follow-ups

Review checklist

  • As an author, I have thoroughly tested my changes and carefully followed the instructions in this template

axis = d3.axisBottom(scale).ticks(5).tickSizeInner(-parseInt(String(this.props.style.height)) + this.margin.top + this.margin.bottom)
.tickFormat((d) => {
const date = d instanceof Date ? d : new Date(d as number);
const rangeIndex = Math.floor((date.getTime() - domain[0].getTime()) / (domain[1].getTime() - domain[0].getTime()) * (sampling.length - 1));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Change to add ticks on edges, not on centers.

axis = d3.axisBottom(scale).ticks(5).tickSizeInner(-parseInt(String(this.props.style.height)) + this.margin.top + this.margin.bottom);
} else if (isRangeSampling(sampling)) {
const timestamps = sampling.map(range => Number(range.start) / 1000000);
console.log('Raw timestamps (ms):', timestamps.slice(0, 3));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

remove all logs.

const dates = timestamps.map(ts => new Date(ts));
console.log('Converted dates:', dates.slice(0, 3));
const domain = d3.extent(dates) as [Date, Date];
console.log('Domain:', domain);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

remove

const timestamps = sampling.map(range => Number(range.start) / 1000000);
console.log('Raw timestamps (ms):', timestamps.slice(0, 3));
const dates = timestamps.map(ts => new Date(ts));
console.log('Converted dates:', dates.slice(0, 3));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

remove

let axis: d3.Axis<any>;

if (isTimestampSampling(sampling)) {
const domain = d3.extent(sampling.map(ts => new Date(Number(ts) / 1000000))) as [Date, Date];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe extract...

allTicks.forEach(tick => {
const tickPos = (scale as any)(tick) as number;
const canAdd = majorTicks.length === 0 ||
Math.abs(tickPos - ((scale as any)(majorTicks[majorTicks.length - 1]) as number)) >= 100;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

definitely extract.

@siwei-zhang-work
Copy link
Contributor

siwei-zhang-work commented Nov 7, 2025

Thanks for doing this. The x labels will be on top of each other when they are long (categorical sampling). I don't see a nice way to solve this, maybe show ... in the x labels when they are too long?

image

@MatthewKhouzam
Copy link
Contributor Author

he x labels will be on top of each other when they are long (categorical sampling). I don't see a nice way to solve this, maybe show ... in the x labels when they are too long?

This makes sense, I will try it out. One issue. Let's say that we have as an example: Month-day as buckets so we have January-Monday, January-Tuesday, etc... And we have 1000 categories on a 1920 pixel wide axis, even ... will fail. I will make it do ... when it can, and nothing when it cannot, is this OK with you?

@siwei-zhang-work
Copy link
Contributor

he x labels will be on top of each other when they are long (categorical sampling). I don't see a nice way to solve this, maybe show ... in the x labels when they are too long?

This makes sense, I will try it out. One issue. Let's say that we have as an example: Month-day as buckets so we have January-Monday, January-Tuesday, etc... And we have 1000 categories on a 1920 pixel wide axis, even ... will fail. I will make it do ... when it can, and nothing when it cannot, is this OK with you?

Yes, sounds good. I think that's the best we can do.

@MatthewKhouzam MatthewKhouzam force-pushed the x-axis branch 4 times, most recently from 1c5f7f9 to 0f6a98f Compare November 13, 2025 20:33
Copy link
Contributor

@bhufmann bhufmann left a comment

Choose a reason for hiding this comment

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

I'm not able to test it because the Function Duration Density view is not rendering the x-axis. So, I just comment based on what the code.

formatX?: (x: number | bigint | string) => string;
formatY?: (y: number) => string;
stacked?: boolean;
timelineUnit?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

Those are never set in any of the existing code (nor the above). Nor the other ones of the props.

cursor: 'default',
showTree: true
showTree: true,
timelineUnit: this.props.timelineUnit || 'ms',
Copy link
Contributor

Choose a reason for hiding this comment

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

the state for timelineUnit is never updated because it's never passed as props nor the unit is taken from the x-axis description of the xy-query, see here for the axis description definition: https://github.com/eclipse-cdt-cloud/trace-server-protocol/blob/72af35675446ed5da6acbe1bc8175ad7e8769166/API.yaml#L2688

showTree: true
showTree: true,
timelineUnit: this.props.timelineUnit || 'ms',
timelineUnitType: this.props.timelineUnitType || 'time'
Copy link
Contributor

Choose a reason for hiding this comment

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

The state timeline unit type is never set otherwise since it's never passed to the props. I think what you mean hear is the data type that is part of the x-axis description: https://github.com/eclipse-cdt-cloud/trace-server-protocol/blob/72af35675446ed5da6acbe1bc8175ad7e8769166/API.yaml#L2688

}

private renderTimeline(): React.ReactNode {
if (this.state.timelineUnitType === 'time' && !this.isTimeAxis) return <svg/>;
Copy link
Contributor

Choose a reason for hiding this comment

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

The timelineUnitType is always 'time', even for ranges as x-axis or categories a x-axis. For ranges or categories it will return here because for those xy series this.isTimeAxis is false.


private updateTimeline(): void {
if (!this.timelineRef.current) return;
if (this.state.timelineUnitType === 'time' && !this.isTimeAxis) return;
Copy link
Contributor

Choose a reason for hiding this comment

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

The timelineUnitType is always 'time', even for ranges as x-axis or categories a x-axis. For ranges or categories it will return here because for those xy series this.isTimeAxis is false.

}

private getTimelineRange(): { start: number; end: number } {
switch (this.state.timelineUnitType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think what you intended was to use the data type of the axis description. See here for the supported data types:
https://github.com/eclipse-cdt-cloud/trace-server-protocol/blob/72af35675446ed5da6acbe1bc8175ad7e8769166/API.yaml#L2688

In your switch-case statement you are using non-specified values. The would need to be added to the TSP or removed here for now.

}

private formatDataRate(rate: number): string {
const units = ['B/s', 'KB/s', 'MB/s', 'GB/s'];
Copy link
Contributor

Choose a reason for hiding this comment

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

Those format methods should be in a central utility. I think we have already some code somewhere that is related. Check if we can unify those in a common utility.

case 'bytes/sec':
case 'iterations/sec':
// For non-time units, use the data range
const labels = this.state.xyData.labels;
Copy link
Contributor

Choose a reason for hiding this comment

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

The labels are strings and not actual numbers. I think it will fail for the case that the x-values are ranges or categories. It those case the labels are formatted, for example for ranges:
[${range.start} ${unit}, ${range.end} ${unit}].

timelineUnitType: TimelineUnitType;
}

type TimelineUnitType = 'time' | 'cycles' | 'bytes' | 'calls' | 'bytes/sec' | 'iterations/sec';
Copy link
Contributor

Choose a reason for hiding this comment

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

They should be aligned with the data type of the x-axis: https://github.com/eclipse-cdt-cloud/trace-server-protocol/blob/72af35675446ed5da6acbe1bc8175ad7e8769166/API.yaml#L2688. If some are missing then need to add them in the TSP and in the backend

@bhufmann
Copy link
Contributor

@MatthewKhouzam @siwei-zhang-work @PatrickTasse Reminder that due to eclipse-tracecompass-incubator/org.eclipse.tracecompass.incubator#236, to use latest Trace Compass trace server and vscode-trace-extension for testing.

@MatthewKhouzam MatthewKhouzam force-pushed the x-axis branch 3 times, most recently from ba893ea to db92a31 Compare November 14, 2025 13:58
} from 'tsp-typescript-client/lib/models/sampling';
import { signalManager } from 'traceviewer-base/lib/signals/signal-manager';
import { EntryTree } from './utils/filter-tree/entry-tree';
import { TimeRange } from 'traceviewer-base/src/utils/time-range';
Copy link
Contributor

Choose a reason for hiding this comment

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

This line is incorrect and causes the build to fail. It should reference the lib directory and not the src directory.

@MatthewKhouzam MatthewKhouzam force-pushed the x-axis branch 4 times, most recently from d03ba61 to 2faa6db Compare November 14, 2025 15:39
- Add configurable timeline bar below XY charts with D3.js
- Support multiple unit types: time, cycles, bytes, calls, data rates
- Include automatic unit formatting (bytes/KB/MB/GB, rates)
- Add timeline props: timelineUnit and timelineUnitType
- Update timeline on chart resize and data changes

Note: this patch was made with the assistance of claude-sonnet-4

Signed-off-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Copy link
Contributor

@bhufmann bhufmann left a comment

Choose a reason for hiding this comment

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

The recent changes work well with the Function Duration Density of the Trace Compass server where the x-values are duration ranges and the buckets are evenly distributed over the full range of all buckets. However, for other cases where the buckets are not evenly distributed, the x-axis not correct.

Please let me know if you'd like to merge this PR as is and then add another PR where the x-axis is handling the second case above?

@MatthewKhouzam
Copy link
Contributor Author

Let's merge and fix for the other. Upon inspection, I think the axis is correct, not the chart. I will confirm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants