Skip to content

Commit

Permalink
Preliminary geo intervals init.
Browse files Browse the repository at this point in the history
  • Loading branch information
arvind committed Jan 19, 2022
1 parent 301d7c0 commit 3a383f3
Show file tree
Hide file tree
Showing 4 changed files with 816 additions and 781 deletions.
28 changes: 22 additions & 6 deletions src/compile/selection/interval.ts
Expand Up @@ -14,6 +14,7 @@ import scales from './scales';

export const BRUSH = '_brush';
export const SCALE_TRIGGER = '_scale_trigger';
const INIT = '_init';

export interface IntervalSelectionComponent {
type: 'id' | 'scaled';
Expand Down Expand Up @@ -70,7 +71,7 @@ const interval: SelectionCompiler<'interval'> = {
const init = selCmpt.init ? selCmpt.init[0] : null;

signals.push(
...channels.reduce((arr, proj, i) => arr.concat(channelSignals(model, selCmpt, proj, init && init[i])), [])
...channels.reduce((arr, proj) => arr.concat(channelSignals(model, selCmpt, proj, init && init[proj.index])), [])
);

if (selCmpt.interval.type === 'scaled') {
Expand Down Expand Up @@ -125,9 +126,12 @@ const interval: SelectionCompiler<'interval'> = {
: {})
});
} else {
const projection = stringValue(model.projectionName());
const {x, y} = selCmpt.project.hasChannel;
const xvname = x && x.signals.visual;
const yvname = y && y.signals.visual;
const xinit = init && init[x.index];
const yinit = init && init[y.index];
const bbox =
`[` +
`[${xvname ? xvname + '[0]' : '0'}, ${yvname ? yvname + '[0]' : '0'}],` +
Expand All @@ -137,10 +141,19 @@ const interval: SelectionCompiler<'interval'> = {
const intersect = `intersect(${bbox}, {markname: ${stringValue(model.getName('marks'))}}, unit.mark)`;
const base = `{unit: ${unitName(model)}, fields: [${name + TUPLE_FIELDS}[${selCmpt.project.selectionIdIdx}]]}`;

return signals.concat({
name: tupleSg,
update: `vlSelectionTuples(${intersect}, ${base})`
});
return [
{
name: name + INIT,
init: init
? `[scale(${projection}, [${xinit[0]}, ${yinit[0]}]), scale(${projection}, [${xinit[1]}, ${yinit[1]}])]`
: null
},
...signals,
{
name: tupleSg,
update: `vlSelectionTuples(${intersect}, ${base})`
}
];
}
},

Expand Down Expand Up @@ -243,7 +256,6 @@ function channelSignals(

const scaleName = stringValue(scaledInterval ? model.scaleName(channel) : model.projectionName());
const scaled = (str: string) => `scale(${scaleName}, ${str})`;
const vinit: SignalValue = init ? {init: assembleInit(init, true, scaled)} : {value: []};

const size = model.getSizeSignalRef(channel === X ? 'width' : 'height').signal;
const coord = `${channel}(unit)`;
Expand All @@ -260,6 +272,7 @@ function channelSignals(
const hasScales = scales.defined(selCmpt);
const scale = model.getScaleComponent(channel as ScaleChannel);
const scaleType = scale ? scale.get('type') : undefined;
const vinit: SignalValue = init ? {init: assembleInit(init, true, scaled)} : {value: []};

// React to pan/zooms of continuous scales. Non-continuous scales
// (band, point) cannot be pan/zoomed and any other changes
Expand All @@ -285,6 +298,9 @@ function channelSignals(
}
];
} else {
const initIdx = channel === X ? 0 : 1;
const initSg = selCmpt.name + INIT;
const vinit: SignalValue = init ? {init: `[${initSg}[0][${initIdx}], ${initSg}[1][${initIdx}]]`} : {value: []};
return [{name: vname, ...vinit, on: von}];
}
}
34 changes: 23 additions & 11 deletions src/compile/selection/project.ts
@@ -1,5 +1,6 @@
import {array, isObject} from 'vega-util';
import {
GeoPositionChannel,
getPositionChannelFromLatLong,
isGeoPositionChannel,
isScaleChannel,
Expand Down Expand Up @@ -28,7 +29,9 @@ export type TupleStoreType =
export interface SelectionProjection {
type: TupleStoreType;
field: string;
index: number;
channel?: SingleDefUnitChannel;
geoChannel?: GeoPositionChannel;
signals?: {data?: string; visual?: string};
hasLegend?: boolean;
}
Expand Down Expand Up @@ -81,6 +84,10 @@ const project: SelectionCompiler = {
? (array(selDef.value as any) as SelectionInitMapping[] | SelectionInitIntervalMapping[])
: null;

if (init && selCmpt.type === 'interval' && model.hasProjection && init[0].length !== 2) {
log.warn(log.message.INITIALIZE_GEO_INTERVAL);
}

// If no explicit projection (either fields or encodings) is specified, set some defaults.
// If an initial value is set, try to infer projections.
let {fields, encodings} = (isObject(selDef.select) ? selDef.select : {}) as PointSelectionConfig;
Expand All @@ -96,10 +103,10 @@ const project: SelectionCompiler = {
(encodings || (encodings = [])).push(key as SingleDefUnitChannel);
} else {
if (type === 'interval') {
log.warn(log.message.INTERVAL_INITIALIZED_WITH_X_Y);
log.warn(log.message.INTERVAL_INITIALIZED_WITH_POS);
encodings = cfg.encodings;
} else {
(fields || (fields = [])).push(key);
(fields ??= []).push(key);
}
}
}
Expand Down Expand Up @@ -159,15 +166,18 @@ const project: SelectionCompiler = {
? 'R-RE'
: 'E';

const posChannel = isGeoPositionChannel(channel) ? getPositionChannelFromLatLong(channel) : channel;
const p: SelectionProjection = {
field,
channel: posChannel,
type: tplType
};
const p: SelectionProjection = {field, channel, type: tplType, index: proj.items.length};
p.signals = {...signalName(p, 'data'), ...signalName(p, 'visual')};
proj.items.push((parsed[field] = p));
proj.hasField[field] = proj.hasChannel[posChannel] = parsed[field];
proj.hasField[field] = parsed[field];

if (isGeoPositionChannel(channel)) {
p.geoChannel = channel;
p.channel = getPositionChannelFromLatLong(channel);
proj.hasChannel[p.channel] = parsed[field];
} else {
proj.hasChannel[channel] = parsed[field];
}
}
} else {
log.warn(log.message.cannotProjectOnChannelWithoutField(channel));
Expand All @@ -176,7 +186,7 @@ const project: SelectionCompiler = {

for (const field of fields ?? []) {
if (proj.hasField[field]) continue;
const p: SelectionProjection = {type: 'E', field};
const p: SelectionProjection = {type: 'E', field, index: proj.items.length};
p.signals = {...signalName(p, 'data')};
proj.items.push(p);
proj.hasField[field] = p;
Expand All @@ -187,7 +197,9 @@ const project: SelectionCompiler = {
selCmpt.init = (init as any).map((v: SelectionInitMapping | SelectionInitIntervalMapping) => {
// Selections can be initialized either with a full object that maps projections to values
// or scalar values to smoothen the abstraction gradient from variable params to point selections.
return proj.items.map(p => (isObject(v) ? (v[p.channel] !== undefined ? v[p.channel] : v[p.field]) : v));
return proj.items.map(p =>
isObject(v) ? (v[p.geoChannel || p.channel] !== undefined ? v[p.geoChannel || p.channel] : v[p.field]) : v
);
});
}

Expand Down
6 changes: 5 additions & 1 deletion src/log/message.ts
Expand Up @@ -97,7 +97,11 @@ export function noSameUnitLookup(name: string) {

export const NEEDS_SAME_SELECTION = 'The same selection must be used to override scale domains in a layered view.';

export const INTERVAL_INITIALIZED_WITH_X_Y = 'Interval selections should be initialized using "x" and/or "y" keys.';
export const INTERVAL_INITIALIZED_WITH_POS =
'Interval selections should be initialized using "x", "y", "longitude", or "latitude" keys.';

export const INITIALIZE_GEO_INTERVAL =
'Interval selections over cartographic projections must be initialized with both longitude and latitude.';

// REPEAT
export function noSuchRepeatedValue(field: string) {
Expand Down

0 comments on commit 3a383f3

Please sign in to comment.