Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
34994fe
feat: mvp of Publish package
rpechuk Feb 2, 2025
5843720
Merge branch 'main' of https://github.com/uwdata/mosaic into ron/mosa…
rpechuk Feb 9, 2025
bf8cf2d
feat: consolidated activatable logic to core
rpechuk Feb 9, 2025
4e6d78b
feat: consolidated activatable logic to core
rpechuk Feb 9, 2025
97c3c6e
Merge branch 'ron/activatable-interface' into ron/mosaic-publish
rpechuk Feb 9, 2025
a2d1a18
fix: implement Activatable interface for inputs and interactors
rpechuk Feb 9, 2025
db3a4b3
Merge branch 'ron/activatable-interface' of https://github.com/uwdata…
rpechuk Feb 9, 2025
7a13cf7
fix: change Activatable to interface and remove ActivatableClient
rpechuk Feb 10, 2025
a50afba
fix: Changed intteractors to use new Activatable interface
rpechuk Feb 10, 2025
b9b11ac
Merge branch 'ron/activatable-interface' of https://github.com/uwdata…
rpechuk Feb 10, 2025
0730ad2
fix: update to use new isActivatable duck typing method
rpechuk Feb 10, 2025
394563e
fix: Add typedef to fix JSDoc import with implements errors
rpechuk Feb 10, 2025
6ba5556
Merge branch 'ron/activatable-interface' of https://github.com/uwdata…
rpechuk Feb 11, 2025
f423519
chore: address and remove TODOs
rpechuk Feb 11, 2025
5060a19
fix: Change core type export to index-types.ts pattern
rpechuk Feb 12, 2025
6a74d28
Merge branch 'ron/activatable-interface' of https://github.com/uwdata…
rpechuk Feb 12, 2025
6de3271
chore: move exports to index-types and remove redundant file
rpechuk Feb 13, 2025
8fac484
Merge branch 'ron/activatable-interface' of https://github.com/uwdata…
rpechuk Feb 13, 2025
51f32ce
Merge branch 'main' into ron/mosaic-publish
rpechuk Feb 16, 2025
c3607e5
chore: update package-lock.json
rpechuk Feb 16, 2025
97fc6a9
fix: fix core import to work with new types exporting
rpechuk Feb 16, 2025
ccbed15
fix: Fix core import in renderHelpers
rpechuk Feb 16, 2025
229cf72
fix: remove unnecessary changes from #682
rpechuk Feb 16, 2025
cf778e6
feat: polish README
rpechuk Feb 16, 2025
d4cc267
feat: improve logging and npm exporting
rpechuk Feb 17, 2025
bb9e3e6
feat: added basic testing
rpechuk Feb 17, 2025
529c325
fix: add better typing to ast-to-dom and InstantiateContext constructor
rpechuk Feb 21, 2025
1539814
Merge branch 'ron/ast-to-dom-typing' of https://github.com/uwdata/mos…
rpechuk Feb 21, 2025
2f1e7c5
fix: remove any cast from astToDom call
rpechuk Feb 21, 2025
81e069a
feat: Add cache exporting and hydration capabilities (#705)
rpechuk Mar 11, 2025
600c702
feat: add rollup to not be dependent on cdn
rpechuk Mar 20, 2025
4bb3102
Merge branch 'main' of https://github.com/uwdata/mosaic into ron/mosa…
rpechuk Mar 20, 2025
c580394
fix: remove dependence on fields method
rpechuk Mar 30, 2025
9260640
fix: handle undefinied
rpechuk Mar 30, 2025
218e8e8
fix: cache exporting
rpechuk Mar 30, 2025
58bf43c
feat: add more fine-grain control over optimizations
rpechuk Apr 25, 2025
f1e583a
feat: changes for VIS short paper
rpechuk May 6, 2025
213971c
Merge branch 'main' of https://github.com/uwdata/mosaic into ron/mosa…
rpechuk May 6, 2025
182fbf1
fix: build path + tsc errors
rpechuk May 6, 2025
ce0772d
fix: change binary path in tests
rpechuk May 6, 2025
428084b
feat: add further fine-grained control via CLI options
rpechuk May 11, 2025
694dfdf
Merge branch 'main' of https://github.com/uwdata/mosaic into ron/mosa…
rpechuk May 11, 2025
d17fbba
fix: update tests
rpechuk May 11, 2025
86faf50
fix: increase timeout 10sec
rpechuk May 11, 2025
c13f464
fix: change to non-deprecated timeout adjustment
rpechuk May 11, 2025
52b2a58
feat: update README with new usage
rpechuk May 27, 2025
f921a9f
feat: update README with new usage
rpechuk May 27, 2025
4a58f80
fix: update README to include experimental warning
rpechuk Jun 5, 2025
6f5ca44
feat: Create `ClauseSource` interface and add book keeping to `Coordi…
rpechuk Jun 6, 2025
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
2,022 changes: 1,293 additions & 729 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions packages/core/src/Coordinator.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
/** @import { QueryResult } from './util/query-result.js' */
/** @import { SelectionClause } from './util/selection-types.js' */
/** @import { MosaicClient } from './MosaicClient.js' */
/** @import { ClauseSource } from './util/selection-types.js' */
/** @import { Selection } from './Selection.js' */
/** @import { Logger, QueryType } from './types.js' */
import { socketConnector } from './connectors/socket.js';
import { PreAggregator } from './preagg/PreAggregator.js';
import { voidLogger } from './util/void-logger.js';
import { isClauseSource } from './util/is-clause-source.js';
import { QueryManager, Priority } from './QueryManager.js';

/**
Expand Down Expand Up @@ -75,6 +77,7 @@ export class Coordinator {
this.filterGroups = new Map;
this.clients?.forEach(client => this.disconnect(client));
this.clients = new Set;
this.clauseSources = new Set;
}
if (cache) this.manager.cache().clear();
}
Expand Down Expand Up @@ -222,6 +225,22 @@ export class Coordinator {
: Promise.resolve(client.update());
}

/**
* Register a clause source with the coordinator.
* @param {ClauseSource} clauseSource The clause source to register.
*/
connectClauseSource(clauseSource) {
this.clauseSources.add(clauseSource);
}

/**
* Deregister a clause source from the coordinator.
* @param {ClauseSource} clauseSource The clause source to deregister.
*/
disconnectClauseSource(clauseSource) {
this.clauseSources.delete(clauseSource);
}

/**
* Connect a client to the coordinator.
* @param {MosaicClient} client The Mosaic client to connect.
Expand All @@ -244,6 +263,11 @@ export class Coordinator {

// connect filter selection
connectSelection(this, client.filterBy, client);

// Connect this client as a clause source if necessary
if (isClauseSource(client)) {
this.connectClauseSource(client);
}
}

/**
Expand All @@ -256,6 +280,11 @@ export class Coordinator {
clients.delete(client);
client.coordinator = null;

// Remove direct clause sources
if (isClauseSource(client)) {
this.disconnectClauseSource(client);
}

const group = filterGroups.get(client.filterBy);
if (group) {
group.clients.delete(client);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/QueryManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ export class QueryManager {

/**
* Get or set the current query cache.
* @param {Cache | boolean} [value]
* @returns {Cache}
* @param {Cache | boolean} [value] The cache instance or `true` to create an LRU cache.
* @returns {Cache} The cache instance.
*/
cache(value) {
return value !== undefined
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ export { throttle } from './util/throttle.js';
export { toDataColumns } from './util/to-data-columns.js';
export { queryFieldInfo } from './util/field-info.js';
export { jsType } from './util/js-type.js';
export { isActivatable } from './util/is-activatable.js';
export { isClauseSource } from './util/is-clause-source.js';
17 changes: 7 additions & 10 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,20 @@ export interface ColumnDescription {
null: 'YES' | 'NO'
}

/**
* Interface for components that perform selection activation.
*/
export interface Activatable {
/**
* Activate the selection that this component publishes to.
*/
activate(): void;
}

/**
* Interface for cache implementations.
*/
export interface Cache {
/** Retrieves a value from the cache. */
get(key: string): any;
/** Stores a value in the cache and returns it. */
set(key: string, value: any): any;
/** Clears all entries in the cache. */
clear(): void;
/** Exports the cache as a Map where keys are strings and values are Arrow IPC binary format (Uint8Array), or null if the cache is empty. */
export(): Map<string, Uint8Array> | null;
/** Imports the cache from a Map where keys are strings and values are Arrow IPC binary format (Uint8Array). */
import(data: Map<string, Uint8Array>): void;
}

/**
Expand Down
54 changes: 35 additions & 19 deletions packages/core/src/util/cache.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
/** @import { Cache } from '../types.js' */
import { tableToIPC } from "@uwdata/flechette";
import { decodeIPC } from "./decode-ipc.js";

const requestIdle = typeof requestIdleCallback !== 'undefined'
? requestIdleCallback
: setTimeout;

/**
* Create a new cache that ignores all values.
* @returns {Cache}
* @returns {Cache} A void cache that doesn't store or retrieve values.
*/
export function voidCache() {
return {
get: () => undefined,
set: (key, value) => value,
clear: () => {}
clear: () => {},
export: () => null,
import: () => {}
};
}

/**
* Create a new cache that uses an LRU eviction policy.
* @param {object} [options] Cache options.
* @param {number} [options.max] Maximum number of cache entries.
* @param {number} [options.ttl] Time-to-live for cache entries.
* @returns {Cache}
* @param {number} [options.max=1000] Maximum number of cache entries.
* @param {number} [options.ttl=10800000] Time-to-live for cache entries in ms (default: 3 hours).
* @returns {Cache} An LRU cache instance.
*/
export function lruCache({
max = 1000, // max entries
ttl = 3 * 60 * 60 * 1000 // time-to-live, default 3 hours
max = 1000,
ttl = 3 * 60 * 60 * 1000
} = {}) {
let cache = new Map;

Expand Down Expand Up @@ -55,19 +59,31 @@ export function lruCache({
}
}

function get(key) {
const entry = cache.get(key);
if (entry) {
entry.last = performance.now();
return entry.value;
}
}

function set(key, value) {
cache.set(key, { last: performance.now(), value });
if (cache.size > max) requestIdle(evict);
return value;
}

return {
get(key) {
const entry = cache.get(key);
if (entry) {
entry.last = performance.now();
return entry.value;
}
},
set(key, value) {
cache.set(key, { last: performance.now(), value });
if (cache.size > max) requestIdle(evict);
return value;
get,
set,
clear() { cache = new Map; },
export() {
return new Map(Array.from(cache).map(([key, entry]) => [key, tableToIPC(entry.value, {})]));
},
clear() { cache = new Map; }
import(data) {
for (const [key, entry] of data) {
set(key, decodeIPC(entry));
}
}
};
}
8 changes: 0 additions & 8 deletions packages/core/src/util/is-activatable.js

This file was deleted.

12 changes: 12 additions & 0 deletions packages/core/src/util/is-clause-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Test if a value implements the ClauseSource interface.
* @param {*} value The value to test.
* @returns {value is import('./selection-types.js').ClauseSource}
*/
export function isClauseSource(value) {
return typeof value?.activate === 'function' && // Activate exists
value.activate.length === 0 && // No arguments
typeof value?.reset === 'function' && // Reset exists
value.reset.length === 0 && // No arguments
typeof value?.clause === 'function'; // Clause exists
}
26 changes: 26 additions & 0 deletions packages/core/src/util/selection-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,29 @@ export interface SelectionClause {
*/
meta?: ClauseMetadata;
}

/**
* Interface for components that perform selection activation.
*/
export interface ClauseSource {
/**
* Activate the selection that this component publishes to.
* Optionally, no-op
*/
activate: () => void;

/**
* Reset this component and respective selection(s) removing all published clauses.
* Optionally, no-op
*/
reset: () => void;


/**
* Get the selection clause that this component publishes.
* Note: this function **must** be callable with no arguments
*
* @param {...unknown} args - Any number of arguments that may be needed to construct the clause.
*/
clause: (...args: unknown[]) => SelectionClause;
}
11 changes: 7 additions & 4 deletions packages/inputs/src/Menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,22 +149,25 @@ export class Menu extends Input {
}
}

clause(value) {
return clausePoint(this.field, value ?? 0, { source: this });
}

reset() {
this.select.selectedIndex = this.from ? 0 : -1;
}

activate() {
if (isSelection(this.selection)) {
this.selection.activate(clausePoint(this.field, 0, { source: this }));
this.selection.activate(this.clause());
}
}

publish(value) {
const { selection, field } = this;
const { selection } = this;
if (isSelection(selection)) {
if (value === '') value = undefined; // 'All' option
const clause = clausePoint(field, value, { source: this });
selection.update(clause);
selection.update(this.clause(value));
} else if (isParam(selection)) {
selection.update(value);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/inputs/src/Slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ export class Slider extends Input {
}
}

reset() { /* No-op */ }

activate() {
if (isSelection(this.selection)) {
this.selection.activate(this.clause(0));
Expand Down
4 changes: 4 additions & 0 deletions packages/inputs/src/Table.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ export class Table extends Input {
}
}

reset() {
this.currentRow = -1;
}

sort(event, column) {
if (column === this.sortColumn) {
this.sortDesc = !this.sortDesc;
Expand Down
13 changes: 11 additions & 2 deletions packages/inputs/src/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export function input(InputClass, ...params) {

/**
* Base class for input components.
* @import {Activatable} from '@uwdata/mosaic-core'
* @implements {Activatable}
* @import {ClauseSource} from '@uwdata/mosaic-core'
* @implements {ClauseSource}
*/
export class Input extends MosaicClient {
/**
Expand All @@ -37,4 +37,13 @@ export class Input extends MosaicClient {
activate() {
// subclasses should override
}

reset() {
// subclasses should override
}

clause() {
// subclasses should override
return null;
}
}
3 changes: 2 additions & 1 deletion packages/inputs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"emitDeclarationOnly": true,
"outDir": "dist/types",
"module": "node16",
"skipLibCheck": true
"skipLibCheck": true,
"strictFunctionTypes": true
}
}
13 changes: 9 additions & 4 deletions packages/plot/src/interactors/Interval1D.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { invert } from './util/invert.js';
import { sanitizeStyles } from './util/sanitize-styles.js';

/**
* @import {Activatable} from '@uwdata/mosaic-core'
* @implements {Activatable}
* @import {ClauseSource} from '@uwdata/mosaic-core'
* @implements {ClauseSource}
*/
export class Interval1D {
constructor(mark, {
Expand All @@ -28,6 +28,9 @@ export class Interval1D {
this.style = style && sanitizeStyles(style);
this.brush = channel === 'y' ? brushY() : brushX();
this.brush.on('brush end', ({ selection }) => this.publish(selection));

// Register with coordinator
mark.coordinator?.connectClauseSource(this);
}

reset() {
Expand All @@ -36,7 +39,7 @@ export class Interval1D {
}

activate() {
this.selection.activate(this.clause(this.value || [0, 1]));
this.selection.activate(this.clause());
}

publish(extent) {
Expand All @@ -55,7 +58,9 @@ export class Interval1D {

clause(value) {
const { mark, pixelSize, field, scale } = this;
return clauseInterval(field, value, {
const clauseValue = value || this.value || [0, 1];

return clauseInterval(field, clauseValue, {
source: this,
clients: this.peers ? mark.plot.markSet : new Set().add(mark),
scale,
Expand Down
Loading