@sketchlog/react 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/components/CDFCurve.d.ts +8 -0
- package/dist/components/CardinalitySparkline.d.ts +9 -0
- package/dist/components/QuantileHeatmap.d.ts +9 -0
- package/dist/components/SketchLogContext.d.ts +3 -0
- package/dist/components/SketchLogProvider.d.ts +7 -0
- package/dist/counter.d.ts +2 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +239 -0
- package/dist/types.d.ts +42 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# `@sketchlog/react`
|
|
2
|
+
|
|
3
|
+
React components for a live SketchLog stream.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import {
|
|
7
|
+
SketchLogProvider,
|
|
8
|
+
QuantileHeatmap,
|
|
9
|
+
CardinalitySparkline,
|
|
10
|
+
} from "@sketchlog/react";
|
|
11
|
+
|
|
12
|
+
export function Dashboard() {
|
|
13
|
+
return (
|
|
14
|
+
<SketchLogProvider url="wss://metrics.example/v1/streams/api/ws">
|
|
15
|
+
<QuantileHeatmap />
|
|
16
|
+
<CardinalitySparkline />
|
|
17
|
+
</SketchLogProvider>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For authenticated browser connections, have a same-site gateway set the
|
|
23
|
+
`sketchlog_auth` cookie with `HttpOnly`, `Secure`, and an appropriate
|
|
24
|
+
`SameSite` policy before opening the WebSocket. Tokens are not placed in URLs.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface QuantileHeatmapProps {
|
|
3
|
+
width?: number;
|
|
4
|
+
height?: number;
|
|
5
|
+
colorScheme?: readonly string[] | string | ((value: number) => string);
|
|
6
|
+
historySize?: number;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const QuantileHeatmap: React.FC<QuantileHeatmapProps>;
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("react");c=s(c,1);let l=require("react-use-websocket");l=s(l,1);let u=require("react/jsx-runtime"),d=require("d3");d=s(d,1);var f=(0,c.createContext)({state:null,isConnected:!1,error:null}),p=()=>(0,c.useContext)(f),m=({url:e,children:t})=>{let[n,r]=(0,c.useState)(null),[i,a]=(0,c.useState)(null),{lastJsonMessage:o,readyState:s}=(0,l.default)(e,{shouldReconnect:()=>!0,reconnectAttempts:10,reconnectInterval:3e3,onError:()=>a(Error(`WebSocket connection error`))});(0,c.useEffect)(()=>{if(!o)return;let e=window.setTimeout(()=>{`error`in o?(a(Error(o.error)),r(null)):(r(o),a(null))},0);return()=>window.clearTimeout(e)},[o]);let d=s===1;return(0,u.jsx)(f.Provider,{value:{state:n,isConnected:d,error:i},children:t})};function h(e){let t=typeof e==`string`?Number(e):e;return Number.isFinite(t)&&t>=0?t:0}function g(e,t){let n=(1+e)/(1-e);return 2/(1+n)*n**+t}var _=({width:e=600,height:t=300,color:n=`#8b5cf6`,className:r=``})=>{let{state:i,isConnected:a}=p(),o=(0,c.useRef)(null),s=(0,c.useMemo)(()=>{if(!i||!i.latency)return[];let e=i.latency,t=0,n=[],r=h(e.count);if(r===0)return[];let a=Object.keys(e.negative).map(Number).sort((e,t)=>t-e);for(let i of a)t+=h(e.negative[i]),n.push({x:-g(e.alpha,i),y:t/r});h(e.zero_count)>0&&(t+=h(e.zero_count),n.push({x:0,y:t/r}));let o=Object.keys(e.positive).map(Number).sort((e,t)=>e-t);for(let i of o)t+=h(e.positive[i]),n.push({x:g(e.alpha,i),y:t/r});return n.length>0&&(n[n.length-1].y=1),n},[i]);return(0,c.useEffect)(()=>{if(!o.current||s.length===0)return;let r=d.select(o.current);r.selectAll(`*`).remove();let i={top:20,right:30,bottom:40,left:50},a=e-i.left-i.right,c=t-i.top-i.bottom,l=d.scaleLog().domain([Math.max(.1,s[0].x>0?s[0].x:.1),Math.max(.1,d.max(s,e=>e.x)||100)]).range([0,a]).clamp(!0),u=d.scaleLinear().domain([0,1]).range([c,0]),f=r.append(`g`).attr(`transform`,`translate(${i.left},${i.top})`);f.append(`g`).attr(`class`,`grid`).attr(`transform`,`translate(0,${c})`).call(d.axisBottom(l).ticks(5).tickSize(-c).tickFormat(()=>``)).attr(`stroke-opacity`,.1).attr(`stroke`,`currentColor`),f.append(`g`).attr(`class`,`grid`).call(d.axisLeft(u).ticks(5).tickSize(-a).tickFormat(()=>``)).attr(`stroke-opacity`,.1).attr(`stroke`,`currentColor`),f.append(`g`).attr(`transform`,`translate(0,${c})`).call(d.axisBottom(l).ticks(5,`~s`)).attr(`color`,`rgba(255, 255, 255, 0.5)`),f.append(`g`).call(d.axisLeft(u).tickFormat(d.format(`.0%`))).attr(`color`,`rgba(255, 255, 255, 0.5)`);let p=d.line().x(e=>l(Math.max(.1,e.x))).y(e=>u(e.y)).curve(d.curveStepAfter),m=d.area().x(e=>l(Math.max(.1,e.x))).y0(c).y1(e=>u(e.y)).curve(d.curveStepAfter),h=`cdf-gradient-${Math.random().toString(36).substring(2)}`,g=r.append(`defs`).append(`linearGradient`).attr(`id`,h).attr(`x1`,`0%`).attr(`y1`,`0%`).attr(`x2`,`0%`).attr(`y2`,`100%`);g.append(`stop`).attr(`offset`,`0%`).attr(`stop-color`,n).attr(`stop-opacity`,.4),g.append(`stop`).attr(`offset`,`100%`).attr(`stop-color`,n).attr(`stop-opacity`,0),f.append(`path`).datum(s).attr(`fill`,`url(#${h})`).attr(`d`,m).style(`transition`,`d 0.5s ease`),f.append(`path`).datum(s).attr(`fill`,`none`).attr(`stroke`,n).attr(`stroke-width`,2).attr(`stroke-linejoin`,`round`).attr(`stroke-linecap`,`round`).attr(`d`,p).style(`transition`,`d 0.5s ease`)},[s,e,t,n]),(0,u.jsxs)(`div`,{className:`relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-4 shadow-2xl ${r}`,children:[(0,u.jsx)(`h3`,{className:`text-white/90 font-medium mb-2 text-sm`,children:`Latency CDF`}),(0,u.jsxs)(`div`,{className:`absolute top-4 right-4 flex items-center gap-2`,children:[(0,u.jsxs)(`span`,{className:`relative flex h-2 w-2`,children:[a&&(0,u.jsx)(`span`,{className:`animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75`}),(0,u.jsx)(`span`,{className:`relative inline-flex rounded-full h-2 w-2 ${a?`bg-green-500`:`bg-red-500`}`})]}),(0,u.jsx)(`span`,{className:`text-xs text-white/50`,children:a?`LIVE`:`OFFLINE`})]}),(0,u.jsx)(`svg`,{ref:o,width:e,height:t,className:`w-full h-full text-white/80`,style:{minHeight:t}})]})};function v(e,t){let n=(1+e)/(1-e);return 2/(1+n)*n**+t}var y=({width:e=600,height:t=300,colorScheme:n=d.interpolateInferno,historySize:r=30,className:i=``})=>{let{state:a,isConnected:o}=p(),s=(0,c.useRef)(null),[l,f]=(0,c.useState)([]);return(0,c.useEffect)(()=>{if(!a||!a.latency)return;let e=a.latency,t=Date.now(),n=new Map,i=Object.keys(e.positive).map(Number);for(let t of i)n.set(t,h(e.positive[t]));let o=Object.keys(e.negative).map(Number);for(let t of o)n.set(-t,(n.get(-t)||0)+h(e.negative[t]));h(e.zero_count)>0&&n.set(0,(n.get(0)||0)+h(e.zero_count));let s=window.setTimeout(()=>{f(i=>[...i,{time:t,total:h(e.count),bins:n}].slice(-r))},0);return()=>window.clearTimeout(s)},[a,r]),(0,c.useEffect)(()=>{if(!s.current||l.length===0)return;let i=d.select(s.current);i.selectAll(`*`).remove();let o={top:20,right:10,bottom:30,left:50},c=e-o.left-o.right,u=t-o.top-o.bottom,f=1/0,p=-1/0,m=0,h=[];for(let e=1;e<l.length;e++){let t=l[e-1],n=l[e],r=new Map;for(let[e,i]of n.bins.entries()){let n=t.bins.get(e)||0,a=Math.max(0,i-n);a>0&&(r.set(e,a),f=Math.min(f,e),p=Math.max(p,e),m=Math.max(m,a))}h.push({time:n.time,bins:r})}if(h.length===0||p===-1/0)return;let g=a?.latency?.alpha||.01,_=d.scaleTime().domain([l[0].time,l[l.length-1].time]).range([0,c]),y=d.scaleLinear().domain([f-1,p+1]).range([u,0]),b;b=typeof n==`function`?n:typeof n==`string`?d.interpolateRgb(`rgba(255,255,255,0)`,n):d.piecewise(d.interpolateRgb,[...n]);let x=d.scaleSequential(b).domain([0,m]),S=i.append(`g`).attr(`transform`,`translate(${o.left},${o.top})`),C=c/Math.max(1,r-1),w=Math.abs(y(f)-y(f+1));h.forEach(e=>{for(let[t,n]of e.bins.entries())S.append(`rect`).attr(`x`,_(e.time)-C).attr(`y`,y(t)-w/2).attr(`width`,C+1).attr(`height`,w+1).attr(`fill`,x(n)).attr(`rx`,2).attr(`ry`,2).attr(`opacity`,.9)}),S.append(`g`).attr(`transform`,`translate(0,${u})`).call(d.axisBottom(_).ticks(5)).attr(`color`,`rgba(255, 255, 255, 0.5)`);let T=d.axisLeft(y).tickValues(d.range(f,p+1,Math.max(1,Math.floor((p-f)/5)))).tickFormat(e=>d.format(`.1f`)(v(g,e)));S.append(`g`).call(T).attr(`color`,`rgba(255, 255, 255, 0.5)`)},[l,e,t,n,a,r]),(0,u.jsxs)(`div`,{className:`relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-4 shadow-2xl ${i}`,children:[(0,u.jsx)(`h3`,{className:`text-white/90 font-medium mb-2 text-sm`,children:`Quantile Heatmap (Live)`}),(0,u.jsx)(`div`,{className:`absolute top-4 right-4 flex items-center gap-2`,children:(0,u.jsxs)(`span`,{className:`relative flex h-2 w-2`,children:[o&&(0,u.jsx)(`span`,{className:`animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75`}),(0,u.jsx)(`span`,{className:`relative inline-flex rounded-full h-2 w-2 ${o?`bg-blue-500`:`bg-red-500`}`})]})}),(0,u.jsx)(`svg`,{ref:s,width:e,height:t,className:`w-full h-full text-white/80`,style:{minHeight:t}})]})},b=({width:e=300,height:t=100,color:n=`#10b981`,historySize:r=60,className:i=``})=>{let{state:a}=p(),o=(0,c.useRef)(null),[s,l]=(0,c.useState)([]);return(0,c.useEffect)(()=>{if(!a)return;let e=h(a.metrics?.unique_count??0),t=Date.now(),n=window.setTimeout(()=>{l(n=>[...n,{time:t,value:e}].slice(-r))},0);return()=>window.clearTimeout(n)},[a,r]),(0,c.useEffect)(()=>{if(!o.current||s.length<2)return;let r=d.select(o.current);r.selectAll(`*`).remove();let i={top:5,right:5,bottom:5,left:5},a=e-i.left-i.right,c=t-i.top-i.bottom,l=d.scaleTime().domain(d.extent(s,e=>e.time)).range([0,a]),u=d.scaleLinear().domain([0,d.max(s,e=>e.value)||10]).range([c,0]),f=r.append(`g`).attr(`transform`,`translate(${i.left},${i.top})`),p=d.line().x(e=>l(e.time)).y(e=>u(e.value)).curve(d.curveMonotoneX),m=d.area().x(e=>l(e.time)).y0(c).y1(e=>u(e.value)).curve(d.curveMonotoneX),h=`spark-gradient-${Math.random().toString(36).substring(2)}`,g=r.append(`defs`).append(`linearGradient`).attr(`id`,h).attr(`x1`,`0%`).attr(`y1`,`0%`).attr(`x2`,`0%`).attr(`y2`,`100%`);g.append(`stop`).attr(`offset`,`0%`).attr(`stop-color`,n).attr(`stop-opacity`,.3),g.append(`stop`).attr(`offset`,`100%`).attr(`stop-color`,n).attr(`stop-opacity`,0),f.append(`path`).datum(s).attr(`fill`,`url(#${h})`).attr(`d`,m).style(`transition`,`d 0.3s linear`),f.append(`path`).datum(s).attr(`fill`,`none`).attr(`stroke`,n).attr(`stroke-width`,2).attr(`stroke-linecap`,`round`).attr(`d`,p).style(`transition`,`d 0.3s linear`);let _=s[s.length-1].value;r.append(`text`).attr(`x`,i.left).attr(`y`,i.top+15).text(d.format(`~s`)(_)).attr(`fill`,`white`).attr(`font-size`,`14px`).attr(`font-weight`,`600`)},[s,e,t,n]),(0,u.jsxs)(`div`,{className:`relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-2 shadow-xl flex flex-col ${i}`,children:[(0,u.jsx)(`h3`,{className:`text-white/70 font-medium mb-1 text-xs px-2 pt-1`,children:`Estimated Cardinality`}),(0,u.jsx)(`svg`,{ref:o,width:e,height:t,className:`w-full text-white/80`,style:{minHeight:t}})]})};exports.CDFCurve=_,exports.CardinalitySparkline=b,exports.QuantileHeatmap=y,exports.SketchLogContext=f,exports.SketchLogProvider=m,exports.useSketchLog=p;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { createContext as e, useContext as t, useEffect as n, useMemo as r, useRef as i, useState as a } from "react";
|
|
2
|
+
import o from "react-use-websocket";
|
|
3
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
4
|
+
import * as l from "d3";
|
|
5
|
+
//#region src/components/SketchLogContext.ts
|
|
6
|
+
var u = e({
|
|
7
|
+
state: null,
|
|
8
|
+
isConnected: !1,
|
|
9
|
+
error: null
|
|
10
|
+
}), d = () => t(u), f = ({ url: e, children: t }) => {
|
|
11
|
+
let [r, i] = a(null), [c, l] = a(null), { lastJsonMessage: d, readyState: f } = o(e, {
|
|
12
|
+
shouldReconnect: () => !0,
|
|
13
|
+
reconnectAttempts: 10,
|
|
14
|
+
reconnectInterval: 3e3,
|
|
15
|
+
onError: () => l(/* @__PURE__ */ Error("WebSocket connection error"))
|
|
16
|
+
});
|
|
17
|
+
n(() => {
|
|
18
|
+
if (!d) return;
|
|
19
|
+
let e = window.setTimeout(() => {
|
|
20
|
+
"error" in d ? (l(Error(d.error)), i(null)) : (i(d), l(null));
|
|
21
|
+
}, 0);
|
|
22
|
+
return () => window.clearTimeout(e);
|
|
23
|
+
}, [d]);
|
|
24
|
+
let p = f === 1;
|
|
25
|
+
return /* @__PURE__ */ s(u.Provider, {
|
|
26
|
+
value: {
|
|
27
|
+
state: r,
|
|
28
|
+
isConnected: p,
|
|
29
|
+
error: c
|
|
30
|
+
},
|
|
31
|
+
children: t
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/counter.ts
|
|
36
|
+
function p(e) {
|
|
37
|
+
let t = typeof e == "string" ? Number(e) : e;
|
|
38
|
+
return Number.isFinite(t) && t >= 0 ? t : 0;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/components/CDFCurve.tsx
|
|
42
|
+
function m(e, t) {
|
|
43
|
+
let n = (1 + e) / (1 - e);
|
|
44
|
+
return 2 / (1 + n) * n ** +t;
|
|
45
|
+
}
|
|
46
|
+
var h = ({ width: e = 600, height: t = 300, color: a = "#8b5cf6", className: o = "" }) => {
|
|
47
|
+
let { state: u, isConnected: f } = d(), h = i(null), g = r(() => {
|
|
48
|
+
if (!u || !u.latency) return [];
|
|
49
|
+
let e = u.latency, t = 0, n = [], r = p(e.count);
|
|
50
|
+
if (r === 0) return [];
|
|
51
|
+
let i = Object.keys(e.negative).map(Number).sort((e, t) => t - e);
|
|
52
|
+
for (let a of i) t += p(e.negative[a]), n.push({
|
|
53
|
+
x: -m(e.alpha, a),
|
|
54
|
+
y: t / r
|
|
55
|
+
});
|
|
56
|
+
p(e.zero_count) > 0 && (t += p(e.zero_count), n.push({
|
|
57
|
+
x: 0,
|
|
58
|
+
y: t / r
|
|
59
|
+
}));
|
|
60
|
+
let a = Object.keys(e.positive).map(Number).sort((e, t) => e - t);
|
|
61
|
+
for (let i of a) t += p(e.positive[i]), n.push({
|
|
62
|
+
x: m(e.alpha, i),
|
|
63
|
+
y: t / r
|
|
64
|
+
});
|
|
65
|
+
return n.length > 0 && (n[n.length - 1].y = 1), n;
|
|
66
|
+
}, [u]);
|
|
67
|
+
return n(() => {
|
|
68
|
+
if (!h.current || g.length === 0) return;
|
|
69
|
+
let n = l.select(h.current);
|
|
70
|
+
n.selectAll("*").remove();
|
|
71
|
+
let r = {
|
|
72
|
+
top: 20,
|
|
73
|
+
right: 30,
|
|
74
|
+
bottom: 40,
|
|
75
|
+
left: 50
|
|
76
|
+
}, i = e - r.left - r.right, o = t - r.top - r.bottom, s = l.scaleLog().domain([Math.max(.1, g[0].x > 0 ? g[0].x : .1), Math.max(.1, l.max(g, (e) => e.x) || 100)]).range([0, i]).clamp(!0), c = l.scaleLinear().domain([0, 1]).range([o, 0]), u = n.append("g").attr("transform", `translate(${r.left},${r.top})`);
|
|
77
|
+
u.append("g").attr("class", "grid").attr("transform", `translate(0,${o})`).call(l.axisBottom(s).ticks(5).tickSize(-o).tickFormat(() => "")).attr("stroke-opacity", .1).attr("stroke", "currentColor"), u.append("g").attr("class", "grid").call(l.axisLeft(c).ticks(5).tickSize(-i).tickFormat(() => "")).attr("stroke-opacity", .1).attr("stroke", "currentColor"), u.append("g").attr("transform", `translate(0,${o})`).call(l.axisBottom(s).ticks(5, "~s")).attr("color", "rgba(255, 255, 255, 0.5)"), u.append("g").call(l.axisLeft(c).tickFormat(l.format(".0%"))).attr("color", "rgba(255, 255, 255, 0.5)");
|
|
78
|
+
let d = l.line().x((e) => s(Math.max(.1, e.x))).y((e) => c(e.y)).curve(l.curveStepAfter), f = l.area().x((e) => s(Math.max(.1, e.x))).y0(o).y1((e) => c(e.y)).curve(l.curveStepAfter), p = `cdf-gradient-${Math.random().toString(36).substring(2)}`, m = n.append("defs").append("linearGradient").attr("id", p).attr("x1", "0%").attr("y1", "0%").attr("x2", "0%").attr("y2", "100%");
|
|
79
|
+
m.append("stop").attr("offset", "0%").attr("stop-color", a).attr("stop-opacity", .4), m.append("stop").attr("offset", "100%").attr("stop-color", a).attr("stop-opacity", 0), u.append("path").datum(g).attr("fill", `url(#${p})`).attr("d", f).style("transition", "d 0.5s ease"), u.append("path").datum(g).attr("fill", "none").attr("stroke", a).attr("stroke-width", 2).attr("stroke-linejoin", "round").attr("stroke-linecap", "round").attr("d", d).style("transition", "d 0.5s ease");
|
|
80
|
+
}, [
|
|
81
|
+
g,
|
|
82
|
+
e,
|
|
83
|
+
t,
|
|
84
|
+
a
|
|
85
|
+
]), /* @__PURE__ */ c("div", {
|
|
86
|
+
className: `relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-4 shadow-2xl ${o}`,
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ s("h3", {
|
|
89
|
+
className: "text-white/90 font-medium mb-2 text-sm",
|
|
90
|
+
children: "Latency CDF"
|
|
91
|
+
}),
|
|
92
|
+
/* @__PURE__ */ c("div", {
|
|
93
|
+
className: "absolute top-4 right-4 flex items-center gap-2",
|
|
94
|
+
children: [/* @__PURE__ */ c("span", {
|
|
95
|
+
className: "relative flex h-2 w-2",
|
|
96
|
+
children: [f && /* @__PURE__ */ s("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }), /* @__PURE__ */ s("span", { className: `relative inline-flex rounded-full h-2 w-2 ${f ? "bg-green-500" : "bg-red-500"}` })]
|
|
97
|
+
}), /* @__PURE__ */ s("span", {
|
|
98
|
+
className: "text-xs text-white/50",
|
|
99
|
+
children: f ? "LIVE" : "OFFLINE"
|
|
100
|
+
})]
|
|
101
|
+
}),
|
|
102
|
+
/* @__PURE__ */ s("svg", {
|
|
103
|
+
ref: h,
|
|
104
|
+
width: e,
|
|
105
|
+
height: t,
|
|
106
|
+
className: "w-full h-full text-white/80",
|
|
107
|
+
style: { minHeight: t }
|
|
108
|
+
})
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/components/QuantileHeatmap.tsx
|
|
114
|
+
function g(e, t) {
|
|
115
|
+
let n = (1 + e) / (1 - e);
|
|
116
|
+
return 2 / (1 + n) * n ** +t;
|
|
117
|
+
}
|
|
118
|
+
var _ = ({ width: e = 600, height: t = 300, colorScheme: r = l.interpolateInferno, historySize: o = 30, className: u = "" }) => {
|
|
119
|
+
let { state: f, isConnected: m } = d(), h = i(null), [_, v] = a([]);
|
|
120
|
+
return n(() => {
|
|
121
|
+
if (!f || !f.latency) return;
|
|
122
|
+
let e = f.latency, t = Date.now(), n = /* @__PURE__ */ new Map(), r = Object.keys(e.positive).map(Number);
|
|
123
|
+
for (let t of r) n.set(t, p(e.positive[t]));
|
|
124
|
+
let i = Object.keys(e.negative).map(Number);
|
|
125
|
+
for (let t of i) n.set(-t, (n.get(-t) || 0) + p(e.negative[t]));
|
|
126
|
+
p(e.zero_count) > 0 && n.set(0, (n.get(0) || 0) + p(e.zero_count));
|
|
127
|
+
let a = window.setTimeout(() => {
|
|
128
|
+
v((r) => [...r, {
|
|
129
|
+
time: t,
|
|
130
|
+
total: p(e.count),
|
|
131
|
+
bins: n
|
|
132
|
+
}].slice(-o));
|
|
133
|
+
}, 0);
|
|
134
|
+
return () => window.clearTimeout(a);
|
|
135
|
+
}, [f, o]), n(() => {
|
|
136
|
+
if (!h.current || _.length === 0) return;
|
|
137
|
+
let n = l.select(h.current);
|
|
138
|
+
n.selectAll("*").remove();
|
|
139
|
+
let i = {
|
|
140
|
+
top: 20,
|
|
141
|
+
right: 10,
|
|
142
|
+
bottom: 30,
|
|
143
|
+
left: 50
|
|
144
|
+
}, a = e - i.left - i.right, s = t - i.top - i.bottom, c = Infinity, u = -Infinity, d = 0, p = [];
|
|
145
|
+
for (let e = 1; e < _.length; e++) {
|
|
146
|
+
let t = _[e - 1], n = _[e], r = /* @__PURE__ */ new Map();
|
|
147
|
+
for (let [e, i] of n.bins.entries()) {
|
|
148
|
+
let n = t.bins.get(e) || 0, a = Math.max(0, i - n);
|
|
149
|
+
a > 0 && (r.set(e, a), c = Math.min(c, e), u = Math.max(u, e), d = Math.max(d, a));
|
|
150
|
+
}
|
|
151
|
+
p.push({
|
|
152
|
+
time: n.time,
|
|
153
|
+
bins: r
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (p.length === 0 || u === -Infinity) return;
|
|
157
|
+
let m = f?.latency?.alpha || .01, v = l.scaleTime().domain([_[0].time, _[_.length - 1].time]).range([0, a]), y = l.scaleLinear().domain([c - 1, u + 1]).range([s, 0]), b;
|
|
158
|
+
b = typeof r == "function" ? r : typeof r == "string" ? l.interpolateRgb("rgba(255,255,255,0)", r) : l.piecewise(l.interpolateRgb, [...r]);
|
|
159
|
+
let x = l.scaleSequential(b).domain([0, d]), S = n.append("g").attr("transform", `translate(${i.left},${i.top})`), C = a / Math.max(1, o - 1), w = Math.abs(y(c) - y(c + 1));
|
|
160
|
+
p.forEach((e) => {
|
|
161
|
+
for (let [t, n] of e.bins.entries()) S.append("rect").attr("x", v(e.time) - C).attr("y", y(t) - w / 2).attr("width", C + 1).attr("height", w + 1).attr("fill", x(n)).attr("rx", 2).attr("ry", 2).attr("opacity", .9);
|
|
162
|
+
}), S.append("g").attr("transform", `translate(0,${s})`).call(l.axisBottom(v).ticks(5)).attr("color", "rgba(255, 255, 255, 0.5)");
|
|
163
|
+
let T = l.axisLeft(y).tickValues(l.range(c, u + 1, Math.max(1, Math.floor((u - c) / 5)))).tickFormat((e) => l.format(".1f")(g(m, e)));
|
|
164
|
+
S.append("g").call(T).attr("color", "rgba(255, 255, 255, 0.5)");
|
|
165
|
+
}, [
|
|
166
|
+
_,
|
|
167
|
+
e,
|
|
168
|
+
t,
|
|
169
|
+
r,
|
|
170
|
+
f,
|
|
171
|
+
o
|
|
172
|
+
]), /* @__PURE__ */ c("div", {
|
|
173
|
+
className: `relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-4 shadow-2xl ${u}`,
|
|
174
|
+
children: [
|
|
175
|
+
/* @__PURE__ */ s("h3", {
|
|
176
|
+
className: "text-white/90 font-medium mb-2 text-sm",
|
|
177
|
+
children: "Quantile Heatmap (Live)"
|
|
178
|
+
}),
|
|
179
|
+
/* @__PURE__ */ s("div", {
|
|
180
|
+
className: "absolute top-4 right-4 flex items-center gap-2",
|
|
181
|
+
children: /* @__PURE__ */ c("span", {
|
|
182
|
+
className: "relative flex h-2 w-2",
|
|
183
|
+
children: [m && /* @__PURE__ */ s("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75" }), /* @__PURE__ */ s("span", { className: `relative inline-flex rounded-full h-2 w-2 ${m ? "bg-blue-500" : "bg-red-500"}` })]
|
|
184
|
+
})
|
|
185
|
+
}),
|
|
186
|
+
/* @__PURE__ */ s("svg", {
|
|
187
|
+
ref: h,
|
|
188
|
+
width: e,
|
|
189
|
+
height: t,
|
|
190
|
+
className: "w-full h-full text-white/80",
|
|
191
|
+
style: { minHeight: t }
|
|
192
|
+
})
|
|
193
|
+
]
|
|
194
|
+
});
|
|
195
|
+
}, v = ({ width: e = 300, height: t = 100, color: r = "#10b981", historySize: o = 60, className: u = "" }) => {
|
|
196
|
+
let { state: f } = d(), m = i(null), [h, g] = a([]);
|
|
197
|
+
return n(() => {
|
|
198
|
+
if (!f) return;
|
|
199
|
+
let e = p(f.metrics?.unique_count ?? 0), t = Date.now(), n = window.setTimeout(() => {
|
|
200
|
+
g((n) => [...n, {
|
|
201
|
+
time: t,
|
|
202
|
+
value: e
|
|
203
|
+
}].slice(-o));
|
|
204
|
+
}, 0);
|
|
205
|
+
return () => window.clearTimeout(n);
|
|
206
|
+
}, [f, o]), n(() => {
|
|
207
|
+
if (!m.current || h.length < 2) return;
|
|
208
|
+
let n = l.select(m.current);
|
|
209
|
+
n.selectAll("*").remove();
|
|
210
|
+
let i = {
|
|
211
|
+
top: 5,
|
|
212
|
+
right: 5,
|
|
213
|
+
bottom: 5,
|
|
214
|
+
left: 5
|
|
215
|
+
}, a = e - i.left - i.right, o = t - i.top - i.bottom, s = l.scaleTime().domain(l.extent(h, (e) => e.time)).range([0, a]), c = l.scaleLinear().domain([0, l.max(h, (e) => e.value) || 10]).range([o, 0]), u = n.append("g").attr("transform", `translate(${i.left},${i.top})`), d = l.line().x((e) => s(e.time)).y((e) => c(e.value)).curve(l.curveMonotoneX), f = l.area().x((e) => s(e.time)).y0(o).y1((e) => c(e.value)).curve(l.curveMonotoneX), p = `spark-gradient-${Math.random().toString(36).substring(2)}`, g = n.append("defs").append("linearGradient").attr("id", p).attr("x1", "0%").attr("y1", "0%").attr("x2", "0%").attr("y2", "100%");
|
|
216
|
+
g.append("stop").attr("offset", "0%").attr("stop-color", r).attr("stop-opacity", .3), g.append("stop").attr("offset", "100%").attr("stop-color", r).attr("stop-opacity", 0), u.append("path").datum(h).attr("fill", `url(#${p})`).attr("d", f).style("transition", "d 0.3s linear"), u.append("path").datum(h).attr("fill", "none").attr("stroke", r).attr("stroke-width", 2).attr("stroke-linecap", "round").attr("d", d).style("transition", "d 0.3s linear");
|
|
217
|
+
let _ = h[h.length - 1].value;
|
|
218
|
+
n.append("text").attr("x", i.left).attr("y", i.top + 15).text(l.format("~s")(_)).attr("fill", "white").attr("font-size", "14px").attr("font-weight", "600");
|
|
219
|
+
}, [
|
|
220
|
+
h,
|
|
221
|
+
e,
|
|
222
|
+
t,
|
|
223
|
+
r
|
|
224
|
+
]), /* @__PURE__ */ c("div", {
|
|
225
|
+
className: `relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-2 shadow-xl flex flex-col ${u}`,
|
|
226
|
+
children: [/* @__PURE__ */ s("h3", {
|
|
227
|
+
className: "text-white/70 font-medium mb-1 text-xs px-2 pt-1",
|
|
228
|
+
children: "Estimated Cardinality"
|
|
229
|
+
}), /* @__PURE__ */ s("svg", {
|
|
230
|
+
ref: m,
|
|
231
|
+
width: e,
|
|
232
|
+
height: t,
|
|
233
|
+
className: "w-full text-white/80",
|
|
234
|
+
style: { minHeight: t }
|
|
235
|
+
})]
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
//#endregion
|
|
239
|
+
export { h as CDFCurve, v as CardinalitySparkline, _ as QuantileHeatmap, u as SketchLogContext, f as SketchLogProvider, d as useSketchLog };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type SketchLogCounter = number | string;
|
|
2
|
+
export interface SketchLogLatencyState {
|
|
3
|
+
alpha: number;
|
|
4
|
+
zero_count: SketchLogCounter;
|
|
5
|
+
count: SketchLogCounter;
|
|
6
|
+
min: number | null;
|
|
7
|
+
max: number | null;
|
|
8
|
+
positive: Record<string, SketchLogCounter>;
|
|
9
|
+
negative: Record<string, SketchLogCounter>;
|
|
10
|
+
}
|
|
11
|
+
export interface SketchLogEventsState {
|
|
12
|
+
width: number;
|
|
13
|
+
depth: number;
|
|
14
|
+
table: SketchLogCounter[][];
|
|
15
|
+
total: SketchLogCounter;
|
|
16
|
+
}
|
|
17
|
+
export interface SketchLogUniquesState {
|
|
18
|
+
precision: number;
|
|
19
|
+
registers: number[];
|
|
20
|
+
}
|
|
21
|
+
export interface SketchLogState {
|
|
22
|
+
version: number;
|
|
23
|
+
total: SketchLogCounter;
|
|
24
|
+
deterministic: boolean;
|
|
25
|
+
latency: SketchLogLatencyState;
|
|
26
|
+
events: SketchLogEventsState;
|
|
27
|
+
uniques: SketchLogUniquesState;
|
|
28
|
+
metrics?: {
|
|
29
|
+
p50: number;
|
|
30
|
+
p95: number;
|
|
31
|
+
p99: number;
|
|
32
|
+
p99_9: number;
|
|
33
|
+
unique_count: SketchLogCounter;
|
|
34
|
+
total_events: SketchLogCounter;
|
|
35
|
+
memory_footprint_bytes: SketchLogCounter;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export interface SketchLogContextType {
|
|
39
|
+
state: SketchLogState | null;
|
|
40
|
+
isConnected: boolean;
|
|
41
|
+
error: Error | null;
|
|
42
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sketchlog/react",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "1.2.3",
|
|
5
|
+
"description": "React provider, hooks, and visualizations for SketchLog streams",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": ["dist", "README.md"],
|
|
18
|
+
"keywords": ["sketchlog", "react", "observability", "visualization"],
|
|
19
|
+
"author": "Bala Vignesh S",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/SBALAVIGNESH123/sketchlog.git",
|
|
24
|
+
"directory": "frontend/react-sketchlog"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/SBALAVIGNESH123/sketchlog/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/SBALAVIGNESH123/sketchlog/tree/main/frontend/react-sketchlog",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"provenance": true
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "vite",
|
|
39
|
+
"build": "tsc -b && vite build && tsc -p tsconfig.lib.json",
|
|
40
|
+
"lint": "eslint .",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"preview": "vite preview"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"d3": "^7.9.0",
|
|
46
|
+
"react": ">=18",
|
|
47
|
+
"react-dom": ">=18",
|
|
48
|
+
"react-use-websocket": "^4.13.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@eslint/js": "^10.0.1",
|
|
52
|
+
"@types/d3": "^7.4.3",
|
|
53
|
+
"@types/node": "^24.13.2",
|
|
54
|
+
"@types/react": "^19.2.17",
|
|
55
|
+
"@types/react-dom": "^19.2.3",
|
|
56
|
+
"@testing-library/react": "^16.3.0",
|
|
57
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
58
|
+
"eslint": "^10.5.0",
|
|
59
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
60
|
+
"eslint-plugin-react-refresh": "^0.5.3",
|
|
61
|
+
"globals": "^17.6.0",
|
|
62
|
+
"jsdom": "^26.1.0",
|
|
63
|
+
"typescript": "~6.0.2",
|
|
64
|
+
"typescript-eslint": "^8.61.0",
|
|
65
|
+
"vite": "^8.1.0",
|
|
66
|
+
"vitest": "^3.2.4"
|
|
67
|
+
}
|
|
68
|
+
}
|