UNPKG

veritatisquam

Version:
252 lines (211 loc) 6.58 kB
// 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()); }); } }