compassql
Version:
CompassQL visualization query language
292 lines (266 loc) • 8.21 kB
text/typescript
import * as CHANNEL from 'vega-lite/build/src/channel';
import * as MARK from 'vega-lite/build/src/mark';
import {Mark} from 'vega-lite/build/src/mark';
import {QueryConfig} from '../../config';
import {SpecQueryModel} from '../../model';
import {Schema} from '../../schema';
import {Dict, forEach} from '../../util';
import {FeatureScore} from '../ranking';
import {Scorer} from './base';
import {BIN_Q, ExtendedType, getExtendedType, K, N, NONE, O, Q, T, TIMEUNIT_O, TIMEUNIT_T} from './type';
export class MarkScorer extends Scorer {
constructor() {
super('Mark');
}
protected initScore() {
return init();
}
public getScore(specM: SpecQueryModel, _: Schema, __: QueryConfig): FeatureScore[] {
let mark = specM.getMark() as Mark;
if (mark === MARK.CIRCLE || mark === MARK.SQUARE) {
mark = MARK.POINT;
}
const xEncQ = specM.getEncodingQueryByChannel(CHANNEL.X);
const xType = xEncQ ? getExtendedType(xEncQ) : NONE;
const yEncQ = specM.getEncodingQueryByChannel(CHANNEL.Y);
const yType = yEncQ ? getExtendedType(yEncQ) : NONE;
const isOccluded = !specM.isAggregate(); // FIXME
const feature = xType + '_' + yType + '_' + isOccluded + '_' + mark;
const featureScore = this.getFeatureScore(feature);
if (featureScore) {
return [featureScore];
}
console.error('feature score missing for', feature);
return [];
}
}
export function featurize(xType: ExtendedType, yType: ExtendedType, hasOcclusion: boolean, mark: Mark) {
return xType + '_' + yType + '_' + hasOcclusion + '_' + mark;
}
function init() {
const MEASURES = [Q, T];
const DISCRETE = [BIN_Q, TIMEUNIT_O, O, N, K];
const DISCRETE_OR_NONE = DISCRETE.concat([NONE]);
let SCORE = {} as Dict<number>;
// QxQ
MEASURES.forEach(xType => {
MEASURES.forEach(yType => {
// has occlusion
const occludedQQMark = {
point: 0,
text: -0.2,
tick: -0.5,
rect: -1,
bar: -2,
line: -2,
area: -2,
rule: -2.5
};
forEach(occludedQQMark, (score, mark: Mark) => {
const feature = featurize(xType, yType, true, mark);
SCORE[feature] = score;
});
// no occlusion
// TODO: possible to use connected scatter plot
const noOccludedQQMark = {
point: 0,
text: -0.2,
tick: -0.5,
bar: -2,
line: -2,
area: -2,
rule: -2.5
};
forEach(noOccludedQQMark, (score, mark: Mark) => {
const feature = featurize(xType, yType, false, mark);
SCORE[feature] = score;
});
});
});
// DxQ, QxD
MEASURES.forEach(xType => {
// HAS OCCLUSION
DISCRETE_OR_NONE.forEach(yType => {
const occludedDimensionMeasureMark = {
tick: 0,
point: -0.2,
text: -0.5,
bar: -2,
line: -2,
area: -2,
rule: -2.5
};
forEach(occludedDimensionMeasureMark, (score, mark: Mark) => {
const feature = featurize(xType, yType, true, mark);
SCORE[feature] = score;
// also do the inverse
const feature2 = featurize(yType, xType, true, mark);
SCORE[feature2] = score;
});
});
[TIMEUNIT_T].forEach(yType => {
const occludedDimensionMeasureMark = {
// For Time Dimension with time scale, tick is not good
point: 0,
text: -0.5,
tick: -1,
bar: -2,
line: -2,
area: -2,
rule: -2.5
};
forEach(occludedDimensionMeasureMark, (score, mark: Mark) => {
const feature = featurize(xType, yType, true, mark);
SCORE[feature] = score;
// also do the inverse
const feature2 = featurize(yType, xType, true, mark);
SCORE[feature2] = score;
});
});
// NO OCCLUSION
[NONE, N, O, K].forEach(yType => {
const noOccludedQxN = {
bar: 0,
point: -0.2,
tick: -0.25,
text: -0.3,
// Line / Area can mislead trend for N
line: -2, // FIXME line vs area?
area: -2,
// Non-sense to use rule here
rule: -2.5
};
forEach(noOccludedQxN, (score, mark: Mark) => {
const feature = featurize(xType, yType, false, mark);
SCORE[feature] = score;
// also do the inverse
const feature2 = featurize(yType, xType, false, mark);
SCORE[feature2] = score;
});
});
[BIN_Q].forEach(yType => {
const noOccludedQxBinQ = {
bar: 0,
point: -0.2,
tick: -0.25,
text: -0.3,
// Line / Area isn't the best fit for bin
line: -0.5, // FIXME line vs area?
area: -0.5,
// Non-sense to use rule here
rule: -2.5
};
forEach(noOccludedQxBinQ, (score, mark: Mark) => {
const feature = featurize(xType, yType, false, mark);
SCORE[feature] = score;
// also do the inverse
const feature2 = featurize(yType, xType, false, mark);
SCORE[feature2] = score;
});
});
[TIMEUNIT_T, TIMEUNIT_O].forEach(yType => {
// For aggregate / surely no occlusion plot, Temporal with time or ordinal
// are not that different.
const noOccludedQxBinQ = {
line: 0,
area: -0.1,
bar: -0.2,
point: -0.3,
tick: -0.35,
text: -0.4,
// Non-sense to use rule here
rule: -2.5
};
forEach(noOccludedQxBinQ, (score, mark: Mark) => {
const feature = featurize(xType, yType, false, mark);
SCORE[feature] = score;
// also do the inverse
const feature2 = featurize(yType, xType, false, mark);
SCORE[feature2] = score;
});
});
});
[TIMEUNIT_T].forEach(xType => {
[TIMEUNIT_T].forEach(yType => {
// has occlusion
const ttMark = {
point: 0,
rect: -0.1, // assuming rect will be small
text: -0.5,
tick: -1,
bar: -2,
line: -2,
area: -2,
rule: -2.5
};
// No difference between has occlusion and no occlusion
// as most of the time, it will be the occluded case.
forEach(ttMark, (score, mark: Mark) => {
const feature = featurize(xType, yType, true, mark);
SCORE[feature] = score;
});
forEach(ttMark, (score, mark: Mark) => {
const feature = featurize(xType, yType, false, mark);
SCORE[feature] = score;
});
});
DISCRETE_OR_NONE.forEach(yType => {
// has occlusion
const tdMark = {
tick: 0,
point: -0.2,
text: -0.5,
rect: -1,
bar: -2,
line: -2,
area: -2,
rule: -2.5
};
// No difference between has occlusion and no occlusion
// as most of the time, it will be the occluded case.
forEach(tdMark, (score, mark: Mark) => {
const feature = featurize(xType, yType, true, mark);
SCORE[feature] = score;
});
forEach(tdMark, (score, mark: Mark) => {
const feature = featurize(yType, xType, true, mark);
SCORE[feature] = score;
});
forEach(tdMark, (score, mark: Mark) => {
const feature = featurize(xType, yType, false, mark);
SCORE[feature] = score;
});
forEach(tdMark, (score, mark: Mark) => {
const feature = featurize(yType, xType, false, mark);
SCORE[feature] = score;
});
});
});
// DxD
// Note: We use for loop here because using forEach sometimes leads to a mysterious bug
for (const xType of DISCRETE_OR_NONE) {
for (const yType of DISCRETE_OR_NONE) {
// has occlusion
const ddMark = {
point: 0,
rect: 0,
text: -0.1,
tick: -1,
bar: -2,
line: -2,
area: -2,
rule: -2.5
};
forEach(ddMark, (score, mark: Mark) => {
const feature = featurize(xType, yType, true, mark);
SCORE[feature] = score;
});
// same for no occlusion.
forEach(ddMark, (score, mark: Mark) => {
const feature = featurize(xType, yType, false, mark);
SCORE[feature] = score;
});
}
}
return SCORE;
}