Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: frame animations with time encoding and timer param #8921

Merged
merged 72 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
6e0d637
mvp timer signals
jonathanzong May 24, 2023
5bc98a7
mvp datasets
jonathanzong May 24, 2023
983e189
mvp rewrite data source in marks
jonathanzong May 24, 2023
de5d7a5
marginally better
jonathanzong May 24, 2023
93c21e7
add time to encoding channel
jonathanzong May 24, 2023
7d1d18f
whatever
jonathanzong May 24, 2023
c957f61
transforms working
jonathanzong May 25, 2023
7a74096
cleanup
jonathanzong May 25, 2023
b26587f
generalized the signals a bit
jonathanzong May 25, 2023
f66418c
add comment about easing
jonathanzong May 25, 2023
f393554
make existing test suites pass
jonathanzong May 25, 2023
bf20107
cleanup
jonathanzong May 25, 2023
53f0127
time rescale optional
jonathanzong May 25, 2023
c0f6089
some point tests
jonathanzong May 25, 2023
9ca3efd
more unit tests
jonathanzong May 25, 2023
79bf0b2
runtime tests
jonathanzong May 25, 2023
8fea282
lookup dataset name
jonathanzong May 25, 2023
bcb77fb
schema
jonathanzong May 25, 2023
cb75f60
make tests pass
jonathanzong May 25, 2023
7124ce9
comment unused signals
jonathanzong May 25, 2023
916a325
check for source data filter before copying
jonathanzong May 26, 2023
8733403
export signal constants in point
joshpoll Sep 19, 2023
333eb6a
move correctDataNames into unit model
joshpoll Sep 19, 2023
6a4d2b9
expose default values for time ranges
joshpoll Sep 19, 2023
01f6a0e
time name no longer hardcoded
joshpoll Sep 20, 2023
6040f0b
remove vlSelectionIdTest check
jonathanzong Sep 25, 2023
b702df1
update defined for toggle and clear
jonathanzong Sep 25, 2023
10f26f6
cleanup
jonathanzong Sep 25, 2023
7b4a25c
add example specs
jonathanzong Sep 25, 2023
e03b2f9
update example specs
jonathanzong Sep 25, 2023
321a4a5
update timer selection unit test
jonathanzong Sep 25, 2023
02bf1a2
build schema
jonathanzong Sep 25, 2023
ccd861c
move clock signals to toplevelsignals
jonathanzong Sep 25, 2023
9f5bdb6
update test to reflect clock signal moved to top level
jonathanzong Sep 25, 2023
63762ec
wip
jonathanzong Jan 22, 2024
72a21f0
with sleep
jonathanzong Jan 22, 2024
e8c92a9
test for loop
jonathanzong Jan 22, 2024
d5b928b
test for forward progress
jonathanzong Jan 22, 2024
26b05d5
correct 1965
jonathanzong Jan 22, 2024
07d3fdf
get data and signals in the same await
jonathanzong Jan 22, 2024
ff95d7b
renamed variable
jonathanzong Jan 22, 2024
5b22676
add is_playing signal
jonathanzong Jan 23, 2024
6a28094
use pause signal to test frame rendering
jonathanzong Jan 23, 2024
a6d51d8
get time test to run
jonathanzong Jan 23, 2024
cf936fe
rename mousemove to pointermove
jonathanzong Jan 23, 2024
0a6d78a
reduce sleep on forward progress test
jonathanzong Jan 23, 2024
0ab6b3e
coverage tests passing
jonathanzong Jan 26, 2024
37f93fe
coverage actually passing
jonathanzong Jan 26, 2024
c8e3ab8
actually commit the file
jonathanzong Jan 26, 2024
5e31e79
put linear time scales behind a todo
jonathanzong Jan 29, 2024
2167087
update tests
jonathanzong Jan 29, 2024
a7c7cf0
update clock extent for runtime test
jonathanzong Jan 29, 2024
ceb4e28
Empty-Commit
mattijn Apr 6, 2024
488dece
modify src
jonathanzong Jul 27, 2024
008da6f
fix test
jonathanzong Oct 29, 2024
0c7df9a
better name for selection in examples
jonathanzong Oct 30, 2024
25d1b1c
clean up comment unrelated to change
jonathanzong Oct 30, 2024
b990bbe
warn on multiple timer selections
jonathanzong Oct 30, 2024
5007d02
unit test for warn
jonathanzong Oct 30, 2024
48fc073
remove selcmpt if ignoring
jonathanzong Oct 30, 2024
7db1af4
unit test for no duplicate signals
jonathanzong Oct 30, 2024
c2b4b59
delete redundant test
jonathanzong Oct 30, 2024
43e7dd2
typecheck
jonathanzong Oct 30, 2024
27946f7
fixed dataset lookup for facets
jonathanzong Oct 30, 2024
3d71947
move warns to messages file as consts/functions
jonathanzong Oct 31, 2024
475aca5
add error for unsupported multi-view animation
jonathanzong Oct 31, 2024
b398bca
fix very incorrect if statement
jonathanzong Oct 31, 2024
80303bc
delete not working facet example
jonathanzong Oct 31, 2024
329544e
add test for dataset names
jonathanzong Oct 31, 2024
7850444
do dumb things to appease codecov
jonathanzong Oct 31, 2024
769b7ae
better way to check for multiview animation plus tests
jonathanzong Oct 31, 2024
e1b0ed1
fix test
jonathanzong Oct 31, 2024
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
289 changes: 289 additions & 0 deletions build/vega-lite-schema.json

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions examples/specs/animated_gapminder.vl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {
"url": "data/gapminder.json"
},
"mark": "point",
"params": [
{
"name": "animation_frame",
"select": {
"type": "point",
"fields": [
"year"
],
"on": "timer"
}
}
],
"transform": [
{
"filter": {
"param": "animation_frame"
}
}
],
"encoding": {
"color": {
"field": "country"
},
"x": {
"field": "fertility",
"type": "quantitative"
},
"y": {
"field": "life_expect",
"type": "quantitative"
},
"time": {
"field": "year",
"type": "ordinal"
}
}
}
41 changes: 41 additions & 0 deletions examples/specs/animated_hop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {
"url": "data/seattle-weather.csv"
},
"mark": "tick",
"config": {
"tick": {
"thickness": 3
}
},
"params": [
{
"name": "animation_frame",
"select": {
"type": "point",
"fields": [
"date"
],
"on": "timer"
}
}
],
"transform": [
{
"filter": {
"param": "animation_frame"
}
}
],
"encoding": {
"y": {
"field": "precipitation",
"type": "quantitative"
},
"time": {
"field": "date",
"type": "ordinal"
}
}
}
19 changes: 19 additions & 0 deletions src/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export const LONGITUDE = 'longitude' as const;
export const LATITUDE2 = 'latitude2' as const;
export const LONGITUDE2 = 'longitude2' as const;

// Time
export const TIME = 'time' as const;

// Mark property with scale
export const COLOR = 'color' as const;

Expand Down Expand Up @@ -136,6 +139,9 @@ const UNIT_CHANNEL_INDEX: Flag<Channel> = {
fill: 1,
stroke: 1,

// time
time: 1,

// other non-position with scale
opacity: 1,
fillOpacity: 1,
Expand Down Expand Up @@ -425,6 +431,16 @@ export function isXorYOffset(channel: Channel): channel is OffsetScaleChannel {
return hasOwnProperty(OFFSET_SCALE_CHANNEL_INDEX, channel);
}

const TIME_SCALE_CHANNEL_INDEX = {
time: 1
} as const;
export const TIME_SCALE_CHANNELS = keys(TIME_SCALE_CHANNEL_INDEX);
export type TimeScaleChannel = keyof typeof TIME_SCALE_CHANNEL_INDEX;

export function isTime(channel: ExtendedChannel): channel is TimeScaleChannel {
return channel in TIME_SCALE_CHANNEL_INDEX;
}

// NON_POSITION_SCALE_CHANNEL = SCALE_CHANNELS without position / offset
const {
// x2 and y2 share the same scale as x and y
Expand Down Expand Up @@ -465,6 +481,7 @@ export function supportLegend(channel: NonPositionScaleChannel) {
case FILLOPACITY:
case STROKEOPACITY:
case ANGLE:
case TIME:
return false;
}
}
Expand Down Expand Up @@ -552,6 +569,7 @@ function getSupportedMark(channel: ExtendedChannel): SupportedMark {
case YOFFSET:
case LATITUDE:
case LONGITUDE:
case TIME:
// all marks except geoshape. geoshape does not use X, Y -- it uses a projection
return ALL_MARKS_EXCEPT_GEOSHAPE;
case X2:
Expand Down Expand Up @@ -626,6 +644,7 @@ export function rangeType(channel: ExtendedChannel): RangeType {
case OPACITY:
case FILLOPACITY:
case STROKEOPACITY:
case TIME:

// X2 and Y2 use X and Y scales, so they similarly have continuous range. [falls through]
case X2:
Expand Down
14 changes: 11 additions & 3 deletions src/channeldef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
TEXT,
THETA,
THETA2,
TIME,
TOOLTIP,
URL,
X,
Expand Down Expand Up @@ -518,6 +519,12 @@ export interface PositionMixins {

export type PolarDef<F extends Field> = PositionFieldDefBase<F> | PositionDatumDefBase<F> | PositionValueDef;

export type TimeDef<F extends Field> = TimeFieldDef<F>;
export interface TimeMixins {
rescale?: boolean;
}
export type TimeFieldDef<F extends Field> = ScaleFieldDef<F, StandardType> & TimeMixins;

export function getBandPosition({
fieldDef,
fieldDef2,
Expand Down Expand Up @@ -1303,6 +1310,7 @@ export function channelCompatibility(
case RADIUS2:
case X2:
case Y2:
case TIME:
if (type === 'nominal' && !(fieldDef as any)['sort']) {
return {
compatible: false,
Expand Down Expand Up @@ -1338,13 +1346,13 @@ export function channelCompatibility(
*/
export function isFieldOrDatumDefForTimeFormat(fieldOrDatumDef: FieldDef<string> | DatumDef): boolean {
const {formatType} = getFormatMixins(fieldOrDatumDef);
return formatType === 'time' || (!formatType && isTimeFieldDef(fieldOrDatumDef));
return formatType === 'time' || (!formatType && isTemporalFieldDef(fieldOrDatumDef));
}

/**
* Check if field def has type `temporal`. If you want to also cover field defs that use a time format, use `isTimeFormatFieldDef`.
* Check if field def has type `temporal`. If you want to also cover field defs that use a time format, use `isFieldOrDatumDefForTimeFormat`.
*/
export function isTimeFieldDef(def: FieldDef<any> | DatumDef): boolean {
export function isTemporalFieldDef(def: FieldDef<any> | DatumDef): boolean {
return def && ((def as any)['type'] === 'temporal' || (isFieldDef(def) && !!def.timeUnit));
}

Expand Down
7 changes: 2 additions & 5 deletions src/compile/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,8 @@ function assembleTopLevelModel(
// Config with Vega-Lite only config removed.
const vgConfig = model.config ? stripAndRedirectConfig(model.config) : undefined;

const data = [].concat(
model.assembleSelectionData([]),
// only assemble data in the root
assembleRootData(model.component.data, datasets)
);
const rootData = assembleRootData(model.component.data, datasets);
const data = model.assembleSelectionData(rootData);

const projections = model.assembleProjections();
const title = model.assembleTitle();
Expand Down
6 changes: 6 additions & 0 deletions src/compile/concat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {parseData} from './data/parse';
import {assembleLayoutSignals} from './layoutsize/assemble';
import {parseConcatLayoutSize} from './layoutsize/parse';
import {Model} from './model';
import {MULTI_VIEW_ANIMATION_UNSUPPORTED} from '../log/message';
import {isTimerSelection} from './selection';

export class ConcatModel extends Model {
public readonly children: Model[];
Expand Down Expand Up @@ -43,6 +45,10 @@ export class ConcatModel extends Model {
this.component.selection[key] = child.component.selection[key];
}
}

if (Object.values(this.component.selection).some(selCmpt => isTimerSelection(selCmpt))) {
log.error(MULTI_VIEW_ANIMATION_UNSUPPORTED);
}
}

public parseMarkGroup() {
Expand Down
6 changes: 6 additions & 0 deletions src/compile/facet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {parseChildrenLayoutSize} from './layoutsize/parse';
import {Model, ModelWithField} from './model';
import {assembleDomain, getFieldFromDomain} from './scale/domain';
import {assembleFacetSignals} from './selection/assemble';
import {isTimerSelection} from './selection';
import {MULTI_VIEW_ANIMATION_UNSUPPORTED} from '../log/message';

export function facetSortFieldName(
fieldDef: FacetFieldDef<string>,
Expand Down Expand Up @@ -113,6 +115,10 @@ export class FacetModel extends ModelWithField {
// within its unit.
this.child.parseSelections();
this.component.selection = this.child.component.selection;

if (Object.values(this.component.selection).some(selCmpt => isTimerSelection(selCmpt))) {
log.error(MULTI_VIEW_ANIMATION_UNSUPPORTED);
}
}

public parseMarkGroup() {
Expand Down
6 changes: 6 additions & 0 deletions src/compile/layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {assembleLegends} from './legend/assemble';
import {Model} from './model';
import {assembleLayerSelectionMarks} from './selection/assemble';
import {UnitModel} from './unit';
import {isTimerSelection} from './selection';
import {MULTI_VIEW_ANIMATION_UNSUPPORTED} from '../log/message';

export class LayerModel extends Model {
// HACK: This should be (LayerModel | UnitModel)[], but setting the correct type leads to weird error.
Expand Down Expand Up @@ -68,6 +70,10 @@ export class LayerModel extends Model {
this.component.selection[key] = child.component.selection[key];
}
}

if (Object.values(this.component.selection).some(selCmpt => isTimerSelection(selCmpt))) {
log.error(MULTI_VIEW_ANIMATION_UNSUPPORTED);
}
}

public parseMarkGroup() {
Expand Down
19 changes: 0 additions & 19 deletions src/compile/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,25 +598,6 @@ export abstract class Model {
return undefined;
}

/**
* Corrects the data references in marks after assemble.
*/
public correctDataNames = (mark: VgMarkGroup) => {
// TODO: make this correct

// for normal data references
if (mark.from?.data) {
mark.from.data = this.lookupDataSource(mark.from.data);
}

// for access to facet data
if (mark.from?.facet?.data) {
mark.from.facet.data = this.lookupDataSource(mark.from.facet.data);
}

return mark;
};

/**
* Traverse a model's hierarchy to get the scale component for a particular channel.
*/
Expand Down
10 changes: 9 additions & 1 deletion src/compile/scale/range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
X,
XOFFSET,
Y,
YOFFSET
YOFFSET,
TIME
} from '../../channel';
import {
getBandPosition,
Expand Down Expand Up @@ -317,6 +318,13 @@ function defaultRange(channel: ScaleChannel, model: UnitModel): VgRange {
];
}

case TIME: {
// if (scaleType === 'band') {
return {step: 1000 / config.scale.framesPerSecond};
// }
// return [0, config.scale.animationDuration * 1000]; // TODO(jzong): uncomment for linear scales when interpolation is implemented
}

case STROKEWIDTH:
// TODO: support custom rangeMin, rangeMax
return [config.scale.minStrokeWidth, config.scale.maxStrokeWidth];
Expand Down
12 changes: 12 additions & 0 deletions src/compile/scale/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getSizeChannel,
isColorChannel,
isScaleChannel,
isTime,
isXorY,
isXorYOffset,
rangeType,
Expand Down Expand Up @@ -76,6 +77,10 @@ function defaultType(
return 'ordinal';
}

if (isTime(channel)) {
return 'band';
}

if (isXorY(channel) || isXorYOffset(channel)) {
if (util.contains(['rect', 'bar', 'image', 'rule', 'tick'], mark.type)) {
// The rect/bar/tick mark should fit into a band.
Expand Down Expand Up @@ -111,7 +116,11 @@ function defaultType(
return 'ordinal';
} else if (isFieldDef(fieldDef) && fieldDef.timeUnit && normalizeTimeUnit(fieldDef.timeUnit).utc) {
return 'utc';
} else if (isTime(channel)) {
// return 'linear';
return 'band'; // TODO(jzong): when interpolation is implemented, this should be 'linear'
}

return 'time';

case 'quantitative':
Expand All @@ -125,6 +134,9 @@ function defaultType(
log.warn(log.message.discreteChannelCannotEncode(channel, 'quantitative'));
// TODO: consider using quantize (equivalent to binning) once we have it
return 'ordinal';
} else if (isTime(channel)) {
// return 'linear';
return 'band'; // TODO(jzong): when interpolation is implemented, this should be 'linear'
}

return 'linear';
Expand Down
Loading