veritatisquam
Version:
Carbon charting components, lerna monorepo
252 lines (211 loc) • 6.58 kB
text/typescript
// Internal Imports
import { Component } from '../component';
import {
LayoutDirection,
LayoutGrowth,
LayoutComponentChild,
LayoutConfigs,
} from '../../interfaces/index';
import { Tools } from '../../tools';
import { DOMUtils } from '../../services';
import { ChartModel } from '../../model';
// D3 Imports
import { select } from 'd3-selection';
import { hierarchy, treemap, treemapSlice, treemapDice } from 'd3-hierarchy';
// TODO - What if there is no "growth" object?
export class LayoutComponent extends Component {
// Give every layout component a distinct ID
// so they don't interfere when querying elements
static instanceID = Math.floor(Math.random() * 99999999999);
type = 'layout';
children: LayoutComponentChild[];
private _instanceID: number;
constructor(
model: ChartModel,
services: any,
children: LayoutComponentChild[],
configs?: LayoutConfigs
) {
super(model, services, configs);
this.configs = configs;
this.children = children;
this._instanceID = LayoutComponent.instanceID++;
// Pass children data to the hierarchy layout
// And calculate sum of sizes
const directionIsReversed =
this.configs.direction === LayoutDirection.ROW_REVERSE ||
this.configs.direction === LayoutDirection.COLUMN_REVERSE;
if (directionIsReversed) {
this.children = this.children.reverse();
}
this.init();
}
init() {
this.children.forEach((child) => {
child.components.forEach((component) => {
component.init();
});
});
}
getPreferedAndFixedSizeSum(): number {
const svg = this.parent;
let sum = 0;
svg.selectAll(`svg.layout-child-${this._instanceID}`)
.filter((d: any) => {
const growth = Tools.getProperty(d, 'data', 'growth', 'x');
return (
growth === LayoutGrowth.PREFERRED ||
growth === LayoutGrowth.FIXED
);
})
.each(function (d: any) {
sum += d.data.size;
});
return sum;
}
getNumOfStretchChildren(): number {
const svg = this.parent;
return svg
.selectAll(`svg.layout-child-${this._instanceID}`)
.filter((d: any) => {
const growth = Tools.getProperty(d, 'data', 'growth', 'x');
return growth === LayoutGrowth.STRETCH;
})
.size();
}
render(animate = true) {
// Get parent SVG to render inside of
const svg = this.parent;
const { width, height } = DOMUtils.getSVGElementSize(svg, {
useAttrs: true,
});
let root = hierarchy({
children: this.children,
}).sum((d: any) => d.size);
// Grab the correct treemap tile function based on direction
const tileType =
this.configs.direction === LayoutDirection.ROW ||
this.configs.direction === LayoutDirection.ROW_REVERSE
? treemapDice
: treemapSlice;
// Compute the position of all elements within the layout
treemap().tile(tileType).size([width, height])(root);
// TODORF - Remove
const horizontal =
this.configs.direction === LayoutDirection.ROW ||
this.configs.direction === LayoutDirection.ROW_REVERSE;
// Add new SVGs to the DOM for each layout child
const updatedSVGs = svg
.selectAll(`svg.layout-child-${this._instanceID}`)
.data(root.leaves(), (d: any) => d.data.id);
updatedSVGs
.attr('width', (d: any) => d.x1 - d.x0)
.attr('height', (d: any) => d.y1 - d.y0);
const enteringSVGs = updatedSVGs
.enter()
.append('svg')
.attr(
'class',
(d: any) =>
`layout-child layout-child-${this._instanceID} ${d.data.id}`
)
.attr('x', (d: any) => d.x0)
.attr('y', (d: any) => d.y0);
enteringSVGs
.merge(svg.selectAll(`svg.layout-child-${this._instanceID}`))
.each(function (d: any) {
// Set parent component for each child
d.data.components.forEach((itemComponent) => {
itemComponent.setParent(select(this));
// Render preffered & fixed items
const growth = Tools.getProperty(d, 'data', 'growth', 'x');
if (
growth === LayoutGrowth.PREFERRED ||
growth === LayoutGrowth.FIXED
) {
itemComponent.render(animate);
}
});
});
svg.selectAll(`svg.layout-child-${this._instanceID}`).each(function (
d: any
) {
// Calculate preffered children sizes after internal rendering
const growth = Tools.getProperty(d, 'data', 'growth', 'x');
const matchingSVGDimensions = DOMUtils.getSVGElementSize(
select(this),
{ useBBox: true }
);
if (d.data.id === 'legend') {
const svgSize = DOMUtils.getSVGElementSize(select(this), {
useAttrs: true,
});
if (svgSize.height < 40) {
matchingSVGDimensions.height = svgSize.height;
}
}
if (growth === LayoutGrowth.PREFERRED) {
const matchingSVGWidth = horizontal
? matchingSVGDimensions.width
: matchingSVGDimensions.height;
const svgWidth = horizontal ? width : height;
d.data.size = (matchingSVGWidth / svgWidth) * 100;
}
});
updatedSVGs.exit().remove();
// Run through stretch x-items
this.children
.filter((child) => {
const growth = Tools.getProperty(child, 'growth', 'x');
return growth === LayoutGrowth.STRETCH;
})
.forEach((child, i) => {
child.size =
(100 - +this.getPreferedAndFixedSizeSum()) /
+this.getNumOfStretchChildren();
});
// Pass children data to the hierarchy layout
// And calculate sum of sizes
root = hierarchy({
children: this.children,
}).sum((d: any) => d.size);
// Compute the position of all elements within the layout
treemap().tile(tileType).size([width, height]).padding(0)(root);
// Add new SVGs to the DOM for each layout child
svg.selectAll(`svg.layout-child-${this._instanceID}`)
.data(root.leaves(), (d: any) => d.data.id)
.attr('x', (d: any) => d.x0)
.attr('y', (d: any) => d.y0)
.attr('width', (d: any) => d.x1 - d.x0)
.attr('height', (d: any) => d.y1 - d.y0)
.each(function (d: any, i) {
d.data.components.forEach((itemComponent) => {
const growth = Tools.getProperty(d, 'data', 'growth', 'x');
if (growth === LayoutGrowth.STRETCH) {
itemComponent.render(animate);
}
});
});
}
// Pass on model to children as well
setModel(newObj) {
super.setModel(newObj);
this.children.forEach((child) => {
child.components.forEach((component) => component.setModel(newObj));
});
}
// Pass on essentials to children as well
setServices(newObj) {
super.setServices(newObj);
this.children.forEach((child) => {
child.components.forEach((component) =>
component.setServices(newObj)
);
});
}
destroy() {
this.children.forEach((child) => {
child.components.forEach((component) => component.destroy());
});
}
}