in packages/sanddance-specs/src/layouts/stack.ts [61:259]
public build(): InnerScope {
const { names, props } = this;
const { globalScope, groupings, parentScope, sort } = props;
const { sizeSignals } = parentScope;
addTransforms(globalScope.data,
{
type: 'joinaggregate',
groupby: getGroupBy(groupings).map(safeFieldName),
ops: ['count'],
as: [names.count]
},
{
type: 'extent',
field: names.count,
signal: names.globalExtent
},
{
type: 'stack',
groupby: getGroupBy(groupings).map(safeFieldName),
as: [names.stack0, names.stack1],
...sort && {
sort: {
field: safeFieldName(sort.name),
order: 'ascending'
}
}
}
);
addData(globalScope.scope,
{
name: names.sequence,
transform: [
{
type: 'sequence',
start: 1,
stop: {
signal: `sqrt(${names.globalExtent}[1])`
}
},
{
type: 'formula',
expr: 'datum.data * datum.data',
as: 'squared'
},
{
type: 'formula',
expr: `ceil(${names.globalExtent}[1] / datum.squared)`,
as: 'maxlevels'
},
{
type: 'formula',
expr: `(${names.size} - (datum.data - 1) * datum.data) / datum.data`,
as: 'side'
},
{
type: 'formula',
expr: 'datum.side * datum.maxlevels + datum.maxlevels - 1',
as: 'sidecubeheight'
},
{
type: 'formula',
expr: `abs(${globalScope.zSize} - datum.sidecubeheight)`,
as: 'heightmatch'
},
{
type: 'collect',
sort: {
field: 'heightmatch',
order: 'ascending'
}
},
{
type: 'window',
ops: ['row_number']
},
{
type: 'filter',
expr: 'datum.row_number === 1'
}
]
}
);
addSignals(globalScope.scope,
{
name: names.size,
update: `min((${sizeSignals.layoutHeight}), (${sizeSignals.layoutWidth}))`
},
{
name: names.squared,
update: `data('${names.sequence}')[0].squared`
},
{
name: names.sides,
update: `sqrt(${names.squared})`
},
{
name: names.cube,
update: `(${names.size} - (${names.sides} - 1)) / ${names.sides}`
},
{
name: names.maxLevels,
update: `data('${names.sequence}')[0].maxlevels`
},
{
name: names.maxCount,
update: `${names.maxLevels} * ${names.squared}`
}
);
const zLevel = `floor(datum[${JSON.stringify(names.stack0)}] / ${names.squared})`;
const layerOrdinal = `(datum[${JSON.stringify(names.stack0)}] % ${names.squared})`;
const cubeX = `(${layerOrdinal} % ${names.sides})`;
const cubeY = `floor(${layerOrdinal} / ${names.sides})`;
const groupX = `(${sizeSignals.layoutWidth} - ${names.size}) / 2`;
const groupY = `(${sizeSignals.layoutHeight} - ${names.size}) / 2`;
const offsets: LayoutOffsets = {
x: addOffsets(parentScope.offsets.x, groupX, `${cubeX} * (${names.cube} + 1)`),
y: addOffsets(parentScope.offsets.y, groupY, `${cubeY} * (${names.cube} + 1)`),
h: names.size,
w: names.size
};
const mark: RectMark = {
type: 'rect',
from: { data: this.names.levelDataName },
encode: {
update: {
z: {
signal: `${zLevel} * (${names.cube} + 1)`
},
height: {
signal: names.cube
},
width: {
signal: names.cube
},
depth: {
signal: names.cube
}
}
}
};
addMarks(globalScope.markGroup, mark);
const zScale: LinearScale = {
type: 'linear',
name: names.zScale,
domain: [
0,
{
signal: names.maxCount
}
],
range: [
0,
{
signal: `${names.maxLevels} * (${names.cube} + 1) - 1`
}
],
nice: false
};
return {
offsets,
mark,
sizeSignals: {
layoutHeight: names.size,
layoutWidth: names.size
},
globalScales: {
showAxes: true,
scales: {
z: [zScale]
}
},
encodingRuleMap: {
y: [{
test: testForCollapseSelection(),
signal: parentScope.offsets.y
}],
z: [{
test: testForCollapseSelection(),
value: 0
}],
depth: [{
test: testForCollapseSelection(),
value: 0
}],
height: [{
test: testForCollapseSelection(),
value: 0
}]
}
};
}