chore: wip/poc

This commit is contained in:
Galen 2025-09-23 16:52:36 -05:00
parent dafedf84ce
commit b4110b7df6
No known key found for this signature in database
6 changed files with 69 additions and 10 deletions

View File

@ -286,6 +286,7 @@ type UPlotConfigPrepOpts<T extends Record<string, unknown> = {}> = {
tweakAxis?: (opts: AxisProps, forField: Field) => AxisProps;
hoverProximity?: number;
orientation?: VizOrientation;
annotations?: DataFrame[];
} & T;
/** @alpha */

View File

@ -40,7 +40,13 @@ export interface GraphNGProps extends Themeable2 {
tweakAxis?: (opts: AxisProps, forField: Field) => AxisProps;
onLegendClick?: (event: GraphNGLegendEvent) => void;
children?: (builder: UPlotConfigBuilder, alignedFrame: DataFrame) => React.ReactNode;
prepConfig: (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => UPlotConfigBuilder;
// @todo rename to annoLanes, pass count not frames
prepConfig: (
alignedFrame: DataFrame,
allFrames: DataFrame[],
getTimeRange: () => TimeRange,
annotationFrames?: DataFrame[]
) => UPlotConfigBuilder;
propsToDiff?: Array<string | PropDiffFn>;
preparePlotFrame?: (frames: DataFrame[], dimFields: XYFieldMatchers) => DataFrame | null;
renderLegend: (config: UPlotConfigBuilder) => React.ReactElement | null;
@ -63,6 +69,9 @@ export interface GraphNGProps extends Themeable2 {
* similar to structureRev. then we can drop propsToDiff entirely.
*/
options?: Record<string, any>;
// Panel annotations
annotations?: DataFrame[];
}
function sameProps<T extends Record<string, unknown>>(
@ -191,7 +200,7 @@ export class GraphNG extends Component<GraphNGProps, GraphNGState> {
let config = this.state?.config;
if (withConfig) {
config = props.prepConfig(alignedFrameFinal, this.props.frames, this.getTimeRange);
config = props.prepConfig(alignedFrameFinal, this.props.frames, this.getTimeRange, this.props.annotations);
pluginLog('GraphNG', false, 'config prepared', config);
}
@ -225,12 +234,19 @@ export class GraphNG extends Component<GraphNGProps, GraphNGState> {
timeZone !== prevProps.timeZone ||
cursorSync !== prevProps.cursorSync ||
structureRev !== prevProps.structureRev ||
this.props.annotations?.length !== prevProps.annotations?.length ||
!structureRev ||
propsChanged;
if (shouldReconfig) {
newState.config = this.props.prepConfig(newState.alignedFrame, this.props.frames, this.getTimeRange);
newState.config = this.props.prepConfig(
newState.alignedFrame,
this.props.frames,
this.getTimeRange,
this.props.annotations
);
pluginLog('GraphNG', false, 'config recreated', newState.config);
console.log('GraphNG', 'config recreated', newState.config);
}
newState.alignedData = newState.config!.prepData!([newState.alignedFrame]) as AlignedData;

View File

@ -13,7 +13,12 @@ const propsToDiff: Array<string | PropDiffFn> = ['legend', 'options', 'theme'];
type TimeSeriesProps = Omit<GraphNGProps, 'prepConfig' | 'propsToDiff' | 'renderLegend'>;
export class UnthemedTimeSeries extends Component<TimeSeriesProps> {
prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => {
prepConfig = (
alignedFrame: DataFrame,
allFrames: DataFrame[],
getTimeRange: () => TimeRange,
annotationFrames?: DataFrame[]
) => {
const { theme, timeZone, options, renderers, tweakAxis, tweakScale } = this.props;
return preparePlotConfigBuilder({
@ -27,6 +32,7 @@ export class UnthemedTimeSeries extends Component<TimeSeriesProps> {
tweakAxis,
hoverProximity: options?.tooltip?.hoverProximity,
orientation: options?.orientation,
annotations: annotationFrames?.length ? annotationFrames : this.props.annotations,
});
};

View File

@ -90,7 +90,9 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({
tweakAxis = (opts) => opts,
hoverProximity,
orientation = VizOrientation.Horizontal,
annotations,
}) => {
console.log('preparePlotConfigBuilder', annotations);
// we want the Auto and Horizontal orientation to default to Horizontal
const isHorizontal = orientation !== VizOrientation.Vertical;
const builder = new UPlotConfigBuilder(timeZones[0]);
@ -144,9 +146,29 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({
}
: undefined;
// HERE
// console.log('build axis', annotations)
// https://github.com/leeoniya/uPlot/blob/master/src/opts.js#L552
const annotationLaneHeight = 5;
const defaultAxisSize = 50;
const annotationLanesSize = ((annotations?.length ?? 1) - 1) * annotationLaneHeight;
const size = defaultAxisSize + annotationLanesSize;
// Add a bit of space below the last annotation lane to show the grid-lines
const gapSize = annotationLanesSize + 5;
for (let i = 0; i < timeZones.length; i++) {
const timeZone = timeZones[i];
// tick length to match the gap length?
// want gridlines showing below lowest lane a few px
builder.addAxis({
// HERE update axis size
ticks: {
size: gapSize,
},
gap: gapSize,
size,
// size is everything
scaleKey: xScaleKey,
isTime: true,
placement: xFieldAxisPlacement,

View File

@ -131,8 +131,10 @@ export const TimeSeriesPanel = ({
replaceVariables={replaceVariables}
dataLinkPostProcessor={dataLinkPostProcessor}
cursorSync={cursorSync}
annotations={data.annotations}
>
{(uplotConfig, alignedFrame) => {
console.log('time series config (re)loaded', data.annotations?.length, data.annotations);
return (
<>
<KeyboardPlugin config={uplotConfig} />

View File

@ -132,7 +132,9 @@ export const AnnotationsPlugin2 = ({
ctx.rect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);
ctx.clip();
annos.forEach((frame) => {
annos.forEach((frame, frameIndex) => {
const annotationLaneYOffset = frameIndex * 30;
console.log('annotationLaneYOffset', frameIndex, annotationLaneYOffset);
let vals = getVals(frame);
if (frame.name === 'xymark') {
@ -166,19 +168,28 @@ export const AnnotationsPlugin2 = ({
ctx.strokeRect(x0, y0, x1 - x0, y1 - y0);
}
} else {
let y0 = u.bbox.top;
let y1 = y0 + u.bbox.height;
// if multiple regions, don't shade
// @todo toggle functionality, new annotation config option?
let y0 = u.bbox.top - annotationLaneYOffset;
let y1 = y0 + u.bbox.height + annotationLaneYOffset;
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
// @todo Don't render the vertical lines if multi lane is enabled
//@todo Don't render shaded region if multi-lane is enabled
// skipping this loop should do it
for (let i = 0; i < vals.time.length; i++) {
let color = getColorByName(vals.color?.[i] || DEFAULT_ANNOTATION_COLOR_HEX8);
let x0 = u.valToPos(vals.time[i], 'x', true);
renderLine(ctx, y0, y1, x0, color);
if (vals.isRegion?.[i]) {
// If dataframe does not have end times, let's omit rendering the region for now
// @todo do we want to fix isRegion to render a point when we're missing timeEnd?
if (vals.isRegion?.[i] && vals.timeEnd?.[i]) {
let x1 = u.valToPos(vals.timeEnd[i], 'x', true);
renderLine(ctx, y0, y1, x1, color);
@ -215,6 +226,7 @@ export const AnnotationsPlugin2 = ({
let markers: React.ReactNode[] = [];
const top = frameIdx * 5;
for (let i = 0; i < vals.time.length; i++) {
let color = getColorByName(vals.color?.[i] || DEFAULT_ANNOTATION_COLOR);
let left = Math.round(plot.valToPos(vals.time[i], 'x')) || 0; // handles -0
@ -231,14 +243,14 @@ export const AnnotationsPlugin2 = ({
let clampedLeft = Math.max(0, left);
let clampedRight = Math.min(plot.rect.width, right);
style = { left: clampedLeft, background: color, width: clampedRight - clampedLeft };
style = { left: clampedLeft, background: color, width: clampedRight - clampedLeft, top };
className = styles.annoRegion;
}
} else {
isVisible = left >= 0 && left <= plot.rect.width;
if (isVisible) {
style = { left, borderBottomColor: color };
style = { left, borderBottomColor: color, top };
className = styles.annoMarker;
}
}