Skip to content

Commit

Permalink
Add top-level width and height.
Browse files Browse the repository at this point in the history
Fix #1421
  • Loading branch information
kanitw committed Jul 31, 2016
1 parent b155b37 commit af144c8
Show file tree
Hide file tree
Showing 10 changed files with 379 additions and 82 deletions.
4 changes: 2 additions & 2 deletions src/compile/axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,11 @@ export function title(model: Model, channel: Channel) {
} else if (channel === X && !model.isOrdinalScale(X)) {
const unitModel: UnitModel = model as any; // only unit model has channel x
// For non-ordinal scale, we know cell size at compile time, we can guess max length
maxLength = unitModel.config().cell.width / model.axis(X).characterWidth;
maxLength = unitModel.width / model.axis(X).characterWidth;
} else if (channel === Y && !model.isOrdinalScale(Y)) {
const unitModel: UnitModel = model as any; // only unit model has channel y
// For non-ordinal scale, we know cell size at compile time, we can guess max length
maxLength = unitModel.config().cell.height / model.axis(Y).characterWidth;
maxLength = unitModel.height / model.axis(Y).characterWidth;
}

// FIXME: we should use template to truncate instead
Expand Down
28 changes: 27 additions & 1 deletion src/compile/layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,30 @@ import {VgData, VgAxis, VgLegend, isUnionedDomain, isDataRefDomain, VgDataRef} f
export class LayerModel extends Model {
private _children: UnitModel[];

/**
* Fixed width for the unit visualization.
* If undefined (e.g., for ordinal scale), the width of the
* visualization will be calculated dynamically.
*/
private _width: number;

/**
* Fixed height for the unit visualization.
* If undefined (e.g., for ordinal scale), the height of the
* visualization will be calculated dynamically.
*/
private _height: number;


constructor(spec: LayerSpec, parent: Model, parentGivenName: string) {
super(spec, parent, parentGivenName);

this._width = spec.width;
this._height = spec.height;

this._config = this._initConfig(spec.config, parent);
this._children = spec.layers.map((layer, i) => {
// we know that the model has to be a unit model beacuse we pass in a unit spec
// we know that the model has to be a unit model because we pass in a unit spec
return buildModel(layer, this, this.name('layer_' + i)) as UnitModel;
});
}
Expand All @@ -29,6 +47,14 @@ export class LayerModel extends Model {
return mergeDeep(duplicate(defaultConfig), specConfig, parent ? parent.config() : {});
}

public get width(): number {
return this._width;
}

public get height(): number {
return this._height;
}

public has(channel: Channel): boolean {
// layer does not have any channels
return false;
Expand Down
32 changes: 9 additions & 23 deletions src/compile/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ export function assembleLayout(model: Model, layoutData: VgData[]): VgData[] {
name: model.dataName(LAYOUT),
source: model.dataTable(),
transform: [{
type: 'aggregate',
summarize: distinctFields.map(function(field) {
return { field: field, ops: ['distinct'] };
})
}].concat(formula)
type: 'aggregate',
summarize: distinctFields.map(function(field) {
return { field: field, ops: ['distinct'] };
})
}].concat(formula)
} : {
name: model.dataName(LAYOUT),
values: [{}],
Expand All @@ -72,39 +72,25 @@ export function parseUnitLayout(model: UnitModel): LayoutComponent {
}

function parseUnitSizeLayout(model: UnitModel, channel: Channel): SizeComponent {
// TODO: think about whether this config has to be the cell or facet cell config
const cellConfig = model.config().cell;

// TODO: read top-level width / height
const staticCellSize = channel === X ? cellConfig.width : cellConfig.height;

return {
distinct: getDistinct(model, channel),
formula: [{
field: model.channelSizeName(channel),
expr: unitSizeExpr(model, channel, staticCellSize)
expr: unitSizeExpr(model, channel)
}]
};
}

export function unitSizeExpr(model: UnitModel, channel: Channel, staticCellSize: number): string {
export function unitSizeExpr(model: UnitModel, channel: Channel): string {
const scale = model.scale(channel);
if (scale) {
if (scale.type === ScaleType.ORDINAL && scale.bandSize !== BANDSIZE_FIT) {
return '(' + cardinalityExpr(model, channel) +
' + ' + 1 +
') * ' + scale.bandSize;
} else {
return staticCellSize + '';
}
} else {
// TODO: need a way to set this to fit when using with layering.
if (model.mark() === TEXTMARK && channel === X) {
// for text table without x/y scale we need wider bandSize
return model.config().scale.textBandWidth + '';
}
return model.config().scale.bandSize + '';
}
return (channel === X ? model.width : model.height) + '';
}

export function parseFacetLayout(model: FacetModel): LayoutComponent {
Expand Down Expand Up @@ -205,5 +191,5 @@ export function cardinalityExpr(model: Model, channel: Channel) {

// FIXME: production rule will break here!
return timeUnitDomain !== null ? timeUnitDomain.length :
model.field(channel, {datum: true, prefn: 'distinct_'});
model.field(channel, {datum: true, prefn: 'distinct_'});
}
36 changes: 33 additions & 3 deletions src/compile/scale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {Orient} from '../config';
import {SOURCE, STACKED_SCALE} from '../data';
import {FieldDef, field, isMeasure} from '../fielddef';
import {Mark, BAR, TEXT as TEXTMARK, RULE, TICK} from '../mark';
import {Scale, ScaleType, NiceTime, BANDSIZE_FIT} from '../scale';
import {Scale, ScaleConfig, ScaleType, NiceTime, BANDSIZE_FIT, BandSize} from '../scale';
import {isSortField, SortOrder} from '../sort';
import {StackOffset} from '../stack';
import {NOMINAL, ORDINAL, QUANTITATIVE, TEMPORAL} from '../type';
Expand Down Expand Up @@ -209,6 +209,34 @@ export function scaleType(scale: Scale, fieldDef: FieldDef, channel: Channel, ma
return null;
}

export function scaleBandSize(scaleType: ScaleType, bandSize: number | BandSize, scaleConfig: ScaleConfig, topLevelSize: number, mark: Mark, channel: Channel): number | BandSize {
if (scaleType === ScaleType.ORDINAL) {
if (topLevelSize === undefined) {

if (bandSize) {
// Use manually specified bandSize
return bandSize;
} else if (channel === X && mark === TEXTMARK) {
return scaleConfig.textBandWidth;
} else {
return scaleConfig.bandSize;
}
} else {
// If top-level is specified, use bandSize fit
if (bandSize) {
// If top-level size is specified, we override specified bandSize with "fit"
console.warn('bandSize for', channel, 'overridden as top-level',
channel === X ? 'width' : 'height', 'is provided.');
}
return BANDSIZE_FIT;
}
} else {
// bandSize is not applicable for non-ordinal scale.
return undefined;

}
}

export function domain(scale: Scale, model: Model, channel:Channel): any {
const fieldDef = model.fieldDef(channel);

Expand Down Expand Up @@ -380,11 +408,13 @@ export function rangeMixins(scale: Scale, model: Model, channel: Channel): any {

return {
rangeMin: 0,
rangeMax: unitModel.config().cell.width // Fixed cell width for non-ordinal
// TODO: replace
rangeMax: unitModel.width // Fixed cell width for non-ordinal
};
case Y:
return {
rangeMin: unitModel.config().cell.height, // Fixed cell height for non-ordinal
// TODO: replace
rangeMin: unitModel.height, // Fixed cell height for non-ordinal
rangeMax: 0
};
case SIZE:
Expand Down
79 changes: 71 additions & 8 deletions src/compile/unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as vlEncoding from '../encoding'; // TODO: remove
import {FieldDef, FieldRefOption, field} from '../fielddef';
import {Legend} from '../legend';
import {Mark, TEXT as TEXTMARK} from '../mark';
import {Scale, ScaleType, BANDSIZE_FIT} from '../scale';
import {BANDSIZE_FIT, Scale, ScaleConfig, ScaleType} from '../scale';
import {ExtendedUnitSpec} from '../spec';
import {getFullName, QUANTITATIVE} from '../type';
import {duplicate, extend, mergeDeep, Dict} from '../util';
Expand All @@ -22,13 +22,26 @@ import {parseLegendComponent} from './legend';
import {assembleLayout, parseUnitLayout} from './layout';
import {Model} from './model';
import {parseMark} from './mark/mark';
import {parseScaleComponent, scaleType} from './scale';
import {parseScaleComponent, scaleBandSize, scaleType} from './scale';
import {stack, StackProperties} from '../stack';

/**
* Internal model of Vega-Lite specification for the compiler.
*/
export class UnitModel extends Model {
/**
* Fixed width for the unit visualization.
* If undefined (e.g., for ordinal scale), the width of the
* visualization will be calculated dynamically.
*/
private _width: number;

/**
* Fixed height for the unit visualization.
* If undefined (e.g., for ordinal scale), the height of the
* visualization will be calculated dynamically.
*/
private _height: number;

private _mark: Mark;
private _encoding: Encoding;
Expand All @@ -37,14 +50,27 @@ export class UnitModel extends Model {
constructor(spec: ExtendedUnitSpec, parent: Model, parentGivenName: string) {
super(spec, parent, parentGivenName);

// use top-level width / height or parent's top-level width / height
const providedWidth = spec.width !== undefined ? spec.width :
parent ? parent['width'] : undefined; // only exists if parent is layer
const providedHeight = spec.height !== undefined ? spec.height :
parent ? parent['height'] : undefined; // only exists if parent is layer

const mark = this._mark = spec.mark;
const encoding = this._encoding = this._initEncoding(mark, spec.encoding || {});
const config = this._config = this._initConfig(spec.config, parent, mark, encoding);

this._scale = this._initScale(mark, encoding, config);
this._scale = this._initScale(mark, encoding, config, providedWidth, providedHeight);
this._axis = this._initAxis(encoding, config);
this._legend = this._initLegend(encoding, config);

// width / height
this._initSize(mark, this._scale,
providedWidth,
providedHeight,
config.cell, config.scale
);

// calculate stack properties
this._stack = stack(mark, encoding, config);
}
Expand Down Expand Up @@ -81,7 +107,7 @@ export class UnitModel extends Model {
return config;
}

private _initScale(mark: Mark, encoding: Encoding, config: Config): Dict<Scale> {
private _initScale(mark: Mark, encoding: Encoding, config: Config, topLevelWidth:number, topLevelHeight: number): Dict<Scale> {
return UNIT_SCALE_CHANNELS.reduce(function(_scale, channel) {
if (vlEncoding.has(encoding, channel) ||
(channel === X && vlEncoding.has(encoding, X2)) ||
Expand All @@ -92,19 +118,48 @@ export class UnitModel extends Model {
const scaleSpec = (channelDef || {}).scale || {};
const _scaleType = scaleType(scaleSpec, channelDef, channel, mark);

_scale[channel] = extend({
var scale = _scale[channel] = extend({
type: _scaleType,
round: config.scale.round,
padding: config.scale.padding,
useRawDomain: config.scale.useRawDomain,
bandSize: channel === X && _scaleType === ScaleType.ORDINAL && mark === TEXTMARK && config.scale.bandSize !== BANDSIZE_FIT ?
config.scale.textBandWidth : config.scale.bandSize
useRawDomain: config.scale.useRawDomain
}, scaleSpec);

// bandSize depends on top-level size (width/height) and scale type
// If top-level size is specified, we override specified bandSize with "fit".
scale.bandSize = scaleBandSize(scale.type, scale.bandSize, config.scale, channel === X ? topLevelWidth : topLevelHeight, mark, channel);
}
return _scale;
}, {} as Dict<Scale>);
}

private _initSize(mark: Mark, scale: Dict<Scale>, width: number, height: number, cellConfig: CellConfig, scaleConfig: ScaleConfig) {
if (width !== undefined) {
this._width = width;
} else if (scale[X]) {
if (scale[X].type !== ScaleType.ORDINAL || scale[X].bandSize === BANDSIZE_FIT) {
this._width = cellConfig.width;
} // else: Do nothing, use dynamic width.
} else { // No scale X
if (mark === TEXTMARK) {
// for text table without x/y scale we need wider bandSize
this._width = scaleConfig.textBandWidth;
} else {
this._width = scaleConfig.bandSize;
}
}

if (height !== undefined) {
this._height = height;
} else if (scale[Y]) {
if (scale[Y].type !== ScaleType.ORDINAL || scale[Y].bandSize === BANDSIZE_FIT) {
this._height = cellConfig.height;
} // else: Do nothing, use dynamic height .
} else {
this._height = scaleConfig.bandSize;
}
}

private _initAxis(encoding: Encoding, config: Config): Dict<Axis> {
return [X, Y].reduce(function(_axis, channel) {
// Position Axis
Expand Down Expand Up @@ -138,6 +193,14 @@ export class UnitModel extends Model {
}, {} as Dict<Legend>);
}

public get width(): number {
return this._width;
}

public get height(): number {
return this._height;
}

public parseData() {
this.component.data = parseUnitData(this);
}
Expand Down
18 changes: 18 additions & 0 deletions src/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export interface BaseSpec {
}

export interface UnitSpec extends BaseSpec {
// FIXME description for top-level width
width?: number;

// FIXME description for top-level width
height?: number;

/**
* The mark type.
* One of `"bar"`, `"circle"`, `"square"`, `"tick"`, `"line"`,
Expand All @@ -64,6 +70,12 @@ export interface UnitSpec extends BaseSpec {
* @required ["mark", "encoding"]
*/
export interface ExtendedUnitSpec extends BaseSpec {
// FIXME description for top-level width
width?: number;

// FIXME description for top-level width
height?: number;

/**
* The mark type.
* One of `"bar"`, `"circle"`, `"square"`, `"tick"`, `"line"`,
Expand All @@ -83,6 +95,12 @@ export interface FacetSpec extends BaseSpec {
}

export interface LayerSpec extends BaseSpec {
// FIXME description for top-level width
width?: number;

// FIXME description for top-level width
height?: number;

/**
* Unit specs that will be layered.
*/
Expand Down
Loading

0 comments on commit af144c8

Please sign in to comment.