// declare let d3; import * as d3 from "d3"; import {convertValueToAttribute, OrientationEnum} from "../mmviz-common/index"; import {createViewModel} from "./layout"; import {LayoutScale} from "./scale"; export function getBarDomain(dataArray, valueMap){ 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; } export function barGroupLayoutCreator(orientationEnum = OrientationEnum.VERTICAL, colorScaleKey = "color") { // layout extender return (dataModel, layoutScale: LayoutScale) => { let xDomain, yDomain, zeroRange: number, innerBarCategory, innerBarScale, isVertical, isHorizontal; isVertical = OrientationEnum.VERTICAL === orientationEnum; isHorizontal = OrientationEnum.HORIZONTAL === orientationEnum; if (isVertical) { yDomain = getBarDomain(dataModel.dataArray, dataModel.valueMap); xDomain = dataModel.dataArray.map(dataModel.categoryMap); } else if (isHorizontal) { xDomain = getBarDomain(dataModel.dataArray, dataModel.valueMap); yDomain = dataModel.dataArray.map(dataModel.categoryMap); } // layoutScale does the mapping from data to visual elements. it calls d3 functions, like linear scale, category scale etc // it is used to build chart visuals layoutScale.extendDomainX(xDomain).extendDomainY(yDomain); // get category for inner bars innerBarCategory = dataModel.dataArray.map(dataModel.colorValueMap); // using d3 to create another band scale to map inner bars, domain is inner bar category, range is the outer bar width if (isVertical) { innerBarScale = d3.scaleBand().domain(innerBarCategory).rangeRound([0, layoutScale.xScale.bandwidth()]); } else if (isHorizontal){ innerBarScale = d3.scaleBand().domain(innerBarCategory).rangeRound([layoutScale.yScale.bandwidth(), 0]); } function layoutDatum(d){ /** * processing each data item (each row of data, the parameter d), to get the actual visual layout * such as width, height, x, y */ const category = dataModel.categoryMap(d), key = dataModel.keyMap(d), value = dataModel.valueMap(d), color = dataModel.colorValueMap(d), dLayout: any = {}; // dLayout contains processed data like key, value, and raw data like d from param dLayout.key = key; // keyMap from data model needs to uniquely identify d dLayout.value = value; dLayout.original = d; // store the original data // dlayout.color is an object with key and value dLayout.color = layoutScale.mapValue(colorScaleKey, dataModel, d); // color is mapped using colorValueMap function from dataModel // The categoryAttr attribute is added to the html element as class, it can be used as a JavaScript or css selector. dLayout.category = dLayout.color.key; dLayout.categoryAttr = "category-" + convertValueToAttribute(dLayout.color.key); // doing actual mapping from data to visual space if (OrientationEnum.VERTICAL === orientationEnum) { // determine the inner bar x value by adding innerBarScale(color) dLayout.x = layoutScale.xScale(category) + innerBarScale(color); dLayout.x0 = dLayout.x; dLayout.y0 = zeroRange; dLayout.width = innerBarScale.bandwidth(); if (value >= 0) { dLayout.height = (zeroRange - layoutScale.yScale(value)); dLayout.y = layoutScale.yScale(value); } else { dLayout.height = (layoutScale.yScale(value) - zeroRange); dLayout.y = zeroRange; } } else if (OrientationEnum.HORIZONTAL === orientationEnum) { dLayout.x0 = zeroRange; dLayout.y = layoutScale.yScale(category) + innerBarScale(color); dLayout.y0 = dLayout.y; dLayout.height = innerBarScale.bandwidth(); if (value >= 0){ dLayout.x = zeroRange; dLayout.width = (layoutScale.xScale(value) - zeroRange); } else { dLayout.width = (zeroRange - layoutScale.xScale(value)); dLayout.x = (zeroRange - dLayout.width); } } // one dLayout is one data item for the inner bar return dLayout; } // layouter return function(){ let d, dLayout, viewModel = createViewModel(); if (OrientationEnum.VERTICAL === orientationEnum) { zeroRange = layoutScale.yScale(0); } else if (OrientationEnum.HORIZONTAL === orientationEnum) { zeroRange = layoutScale.xScale(0); } // loop over each d to get visual layout stored in viewModel for (d of dataModel.dataArray){ dLayout = layoutDatum(d); if (dataModel.detailsMap){ dLayout.details = dataModel.detailsMap(d); } viewModel.dataArray.push(dLayout); } return viewModel; }; }; }