/** * Created by michaelbessey on 10/30/16. */ import * as d3 from "d3"; import { addLegendItemToggleBehavior, addLegendItemToggleResponderBehavior, DetailsBehavior } from "../mmviz-behavior/index"; import { ChartView, convertSelectorToAttribute, Theme } from "../mmviz-common/index"; import {LegendComponent, TemplateComponent} from "../mmviz-component-dom/index"; import {AxisComponentSvg} from "../mmviz-component-svg/index"; import { axisLayouter, AxisLocationEnum, getAxisSelector, LayoutScale, ScaleContainer, ScaleContainerTypeEnum, ScaleTypeEnum } from "../mmviz-layout/index"; export class ChartBuilderSvg { id; title; description; dataModel; chartComponent: TemplateComponent; chartView: ChartView; layoutScale: LayoutScale; axisTopComponent: AxisComponentSvg; axisRightComponent: AxisComponentSvg; axisBottomComponent: AxisComponentSvg; axisLeftComponent: AxisComponentSvg; xAxisConfigFactory: Function; yAxisConfigFactory: Function; legendComponentMap; colorLegendComponent: LegendComponent; fillLegendComponent: LegendComponent; areaLegendComponent: LegendComponent; doMapColorScale: boolean; colorScaleKey: string; customColorScale: any; colorRangeMapper: Function; insertChartDom; isFocusable; aspectRatioPreservation; detailsBehavior: DetailsBehavior; doUpdateComponents; constructor(public selector: string) { this.id = convertSelectorToAttribute(this.selector); this.aspectRatioPreservation = {alignment: "xMidYMin", scaling: "meet"}; this.insertChartDom = true; this.isFocusable = false; this.chartComponent = new TemplateComponent("chart"); this.legendComponentMap = {}; this.doMapColorScale = true; this.colorScaleKey = "color"; this.chartView = new ChartView(this.selector); this.doUpdateComponents = true; } get drawArea(){ return this.chartView.drawArea; } updateConfig(c: any){ if (c.title !== undefined){ this.title = c.title; } if (c.description !== undefined){ this.description = c.description; } if (c.isFocusable !== undefined){ this.isFocusable = c.isFocusable; } if (c.insertChartDom !== undefined){ this.insertChartDom = c.insertChartDom; } if (c.doUpdateComponents !== undefined){ this.doUpdateComponents = c.doUpdateComponents; } if (c.doMapColorScale !== undefined){ this.doMapColorScale = c.doMapColorScale; } return this; } setDataModel(dataModel){ this.dataModel = dataModel; return this; } addDefaultPadding() { this.chartView.padding = {top: 41, right: 0, bottom: 48, left: 0}; return this; } addPadding(padding) { this.chartView.padding = padding; return this; } addAxisTop(){ return this; } addAxisRight(label: string = null){ let locationEnum: AxisLocationEnum = AxisLocationEnum.RIGHT; this.axisRightComponent = new AxisComponentSvg(this.selector, getAxisSelector(locationEnum), label); return this; } addAxisBottom(label: string = null){ let locationEnum: AxisLocationEnum = AxisLocationEnum.BOTTOM; this.axisBottomComponent = new AxisComponentSvg(this.selector, getAxisSelector(locationEnum), label); return this; } addAxisLeft(label: string = null){ let locationEnum: AxisLocationEnum = AxisLocationEnum.LEFT; this.axisLeftComponent = new AxisComponentSvg(this.selector, getAxisSelector(locationEnum), label); return this; } addColorLegend(config: any = {}){ let keyType = "color"; this.addLegend(keyType, keyType, config); return this; } addAreaLegend(config: any = {}){ let keyType = "area"; this.addLegend(keyType, keyType, config); return this; } addLegend(key: string, type: string, config: any = {}){ // config is an object that can contain element: title: string, isTogglable: boolean, variable: string let legendComponent = new LegendComponent(".chart-legend-" + key, "chart_legend_" + type); legendComponent.config = config; this.legendComponentMap[key] = legendComponent; return this; } addScaleContainer(key: string, scaleContainer: ScaleContainer) { this.layoutScale.addScaleContainer(key, scaleContainer); return this; } fixAspectRatio() { this.chartView.syncViewBoxDimension(); this.chartView.preserveAspectRatio = this.aspectRatioPreservation; return this; } displayAxies(){ let axisViewModel, drawArea = this.drawArea, widthSize = drawArea.widthSize ; if (this.axisLeftComponent) { axisViewModel = axisLayouter(AxisLocationEnum.LEFT, this.layoutScale, {label: this.axisLeftComponent.label}); if(this.yAxisConfigFactory){ this.axisLeftComponent.config = this.yAxisConfigFactory(widthSize); } this.axisLeftComponent.updateView(this.chartView.stage, axisViewModel); } if (this.axisBottomComponent) { axisViewModel = axisLayouter(AxisLocationEnum.BOTTOM, this.layoutScale, {label: this.axisBottomComponent.label}); if(this.xAxisConfigFactory){ this.axisBottomComponent.config = this.xAxisConfigFactory(widthSize); } this.axisBottomComponent.updateView(this.chartView.stage, axisViewModel); } if (this.axisRightComponent) { axisViewModel = axisLayouter(AxisLocationEnum.RIGHT, this.layoutScale, {label: this.axisRightComponent.label}); if (this.yAxisConfigFactory){ this.axisLeftComponent.config = this.yAxisConfigFactory(widthSize); } this.axisRightComponent.updateView(this.chartView.stage, axisViewModel); } } mapColorScale(){ if (this.doMapColorScale){ // setup color scale if (this.colorRangeMapper){ this.layoutScale.createMapCustomOrdinalScaleContainer( this.colorScaleKey, this.dataModel, this.colorRangeMapper); } else if (this.customColorScale){ this.layoutScale.createCustomScaleObjectContainer( this.colorScaleKey, ScaleTypeEnum.ORDINAL, this.customColorScale); } else { this.layoutScale.createScaleContainer( this.colorScaleKey, ScaleContainerTypeEnum.COLOR_CATEGORICAL_DARK); } } } setContentDimensions(dim){ this.chartView.contentDimensions = dim; return this; } updateCalculatedLeftPadding() { let theme: Theme = Theme.getInstance(); if (this.axisLeftComponent) { let maxWidthLabel = this.axisLeftComponent.maxWidthAxisLabel // labelBBox = d3.select(".axis-left-label").select("text").node().getBBox(), ; if (maxWidthLabel){ this.chartView.left = maxWidthLabel.bbox.width + 5; } } return this; } updateCalculatedBottomPadding() { let theme: Theme = Theme.getInstance(); if (this.axisBottomComponent) { let labelBBox = d3.select(".axis-bottom-label").select("text").node().getBBox(), maxLabel = this.axisBottomComponent.maxHeightAxisLabel; this.chartView.bottom = maxLabel.bbox.height + (labelBBox.height * 1.5); } return this; } createView(){ const viewModel: any = { id: this.id, selector: this.selector, title: this.title, description: this.description, isFocusable: this.isFocusable }; if (this.insertChartDom){ this.chartComponent.createView(this.selector, viewModel); } this.layoutScale = new LayoutScale(this.chartView.drawArea); return this; } updateView() { if (!this.chartView.isViewable){ return; } this.layoutScale.drawArea = this.chartView.drawArea; if (Object.keys(this.legendComponentMap).length > 0) { this.chartView.showLegend(); } this.chartView.applyResponsiveBehavior(); } updateComponents(){ let key, legendComponent; if (!this.doUpdateComponents){ return; } for (key in this.legendComponentMap) { if (this.legendComponentMap.hasOwnProperty(key)) { legendComponent = this.legendComponentMap[key]; if (legendComponent){ legendComponent.appendView(this.chartView.legendsContainer, this.layoutScale.getScaleModel(key)); if (legendComponent.isTogglable){ addLegendItemToggleBehavior(this.selector, legendComponent.selector); } } } } if (this.detailsBehavior){ this.detailsBehavior.setupBehavior(); } } addLegendItemToggleBehavior(legendKey, dataPipeline, dataModelMaker){ let legendToggleResponder, legendComponent = this.legendComponentMap[legendKey]; if (legendComponent) { addLegendItemToggleResponderBehavior(this, legendComponent.selector, dataPipeline, dataModelMaker); } return this; } }