Skip to content

Commit 84be133

Browse files
authored
Fix widget filters, dates, and Colab demo (#64)
1 parent fcfcb7d commit 84be133

4 files changed

Lines changed: 66 additions & 15 deletions

File tree

examples/widget_demo.ipynb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@
9999
"from sidemantic.widget import MetricsExplorer\n",
100100
"\n",
101101
"# Simple mode: just pass data (DuckDB relation)\n",
102-
"widget = MetricsExplorer(conn.table(\"auctions\"))\n",
102+
"max_cardinality = 50 if \"COLAB_RELEASE_TAG\" in os.environ else None\n",
103+
"widget = MetricsExplorer(conn.table(\"auctions\"), max_dimension_cardinality=max_cardinality)\n",
103104
"widget"
104105
]
105106
},

js/widget.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,30 @@ import { tableFromIPC } from "apache-arrow";
1212
// ============================================================================
1313

1414
function formatNumber(value, format = "number") {
15-
if (value == null || !Number.isFinite(value)) return "—";
15+
if (value == null) return "—";
16+
if (typeof value === "bigint") {
17+
return value.toLocaleString("en-US");
18+
}
19+
if (typeof value === "string") {
20+
const trimmed = value.trim();
21+
if (/^-?\d+$/.test(trimmed)) {
22+
try {
23+
return BigInt(trimmed).toLocaleString("en-US");
24+
} catch {}
25+
}
26+
const parsed = Number(trimmed);
27+
if (!Number.isFinite(parsed)) return "—";
28+
if (format === "currency") {
29+
return `$${parsed.toFixed(2)}`;
30+
}
31+
return parsed.toLocaleString();
32+
}
33+
const numericValue = Number(value);
34+
if (!Number.isFinite(numericValue)) return "—";
1635
if (format === "currency") {
17-
return `$${Number(value).toFixed(2)}`;
36+
return `$${numericValue.toFixed(2)}`;
1837
}
19-
return Number(value).toLocaleString();
38+
return numericValue.toLocaleString();
2039
}
2140

2241
function formatDate(date) {

sidemantic/widget/_widget.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,34 @@
1616
from sidemantic.sql.generator import SQLGenerator
1717

1818

19-
def _table_to_ipc(table) -> str:
20-
"""Serialize Arrow table to IPC format (base64 for widget transport)."""
19+
def _table_to_ipc(table, *, decimal_mode: str = "float") -> str:
20+
"""Serialize Arrow table to IPC format (base64 for widget transport).
21+
22+
Args:
23+
table: PyArrow table
24+
decimal_mode: "float" to cast decimals to float64, "string" to preserve precision as strings
25+
"""
2126
import base64
2227

2328
import pyarrow as pa
29+
import pyarrow.compute as pc
30+
31+
if any(pa.types.is_decimal(field.type) for field in table.schema):
32+
arrays = []
33+
fields = []
34+
for field in table.schema:
35+
column = table[field.name]
36+
if pa.types.is_decimal(field.type):
37+
if decimal_mode == "string":
38+
cast_type = pa.string()
39+
else:
40+
cast_type = pa.float64()
41+
arrays.append(pc.cast(column, cast_type))
42+
fields.append(pa.field(field.name, cast_type))
43+
else:
44+
arrays.append(column)
45+
fields.append(field)
46+
table = pa.table(arrays, schema=pa.schema(fields))
2447

2548
sink = io.BytesIO()
2649
with pa.ipc.new_file(sink, table.schema) as writer:
@@ -590,7 +613,7 @@ def _refresh_metrics(self, *, sync_status: bool = True):
590613
skip_default_time_dimensions=True,
591614
)
592615
result = self._conn.execute(sql).arrow()
593-
self.metric_series_data = _table_to_ipc(result)
616+
self.metric_series_data = _table_to_ipc(result, decimal_mode="float")
594617

595618
totals_sql = self._generator.generate(
596619
metrics=metric_refs,
@@ -603,7 +626,15 @@ def _refresh_metrics(self, *, sync_status: bool = True):
603626
totals = {}
604627
if totals_row:
605628
for i, metric_ref in enumerate(metric_refs):
606-
totals[metric_ref.split(".")[-1]] = totals_row[i]
629+
value = totals_row[i]
630+
try:
631+
from decimal import Decimal
632+
633+
if isinstance(value, Decimal):
634+
value = str(value)
635+
except Exception:
636+
pass
637+
totals[metric_ref.split(".")[-1]] = value
607638
self.metric_totals = totals
608639
self._record_query_intent(metric_refs, [time_dim_ref], grain)
609640
except Exception as e:
@@ -646,7 +677,7 @@ def _refresh_dimensions(self, *, sync_status: bool = True):
646677
use_preaggregations=self._use_preaggregations,
647678
)
648679
result = self._conn.execute(sql).arrow()
649-
dimension_data[dim_key] = _table_to_ipc(result)
680+
dimension_data[dim_key] = _table_to_ipc(result, decimal_mode="string")
650681
self.dimension_data = dict(dimension_data)
651682
self._record_query_intent([selected_metric_ref], [dim_ref], self.time_grain or "day")
652683
except Exception as e:
@@ -680,7 +711,7 @@ def _refresh_dimensions(self, *, sync_status: bool = True):
680711
use_preaggregations=self._use_preaggregations,
681712
)
682713
result = self._conn.execute(sql).arrow()
683-
dimension_data[dim_key] = _table_to_ipc(result)
714+
dimension_data[dim_key] = _table_to_ipc(result, decimal_mode="string")
684715
self.dimension_data = dict(dimension_data)
685716
self._record_query_intent([selected_metric_ref], [dim_ref], self.time_grain or "day")
686717
except Exception as e:

sidemantic/widget/static/widget.js

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)