Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 15 additions & 24 deletions lib/clients/Histogram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type * as arrow from "apache-arrow";

import { CrossfilterHistogramPlot } from "../utils/CrossfilterHistogramPlot.ts";

import type { Bin, Mark, Scale } from "../types.ts";
import type { Mark } from "../types.ts";
import { assert } from "../utils/assert.ts";

/** An options bag for the Histogram Mosiac client. */
Expand All @@ -28,9 +28,10 @@ interface HistogramOptions {
filterBy: Selection;
}

type BinTable = arrow.Table<{ x1: arrow.Int; x2: arrow.Int; y: arrow.Int }>;

/** Represents a Cross-filtered Histogram */
export class Histogram extends MosaicClient implements Mark {
type = "rectY";
#source: { table: string; column: string; type: "number" | "date" };
#el: HTMLElement = document.createElement("div");
#select: {
Expand All @@ -41,12 +42,8 @@ export class Histogram extends MosaicClient implements Mark {
#interval: mplot.Interval1D | undefined = undefined;
#initialized: boolean = false;
#fieldInfo: FieldInfo | undefined;
svg:
| SVGSVGElement & {
scale: (type: string) => Scale<number, number>;
update(bins: Bin[], opts: { nullCount: number }): void;
}
| undefined;

svg: ReturnType<typeof CrossfilterHistogramPlot> | undefined;

constructor(options: HistogramOptions) {
super(options.filterBy);
Expand Down Expand Up @@ -77,22 +74,12 @@ export class Histogram extends MosaicClient implements Mark {
this.#fieldInfo = info[0];
return this;
}

/**
* Required by `mplot.bin` to get the field info.
*/
channelField(channel: string): FieldInfo {
assert(channel === "x");
assert(this.#fieldInfo, "No field info yet");
return this.#fieldInfo;
}

/**
* Return a query specifying the data needed by this Mark client.
* @param filter The filtering criteria to apply in the query.
* @returns The client query
*/
query(filter?: Array<unknown>): Query {
query(filter: Array<SQLExpression> = []): Query {
return Query
.from({ source: this.#source.table })
.select(this.#select)
Expand All @@ -102,11 +89,8 @@ export class Histogram extends MosaicClient implements Mark {

/**
* Provide query result data to the mark.
* @param {arrow.Table<{ x1: arrow.Int, x2: arrow.Int, y: arrow.Int }>} data
*/
queryResult(
data: arrow.Table<{ x1: arrow.Int; x2: arrow.Int; y: arrow.Int }>,
) {
queryResult(data: BinTable) {
let bins = Array.from(data, (d) => ({
x0: d.x1,
x1: d.x2,
Expand All @@ -132,10 +116,17 @@ export class Histogram extends MosaicClient implements Mark {
return this;
}

/* Required by the Mark interface */
type = "rectY";
/** Required by `mplot.bin` to get the field info. */
channelField(channel: string): FieldInfo {
assert(channel === "x");
assert(this.#fieldInfo, "No field info yet");
return this.#fieldInfo;
}
get plot() {
return {
node: () => this.#el,
/** @param {string} _name */
getAttribute(_name: string) {
return undefined;
},
Expand Down
67 changes: 44 additions & 23 deletions lib/clients/ValueCounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ interface UniqueValuesOptions {
/** The column to use for the histogram. */
column: string;
/** A mosaic selection to filter the data. */
filterBy?: Selection;
filterBy: Selection;
}

type CountTable = arrow.Table<{ key: arrow.Utf8; total: arrow.Int }>;

export class ValueCounts extends MosaicClient {
#table: string;
#column: string;
Expand All @@ -34,31 +36,38 @@ export class ValueCounts extends MosaicClient {
super(options.filterBy);
this.#table = options.table;
this.#column = options.column;
}

clause(value?: unknown) {
return clausePoint(this.#column, value, { source: this });
}

reset() {
assert(this.#plot, "ValueCounts plot not initialized");
this.#plot.selected.value = undefined;
// FIXME: There is some issue with the mosaic client or the query we
// are using here. Updates to the Selection (`filterBy`) seem to be
// missed by the coordinator, and query/queryResult are not called
// by the coordinator when the filterBy is updated.
//
// Here we manually listen for the changes to filterBy and update this
// client internally. It _should_ go through the coordinator.
options.filterBy.addEventListener("value", async () => {
let filters = options.filterBy.predicate();
let query = this.query(filters);
if (this.#plot) {
let data = await this.coordinator.query(query);
this.#plot.data.value = data;
}
});
}

query(filter: Array<SQLExpression>): Query {
let valueCounts = Query
query(filter: Array<SQLExpression> = []): Query {
let counts = Query
.from({ source: this.#table })
.select({
value: sql`CASE
WHEN ${column(this.#column)} IS NULL THEN '__quak_null__'
ELSE ${column(this.#column)}
END`,
count: count(),
})
.from(this.#table)
.where(filter)
.groupby("value");
.groupby("value")
.where(filter);
return Query
.with({ value_counts: valueCounts })
.with({ counts })
.select(
{
key: sql`CASE
Expand All @@ -68,24 +77,36 @@ export class ValueCounts extends MosaicClient {
total: sum("count"),
},
)
.from("value_counts")
.from("counts")
.groupby("key");
}

queryResult(
data: arrow.Table<{ key: arrow.Utf8; total: arrow.Int }>, // type comes from the query above
): this {
queryResult(data: CountTable): this {
if (!this.#plot) {
this.#plot = ValueCountsPlot(data);
this.#el.appendChild(this.#plot);
let plot = this.#plot = ValueCountsPlot(data);
this.#el.appendChild(plot);
effect(() => {
let clause = this.clause(this.#plot!.selected.value);
this.filterBy?.update(clause);
let clause = this.clause(plot.selected.value);
this.filterBy!.update(clause);
});
} else {
this.#plot.data.value = data;
}
return this;
}

clause<T>(value?: T) {
let update = value === "__quak_null__" ? null : value;
return clausePoint(this.#column, update, {
source: this,
});
}

reset() {
assert(this.#plot, "ValueCounts plot not initialized");
this.#plot.selected.value = undefined;
}

get plot() {
return {
node: () => this.#el,
Expand Down
5 changes: 3 additions & 2 deletions lib/deps/mosaic-core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ export interface Clause<Source> {
}

export class Selection {
predicate(client: MosaicClient): Array<unknown>;
predicate(client?: MosaicClient): Array<SQLExpression>;
static crossfilter(): Selection;
clauses: Array<Clause<Interactor>>;
update(clause: Clause<unknown>): void;
activate(clause: Clause<unknown>): void;
addEventListener(event: "activate", listener: () => void): void;
addEventListener<T = unknown>(
event: "value",
Expand Down Expand Up @@ -144,5 +145,5 @@ type Logger = typeof console & {
export declare function clausePoint<T>(
field: string,
value: unknown,
options: { source: T },
options: { source: T; clients?: Set<MosaicClient> },
): Clause<T>;
1 change: 1 addition & 0 deletions lib/deps/mosaic-sql.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export class Query {
groupby(...exprs: unknown[]): Query;
with(...exprs: unknown[]): Query;
clone(): Query;
toString(): string;
}

export declare function desc(column: string): SQLExpression;
Expand Down
Loading