/** @jsxImportSource react */ import { BoundedObject, BoundedObjectConfig, BoundedObjectInstance } from "../svg/BoundedObject"; import { parseStyle } from "../util/parseStyle"; import { VDOM } from "../ui/Widget"; import { Rect } from "../svg/util/Rect"; import { RenderingContext, CxChild } from "../ui/RenderingContext"; import { Prop, NumberProp, StyleProp } from "../ui/Prop"; import type { ChartRenderingContext } from "./Chart"; export interface SwimlaneConfig extends BoundedObjectConfig { /** The `x` value binding or expression. */ x?: Prop; /** The `y` value binding or expression. */ y?: Prop; /** Represents a swimlane size. */ size?: NumberProp; /** Switch to vertical swimlanes. */ vertical?: boolean; /** The laneOffset property adjusts the positioning of lane elements, enhancing their alignment and readability. */ laneOffset?: NumberProp; /** Style object applied to the swimlanes. */ laneStyle?: StyleProp; /** Name of the x-axis. Default is 'x'. */ xAxis?: string; /** Name of the y-axis. Default is 'y'. */ yAxis?: string; } export interface SwimlaneInstance extends BoundedObjectInstance { xAxis?: any; yAxis?: any; bounds?: Rect; } export class Swimlane extends BoundedObject { declare xAxis: string; declare yAxis: string; declare anchors: string; declare baseClass: string; declare size: number; declare laneOffset: number; declare vertical: boolean; declare laneStyle: any; declare CSS: any; constructor(config?: SwimlaneConfig) { super(config); } init() { this.laneStyle = parseStyle(this.laneStyle); super.init(); } declareData(...args: any[]) { super.declareData(...args, { size: undefined, laneOffset: undefined, laneStyle: { structured: true }, vertical: undefined, x: undefined, y: undefined, }); } explore(context: ChartRenderingContext, instance: SwimlaneInstance) { let { data } = instance; super.explore(context, instance); instance.xAxis = context.axes?.[this.xAxis]; instance.yAxis = context.axes?.[this.yAxis]; const d = data as any; if (d.vertical) { instance.xAxis.acknowledge(d.x, d.size, d.laneOffset); } else { instance.yAxis.acknowledge(d.y, d.size, d.laneOffset); } } prepare(context: RenderingContext, instance: SwimlaneInstance) { super.prepare(context, instance); instance.bounds = this.calculateRect(instance); instance.cache("bounds", instance.bounds); if (!instance.bounds.isEqual((instance.cached as any).bounds)) instance.markShouldUpdate(context); context.push("parentRect", instance.bounds); } calculateRect(instance: SwimlaneInstance): Rect { var { data } = instance; const d = data as any; var { size, laneOffset } = d; let bounds: Rect; if (d.vertical) { var x1 = instance.xAxis.map(d.x, laneOffset - size / 2); var x2 = instance.xAxis.map(d.x, laneOffset + size / 2); bounds = new Rect({ l: Math.min(x1, x2), r: Math.max(x1, x2), t: d.bounds.t, b: d.bounds.b, }); } else { var y1 = instance.yAxis.map(d.y, laneOffset - size / 2); var y2 = instance.yAxis.map(d.y, laneOffset + size / 2); bounds = new Rect({ l: d.bounds.l, r: d.bounds.r, t: Math.min(y1, y2), b: Math.max(y1, y2), }); } return bounds; } render(context: RenderingContext, instance: SwimlaneInstance, key: string): CxChild { let { data, xAxis, yAxis, bounds } = instance; let { CSS, baseClass } = this; const d = data as any; let axis = this.vertical ? xAxis : yAxis; if (!axis) return null; let min: number, max: number, valueFunction: (value: any, offset: number) => [any, number]; if (axis.scale) { min = axis.scale.min; max = axis.scale.max; let clamp = (value: number): [number, number] => [Math.max(min, Math.min(max, value)), 0]; valueFunction = (value, offset) => clamp(value + offset); } else if (axis.valueList) { min = 0; max = axis.valueList.length; valueFunction = (value, offset) => [axis.valueList[value], offset]; } if (!(min! < max!)) return null; let rectClass = CSS.element(baseClass, "lane"); if (this.vertical) { let c1 = axis.map(...valueFunction!(d.x, -d.size / 2 + d.laneOffset)); let c2 = axis.map(...valueFunction!(d.x, +d.size / 2 + d.laneOffset)); return ( {this.renderChildren(context, instance)}; ); } else { let c1 = axis.map(...valueFunction!(d.y, -d.size / 2 + d.laneOffset)); let c2 = axis.map(...valueFunction!(d.y, +d.size / 2 + d.laneOffset)); return ( {this.renderChildren(context, instance)}; ); } } } Swimlane.prototype.xAxis = "x"; Swimlane.prototype.yAxis = "y"; Swimlane.prototype.anchors = "0 1 1 0"; Swimlane.prototype.baseClass = "swimlane"; Swimlane.prototype.size = 0.5; Swimlane.prototype.laneOffset = 0; Swimlane.prototype.vertical = false; BoundedObject.alias("swimlane", Swimlane);