diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/snippets.tsx | 80 | ||||
| -rw-r--r-- | src/lib/utils.ts | 26 |
2 files changed, 105 insertions, 1 deletions
diff --git a/src/lib/snippets.tsx b/src/lib/snippets.tsx new file mode 100644 index 0000000..5e95cd2 --- /dev/null +++ b/src/lib/snippets.tsx @@ -0,0 +1,80 @@ +import React from "react"; + +type IQRData = { + min: number; + q1: number; + median: number; + q3: number; + max: number; +}; + +type IQRBoxPlotProps = { + data: IQRData; +}; + +export const IQRBoxPlot: React.FC<IQRBoxPlotProps> = ({data}) => { + const {min, q1, median, q3, max} = data; + + const scale = (value: number): number => + ((value - min) / (max - min)) * 100; + + return ( + <div className="w-full p-4"> + <div className="text-sm mb-2 text-muted-foreground">Box Plot</div> + <svg viewBox="0 0 100 20" className="w-full h-12"> + <line + x1={scale(min)} + x2={scale(q1)} + y1={10} + y2={10} + stroke="#999" + strokeWidth={1} + /> + + <line + x1={scale(q3)} + x2={scale(max)} + y1={10} + y2={10} + stroke="#999" + strokeWidth={1} + /> + + <rect + x={scale(q1)} + y={5} + width={scale(q3) - scale(q1)} + height={10} + fill="#e2e8f0" + stroke="#64748b" + strokeWidth={1} + /> + + <line + x1={scale(median)} + x2={scale(median)} + y1={5} + y2={15} + stroke="#1e293b" + stroke-width={1.5} + /> + + <line + x1={scale(min)} + x2={scale(min)} + y1={7} + y2={13} + stroke="#666" + strokeWidth={1}/> + + <line + x1={scale(max)} + x2={scale(max)} + y1={7} + y2={13} + stroke="#666" + strokeWidth={1}/> + </svg> + </div> + ) +};
\ No newline at end of file diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 8525468..b1cd120 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,5 +1,6 @@ import {type ClassValue, clsx} from "clsx" import {twMerge} from "tailwind-merge" +import React from "react"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) @@ -9,4 +10,27 @@ export function assert(condition: any, msg?: string): asserts condition { if (!condition) { throw new Error(msg); } -}
\ No newline at end of file +} + +export function formatSecondsVerbose(seconds: number): string { + const mins = Math.floor(seconds / 60) + const secs = seconds % 60 + const parts = [] + if (mins > 0) parts.push(`${mins} min`) + if (secs > 0 || mins === 0) parts.push(`${secs} sec`) + return parts.join(" ") +} + +export function formatSeconds(seconds: number): string { + const mins = Math.floor(seconds / 60) + const secs = seconds % 60 + const paddedSecs = secs.toString().padStart(2, '0') + return `${mins}:${paddedSecs}` +} + +export function formatCentsToUSD(cents: number): string { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }).format(cents / 100) +} |
