Skip to content

Commit

Permalink
Stacked Bar: Summed Labels (#3167)
Browse files Browse the repository at this point in the history
  • Loading branch information
CalvinFernandez authored Jan 19, 2017
1 parent f8945f0 commit 5606203
Show file tree
Hide file tree
Showing 8 changed files with 403 additions and 15 deletions.
30 changes: 26 additions & 4 deletions plottable-npm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,12 @@ declare namespace Plottable.Utils.Stacking {
value: number;
offset: number;
};
type StackingResult = Utils.Map<Dataset, Utils.Map<string, StackedDatum>>;
type GenericStackingResult<D> = Utils.Map<Dataset, Utils.Map<D, StackedDatum>>;
/**
* Map of Dataset to stacks.
* @deprecated
*/
type StackingResult = GenericStackingResult<string>;
/**
* Computes the StackingResult (value and offset) for each data point in each Dataset.
*
Expand All @@ -305,11 +310,22 @@ declare namespace Plottable.Utils.Stacking {
* @return {StackingResult} value and offset for each datapoint in each Dataset
*/
function stack(datasets: Dataset[], keyAccessor: Accessor<any>, valueAccessor: Accessor<number>): StackingResult;
/**
* Computes the maximum and minimum extents of each stack individually.
*
* @param {GenericStackingResult} stackingResult The value and offset information for each datapoint in each dataset
* @return { { maximumExtents: Utils.Map<D, number>, minimumExtents: Utils.Map<D, number> } } The maximum and minimum extents
* of each individual stack.
*/
function stackedExtents<D>(stackingResult: GenericStackingResult<D>): {
maximumExtents: Map<D, number>;
minimumExtents: Map<D, number>;
};
/**
* Computes the total extent over all data points in all Datasets, taking stacking into consideration.
*
* @param {StackingResult} stackingResult The value and offset information for each datapoint in each dataset
* @oaram {Accessor<any>} keyAccessor Accessor for the key of the data existent in the stackingResult
* @param {Accessor<any>} keyAccessor Accessor for the key of the data existent in the stackingResult
* @param {Accessor<boolean>} filter A filter for data to be considered when computing the total extent
* @return {[number, number]} The total extent
*/
Expand Down Expand Up @@ -3715,9 +3731,9 @@ declare namespace Plottable.Plots {
private static _BAR_WIDTH_RATIO;
private static _SINGLE_BAR_DIMENSION_RATIO;
private static _BAR_AREA_CLASS;
private static _LABEL_AREA_CLASS;
private static _LABEL_VERTICAL_PADDING;
private static _LABEL_HORIZONTAL_PADDING;
protected static _LABEL_AREA_CLASS: string;
private _baseline;
private _baselineValue;
protected _isVertical: boolean;
Expand Down Expand Up @@ -3838,7 +3854,7 @@ declare namespace Plottable.Plots {
* Makes sure the extent takes into account the widths of the bars
*/
protected _extentsForProperty(property: string): any[];
private _drawLabels();
protected _drawLabels(): void;
private _drawLabel(data, dataset);
protected _generateDrawSteps(): Drawers.DrawStep[];
protected _generateAttrToProjector(): {
Expand Down Expand Up @@ -4099,6 +4115,10 @@ declare namespace Plottable.Plots {
}
declare namespace Plottable.Plots {
class StackedBar<X, Y> extends Bar<X, Y> {
protected static _STACKED_BAR_LABEL_PADDING: number;
private _labelArea;
private _measurer;
private _writer;
private _stackingResult;
private _stackedExtent;
/**
Expand All @@ -4118,6 +4138,8 @@ declare namespace Plottable.Plots {
y(): Plots.TransformableAccessorScaleBinding<Y, number>;
y(y: number | Accessor<number>): this;
y(y: Y | Accessor<Y>, yScale: Scale<Y, number>): this;
protected _setup(): void;
protected _drawLabels(): void;
protected _generateAttrToProjector(): {
[attr: string]: Projector;
};
Expand Down
8 changes: 7 additions & 1 deletion plottable.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ svg.plottable {

.plottable .bar-label-text-area text {
font-family: "Helvetica Neue", sans-serif;
font-size: 14px;
font-size: 12px;
font-style: italic;
}

.plottable .label-area text {
Expand All @@ -83,6 +84,11 @@ svg.plottable {
fill: #32313F;
}

.plottable .stacked-bar-label text {
fill: #32313F;
font-style: normal;
}

.plottable .stacked-bar-plot .off-bar-label {
/* HACKHACK #2795: correct off-bar label logic to be implemented on StackedBar */
visibility: hidden !important;
Expand Down
30 changes: 26 additions & 4 deletions plottable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,12 @@ declare namespace Plottable.Utils.Stacking {
value: number;
offset: number;
};
type StackingResult = Utils.Map<Dataset, Utils.Map<string, StackedDatum>>;
type GenericStackingResult<D> = Utils.Map<Dataset, Utils.Map<D, StackedDatum>>;
/**
* Map of Dataset to stacks.
* @deprecated
*/
type StackingResult = GenericStackingResult<string>;
/**
* Computes the StackingResult (value and offset) for each data point in each Dataset.
*
Expand All @@ -304,11 +309,22 @@ declare namespace Plottable.Utils.Stacking {
* @return {StackingResult} value and offset for each datapoint in each Dataset
*/
function stack(datasets: Dataset[], keyAccessor: Accessor<any>, valueAccessor: Accessor<number>): StackingResult;
/**
* Computes the maximum and minimum extents of each stack individually.
*
* @param {GenericStackingResult} stackingResult The value and offset information for each datapoint in each dataset
* @return { { maximumExtents: Utils.Map<D, number>, minimumExtents: Utils.Map<D, number> } } The maximum and minimum extents
* of each individual stack.
*/
function stackedExtents<D>(stackingResult: GenericStackingResult<D>): {
maximumExtents: Map<D, number>;
minimumExtents: Map<D, number>;
};
/**
* Computes the total extent over all data points in all Datasets, taking stacking into consideration.
*
* @param {StackingResult} stackingResult The value and offset information for each datapoint in each dataset
* @oaram {Accessor<any>} keyAccessor Accessor for the key of the data existent in the stackingResult
* @param {Accessor<any>} keyAccessor Accessor for the key of the data existent in the stackingResult
* @param {Accessor<boolean>} filter A filter for data to be considered when computing the total extent
* @return {[number, number]} The total extent
*/
Expand Down Expand Up @@ -3714,9 +3730,9 @@ declare namespace Plottable.Plots {
private static _BAR_WIDTH_RATIO;
private static _SINGLE_BAR_DIMENSION_RATIO;
private static _BAR_AREA_CLASS;
private static _LABEL_AREA_CLASS;
private static _LABEL_VERTICAL_PADDING;
private static _LABEL_HORIZONTAL_PADDING;
protected static _LABEL_AREA_CLASS: string;
private _baseline;
private _baselineValue;
protected _isVertical: boolean;
Expand Down Expand Up @@ -3837,7 +3853,7 @@ declare namespace Plottable.Plots {
* Makes sure the extent takes into account the widths of the bars
*/
protected _extentsForProperty(property: string): any[];
private _drawLabels();
protected _drawLabels(): void;
private _drawLabel(data, dataset);
protected _generateDrawSteps(): Drawers.DrawStep[];
protected _generateAttrToProjector(): {
Expand Down Expand Up @@ -4098,6 +4114,10 @@ declare namespace Plottable.Plots {
}
declare namespace Plottable.Plots {
class StackedBar<X, Y> extends Bar<X, Y> {
protected static _STACKED_BAR_LABEL_PADDING: number;
private _labelArea;
private _measurer;
private _writer;
private _stackingResult;
private _stackedExtent;
/**
Expand All @@ -4117,6 +4137,8 @@ declare namespace Plottable.Plots {
y(): Plots.TransformableAccessorScaleBinding<Y, number>;
y(y: number | Accessor<number>): this;
y(y: Y | Accessor<Y>, yScale: Scale<Y, number>): this;
protected _setup(): void;
protected _drawLabels(): void;
protected _generateAttrToProjector(): {
[attr: string]: Projector;
};
Expand Down
106 changes: 104 additions & 2 deletions plottable.js
Original file line number Diff line number Diff line change
Expand Up @@ -739,11 +739,43 @@ var Plottable;
return datasetToKeyToStackedDatum;
}
Stacking.stack = stack;
/**
* Computes the maximum and minimum extents of each stack individually.
*
* @param {GenericStackingResult} stackingResult The value and offset information for each datapoint in each dataset
* @return { { maximumExtents: Utils.Map<D, number>, minimumExtents: Utils.Map<D, number> } } The maximum and minimum extents
* of each individual stack.
*/
function stackedExtents(stackingResult) {
var maximumExtents = new Utils.Map();
var minimumExtents = new Utils.Map();
stackingResult.forEach(function (stack) {
stack.forEach(function (datum, key) {
// correctly handle negative bar stacks
var maximalValue = Utils.Math.max([datum.offset + datum.value, datum.offset], datum.offset);
var minimalValue = Utils.Math.min([datum.offset + datum.value, datum.offset], datum.offset);
if (!maximumExtents.has(key)) {
maximumExtents.set(key, maximalValue);
}
else if (maximumExtents.get(key) < maximalValue) {
maximumExtents.set(key, maximalValue);
}
if (!minimumExtents.has(key)) {
minimumExtents.set(key, minimalValue);
}
else if (minimumExtents.get(key) > (minimalValue)) {
minimumExtents.set(key, minimalValue);
}
});
});
return { maximumExtents: maximumExtents, minimumExtents: minimumExtents };
}
Stacking.stackedExtents = stackedExtents;
/**
* Computes the total extent over all data points in all Datasets, taking stacking into consideration.
*
* @param {StackingResult} stackingResult The value and offset information for each datapoint in each dataset
* @oaram {Accessor<any>} keyAccessor Accessor for the key of the data existent in the stackingResult
* @param {Accessor<any>} keyAccessor Accessor for the key of the data existent in the stackingResult
* @param {Accessor<boolean>} filter A filter for data to be considered when computing the total extent
* @return {[number, number]} The total extent
*/
Expand Down Expand Up @@ -9354,9 +9386,9 @@ var Plottable;
Bar._BAR_WIDTH_RATIO = 0.95;
Bar._SINGLE_BAR_DIMENSION_RATIO = 0.4;
Bar._BAR_AREA_CLASS = "bar-area";
Bar._LABEL_AREA_CLASS = "bar-label-text-area";
Bar._LABEL_VERTICAL_PADDING = 5;
Bar._LABEL_HORIZONTAL_PADDING = 5;
Bar._LABEL_AREA_CLASS = "bar-label-text-area";
return Bar;
}(Plottable.XYPlot));
Plots.Bar = Bar;
Expand Down Expand Up @@ -10243,6 +10275,75 @@ var Plottable;
this._updateStackExtentsAndOffsets();
return this;
};
StackedBar.prototype._setup = function () {
_super.prototype._setup.call(this);
this._labelArea = this._renderArea.append("g").classed(Plots.Bar._LABEL_AREA_CLASS, true);
this._measurer = new SVGTypewriter.Measurers.CacheCharacterMeasurer(this._labelArea);
this._writer = new SVGTypewriter.Writers.Writer(this._measurer);
};
StackedBar.prototype._drawLabels = function () {
var _this = this;
_super.prototype._drawLabels.call(this);
// remove all current labels before redrawing
this._labelArea.selectAll("g").remove();
var baselineValue = +this.baselineValue();
var primaryScale = this._isVertical ? this.x().scale : this.y().scale;
var secondaryScale = this._isVertical ? this.y().scale : this.x().scale;
var _a = Plottable.Utils.Stacking.stackedExtents(this._stackingResult), maximumExtents = _a.maximumExtents, minimumExtents = _a.minimumExtents;
var barWidth = this._getBarPixelWidth();
var drawLabel = function (text, measurement, labelPosition) {
var x = labelPosition.x, y = labelPosition.y;
var height = measurement.height, width = measurement.width;
var tooWide = _this._isVertical ? (width > barWidth) : (height > barWidth);
var hideLabel = x < 0
|| y < 0
|| x + width > _this.width()
|| y + height > _this.height()
|| tooWide;
if (!hideLabel) {
var labelContainer = _this._labelArea.append("g").attr("transform", "translate(" + x + ", " + y + ")");
labelContainer.classed("stacked-bar-label", true);
var writeOptions = {
selection: labelContainer,
xAlign: "center",
yAlign: "center",
textRotation: 0,
};
_this._writer.write(text, measurement.width, measurement.height, writeOptions);
}
};
maximumExtents.forEach(function (maximum, axisValue) {
if (maximum !== baselineValue) {
// only draw sums for values not at the baseline
var text = _this.labelFormatter()(maximum);
var measurement = _this._measurer.measure(text);
var primaryTextMeasurement = _this._isVertical ? measurement.width : measurement.height;
var secondaryTextMeasurement = _this._isVertical ? measurement.height : measurement.width;
var x = _this._isVertical
? primaryScale.scale(axisValue) - primaryTextMeasurement / 2
: secondaryScale.scale(maximum) + StackedBar._STACKED_BAR_LABEL_PADDING;
var y = _this._isVertical
? secondaryScale.scale(maximum) - secondaryTextMeasurement - StackedBar._STACKED_BAR_LABEL_PADDING
: primaryScale.scale(axisValue) - primaryTextMeasurement / 2;
drawLabel(text, measurement, { x: x, y: y });
}
});
minimumExtents.forEach(function (minimum, axisValue) {
if (minimum !== baselineValue) {
var text = _this.labelFormatter()(minimum);
var measurement = _this._measurer.measure(text);
var primaryTextMeasurement = _this._isVertical ? measurement.width : measurement.height;
var secondaryTextMeasurement = _this._isVertical ? measurement.height : measurement.width;
var x = _this._isVertical
? primaryScale.scale(axisValue) - primaryTextMeasurement / 2
: secondaryScale.scale(minimum) - secondaryTextMeasurement - StackedBar._STACKED_BAR_LABEL_PADDING;
var y = _this._isVertical
? secondaryScale.scale(minimum) + StackedBar._STACKED_BAR_LABEL_PADDING
: primaryScale.scale(axisValue) - primaryTextMeasurement / 2;
drawLabel(text, measurement, { x: x, y: y });
}
});
};
StackedBar.prototype._generateAttrToProjector = function () {
var _this = this;
var attrToProjector = _super.prototype._generateAttrToProjector.call(this);
Expand Down Expand Up @@ -10304,6 +10405,7 @@ var Plottable;
this._stackingResult = Plottable.Utils.Stacking.stack(datasets, keyAccessor, valueAccessor);
this._stackedExtent = Plottable.Utils.Stacking.stackedExtent(this._stackingResult, keyAccessor, filter);
};
StackedBar._STACKED_BAR_LABEL_PADDING = 5;
return StackedBar;
}(Plots.Bar));
Plots.StackedBar = StackedBar;
Expand Down
5 changes: 3 additions & 2 deletions src/plots/barPlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ namespace Plottable.Plots {
private static _BAR_WIDTH_RATIO = 0.95;
private static _SINGLE_BAR_DIMENSION_RATIO = 0.4;
private static _BAR_AREA_CLASS = "bar-area";
private static _LABEL_AREA_CLASS = "bar-label-text-area";
private static _LABEL_VERTICAL_PADDING = 5;
private static _LABEL_HORIZONTAL_PADDING = 5;
protected static _LABEL_AREA_CLASS = "bar-label-text-area";

private _baseline: d3.Selection<void>;
private _baselineValue: X|Y;
protected _isVertical: boolean;
Expand Down Expand Up @@ -444,7 +445,7 @@ namespace Plottable.Plots {
return extents;
}

private _drawLabels() {
protected _drawLabels() {
let dataToDraw = this._getDataToDraw();
let labelsTooWide = false;
this.datasets().forEach((dataset) => labelsTooWide = labelsTooWide || this._drawLabel(dataToDraw.get(dataset), dataset));
Expand Down
Loading

0 comments on commit 5606203

Please sign in to comment.