diff --git a/src/client/visualizations/bar-chart/bar-chart.scss b/src/client/visualizations/bar-chart/bar-chart.scss index d40b7fcb..7b40b98a 100644 --- a/src/client/visualizations/bar-chart/bar-chart.scss +++ b/src/client/visualizations/bar-chart/bar-chart.scss @@ -19,26 +19,31 @@ stroke: $gray; } - .bars { - pointer-events: none; - - rect { - fill: $brand; + g.bars { + g.bar { + &.not-selected { + fill-opacity: 0.5; + } &.hover { fill-opacity: 0.9; } - &.not-selected { - fill-opacity: 0.5; + > rect.background { + pointer-events: none; + fill: $brand; } - } - } - .bar-ghosts { - rect { - fill: $white; - fill-opacity: 0.001; + > rect.mouse-event-target { + fill: $white; + fill-opacity: 0.001; + } + + .selection { + pointer-events: none; + fill: none; + stroke: $brand; + } } } @@ -46,15 +51,6 @@ fill: $white; } - .bar-highlight { - pointer-events: none; - - rect { - fill: none; - stroke: $brand; - } - } - .slanty-labels { position: absolute; overflow: hidden; diff --git a/src/client/visualizations/bar-chart/bar-chart.tsx b/src/client/visualizations/bar-chart/bar-chart.tsx index 3df824a6..b1d6094e 100644 --- a/src/client/visualizations/bar-chart/bar-chart.tsx +++ b/src/client/visualizations/bar-chart/bar-chart.tsx @@ -3,9 +3,26 @@ require('./bar-chart.css'); import { BaseVisualization, BaseVisualizationState } from '../base-visualization/base-visualization'; import * as React from 'react'; +import { List } from 'immutable'; import { generalEqual } from 'immutable-class'; -import { $, ply, r, Expression, Executor, Dataset, Datum, SortAction, PlywoodValue, Set, TimeRange } from 'plywood'; -import { Stage, Essence, DataSource, Filter, FilterClause, Splits, SplitCombine, Dimension, Measure, Colors, VisualizationProps, DatasetLoad, Resolve } from '../../../common/models/index'; +import { $, ply, r, Expression, Executor, Dataset, Datum, PseudoDatum, SortAction, PlywoodValue, Set, TimeRange } from 'plywood'; + +import { + Stage, + Essence, + DataSource, + Filter, + FilterClause, + Splits, + SplitCombine, + Dimension, + Measure, + Colors, + VisualizationProps, + DatasetLoad, + Resolve +} from '../../../common/models/index'; + import { SPLIT, VIS_H_PADDING } from '../../config/constants'; import { roundToPx, roundToHalfPx, classNames } from '../../utils/dom/dom'; import { VisMeasureLabel } from '../../components/vis-measure-label/vis-measure-label'; @@ -30,24 +47,47 @@ const SELECTION_PAD = 3.5; // Must be a x.5 const SELECTION_CORNERS = 2; const SELECTION_DASHARRAY = "3,3"; // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray -export interface HoverValue { - value: PlywoodValue; +export interface BubbleInfo { + x: number; + y: number; + segmentLabel: string; + measure: Measure; + chartIndex: number; + path: Datum[]; + splitIndex: number; +} + +export interface BarCoordinates { + x: number; + y: number; + height: number; + width: number; + + barOffset: number; + barWidth: number; + stepWidth: number; + children: BarCoordinates[]; } export interface BarChartState extends BaseVisualizationState { - hoverValue?: HoverValue; + hoverInfo?: BubbleInfo; + selectionInfo?: BubbleInfo; // Cached props xTicks?: PlywoodValue[]; - scaleX?: any; + scaleX?: d3.scale.Ordinal; } -function getFilterFromDatum(splits: Splits, datum: Datum, xField: string): Filter { - var segment = datum[xField]; - return Filter.fromClause(new FilterClause({ - expression: splits.get(0).expression, - selection: r(TimeRange.isTimeRange(segment) ? segment : Set.fromJS([segment])) - })); +function getFilterFromDatum(splits: Splits, dataPath: Datum[], dataSource: DataSource): Filter { + return new Filter(List(dataPath.map((datum, i) => { + var split = splits.get(i); + var segment: any = datum[split.getDimension(dataSource.dimensions).name]; + + return new FilterClause({ + expression: split.expression, + selection: r(TimeRange.isTimeRange(segment) ? segment : Set.fromJS([segment])) + }); + }))); } export class BarChart extends BaseVisualization { @@ -64,45 +104,45 @@ export class BarChart extends BaseVisualization { // Auto adjustment var autoChanged = false; - var split = splits.get(0); - var splitDimension = dataSource.getDimensionByExpression(split.expression); - - if (splitDimension.kind === 'boolean') { - booleanBoost = 2; - } - - if (!split.sortAction) { - // Must sort boolean in deciding order! - if (splitDimension.kind === 'boolean') { + splits = splits.map((split: SplitCombine) => { + var splitDimension = dataSource.getDimensionByExpression(split.expression); + + if (!split.sortAction) { + // Must sort boolean in deciding order! + if (splitDimension.kind === 'boolean') { + split = split.changeSortAction(new SortAction({ + expression: $(splitDimension.name), + direction: SortAction.DESCENDING + })); + } else { + split = split.changeSortAction(dataSource.getDefaultSortAction()); + } + autoChanged = true; + } else if (splitDimension.isContinuous() && split.sortAction.refName() !== splitDimension.name) { split = split.changeSortAction(new SortAction({ expression: $(splitDimension.name), - direction: SortAction.DESCENDING + direction: split.sortAction.direction })); - } else { - split = split.changeSortAction(dataSource.getDefaultSortAction()); + autoChanged = true; } - autoChanged = true; - } else if (splitDimension.isContinuous() && split.sortAction.refName() !== splitDimension.name) { - split = split.changeSortAction(new SortAction({ - expression: $(splitDimension.name), - direction: split.sortAction.direction - })); - autoChanged = true; - } - // ToDo: review this - if (!split.limitAction && (autoChanged || splitDimension.kind !== 'time')) { - split = split.changeLimit(25); - autoChanged = true; - } - if (colors) { - colors = null; - autoChanged = true; - } + // ToDo: review this + if (!split.limitAction && (autoChanged || splitDimension.kind !== 'time')) { + split = split.changeLimit(25); + autoChanged = true; + } + + if (colors) { + colors = null; + autoChanged = true; + } + + return split; + }); if (autoChanged) { - return Resolve.automatic(5 + booleanBoost, { splits: Splits.fromSplitCombine(split) }); + return Resolve.automatic(5 + booleanBoost, { splits }); } return Resolve.ready(current ? 10 : (7 + booleanBoost)); @@ -137,7 +177,7 @@ export class BarChart extends BaseVisualization { getDefaultState(): BarChartState { var s = super.getDefaultState() as BarChartState; - s.hoverValue = null; + s.hoverInfo = null; return s; } @@ -156,242 +196,325 @@ export class BarChart extends BaseVisualization { } } - onMouseEnter(measure: Measure, hoverValue: PlywoodValue, e: MouseEvent) { - this.setState({ - hoverValue: { value: hoverValue }, - hoverMeasure: measure - }); + onMouseEnter(hoverInfo: BubbleInfo) { + this.setState({hoverInfo}); } - onMouseLeave(measure: Measure, e: MouseEvent) { - const { hoverMeasure } = this.state; - if (hoverMeasure === measure) { - this.setState({ - hoverValue: null, - hoverMeasure: null - }); + onMouseLeave(targetHoverInfo: BubbleInfo) { + const { hoverInfo } = this.state; + if (hoverInfo && targetHoverInfo.measure === hoverInfo.measure) { + this.setState({hoverInfo: null}); } } - onClick(measure: Measure, datum: Datum, e: MouseEvent) { + onClick(measure: Measure, dataPath: Datum[], splitIndex: number, selectionInfo: BubbleInfo, e: MouseEvent) { const { essence, clicker } = this.props; const { splits, dataSource } = essence; - const dimension = splits.get(0).getDimension(dataSource.dimensions); - - var rowHighlight = getFilterFromDatum(splits, datum, dimension.name); + var rowHighlight = getFilterFromDatum(splits, dataPath, dataSource); if (essence.highlightOn(BarChart.id, measure.name)) { if (rowHighlight.equals(essence.highlight.delta)) { clicker.dropHighlight(); + this.setState({selectionInfo: null}); return; } } + this.setState({selectionInfo}); clicker.changeHighlight(BarChart.id, measure.name, rowHighlight); } - renderChartBubble(dataset: Dataset, measure: Measure, chartIndex: number, containerStage: Stage, chartStage: Stage, extentY: number[], scaleY: any): JSX.Element { - const { essence, clicker, openRawDataModal } = this.props; - const { scrollTop, hoverValue, hoverMeasure, scaleX } = this.state; + getYExtent(data: Datum[], measure: Measure): number[] { + var measureName = measure.name; + var getY = (d: Datum) => d[measureName] as number; + return d3.extent(data, getY); + } + + getYScale(dataset: Dataset, measure: Measure, stage: Stage): d3.scale.Linear { + var { essence } = this.props; + + var splitLength = essence.splits.length(); + var leafData = dataset.flatten({ + order: 'preorder', + nestingName: '__nest', + parentName: '__parent' + }).filter((d: Datum) => d['__nest'] === splitLength - 1); + + var extentY = this.getYExtent(leafData, measure); + + return d3.scale.linear() + .domain([Math.min(extentY[0] * 1.1, 0), Math.max(extentY[1] * 1.1, 0)]) + .range([stage.height, 0]); + } + + hasValidYExtent(measure: Measure, data: Datum[]): boolean { + let [yMin, yMax] = this.getYExtent(data, measure); + return !isNaN(yMin) && !isNaN(yMax); + } + + getStages(chartStage: Stage): {barStage: Stage, xAxisStage: Stage, yAxisStage: Stage} { + var barStage = chartStage.within({ top: TEXT_SPACER, right: Y_AXIS_WIDTH, bottom: X_AXIS_HEIGHT }); + var xAxisStage = chartStage.within({ right: Y_AXIS_WIDTH, top: TEXT_SPACER + barStage.height }); + var yAxisStage = chartStage.within({ top: TEXT_SPACER, left: barStage.width }); + + return { barStage, xAxisStage, yAxisStage }; + } + + getBarDimensions(xRangeBand: number): {stepWidth: number, barWidth: number, barOffset: number} { + var stepWidth = xRangeBand; + var barWidth = Math.max(stepWidth * BAR_PROPORTION, 0); + var barOffset = (stepWidth - barWidth) / 2; + + return { stepWidth, barWidth, barOffset }; + } + + renderSelectionBubble(hoverInfo: BubbleInfo): JSX.Element { + const { essence, stage, clicker, openRawDataModal } = this.props; + const { scrollTop } = this.state; + const chartStage = this.getChartStage(); + const { measure, path, segmentLabel, chartIndex } = hoverInfo; + const { splits, dataSource } = essence; - const dimension = splits.get(0).getDimension(dataSource.dimensions); + const dimension = splits.get(hoverInfo.splitIndex).getDimension(dataSource.dimensions); - var stepWidth = scaleX.rangeBand(); + const leftOffset = stage.x + VIS_H_PADDING + hoverInfo.x; + const topOffset = chartStage.height * chartIndex - scrollTop + hoverInfo.y + TEXT_SPACER - HOVER_BUBBLE_V_OFFSET; - if (essence.highlightOnDifferentMeasure(BarChart.id, measure.name)) return null; + if (topOffset <= 0) return null; - if (essence.highlightOn(BarChart.id, measure.name)) { - var bubbleHighlightDelta = essence.highlight.delta; - var highlightDatum = dataset.data.filter((d) => bubbleHighlightDelta.equals(getFilterFromDatum(splits, d, dimension.name)))[0]; - if (!highlightDatum) return null; - - var leftOffset = containerStage.x + VIS_H_PADDING + scaleX(highlightDatum[dimension.name]) + stepWidth / 2; - var topOffset = chartStage.height * chartIndex - scrollTop + scaleY(highlightDatum[measure.name]) + TEXT_SPACER - HOVER_BUBBLE_V_OFFSET; - if (topOffset > 0) { - return ; - } - } else if (hoverValue && hoverMeasure === measure) { - var hoverDatum = dataset.findDatumByAttribute(dimension.name, hoverValue.value); - var leftOffset = containerStage.x + VIS_H_PADDING + scaleX(hoverValue.value) + stepWidth / 2; - var topOffset = chartStage.height * chartIndex - scrollTop + scaleY(hoverDatum[measure.name]) + TEXT_SPACER - HOVER_BUBBLE_V_OFFSET; - if (topOffset > 0) { - return ; - } + return ; + } + + renderHoverBubble(hoverInfo: BubbleInfo): JSX.Element { + const { stage } = this.props; + const { scrollTop } = this.state; + const chartStage = this.getChartStage(); + const { measure, path, segmentLabel, chartIndex } = hoverInfo; + + const leftOffset = stage.x + VIS_H_PADDING + hoverInfo.x; + const topOffset = chartStage.height * chartIndex - scrollTop + hoverInfo.y + TEXT_SPACER - HOVER_BUBBLE_V_OFFSET; + + if (topOffset <= 0) return null; + + return ; + } + + + isSelected(path: Datum[], measure: Measure): boolean { + const { essence } = this.props; + const { splits, dataSource } = essence; + + if (essence.highlightOnDifferentMeasure(BarChart.id, measure.name)) return false; + + if (essence.highlightOn(BarChart.id, measure.name)) { + return essence.highlight.delta.equals(getFilterFromDatum(splits, path, dataSource)); } - return null; + return false; } - renderChart(dataset: Dataset, measure: Measure, chartIndex: number, containerStage: Stage, chartStage: Stage, getX: any): JSX.Element { + hasAnySelectionGoingOn(): boolean { + return this.props.essence.highlightOn(BarChart.id); + } + + isHovered(path: Datum[], measure: Measure): boolean { const { essence } = this.props; - const { hoverValue, hoverMeasure, xTicks, scaleX } = this.state; + const { hoverInfo } = this.state; const { splits, dataSource } = essence; - const dimension = splits.get(0).getDimension(dataSource.dimensions); - var myDatum: Datum = dataset.data[0]; - var mySplitDataset = myDatum[SPLIT] as Dataset; + if (this.hasAnySelectionGoingOn()) return false; + if (!hoverInfo) return false; + if (hoverInfo.measure !== measure) return false; - var barStage = chartStage.within({ top: TEXT_SPACER, right: Y_AXIS_WIDTH, bottom: X_AXIS_HEIGHT }); - var xAxisStage = chartStage.within({ right: Y_AXIS_WIDTH, top: TEXT_SPACER + barStage.height }); - var yAxisStage = chartStage.within({ top: TEXT_SPACER, left: barStage.width }); + const filter = (p: Datum[]) => getFilterFromDatum(splits, p, dataSource); - var measureName = measure.name; - var getY = (d: Datum) => d[measureName] as number; + return filter(hoverInfo.path).equals(filter(path)); + } - var borderHighlightDelta: Filter = null; - var bubbleHighlightDelta: Filter = null; - if (essence.highlightOn(BarChart.id)) { - borderHighlightDelta = essence.highlight.delta; - if (essence.highlightOn(BarChart.id, measureName)) { - bubbleHighlightDelta = borderHighlightDelta; - } - } + renderBars( + data: Datum[], + measure: Measure, + chartIndex: number, + barStage: Stage, + xAxisStage: Stage, + coordinates: BarCoordinates[], + splitIndex = 0, + path: Datum[] = [] + ): {bars: JSX.Element[], labels: JSX.Element[], bubble?: JSX.Element} { + const { essence } = this.props; + const { selectionInfo } = this.state; - var extentY = d3.extent(mySplitDataset.data, getY); - - var stepWidth = scaleX.rangeBand(); - - var bubble: JSX.Element; - var horizontalGridLines: JSX.Element; - var bars: JSX.Element[]; - var barHighlight: JSX.Element; - var barGhosts: JSX.Element[]; - var slantyLabels: JSX.Element[]; - var verticalAxis: JSX.Element; - if (!isNaN(extentY[0]) && !isNaN(extentY[1])) { - var scaleY = d3.scale.linear() - .domain([Math.min(extentY[0] * 1.1, 0), Math.max(extentY[1] * 1.1, 0)]) - .range([barStage.height, 0]); - - var yTicks = scaleY.ticks(5).filter((n: number) => n !== 0); - - horizontalGridLines = ; - - verticalAxis = ; - - var barWidth = Math.max(stepWidth * BAR_PROPORTION, 0); - var barOffset = (stepWidth - barWidth) / 2; - bars = []; - barGhosts = []; - slantyLabels = []; - var scaleY0 = scaleY(0); - mySplitDataset.data.forEach((d) => { - var segmentValue = d[dimension.name]; - var segmentValueStr = String(segmentValue); - var x = scaleX(getX(d)); - var y = scaleY(getY(d)); - var onMouseEnter = this.onMouseEnter.bind(this, measure, segmentValue); - var onMouseLeave = this.onMouseLeave.bind(this, measure); - - if (barStage.width < x) return; - var hover: boolean; - if (bubbleHighlightDelta) { - hover = false; - } else { - hover = hoverMeasure === measure && hoverValue && generalEqual(hoverValue.value, segmentValue); - } + var bars: JSX.Element[] = []; + var labels: JSX.Element[] = []; - var selected: boolean; - var selectedClass: string; - if (borderHighlightDelta) { - selected = borderHighlightDelta.equals(getFilterFromDatum(splits, d, dimension.name)); - selectedClass = selected ? 'selected' : 'not-selected'; - } + const dimension = essence.splits.get(splitIndex).getDimension(essence.dataSource.dimensions); - var h = scaleY0 - y; - bars.push(= 0 ? y : scaleY0)} - width={roundToPx(barWidth)} - height={roundToPx(Math.abs(h))} - />); + data.forEach((d, i) => { + let segmentValue = d[dimension.name]; + let segmentValueStr = String(segmentValue); + let subPath = path.concat(d); + + let bar: JSX.Element; + let highlight: JSX.Element = null; + let bubble: JSX.Element = null; + let subCoordinates = coordinates[i]; + let { x, y, height, width, stepWidth, barWidth, barOffset } = coordinates[i]; + + + if (splitIndex < essence.splits.length() - 1) { + let subData: Datum[] = (d[SPLIT] as Dataset).data; + let result: any = this.renderBars(subData, measure, chartIndex, barStage, xAxisStage, subCoordinates.children, splitIndex + 1, subPath); + + bar = result.bars; + } else { + let bubbleInfo: BubbleInfo = { + x: x + stepWidth / 2, + y, + segmentLabel: segmentValueStr, + measure, + chartIndex, + path: subPath, + splitIndex + }; + + let isHovered = this.isHovered(subPath, measure); + if (isHovered) { + bubble = this.renderHoverBubble(bubbleInfo); + } + + let selected = this.isSelected(subPath, measure); if (selected) { - barHighlight = ; } - barGhosts.push(); - - slantyLabels.push(
+ + + {highlight} + {bubble} + ; + + } + + bars.push(bar); + + if (splitIndex === 0) { + labels.push(
{segmentValueStr}
); - }); + } + }); + + return { bars, labels }; + } + + getYAxisStuff(dataset: Dataset, measure: Measure, chartStage: Stage, chartIndex: number): { + yGridLines: JSX.Element, yAxis: JSX.Element, yScale: d3.scale.Linear + } { + var { barStage, yAxisStage } = this.getStages(chartStage); + + var yScale = this.getYScale(dataset, measure, barStage); + var yTicks = yScale.ticks(5).filter((n: number) => n !== 0); + + var yGridLines: JSX.Element = ; + + var yAxis: JSX.Element = ; + + return { yGridLines, yAxis, yScale }; + } + + renderChart(dataset: Dataset, coordinates: BarCoordinates[], measure: Measure, chartIndex: number, containerStage: Stage, chartStage: Stage, getX: any): JSX.Element { + const { xTicks, scaleX } = this.state; + var mySplitDataset = dataset.data[0][SPLIT] as Dataset; - bubble = this.renderChartBubble(mySplitDataset, measure, chartIndex, containerStage, chartStage, extentY, scaleY); + // Invalid data, early return + if (!this.hasValidYExtent(measure, mySplitDataset.data)) { + return
+ + +
; } - return
+ let { barStage, xAxisStage, yAxisStage } = this.getStages(chartStage); + + var { yAxis, yGridLines } = this.getYAxisStuff(mySplitDataset, measure, chartStage, chartIndex); + + var { bars, labels } = this.renderBars(mySplitDataset.data, measure, chartIndex, barStage, xAxisStage, coordinates); + + return
- {horizontalGridLines} + {yGridLines} {bars} - {barGhosts} - {verticalAxis} + {yAxis} - {barHighlight} -
{slantyLabels}
- - {bubble} +
{labels}
+
; } @@ -417,14 +540,13 @@ export class BarChart extends BaseVisualization { var getX = (d: Datum) => d[dimension.name] as string; - var myDatum: Datum = dataset.data[0]; - var mySplitDataset = myDatum[SPLIT] as Dataset; + var mySplitDataset = dataset.data[0][SPLIT] as Dataset; var xTicks = mySplitDataset.data.map(getX); var numSteps = xTicks.length; var overallWidth = stage.width - VIS_H_PADDING * 2 - Y_AXIS_WIDTH; var maxAvailableWidth = overallWidth - BARS_MIN_PAD_LEFT - BARS_MIN_PAD_RIGHT; - var stepWidth = Math.max(Math.min(maxAvailableWidth / numSteps, MAX_STEP_WIDTH), MIN_STEP_WIDTH); + var stepWidth = Math.max(Math.min(maxAvailableWidth / numSteps, MAX_STEP_WIDTH * essence.splits.length()), MIN_STEP_WIDTH); var usedWidth = stepWidth * numSteps; var padLeft = Math.max(BARS_MIN_PAD_LEFT, (overallWidth - usedWidth) / 2); @@ -437,38 +559,116 @@ export class BarChart extends BaseVisualization { this.setState(newState); } + getBarsCoordinates(dataset: Dataset, measure: Measure, chartIndex: number, scaleX: d3.scale.Ordinal): BarCoordinates[] { + const { essence } = this.props; + const { splits, dataSource} = essence; + const dimension = splits.get(0).getDimension(dataSource.dimensions); + + var chartStage = this.getChartStage(); + let { barStage, xAxisStage } = this.getStages(chartStage); + var { yScale } = this.getYAxisStuff(dataset, measure, chartStage, chartIndex); + + return this.getSubCoordinates( + dataset.data, + measure, + barStage, + xAxisStage, + (d: Datum) => d[dimension.name] as string, + scaleX, + yScale + ); + } + + getSubCoordinates( + data: Datum[], + measure: Measure, + barStage: Stage, + xAxisStage: Stage, + getX: (d: Datum, i: number) => string, + scaleX: d3.scale.Ordinal, + scaleY: d3.scale.Linear, + splitIndex = 0 + ): BarCoordinates[] { + const { essence } = this.props; + + var { stepWidth, barWidth, barOffset } = this.getBarDimensions(scaleX.rangeBand()); + + var coordinates: BarCoordinates[] = data.map((d, i) => { + let x = scaleX(getX(d, i)); + let y = scaleY(d[measure.name] as number); + let h = scaleY(0) - y; + var children: BarCoordinates[] = []; + var coordinate = { + x, + y: h >= 0 ? y : scaleY(0), + width: roundToPx(barWidth), + height: roundToPx(Math.abs(h)), + stepWidth, + barWidth, + barOffset, + children + }; + + if (splitIndex < essence.splits.length() - 1) { + let subStage: Stage = new Stage({x: x, y: barStage.y, width: barWidth, height: barStage.height}); + let subSplit: SplitCombine = essence.splits.get(splitIndex + 1); + let subGetX: any = (d: Datum, i: number) => String(i); + let subData: Datum[] = (d[SPLIT] as Dataset).data; + let subScaleX = d3.scale.ordinal() + .domain(d3.range(0, subSplit.limitAction.limit).map(String)) + .rangeBands([x + barOffset, x + subStage.width]); + + coordinate.children = this.getSubCoordinates(subData, measure, subStage, xAxisStage, subGetX, subScaleX, scaleY, splitIndex + 1); + } + + return coordinate; + }); + + return coordinates; + } + + getChartStage(): Stage { + var { essence, stage } = this.props; + var measures = essence.getEffectiveMeasures().toArray(); + + var parentWidth = stage.width - VIS_H_PADDING * 2; + var chartHeight = Math.max(MIN_CHART_HEIGHT, Math.floor(stage.height / measures.length)); + return new Stage({ + x: VIS_H_PADDING, + y: 0, + width: parentWidth, + height: chartHeight + }); + } + renderInternals() { var { essence, stage } = this.props; - var { datasetLoad } = this.state; + var { datasetLoad, scaleX } = this.state; var { splits, dataSource } = essence; const dimension = splits.get(0).getDimension(dataSource.dimensions); var measureCharts: JSX.Element[]; if (datasetLoad.dataset && splits.length()) { - var measures = essence.getEffectiveMeasures().toArray(); + let measures = essence.getEffectiveMeasures().toArray(); - var getX = (d: Datum) => d[dimension.name] as string; + let getX = (d: Datum) => d[dimension.name] as string; - var parentWidth = stage.width - VIS_H_PADDING * 2; - var chartHeight = Math.max(MIN_CHART_HEIGHT, Math.floor(stage.height / measures.length)); - var chartStage = new Stage({ - x: VIS_H_PADDING, - y: 0, - width: parentWidth, - height: chartHeight - }); + let chartStage = this.getChartStage(); measureCharts = measures.map((measure, chartIndex) => { - return this.renderChart(datasetLoad.dataset, measure, chartIndex, stage, chartStage, getX); + let mySplitDataset = datasetLoad.dataset.data[0][SPLIT] as Dataset; + let coordinates = this.getBarsCoordinates(mySplitDataset, measure, chartIndex, scaleX); + + return this.renderChart(datasetLoad.dataset, coordinates, measure, chartIndex, stage, chartStage, getX); }); } - var measureChartsStyle = { - maxHeight: stage.height - }; - - return
+ return
{measureCharts}
; } diff --git a/src/client/visualizations/base-visualization/base-visualization.tsx b/src/client/visualizations/base-visualization/base-visualization.tsx index b4707d83..63a95f9d 100644 --- a/src/client/visualizations/base-visualization/base-visualization.tsx +++ b/src/client/visualizations/base-visualization/base-visualization.tsx @@ -151,7 +151,7 @@ export class BaseVisualization error }); } - ); + ).done(); // Not calling done() prevents potential error from being bubbled up } private lastRenderResult: JSX.Element = null;