Skip to content

Commit

Permalink
remove createRenderGroup; standardize render group mark accessor name…
Browse files Browse the repository at this point in the history
…s; add represented object for marks
  • Loading branch information
venkatesh-sivaraman committed Apr 3, 2024
1 parent a5095a8 commit 12912d3
Show file tree
Hide file tree
Showing 15 changed files with 753 additions and 675 deletions.
898 changes: 459 additions & 439 deletions counterpoint/dist/counterpoint-vis.es.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion counterpoint/dist/counterpoint-vis.umd.js

Large diffs are not rendered by default.

130 changes: 112 additions & 18 deletions counterpoint/lib/mark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
curveEaseInOut,
interpolateTo,
} from './animator';
import { Attribute, AttributeListener } from './attribute';
import { Attribute, AttributeDefinition, AttributeListener } from './attribute';
import { Advanceable } from './ticker';
import { TimeProvider, approxEquals } from './utils';

Expand Down Expand Up @@ -39,13 +39,6 @@ export interface MarkAttributes extends AttributeSetBase {
alpha?: Attribute<number, number, any>;
}

type MarkAttributeCopySpec<AttributeSet extends AttributeSetBase> = {
[K in keyof AttributeSet]?:
| AttributeSet[K]['value']
| AttributeSet[K]['valueFn']
| AttributeSet[K];
};

export type MarkUpdateListener<
AttributeSet extends AttributeSetBase,
K extends keyof AttributeSet,
Expand All @@ -68,6 +61,18 @@ export type MarkGraphListener<T extends AttributeSetBase> = (
newAdjacency: Mark<T>[]
) => void;

/**
* Represents initialization for an attribute set where the values can be
* provided as Attributes, or simply as their values or value functions.
*/
export type ImplicitAttributeSet<AttributeSet extends AttributeSetBase> = {
[K in keyof AttributeSet]?:
| AttributeSet[K]['value']
| AttributeSet[K]['valueFn']
| AttributeDefinition<AttributeSet[K]['value']>
| AttributeSet[K];
};

/**
* An object representing something visually depicted, that is described by
* one or more `Attribute`s.
Expand All @@ -89,6 +94,9 @@ export class Mark<AttributeSet extends AttributeSetBase = MarkAttributes>
// marks that have an edge to this mark
private _reverseAdjacency: Set<Mark<AttributeSet>> = new Set();

/** The object that this Mark represents. */
public represented: any | undefined = undefined;

private _updateListeners: {
[key in keyof AttributeSet]?: MarkUpdateListener<
AttributeSet,
Expand All @@ -100,7 +108,7 @@ export class Mark<AttributeSet extends AttributeSetBase = MarkAttributes>
[key: string]: MarkEventListener<AttributeSet>;
} = {};

constructor(id: any, attributes: AttributeSet) {
constructor(id: any, attributes: ImplicitAttributeSet<AttributeSet>) {
this.id = id;
if (attributes === undefined)
console.error(
Expand All @@ -110,9 +118,17 @@ export class Mark<AttributeSet extends AttributeSetBase = MarkAttributes>
Object.keys(attributes).forEach(
<K extends keyof AttributeSet>(attrName: K) => {
let attrib = new Attribute(
Object.assign(Object.assign({}, attributes[attrName]), {
computeArg: this,
})
Object.assign(
Object.assign(
{},
attributes[attrName] instanceof Attribute
? attributes[attrName]
: new Attribute(attributes[attrName])
),
{
computeArg: this,
}
)
);
attrib.addListener((a, animated) =>
this._attributesChanged(attrName, animated)
Expand Down Expand Up @@ -217,6 +233,18 @@ export class Mark<AttributeSet extends AttributeSetBase = MarkAttributes>
return this;
}

/**
* Modifies the mark to indicate that it represents the given object. The value
* will be stored in the `represented` property.
*
* @param rep The object that this mark represents
* @return this Mark
*/
representing(rep: any): Mark<AttributeSet> {
this.represented = rep;
return this;
}

// Warning system to detect when an attribute is being listed as animated
// for no reason
private framesWithUpdate: number = 0;
Expand Down Expand Up @@ -475,7 +503,7 @@ export class Mark<AttributeSet extends AttributeSetBase = MarkAttributes>
*/
copy(
id: any,
newValues: MarkAttributeCopySpec<AttributeSet> = {}
newValues: ImplicitAttributeSet<AttributeSet> = {}
): Mark<AttributeSet> {
return new Mark(id, {
...this.attributes,
Expand All @@ -487,6 +515,8 @@ export class Mark<AttributeSet extends AttributeSetBase = MarkAttributes>
attrName,
this.attributes[attrName].copy({ valueFn: newVal }),
];
else if (newVal.value !== undefined || newVal.valueFn !== undefined)
return [attrName, new Attribute(newVal)];
return [attrName, this.attributes[attrName].copy({ value: newVal })];
})
),
Expand All @@ -504,20 +534,21 @@ export class Mark<AttributeSet extends AttributeSetBase = MarkAttributes>
* @param newMarks an array of marks to set at that edge, overwriting any
* previous marks on that edge
*/
adj(edge: string, newMarks: Mark<AttributeSet>[]): void;
adj(edge: string, newMarks: Mark<AttributeSet>[] | Mark<AttributeSet>): void;
adj(
edge: string,
newMarks: Mark<AttributeSet>[] | undefined = undefined
newMarks: Mark<AttributeSet>[] | Mark<AttributeSet> | undefined = undefined
): void | Mark<AttributeSet>[] {
if (newMarks !== undefined) {
// setting the adjacency
let markArray = Array.isArray(newMarks) ? newMarks : [newMarks];
let oldAdj = this._adjacency[edge] ?? new Set();
this._graphListeners.forEach((l) =>
l(this, edge, Array.from(oldAdj), newMarks)
l(this, edge, Array.from(oldAdj), markArray)
);
oldAdj.forEach((m) => m._removeEdgeFrom(this));
this._adjacency[edge] = new Set(newMarks);
newMarks.forEach((m) => m._addEdgeFrom(this));
this._adjacency[edge] = new Set(markArray);
markArray.forEach((m) => m._addEdgeFrom(this));
return;
}

Expand Down Expand Up @@ -552,3 +583,66 @@ export class Mark<AttributeSet extends AttributeSetBase = MarkAttributes>
return this;
}
}

type AttributeConstructorShorthand<
AttributeSet extends AttributeSetBase,
K extends keyof AttributeSet
> =
| ((val: AttributeSet[K]['value']) => AttributeSet[K])
| ((val: AttributeSet[K]['valueFn']) => AttributeSet[K]);
export type AttributeSetConstructor<AttributeSet extends AttributeSetBase> = {
[K in keyof AttributeSet]:
| AttributeSet[K]
| AttributeConstructorShorthand<AttributeSet, K>;
};

/**
* Defines a new type of mark with a shorthand constructor function.
*
* @param constructorFn A function that takes an ID
*/
export function defineMark<
AttributeSet extends AttributeSetBase = MarkAttributes
>(
constructorFn:
| ((
id: any,
values?: ImplicitAttributeSet<AttributeSet>
) => Mark<AttributeSet> | AttributeSet)
| AttributeSetConstructor<AttributeSet>
) {
let initializer: (
id: any,
values?: ImplicitAttributeSet<AttributeSet>
) => Mark<AttributeSet> | AttributeSet;
if (typeof initializer === 'function')
initializer = constructorFn as (
id: any,
values?: ImplicitAttributeSet<AttributeSet>
) => Mark<AttributeSet> | AttributeSet;
else {
let initObj = constructorFn as AttributeSetConstructor<AttributeSet>;
initializer = (id: any, values?: ImplicitAttributeSet<AttributeSet>) =>
new Mark<AttributeSet>(
id,
Object.fromEntries(
Object.entries(initObj).map(([field, initFn]) => {
if (typeof initFn === 'function')
return [field, initFn(values[field])];
else if (!!values[field]) {
if (values[field] instanceof Attribute)
return [field, values[field]];
return [field, new Attribute(values[field])];
} else return [field, initFn.copy()];
})
) as AttributeSet
);
}
return {
create: (id: any, values?: ImplicitAttributeSet<AttributeSet>) => {
let result = initializer(id, values);
if (result instanceof Mark) return result;
return new Mark(id, result);
},
};
}
78 changes: 20 additions & 58 deletions counterpoint/lib/rendergroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,6 @@ export class MarkRenderGroup<
*/
private marks: Mark<AttributeSet>[] = [];

/**
* A function that generates a mark given an ID, if provided at instantiation.
*/
private factory: ((id: any) => Mark<AttributeSet>) | null = null;
/**
* Controls whether the mark group iterates over the entire set of marks in
* every call to `advance()`. If set to `true`, only the marks that have
Expand Down Expand Up @@ -133,7 +129,7 @@ export class MarkRenderGroup<
* @param opts Options for the mark group (see {@link configure})
*/
constructor(
marks: Mark<AttributeSet>[] | ((id: any) => Mark<AttributeSet>) = [],
marks: Mark<AttributeSet>[] = [],
opts: RenderGroupOptions = {
animationDuration: 1000,
animationCurve: curveEaseInOut,
Expand All @@ -145,13 +141,12 @@ export class MarkRenderGroup<
this._defaultCurve = curveEaseInOut;
this.configure(opts);

if (typeof marks === 'function') this.factory = marks;
else this.marks = marks;
this.marks = marks;

this.marksByID = new Map();
this.marks.forEach((m) => {
if (this.marksByID.has(m.id)) {
console.error(`ID '${m.id}' is duplicated in mark render group`);
console.warn(`ID '${m.id}' is duplicated in mark render group`);
return;
}
this.marksByID.set(m.id, m);
Expand Down Expand Up @@ -226,7 +221,7 @@ export class MarkRenderGroup<
!newNeighbors.includes(neighbor) &&
neighbor.sourceMarks().length == 1
)
this.removeMark(neighbor);
this.deleteMark(neighbor);
});
newNeighbors.forEach((neighbor) => {
if (!oldNeighbors.includes(neighbor)) this.addMark(neighbor);
Expand Down Expand Up @@ -522,22 +517,14 @@ export class MarkRenderGroup<
}

/**
* Retrieves the mark with the given ID, or undefined if it does not exist and
* either no factory was defined or existingOnly is true.
* Retrieves the mark with the given ID, or undefined if it does not exist.
* NOTE: Use of this method assumes there is only one mark ever added with the
* given ID.
*
* @param id the ID of the mark to search for
* @param existingOnly if true, do not use the factory if the mark does not exist
* @returns the `Mark` instance with the given ID or undefined
*/
getMarkByID(
id: any,
existingOnly: boolean = false
): Mark<AttributeSet> | undefined {
if (!this.marksByID.has(id) && !existingOnly) {
let mark: Mark<AttributeSet> | undefined;
if (this.useStaging) mark = this.stage!.getMarkByID(id);
if (!mark && !!this.factory) mark = this.factory(id);
return mark;
}
get(id: any): Mark<AttributeSet> | undefined {
return this.marksByID.get(id);
}

Expand Down Expand Up @@ -631,15 +618,13 @@ export class MarkRenderGroup<
* @returns this render group
*/
addMark(mark: Mark<AttributeSet>): MarkRenderGroup<AttributeSet> {
if (this.marksByID.has(mark.id)) {
console.error('Attempted to add mark with ID that exists:', mark.id);
return this;
}
if (this.marks.includes(mark)) return this;
this.marks.push(mark);
this.marksByID.set(mark.id, mark);
this._setupMark(mark);
this._markListChanged = true;
if (!!this.stage) this.stage.show(mark);
return this;
}

/**
Expand All @@ -648,43 +633,26 @@ export class MarkRenderGroup<
* @param mark the mark to remove
* @returns this render group
*/
removeMark(mark: Mark<AttributeSet>): MarkRenderGroup<AttributeSet> {
deleteMark(mark: Mark<AttributeSet>): MarkRenderGroup<AttributeSet> {
let idx = this.marks.indexOf(mark);
if (idx < 0) {
console.warn('Attempted to remove mark that does not exist');
return this;
}
if (idx < 0) return this;
this.marks.splice(idx, 1);
this.marksByID.delete(mark.id);
this._markListChanged = true;
if (!!this.stage) this.stage.hide(mark);
}

/**
* Convenience function for showing a mark with a given ID, if a factory is defined.
* @param id the id to show
* @param prepareFn a function to call on the mark before showing it
*/
showID(
id: any,
prepareFn: ((mark: Mark<AttributeSet>) => void) | undefined = undefined
): MarkRenderGroup<AttributeSet> {
let mark = this.getMarkByID(id);
if (!!prepareFn) prepareFn(mark);
if (this.has(id)) {
if (this.useStaging) this.stage!.show(mark);
} else this.addMark(mark);
return this;
}

/**
* Convenience function for hiding a mark with a given ID.
* @param id the id to hide
* Removes a mark with the given ID from the render group, or does nothing if
* it does not exist.
*
* @param mark the mark to remove
* @returns this render group
*/
hideID(id: any): MarkRenderGroup<AttributeSet> {
delete(id: any): MarkRenderGroup<AttributeSet> {
if (!this.has(id)) return this;
this.removeMark(this.getMarkByID(id));
return this;
return this.deleteMark(this.get(id)!);
}

/**
Expand Down Expand Up @@ -720,9 +688,3 @@ export class MarkRenderGroup<
);
}
}

export function createRenderGroup<AttributeSet extends AttributeSetBase>(
marks: Mark<AttributeSet>[] = []
): MarkRenderGroup<AttributeSet> {
return new MarkRenderGroup(marks);
}
5 changes: 1 addition & 4 deletions counterpoint/lib/staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,7 @@ export class StageManager<AttributeSet extends AttributeSetBase> {
* to be visible. Otherwise, return any mark in the pool (including exiting).
* @returns the mark with the given ID or undefined
*/
getMarkByID(
id: any,
visibleOnly: boolean = false
): Mark<AttributeSet> | undefined {
get(id: any, visibleOnly: boolean = false): Mark<AttributeSet> | undefined {
let mark = this.marksByID.get(id);
if (!!mark && visibleOnly) {
let state = this.markStates.get(mark);
Expand Down
4 changes: 2 additions & 2 deletions counterpoint/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 12912d3

Please sign in to comment.