/** * Created by mm28969 on 10/24/16. * modified by mm17060 on 6/12/17 */ // declare let d3; import * as d3 from "d3"; import {convertValueToAttribute, OrientationEnum} from "../mmviz-common/index"; import {Layout, createViewModel} from "./layout"; import {LayoutScale} from "./scale"; export function getBarDomain(dataArray, valueMap){ // valueMap: a function to extract the value out of dataArray for d3.extent to determine the length of the bar let domain = d3.extent(dataArray, valueMap); // positive chart, set baseline at zero if(domain[0] > 0 && domain[1] > 0){ domain = [0, d3.max(domain)]; } // negative chart, set baseline at zero else if(domain[0] < 0 && domain[1] < 0){ domain = [d3.min(domain), 0]; } return domain; } /** * return a bar layout creator which is a function that maps data to visual elements * it is able to accept user defined base for the bar chart by providing `valueBaseMap` in dataModel, by default the * bar starts at 0 * @param {OrientationEnum} orientationEnum * @param {string} colorScaleKey * @returns a function which will calculate the viewModel (pixel) that is mapped from dataModel (actual data) * {(dataModel, layoutScale: LayoutScale) => () => {keyMap: (d) => any; dataArray: any[]}} */ export function barLayoutCreator(orientationEnum = OrientationEnum.VERTICAL, colorScaleKey = "color") { // layout extender return function(dataModel,layoutScale: LayoutScale){ let xDomain, yDomain; if (OrientationEnum.VERTICAL === orientationEnum) { if ("valueBaseMap" in dataModel){ // when providing customized bar base, need to consider y0 and y1 together to get the correct y domain const y0Domain = getBarDomain(dataModel.dataArray, dataModel.valueBaseMap), y1Domain = getBarDomain(dataModel.dataArray,dataModel.valueMap); yDomain = d3.extent(y0Domain.concat(y1Domain)); } else{ // yDomain is calculated using the max y, which is y1, also called valueMap in dataModel yDomain = getBarDomain(dataModel.dataArray, dataModel.valueMap); } xDomain = dataModel.dataArray.map(dataModel.keyMap); } else if (OrientationEnum.HORIZONTAL === orientationEnum) { if ("valueBaseMap" in dataModel){ const x0Domain = getBarDomain(dataModel.dataArray, dataModel.valueBaseMap), x1Domain = getBarDomain(dataModel.dataArray, dataModel.valueMap); xDomain = d3.extent(x0Domain.concat(x1Domain)); } else{ xDomain = getBarDomain(dataModel.dataArray, dataModel.valueMap); } yDomain = dataModel.dataArray.map(dataModel.keyMap); } layoutScale.extendDomainX(xDomain).extendDomainY(yDomain); function layoutDatum(d){ let key = dataModel.keyMap(d), value = dataModel.valueMap(d), color, dLayout: any = {}; //dlayout's y0 should be always smaller than y dLayout.key = key; // unique identifier for the bar dLayout.value = value; // y1 value, if the base is undefined, y1 value is the actual value of the bar dLayout.original = d; // keep the original data row // dlayout.color is an object with key and value color = layoutScale.mapValue(colorScaleKey, dataModel, d); // color is mapped using colorValueMap function from dataModel dLayout.color = color; dLayout.category = color.key; // The categoryAttr attribute is added to the html element as class, it can be used as a JavaScript or css selector. dLayout.categoryAttr = "category-" + convertValueToAttribute(color.key); if (OrientationEnum.VERTICAL === orientationEnum) { // (x0, y0) is where the entry point is, x=x0, y is used to calculate the height together with y0 dLayout.x = layoutScale.xScale(key); dLayout.x0 = dLayout.x; if ("valueBaseMap" in dataModel){ dLayout.y0 = layoutScale.yScale(dataModel.valueBaseMap(d)); } else{ dLayout.y0 = layoutScale.yScale(0); // the original zeroRange } dLayout.width = layoutScale.xScale.bandwidth(); if(value >= 0) { dLayout.height = (dLayout.y0 - layoutScale.yScale(value)); dLayout.y = layoutScale.yScale(value); } else { dLayout.height = (layoutScale.yScale(value) - dLayout.y0); dLayout.y = dLayout.y0; } } else if (OrientationEnum.HORIZONTAL === orientationEnum) { if ("valueBaseMap" in dataModel) { dLayout.x0 = layoutScale.xScale(dataModel.valueBaseMap(d)); } else{ dLayout.x0 = layoutScale.xScale(0); } dLayout.y = layoutScale.yScale(key); dLayout.y0 = dLayout.y; dLayout.height = layoutScale.yScale.bandwidth(); if (value >= 0){ dLayout.x = dLayout.x0; dLayout.width = (layoutScale.xScale(value) - dLayout.x0); } else { dLayout.width = (dLayout.x0 - layoutScale.xScale(value)); dLayout.x = (dLayout.x0 - dLayout.width); } } return dLayout; } // layouter return function(){ let d, dLayout, viewModel = createViewModel(); for (d of dataModel.dataArray){ dLayout = layoutDatum(d); if(dataModel.detailsMap){ dLayout.details = dataModel.detailsMap(d); } viewModel.dataArray.push(dLayout); } return viewModel; } } }