lucid-ui
Version:
A UI component library from Xandr.
349 lines (345 loc) • 16.6 kB
JavaScript
;
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PureToolTip = exports.Bars = void 0;
var lodash_1 = __importDefault(require("lodash"));
var react_1 = __importStar(require("react"));
var prop_types_1 = __importDefault(require("prop-types"));
var d3Scale = __importStar(require("d3-scale"));
var style_helpers_1 = require("../../util/style-helpers");
var chart_helpers_1 = require("../../util/chart-helpers");
var chartConstants = __importStar(require("../../constants/charts"));
var Bar_1 = __importDefault(require("../Bar/Bar"));
var ToolTip_1 = require("../ToolTip/ToolTip");
var Legend_1 = __importDefault(require("../Legend/Legend"));
// memoizing to maintain referential equality across renders, for performance
// optimization with shallow comparison
var memoizedExtractFields = lodash_1.default.memoize(chart_helpers_1.extractFields);
var memoizedStackByFields = lodash_1.default.memoize(chart_helpers_1.stackByFields);
var cx = style_helpers_1.lucidClassNames.bind('&-Bars');
var arrayOf = prop_types_1.default.arrayOf, func = prop_types_1.default.func, number = prop_types_1.default.number, object = prop_types_1.default.object, bool = prop_types_1.default.bool, string = prop_types_1.default.string;
/** TODO: Remove this constant when the component is converted to a functional component */
var nonPassThroughs = [
'className',
'data',
'legend',
'hasToolTips',
'palette',
'colorMap',
'xScale',
'xField',
'xFormatter',
'yScale',
'yFields',
'yFormatter',
'yStackedMax',
'yTooltipFormatter',
'isStacked',
'colorOffset',
'renderTooltipBody',
];
var Bars = /** @class */ (function (_super) {
__extends(Bars, _super);
function Bars() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.defaultTooltipFormatter = function (dataPoint) {
var _a = _this.props, colorMap = _a.colorMap, colorOffset = _a.colorOffset, isStacked = _a.isStacked, legend = _a.legend, palette = _a.palette, yFields = _a.yFields, yFormatter = _a.yFormatter, yTooltipFormatter = _a.yTooltipFormatter;
return (react_1.default.createElement(Legend_1.default, { hasBorders: false, isReversed: isStacked }, lodash_1.default.map(yFields, function (field, fieldIndex) { return (react_1.default.createElement(Legend_1.default.Item, { key: fieldIndex, hasPoint: true, pointKind: 1, color: lodash_1.default.get(colorMap, field, palette[(fieldIndex + colorOffset) % palette.length]) }, yTooltipFormatter(lodash_1.default.get(legend, field, field), yFormatter(dataPoint[field], dataPoint), dataPoint[field]))); })));
};
_this.handleMouseEnter = function (hoveringSeriesIndex) {
_this.setState({
hoveringSeriesIndex: hoveringSeriesIndex,
});
};
_this.handleMouseOut = function () {
_this.setState({ hoveringSeriesIndex: null });
};
_this.state = {
hoveringSeriesIndex: null,
};
return _this;
}
Bars.prototype.render = function () {
var _this = this;
var _a = this.props, className = _a.className, data = _a.data, hasToolTips = _a.hasToolTips, palette = _a.palette, colorMap = _a.colorMap, colorOffset = _a.colorOffset, xScale = _a.xScale, xField = _a.xField, xFormatter = _a.xFormatter, yScaleOriginal = _a.yScale, yFields = _a.yFields, yStackedMax = _a.yStackedMax, renderTooltipBody = _a.renderTooltipBody, isStacked = _a.isStacked, passThroughs = __rest(_a, ["className", "data", "hasToolTips", "palette", "colorMap", "colorOffset", "xScale", "xField", "xFormatter", "yScale", "yFields", "yStackedMax", "renderTooltipBody", "isStacked"]);
var hoveringSeriesIndex = this.state.hoveringSeriesIndex;
// This scale is used for grouped bars
var innerXScale = d3Scale
.scaleBand()
.domain(lodash_1.default.times(yFields.length, function (num) { return "".concat(num); }))
.range([0, xScale.bandwidth()])
.round(true);
// Copy the original so we can mutate it
var yScale = yScaleOriginal.copy();
// If we are stacked, we need to calculate a new domain based on the sum of
// the various series' y data. One row per series.
var transformedData = isStacked
? memoizedStackByFields(data, yFields)
: memoizedExtractFields(data, yFields, yScale.domain()[0]);
// If we are stacked, we need to calculate a new domain based on the sum of
// the various group's y data
if (isStacked) {
yScale.domain([
yScale.domain()[0],
//@ts-ignore
yStackedMax || lodash_1.default.max(lodash_1.default.map(transformedData, function (x) { return lodash_1.default.last(lodash_1.default.last(x)); })),
]);
}
return (react_1.default.createElement("g", __assign({}, lodash_1.default.omit(passThroughs, nonPassThroughs), { className: cx(className, '&') }), lodash_1.default.map(transformedData, function (series, seriesIndex) { return (react_1.default.createElement("g", { key: seriesIndex },
lodash_1.default.map(series, function (_a, pointsIndex) {
var start = _a[0], end = _a[1];
return (react_1.default.createElement(Bar_1.default, { key: pointsIndex, x: isStacked
? // @ts-ignore
xScale(data[seriesIndex][xField])
: // prettier-ignore
// @ts-ignore
innerXScale(pointsIndex) +
// prettier-ignore
// @ts-ignore
xScale(data[seriesIndex][xField]), y: yScale(end), height: yScale(start) - yScale(end), width: isStacked ? xScale.bandwidth() : innerXScale.bandwidth(), color: lodash_1.default.get(colorMap, yFields[pointsIndex], palette[(pointsIndex + colorOffset) % palette.length]) }));
}),
react_1.default.createElement(PureToolTip, { isExpanded: hasToolTips && hoveringSeriesIndex === seriesIndex, height: isStacked
? // prettier-ignore
//@ts-ignore
yScale.range()[0] - yScale(lodash_1.default.last(series)[1])
: // prettier-ignore
//@ts-ignore
yScale.range()[0] - yScale(lodash_1.default.max(lodash_1.default.flatten(series))), width: xScale.bandwidth(),
// @ts-ignore
x: xScale(data[seriesIndex][xField]), y: yScale(lodash_1.default.max(lodash_1.default.flatten(series))), series: series, seriesIndex: seriesIndex, onMouseEnter: _this.handleMouseEnter, onMouseOut: _this.handleMouseOut, xFormatter: xFormatter, xField: xField, renderBody: renderTooltipBody || _this.defaultTooltipFormatter, data: data }))); })));
};
Bars.displayName = 'Bars';
Bars.peek = {
description: "For use within an `svg`. `Bars` are typically used to represent categorical data and can be stacked or grouped.",
categories: ['visualizations', 'chart primitives'],
madeFrom: ['Bar', 'ToolTip', 'Legend'],
};
Bars.propTypes = {
/**
Appended to the component-specific class names set on the root element.
*/
className: string,
/**
De-normalized data
[
{ x: 'one', y0: 1, y1: 2, y2: 3, y3: 5 },
{ x: 'two', y0: 2, y1: 3, y2: 4, y3: 6 },
{ x: 'three', y0: 2, y1: 4, y2: 5, y3: 6 },
{ x: 'four', y0: 3, y1: 6, y2: 7, y3: 7 },
{ x: 'five', y0: 4, y1: 8, y2: 9, y3: 8 },
]
*/
data: arrayOf(object).isRequired,
/**
An object with human readable names for fields that will be used for
tooltips. E.g:
{
rev: 'Revenue',
imps: 'Impressions',
}
*/
legend: object,
/**
* Show tool tips on hover.
*/
hasToolTips: bool,
/**
Takes one of the palettes exported from \`lucid.chartConstants\`.
Available palettes:
- \`PALETTE_7\` (default)
- \`PALETTE_30\`
- \`PALETTE_MONOCHROME_0_5\`
- \`PALETTE_MONOCHROME_1_5\`
- \`PALETTE_MONOCHROME_2_5\`
- \`PALETTE_MONOCHROME_3_5\`
- \`PALETTE_MONOCHROME_4_5\`
- \`PALETTE_MONOCHROME_5_5\`
- \`PALETTE_MONOCHROME_6_5\`
*/
palette: arrayOf(string),
/**
You can pass in an object if you want to map fields to
\`lucid.chartConstants\` or custom colors:
{
'imps': COLOR_0,
'rev': COLOR_3,
'clicks': '#abc123',
}
*/
colorMap: object,
/**
The scale for the x axis. Must be a d3 band scale. Lucid exposes the
\`lucid.d3Scale.scaleBand\` library for use here.
*/
xScale: func.isRequired,
/**
The field we should look up your x data by.
*/
xField: string,
/**
Function to format the x data.
*/
xFormatter: func,
/**
The scale for the y axis. Must be a d3 scale. Lucid exposes the
\`lucid.d3Scale\` library for use here.
*/
yScale: func.isRequired,
/**
The field(s) we should look up your y data by. Each entry represents a
series. Your actual y data should be numeric.
*/
yFields: arrayOf(string),
/**
Function to format the y data.
*/
yFormatter: func,
/**
Typically this number can be derived from the yScale. However when we're
\`isStacked\` we need to calculate a new domain for the yScale based on
the sum of the data. If you need explicit control of the y max when
stacking, pass it in here.
*/
yStackedMax: number,
/**
An optional function used to format your y axis titles and data in the
tooltips. The first value is the name of your y field, the second value
is your post-formatted y value, and the third value is your non-formatted
y-value. Signature: \`(yField, yValueFormatted, yValue) => {}\`
*/
yTooltipFormatter: func,
/**
This will stack the data instead of grouping it. In order to stack the
data we have to calculate a new domain for the y scale that is based on
the \`sum\` of the data.
*/
isStacked: bool,
/**
Sometimes you might not want the colors to start rotating at the blue
color, this number will be added the bar index in determining which color
the bars are.
*/
colorOffset: number,
/**
An optional function used to format the entire tooltip body. The only arg is
the associated data point. This formatter will over-ride yTooltipFormatter
and yAxisTooltipDataFormatter. Signature:
\`dataPoint => {}\`
*/
renderTooltipBody: func,
};
Bars.defaultProps = {
hasToolTips: true,
xField: 'x',
xFormatter: lodash_1.default.identity,
yFields: ['y'],
yFormatter: lodash_1.default.identity,
yTooltipFormatter: function (yField, yValueFormatted) {
return "".concat(yField, ": ").concat(yValueFormatted);
},
renderTooltipBody: undefined,
isStacked: false,
colorOffset: 0,
palette: chartConstants.PALETTE_7,
};
return Bars;
}(react_1.PureComponent));
exports.Bars = Bars;
var PureToolTip = /** @class */ (function (_super) {
__extends(PureToolTip, _super);
function PureToolTip() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.handleMouseEnter = function () {
_this.props.onMouseEnter(_this.props.seriesIndex);
};
return _this;
}
PureToolTip.prototype.render = function () {
var _a = this.props, isExpanded = _a.isExpanded, height = _a.height, width = _a.width, x = _a.x, y = _a.y, seriesIndex = _a.seriesIndex, onMouseOut = _a.onMouseOut, renderBody = _a.renderBody, data = _a.data, xFormatter = _a.xFormatter, xField = _a.xField;
return (react_1.default.createElement(ToolTip_1.ToolTipDumb, { isExpanded: isExpanded, flyOutMaxWidth: 'none', isLight: true },
react_1.default.createElement(ToolTip_1.ToolTipDumb.Target, { elementType: 'g' },
react_1.default.createElement("rect", { className: cx('&-tooltip-hover-zone'), height: height, width: width, x: x, y: y, onMouseEnter: this.handleMouseEnter, onMouseOut: onMouseOut })),
react_1.default.createElement(ToolTip_1.ToolTipDumb.Title, null, xFormatter(
//@ts-ignore
data[seriesIndex][xField], data[seriesIndex])),
react_1.default.createElement(ToolTip_1.ToolTipDumb.Body, null, renderBody(data[seriesIndex]))));
};
PureToolTip.displayName = 'PureToolTip';
PureToolTip._isPrivate = true;
PureToolTip.propTypes = {
data: arrayOf(object),
height: number,
isExpanded: bool,
onMouseEnter: func,
onMouseOut: func,
renderBody: func,
seriesIndex: number,
width: number,
x: number,
xField: string,
xFormatter: func,
y: number,
};
return PureToolTip;
}(react_1.PureComponent));
exports.PureToolTip = PureToolTip;
exports.default = Bars;
//# sourceMappingURL=Bars.js.map