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

Add post enumeration modification to generate.ts #187

Merged
merged 28 commits into from
Aug 8, 2016
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d460213
add post enumeration modification to generate.ts
espressoroaster Aug 7, 2016
c1a99d8
move post enumeration modification
espressoroaster Aug 7, 2016
435c733
ENCODING_QUERY_INDEX => encQIndex
espressoroaster Aug 7, 2016
e811e39
put in one reduce and add encQPositionIndex dict
espressoroaster Aug 8, 2016
5c5488a
create stylize.ts
espressoroaster Aug 8, 2016
e9eac3d
Directly access EncQ through yEncQ
espressoroaster Aug 8, 2016
8d3bed4
remove unused import and variable declaration
espressoroaster Aug 8, 2016
5600bcb
update config description
espressoroaster Aug 8, 2016
c9a4dd5
Update name of Stylize config
espressoroaster Aug 8, 2016
6e59c50
Add Stylize.test.ts
espressoroaster Aug 8, 2016
3213642
add generate test for nominalScaleForHighCardinality and other config
espressoroaster Aug 8, 2016
efa4898
add stylize test and generate test case for smallBandSize due to facet
espressoroaster Aug 8, 2016
1452dd6
nominalScaleForHighCardinality --> nominalColorScaleForHighCardinality
espressoroaster Aug 8, 2016
fc97be4
add generate test with nominalColorScaleForHighCardinality config turned
espressoroaster Aug 8, 2016
733680b
move to test to 1D
espressoroaster Aug 8, 2016
2293acd
add palettte string to nominalColorScaleForHighCardinality config
espressoroaster Aug 8, 2016
510255b
Q -> O,
espressoroaster Aug 8, 2016
014bd8c
allow bandSize to be set when scale is enumspec
espressoroaster Aug 8, 2016
f0a9665
add and yEncQ's scaleType is ordinal for bandSize config
espressoroaster Aug 8, 2016
4e5ba6f
nest stylizer tests under describe(stylizer)
espressoroaster Aug 8, 2016
be8149c
allow bandSize to be set if encQ's scale type is enum spec
espressoroaster Aug 8, 2016
34e9917
stylize should not assign a bandSize of 12 if bandSize is already set
espressoroaster Aug 8, 2016
f0bfe63
stylizer should not assign a range if range is already set
espressoroaster Aug 8, 2016
682921f
remove extra space
espressoroaster Aug 8, 2016
c4e21f3
Add missing //
kanitw Aug 8, 2016
234e71c
use config values for maxCardinality and palette range
espressoroaster Aug 8, 2016
ece9ee7
stylize should not assign a bandSize if cardinality of Y is under 10
espressoroaster Aug 8, 2016
3f2cfb1
do not assign a range of category20 if cardinality of color is under 10
espressoroaster Aug 8, 2016
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
11 changes: 9 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ export interface QueryConfig {
maxCardinalityForShape?: number;
typeMatchesSchemaType?: boolean;

// Effectiveness Preference
// STYLIZE
smallBandSizeForHighCardinalityOrFacet?: {maxCardinality: number, bandSize: number};
nominalColorScaleForHighCardinality?: {maxCardinality: number, palette: string};

// EFFECTIVENESS PREFERENCE
maxGoodCardinalityForColor?: number; // FIXME: revise
maxGoodCardinalityForFacet?: number; // FIXME: revise
}
Expand Down Expand Up @@ -156,8 +160,11 @@ export const DEFAULT_QUERY_CONFIG: QueryConfig = {
maxCardinalityForShape: 6,
typeMatchesSchemaType: true,

// Ranking Preference
// STYLIZE
smallBandSizeForHighCardinalityOrFacet: {maxCardinality: 10, bandSize: 12},
nominalColorScaleForHighCardinality: {maxCardinality: 10, palette: 'categorical20'},

// RANKING PREFERENCE
maxGoodCardinalityForFacet: 5, // FIXME: revise
maxGoodCardinalityForColor: 7, // FIXME: revise
};
5 changes: 5 additions & 0 deletions src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {QueryConfig, DEFAULT_QUERY_CONFIG} from './config';
import {SpecQueryModel} from './model';
import {SpecQuery} from './query/spec';
import {Schema} from './schema';
import {stylize} from './stylize';

export function generate(specQ: SpecQuery, schema: Schema, opt: QueryConfig = DEFAULT_QUERY_CONFIG) {
// 1. Build a SpecQueryModel, which also contains enumSpecIndex
Expand All @@ -22,5 +23,9 @@ export function generate(specQ: SpecQuery, schema: Schema, opt: QueryConfig = DE
}
});

if ((opt.nominalColorScaleForHighCardinality !== null) || (opt.smallBandSizeForHighCardinalityOrFacet !== null)) {
return stylize(answerSet, schema, opt);
}

return answerSet;
}
99 changes: 99 additions & 0 deletions src/stylize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {Channel} from 'vega-lite/src/channel';
import {ScaleType} from 'vega-lite/src/scale';
import {Type} from 'vega-lite/src/type';

import {QueryConfig} from './config';
import {SpecQueryModel} from './model';
import {EncodingQuery, ScaleQuery, scaleType} from './query/encoding';
import {Schema} from './schema';
import {contains, Dict} from './util';

export function stylize(answerSet: SpecQueryModel[], schema: Schema, opt: QueryConfig): SpecQueryModel[] {
answerSet = answerSet.map(function(specM) {
if (opt.smallBandSizeForHighCardinalityOrFacet) {
specM = smallBandSizeForHighCardinalityOrFacet(specM, schema);
}

if (opt.nominalColorScaleForHighCardinality) {
specM = nominalColorScaleForHighCardinality(specM, schema);
}
return specM;
});

return answerSet;
}

let encQIndex: Dict<EncodingQuery> = {};

export function smallBandSizeForHighCardinalityOrFacet(specM: SpecQueryModel, schema: Schema): SpecQueryModel {
[Channel.ROW, Channel.Y, Channel.COLUMN, Channel.X].forEach((channel) => {
encQIndex[channel] = specM.getEncodingQueryByChannel(channel);
});

const yEncQ = encQIndex[Channel.Y];
if (yEncQ !== undefined) {
if (encQIndex[Channel.ROW] ||
schema.cardinality(yEncQ) > 10) {

// We check for undefined rather than
// yEncQ.scale = yEncQ.scale || {} to cover the case where
// yEncQ.scale has been set to false/null.
// This prevents us from incorrectly overriding scale and
// assigning a bandSize when scale is set to false.
if (yEncQ.scale === undefined) {
yEncQ.scale = {};
}

// We do not want to assign a bandSize if scale is set to false
// and we only apply this if the scale is (or can be) an ordinal scale.
if (yEncQ.scale && contains([ScaleType.ORDINAL, undefined], scaleType((yEncQ.scale as ScaleQuery).type, yEncQ.timeUnit, yEncQ.type))) {
if (!(yEncQ.scale as ScaleQuery).bandSize) {
(yEncQ.scale as ScaleQuery).bandSize = 12;
}
}
}
}

const xEncQ = encQIndex[Channel.X];
if (xEncQ !== undefined) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as Y

if (encQIndex[Channel.COLUMN] ||
schema.cardinality(xEncQ) > 10) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value in the config


// Just like y, we don't want to do this if scale is null/false
if (xEncQ.scale === undefined) {
xEncQ.scale = {};
}

// We do not want to assign a bandSize if scale is set to false
// and we only apply this if the scale is (or can be) an ordinal scale.
if (xEncQ.scale && contains([ScaleType.ORDINAL, undefined], scaleType((xEncQ.scale as ScaleQuery).type, xEncQ.timeUnit, xEncQ.type))) {
if (!(xEncQ.scale as ScaleQuery).bandSize) {
(xEncQ.scale as ScaleQuery).bandSize = 12;
}
}
}
}

return specM;
}

export function nominalColorScaleForHighCardinality(specM: SpecQueryModel, schema: Schema): SpecQueryModel {
encQIndex[Channel.COLOR] = specM.getEncodingQueryByChannel(Channel.COLOR);

const colorEncQ = encQIndex[Channel.COLOR];
if ((colorEncQ !== undefined) && (colorEncQ.type === Type.NOMINAL) &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why 10? Use value in the config.

(schema.cardinality(colorEncQ) > 10)) {

if (colorEncQ.scale === undefined) {
colorEncQ.scale = {};
}

if (colorEncQ.scale) {
if (!(colorEncQ.scale as ScaleQuery).range) {
(colorEncQ.scale as ScaleQuery).range = 'category20';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use value in the config.

}
}
}

return specM;
}
93 changes: 87 additions & 6 deletions test/generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -539,26 +539,26 @@ describe('generate', function () {
describe('autoAddCount', () => {
describe('ordinal only', () => {
it('should output autoCount in the answer set', () => {
const query = {
const specQ = {
mark: Mark.POINT,
encodings: [
{ channel: Channel.X, field: 'O', type: Type.ORDINAL},
]
};
const answerSet = generate(query, schema, CONFIG_WITH_AUTO_ADD_COUNT);
const answerSet = generate(specQ, schema, CONFIG_WITH_AUTO_ADD_COUNT);
assert.equal(answerSet.length, 1);
assert.isTrue(answerSet[0].getEncodings()[1].autoCount);
});
});

describe('non-binned quantitative only', () => {
const query = {
const specQ = {
mark: Mark.POINT,
encodings: [
{ channel: Channel.X, field: 'Q', type: Type.QUANTITATIVE},
]
};
const answerSet = generate(query, schema, CONFIG_WITH_AUTO_ADD_COUNT);
const answerSet = generate(specQ, schema, CONFIG_WITH_AUTO_ADD_COUNT);

it('should output autoCount=false', () => {
assert.isFalse(answerSet[0].getEncodingQueryByIndex(1).autoCount);
Expand All @@ -570,7 +570,7 @@ describe('generate', function () {
});

describe('enumerate channel for a non-binned quantitative field', () => {
const query = {
const specQ = {
mark: Mark.POINT,
encodings: [
{
Expand All @@ -580,7 +580,7 @@ describe('generate', function () {
}
]
};
const answerSet = generate(query, schema, CONFIG_WITH_AUTO_ADD_COUNT);
const answerSet = generate(specQ, schema, CONFIG_WITH_AUTO_ADD_COUNT);

it('should not output point with only size for color', () => {
answerSet.forEach((model) => {
Expand All @@ -592,4 +592,85 @@ describe('generate', function () {
});
});
});

describe('stylizer', () => {
it('should generate answerSet when all stylizers are turned off', () => {
const specQ = {
mark: Mark.POINT,
encodings: [
{
channel: Channel.X,
field: 'A',
type: Type.QUANTITATIVE
}
]
};

const CONFIG_WITHOUT_HIGH_CARDINALITY_OR_FACET = extend(
{}, DEFAULT_QUERY_CONFIG, {nominalColorScaleForHighCardinality: null}, {smallBandSizeForHighCardinalityOrFacet: null});

const answerSet = generate(specQ, schema, CONFIG_WITHOUT_HIGH_CARDINALITY_OR_FACET);
assert.equal(answerSet.length, 1);
});

describe('nominalColorScaleForHighCardinality', () => {
it('should output range = category20', () => {
const specQ = {
mark: Mark.POINT,
encodings: [
{
channel: Channel.COLOR,
field: 'N20',
scale: {},
type: Type.NOMINAL
}
]
};

const answerSet = generate(specQ, schema, DEFAULT_QUERY_CONFIG);
assert.equal((answerSet[0].getEncodingQueryByIndex(0).scale as ScaleQuery).range, 'category20');
});
});

describe('smallBandSizeForHighCardinalityOrFacet', () => {
it('should output bandSize = 12', () => {
const specQ = {
mark: Mark.BAR,
encodings: [
{
channel: Channel.Y,
field: 'O_100',
scale: {},
type: Type.ORDINAL
}
]
};

const answerSet = generate(specQ, schema, DEFAULT_QUERY_CONFIG);
assert.equal((answerSet[0].getEncodingQueryByIndex(0).scale as ScaleQuery).bandSize, 12);
});

it('should output bandSize = 12', () => {
const specQ = {
mark: Mark.BAR,
encodings: [
{
channel: Channel.Y,
field: 'A',
scale: {},
type: Type.ORDINAL
},
{
channel: Channel.ROW,
field: 'A',
type: Type.ORDINAL
}
]
};

const answerSet = generate(specQ, schema, DEFAULT_QUERY_CONFIG);
assert.equal((answerSet[0].getEncodingQueryByIndex(0).scale as ScaleQuery).bandSize, 12);
});
});
});
});
Loading