/** * Created by mm28969 on 11/23/16. */ // declare let d3; import * as d3 from "d3"; import { areaToRadius, getDomainUnique, ChartDrawArea, ChartView, ChartWidthSizeEnum, getCurrencyAxisConfigY, getLinearAxisConfigX, getLinearAxisConfigY, getTimeAxisConfigX, Theme } from "../mmviz-common/index"; import { buildCustomScaleContainer, buildCustomScaleObjectContainer, ScaleTypeEnum, ScaleContainer, ScaleContainerTypeEnum, scaleContainerFactory } from "./scale_container"; import {AxisLocationEnum} from "../mmviz-layout/axis"; import {albersUsaPr} from "../mmviz-projection"; export enum ProjectionTypeEnum { GEO_ALBERS_USA = 1, GEO_ALBERS_USA_PR, GEO_MERCATOR } export class LayoutScale { DEFAULT_LEGEND_LABEL = "default"; drawArea: ChartDrawArea; scaleContainerMap: any; constructor(drawArea: ChartDrawArea) { this.drawArea = drawArea; this.scaleContainerMap = {}; } createScaleContainer(key: string, type: ScaleContainerTypeEnum): LayoutScale { this.scaleContainerMap[key] = scaleContainerFactory(key, type, this.drawArea); return this; } addScaleContainer(key: string, scaleContainer: ScaleContainer): LayoutScale { this.scaleContainerMap[key] = scaleContainer; return this; } createCustomScaleContainer(key: string, type: ScaleTypeEnum, domain: any[], range: any[], defaultValue?: any): LayoutScale { this.scaleContainerMap[key] = buildCustomScaleContainer(key, type, domain, range, defaultValue); return this; } createCustomScaleObjectContainer(key: string, type: ScaleTypeEnum, scaleObject: any): LayoutScale { this.scaleContainerMap[key] = buildCustomScaleObjectContainer(key, type, scaleObject); return this; } createCustomOrdinalScaleContainer(key: string, domain: any[], rangeMapper: Function): LayoutScale { let d, range = [], scaleContainer = new ScaleContainer(key, ScaleTypeEnum.ORDINAL); for(d of domain){ range.push(rangeMapper(d)); } return this.createCustomScaleContainer(key, ScaleTypeEnum.ORDINAL, domain, range); } createMapCustomOrdinalScaleContainer(key: string, dataModel, rangeMapper: Function): LayoutScale { let d, value, range = [], valueMapper = dataModel[key + "ValueMap"], domain = getDomainUnique(dataModel, key); // for example, mapping domain of categories to range of colors return this.createCustomOrdinalScaleContainer(key, domain, rangeMapper); } getScaleContainer(key: string): ScaleContainer { let scaleContainer: ScaleContainer = this.scaleContainerMap[key]; if(!scaleContainer) { throw `Scale \"${key}\" does not exist`; } return scaleContainer; } extendAreaScale(key: string, newDomain: any[]){ let theme = Theme.getInstance(), scaleContainer: ScaleContainer = this.getScaleContainer(key); scaleContainer .extendDomain(newDomain) .makeRangeDomainMultiple(theme.getArea(this.drawArea)); } makeRangeMultipleOfDomain(key: string, multiple: number){ let scaleContainer : ScaleContainer = this.getScaleContainer(key); scaleContainer.makeRangeDomainMultiple(multiple); } setScaleLabel(key: string, label: string){ let scaleContainer: ScaleContainer = this.getScaleContainer(key); scaleContainer.label = label; return this; } setScaleDefaultValue(key: string, defaultValue){ let scaleContainer: ScaleContainer = this.getScaleContainer(key); scaleContainer.defaultValue = defaultValue; return this; } setScaleDomain(key: string, domain){ let scaleContainer: ScaleContainer = this.getScaleContainer(key); scaleContainer.domain = domain; return this; } setScaleRange(key: string, range){ let scaleContainer: ScaleContainer = this.getScaleContainer(key); scaleContainer.range = range; return this; } getScaleModel(key: string){ let scaleContainer = this.getScaleContainer(key); return scaleContainer.model; } extendScaleDomain(key: string, newDomain: any[]){ let scaleContainer: ScaleContainer = this.getScaleContainer(key); scaleContainer.extendDomain(newDomain); return this; } extendScaleRange(key: string, newRange: any[]){ let scaleContainer: ScaleContainer = this.getScaleContainer(key); scaleContainer.extendRange(newRange); return this; } /** * Map data value via scaleContainer identified by key. * The valueMapper function used to get the data value is located in the dataModel. * If a rangeMapper is provided, * the range value it returns overrides the range value returned by the scaleContainer. * @param key * @param dataModel * @param d */ mapValue(key, dataModel, d){ const valueMapper = dataModel[key + "ValueMap"], rangeMapper = dataModel[key + "RangeMap"]; let result: any = {}; if (valueMapper && rangeMapper){ result.key = String(valueMapper(d)); result.value = rangeMapper(result.key); } else { const scaleContainer: ScaleContainer = this.getScaleContainer(key); if (scaleContainer) { result = scaleContainer.mapValue(dataModel, d); } } return result; } mapAreaRadiusValue(key, dataModel, d){ let scaleContainer = this.getScaleContainer(key), area = scaleContainer.mapValue(dataModel, d); return { key: area.key, value: areaToRadius(area.value) } } valueInvert(key, value){ return this.getScaleContainer(key).scale.invert(value); } xScaleSetup(type: ScaleTypeEnum): LayoutScale { let key = "x", scaleContainer = new ScaleContainer(key, type); scaleContainer.range = this.xRange; this.scaleContainerMap[key] = scaleContainer; return this; } yScaleSetup(type: ScaleTypeEnum): LayoutScale { let key = "y", scaleContainer = new ScaleContainer(key, type); scaleContainer.range = this.yRange; this.scaleContainerMap[key] = scaleContainer; return this; } extendDomainX(newDomainX: any[]): LayoutScale { this.extendScaleDomain("x", newDomainX); return this; } extendDomainY(newDomainY: any[]): LayoutScale { this.extendScaleDomain("y", newDomainY); return this; } /** * Add buffer the domain of the x-axis * @param percent of the current domain extent to increase the domain by */ bufferDomainX(percent: number) { // pad domain const xDomain = this.xDomain, xExtent = xDomain[1] - xDomain[0], xExtentPercent = xExtent * percent, xDomainPadded = [xDomain[0] - xExtentPercent, xDomain[1] + xExtentPercent] ; this.extendDomainX(xDomainPadded); return this; } /** * Add buffer to the domain of the y-axis * @param percent of the current domain extent to increase the domain by */ bufferDomainY(percent: number) { const yDomain = this.yDomain, yExtent = yDomain[1] - yDomain[0], yExtentPercent = yExtent * percent, yDomainBuffer = [yDomain[0] - yExtentPercent, yDomain[1] + yExtentPercent] ; this.extendDomainY(yDomainBuffer); return this; } xyInvert(x, y){ let xInvert = this.valueInvert("x", x), yInvert = this.valueInvert("y", y); return { x: xInvert, y: yInvert }; } get xScaleContainer(){ return this.getScaleContainer("x"); } get xScale(){ return this.xScaleContainer.scale; } get xDomain(){ return this.xScale.domain(); } get xDomainExtent(){ return d3.extent(this.xDomain); } get xRange(){ return this.drawArea.rangeX; } get xRangeExtent(){ return d3.extent(this.xScale.range()); } get yScaleContainer(){ return this.getScaleContainer("y"); } get yScale(){ return this.yScaleContainer.scale; } get yDomain(){ return this.yScale.domain(); } get yDomainExtent(){ return d3.extent(this.yDomain); } get yRange(){ return this.drawArea.rangeReverseY; } get yRangeExtent(){ return d3.extent(this.yScale.range()); } get area(){ let theme = Theme.getInstance(); return theme.getArea(this.drawArea); } get radius(){ let theme = Theme.getInstance(); return areaToRadius(theme.getArea(this.drawArea)); } get lineWidth(){ let theme = Theme.getInstance(); return theme.getLineWidth(this.drawArea); } getProjection(projectionTypeEnum: ProjectionTypeEnum, chartView: ChartView, scaleFactor: number = 0.9) { let projection; switch (projectionTypeEnum) { case ProjectionTypeEnum.GEO_ALBERS_USA: projection = d3.geoAlbersUsa(); break; case ProjectionTypeEnum.GEO_ALBERS_USA_PR: projection = albersUsaPr(); break; case ProjectionTypeEnum.GEO_MERCATOR: projection = d3.geoMercator(); break; default: throw "Unknow projection"; } if(projection){ let dimension = chartView.stageDimension, scale = this.drawArea.drawWidth * scaleFactor; projection = projection.translate([dimension.width/2, dimension.height/2]).scale([scale]); } return projection; } }