aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/snippets.tsx80
-rw-r--r--src/lib/utils.ts26
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)
+}