UNPKG

wix-style-react

Version:
496 lines (494 loc) • 16.6 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = void 0; var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _d3Scale = require("d3-scale"); var _d3Array = require("d3-array"); var _d3Shape = require("d3-shape"); var _d3Selection = require("d3-selection"); var _d3Ease = require("d3-ease"); var _ChartTooltip = require("./ChartTooltip"); var _constants = require("./constants"); var _SparklineChartSt = require("./SparklineChart.st.css"); require("d3-transition"); var _jsxFileName = "/home/builduser/work/a9c1ac8876d5057c/packages/wix-style-react/dist/cjs/SparklineChart/SparklineChart.js"; var LINE_WIDTH = 2; var AREA_MASK_ID = 'areaMaskId'; var TOOLTIP_ELEMENT_RADIUS = 4; var DEFAULT_GRADIENT_ID = 'DEFAULT_GRADIENT_ID'; /** SparklineChart */ class SparklineChart extends _react.default.PureComponent { constructor(props) { super(props); this._shouldShowTooltip = () => { var { hoveredLabel } = this.state; var { getTooltipContent } = this.props; return getTooltipContent && typeof getTooltipContent === 'function' && hoveredLabel; }; this._useCreateContext = () => { var halfWidth = LINE_WIDTH / 2; var { width = 200, height = 40, data, highlightedStartingIndex = 0, color } = this.props; var { highlightedEndingIndex = data.length - 1 } = this.props; var margin = { top: halfWidth + 2, right: halfWidth, bottom: halfWidth, left: halfWidth }; var innerTop = margin.top; var innerLeft = margin.left; var innerHeight = height - innerTop - margin.bottom; var innerWidth = width - innerLeft - margin.right; var maxValue = (0, _d3Array.max)(this._getValues(data)); var firstLabel = this._getLabelAt(data, 0); var lastLabel = this._getLabelAt(data, data.length - 1); var xScale = (0, _d3Scale.scaleTime)().domain([firstLabel, lastLabel]).range([innerLeft, innerWidth]); var yScale = (0, _d3Scale.scaleLinear)().domain([0, maxValue]).range([innerHeight, innerTop]); var lineGenerator = (0, _d3Shape.line)().x((dataPoint, i) => { return xScale(this._getLabelAt(data, i)); }).y(dataPoint => { return yScale(dataPoint); }).curve(_d3Shape.curveMonotoneX); var areaGenerator = (0, _d3Shape.area)().x((dataPoint, i) => { return xScale(this._getLabelAt(data, i)); }).y0(() => innerHeight).y1(dataPoint => { return yScale(dataPoint); }).curve(_d3Shape.curveMonotoneX); return { margin, width, height, innerTop, innerLeft, innerBottom: margin.top + innerHeight, innerWidth, innerHeight, data, xScale, yScale, highlightedStartingIndex, highlightedEndingIndex, lineGenerator, areaGenerator, color }; }; this._getLabelAt = (data, position) => { return data[position] && data[position].label; }; this._getValues = data => data.map(pair => pair.value); this._getLabels = data => data.map(pair => pair.label); this._drawSparkline = () => { var { width, height, data } = this.chartContext; var { onHover } = this.props; var labels = this._getLabels(data); var container = (0, _d3Selection.select)(this.svgRef.current); container.attr('width', width).attr('height', height); var dataContainer = container.select("[data-hook=\"".concat(_constants.dataHooks.dataContainer, "\"]")); this._drawLines(dataContainer); (0, _d3Selection.select)(this.componentRef.current).on('mouseleave', () => { this.setState({ hoveredLabel: null }); }).on('mousemove', d => { var dateUnderPointer = this.chartContext.xScale.invert((0, _d3Selection.pointer)(d)[0]); var currentDateIndex = (0, _d3Array.bisector)(function (date) { return date; }).left(labels, dateUnderPointer, 1); var beforeDateIndex = currentDateIndex - 1; var beforeDate = labels[beforeDateIndex]; var afterDate = labels[currentDateIndex]; var closestDate = +dateUnderPointer - +beforeDate > +afterDate - +dateUnderPointer ? afterDate : beforeDate; if (typeof onHover === 'function' && !this._areDatesEqual(closestDate, this.state.hoveredLabel)) { var labelIndex = labels.indexOf(closestDate); onHover(labelIndex); } this.setState({ hoveredLabel: closestDate }); }); }; this._drawLines = dataContainer => { var { data, lineGenerator, areaGenerator, color } = this.chartContext; var dataSets = [data]; dataContainer.selectAll('.chartLines').data(dataSets).join('g').attr('class', 'chartLines').selectAll('g').data(dataSet => { return [dataSet]; }).join(enter => { var group = enter.append('g'); group.append('path').attr('class', 'innerArea').attr('mask', "url(#".concat(this._getAreaMaskId(this.randomComponentId), ")")).attr('fill', dataSet => { return "url(#".concat(color || DEFAULT_GRADIENT_ID, ")"); }).attr('d', dataSet => { return areaGenerator(dataSet.map(() => 0)); }); group.append('path').attr('class', 'innerLineBack').attr('fill', 'none').attr('stroke-width', LINE_WIDTH + 4).attr('stroke-linecap', 'round').attr('stroke', 'white').attr('d', dataSet => { return lineGenerator(dataSet.map(() => 0)); }); group.append('path').attr('class', 'innerLine').attr('fill', 'none').attr('stroke-width', LINE_WIDTH).attr('stroke-linecap', 'round').attr('stroke', dataSet => { return "url(#".concat(this._getLineColorId(dataSet, this.randomComponentId), ")"); }).attr('d', dataSet => { return lineGenerator(dataSet.map(() => 0)); }); this._updateLines(group); return group; }, update => { this._updateLines(update); return update; }); }; this._updateLines = container => { var { lineGenerator, areaGenerator } = this.chartContext; this._updateComponent(container, '.innerLine', set => { return lineGenerator(this._getValues(set)); }); this._updateComponent(container, '.innerLineBack', set => { return lineGenerator(this._getValues(set)); }); this._updateComponent(container, '.innerArea', set => { return areaGenerator(this._getValues(set)); }); }; this._updateComponent = (container, className, fncUpdater) => { var { animationDuration } = this.props; container.select(className).transition().duration(animationDuration).ease(_d3Ease.easeQuadIn).attr('d', fncUpdater); }; this.randomComponentId = Math.random().toString(); this.chartContext = {}; this.svgRef = /*#__PURE__*/_react.default.createRef(null); this.componentRef = /*#__PURE__*/_react.default.createRef(null); this.state = { hoveredLabel: null }; } _getValueAt(data, position) { return data[position] && data[position].value; } _areDatesEqual(date1, date2) { var date1Time = date1 && date1.getTime(); var date2Time = date2 && date2.getTime(); return date1Time === date2Time; } _getLineColorId(dataSet, componentId) { return "".concat(componentId, "color"); } _getAreaMaskId(componentId) { return "".concat(AREA_MASK_ID).concat(componentId); } componentDidMount() { this._drawSparkline(); } componentDidUpdate(prevProps) { if (prevProps.data !== this.props.data) { this._drawSparkline(); } } _updateContext() { this.chartContext = this._useCreateContext(); } render() { this._updateContext(); var { getTooltipContent, className, dataHook } = this.props; var { hoveredLabel } = this.state; var context = this.chartContext; var { data, highlightedStartingIndex, highlightedEndingIndex, innerWidth, height, width, color } = context; var highlightedStart = context.xScale(this._getLabelAt(data, highlightedStartingIndex)); var highlightedEnd = context.xScale(this._getLabelAt(data, highlightedEndingIndex)); var highlightedStartRelativeLocation = highlightedStart / innerWidth; var highlightedEndRelativeLocation = highlightedEnd / innerWidth; var labels = this._getLabels(data); var hoveredLabelIndex = (0, _d3Array.bisector)(function (d) { return d; }).left(labels, hoveredLabel, 0); var currentHoveredLabel = this._getLabelAt(data, hoveredLabelIndex); var currentHoveredValue = this._getValueAt(data, hoveredLabelIndex); var dataPoint = { content: getTooltipContent && typeof getTooltipContent === 'function' && getTooltipContent(hoveredLabelIndex), xCoordinate: context.xScale(currentHoveredLabel), yCoordinate: context.yScale(currentHoveredValue) - TOOLTIP_ELEMENT_RADIUS / 2 }; return /*#__PURE__*/_react.default.createElement("div", { style: { width, height, position: 'relative' }, ref: this.componentRef, className: className, "data-hook": dataHook, __self: this, __source: { fileName: _jsxFileName, lineNumber: 333, columnNumber: 7 } }, /*#__PURE__*/_react.default.createElement("svg", { style: { overflow: 'visible', zIndex: 1 }, ref: this.svgRef, __self: this, __source: { fileName: _jsxFileName, lineNumber: 339, columnNumber: 9 } }, /*#__PURE__*/_react.default.createElement("defs", { __self: this, __source: { fileName: _jsxFileName, lineNumber: 340, columnNumber: 11 } }, /*#__PURE__*/_react.default.createElement("mask", { id: this._getAreaMaskId(this.randomComponentId), __self: this, __source: { fileName: _jsxFileName, lineNumber: 341, columnNumber: 13 } }, /*#__PURE__*/_react.default.createElement("rect", { x: highlightedStart, y: "0", width: highlightedEnd - highlightedStart, height: height, fill: "white", __self: this, __source: { fileName: _jsxFileName, lineNumber: 342, columnNumber: 15 } })), /*#__PURE__*/_react.default.createElement("linearGradient", { gradientUnits: "userSpaceOnUse", key: "".concat(this.randomComponentId, "a"), id: this._getLineColorId(data, this.randomComponentId), x1: "0px", y1: "0px", x2: "".concat(innerWidth, "px"), y2: "0px", __self: this, __source: { fileName: _jsxFileName, lineNumber: 351, columnNumber: 13 } }, /*#__PURE__*/_react.default.createElement("stop", { offset: highlightedStartRelativeLocation, style: { stopColor: '#dfe5eb', stopOpacity: 1 }, __self: this, __source: { fileName: _jsxFileName, lineNumber: 360, columnNumber: 15 } }), /*#__PURE__*/_react.default.createElement("stop", { offset: highlightedStartRelativeLocation, className: _SparklineChartSt.classes.gradientStop, style: { stopColor: color }, __self: this, __source: { fileName: _jsxFileName, lineNumber: 364, columnNumber: 15 } }), /*#__PURE__*/_react.default.createElement("stop", { offset: highlightedEndRelativeLocation, className: _SparklineChartSt.classes.gradientStop, style: { stopColor: color }, __self: this, __source: { fileName: _jsxFileName, lineNumber: 369, columnNumber: 15 } }), /*#__PURE__*/_react.default.createElement("stop", { offset: highlightedEndRelativeLocation, style: { stopColor: '#dfe5eb', stopOpacity: 1 }, __self: this, __source: { fileName: _jsxFileName, lineNumber: 376, columnNumber: 15 } }), /*#__PURE__*/_react.default.createElement("stop", { offset: 1, style: { stopColor: '#dfe5eb', stopOpacity: 1 }, __self: this, __source: { fileName: _jsxFileName, lineNumber: 380, columnNumber: 15 } })), /*#__PURE__*/_react.default.createElement("linearGradient", { gradientUnits: "userSpaceOnUse", key: this.randomComponentId, id: color || DEFAULT_GRADIENT_ID, x1: "0px", y1: "".concat(context.innerHeight, "px"), x2: "0px", y2: "0px", __self: this, __source: { fileName: _jsxFileName, lineNumber: 386, columnNumber: 13 } }, /*#__PURE__*/_react.default.createElement("stop", { offset: "10%", className: _SparklineChartSt.classes.gradientStopWithoutOpacity, style: { stopColor: color }, __self: this, __source: { fileName: _jsxFileName, lineNumber: 395, columnNumber: 15 } }), /*#__PURE__*/_react.default.createElement("stop", { offset: "90%", className: _SparklineChartSt.classes.gradientStopWithHalfOpacity, style: { stopColor: color }, __self: this, __source: { fileName: _jsxFileName, lineNumber: 400, columnNumber: 15 } }))), /*#__PURE__*/_react.default.createElement("g", { __self: this, __source: { fileName: _jsxFileName, lineNumber: 409, columnNumber: 11 } }, /*#__PURE__*/_react.default.createElement("g", { "data-hook": _constants.dataHooks.dataContainer, __self: this, __source: { fileName: _jsxFileName, lineNumber: 410, columnNumber: 13 } }), this._shouldShowTooltip() && /*#__PURE__*/_react.default.createElement("g", { transform: "translate(".concat(dataPoint.xCoordinate, ", ").concat(dataPoint.yCoordinate + TOOLTIP_ELEMENT_RADIUS / 2, ")"), __self: this, __source: { fileName: _jsxFileName, lineNumber: 412, columnNumber: 15 } }, /*#__PURE__*/_react.default.createElement("circle", { r: TOOLTIP_ELEMENT_RADIUS, className: _SparklineChartSt.classes.tooltipElement, fill: color, __self: this, __source: { fileName: _jsxFileName, lineNumber: 417, columnNumber: 17 } })))), this._shouldShowTooltip() && /*#__PURE__*/_react.default.createElement(_ChartTooltip.ChartTooltip, { dataPoint: dataPoint, __self: this, __source: { fileName: _jsxFileName, lineNumber: 426, columnNumber: 39 } })); } } SparklineChart.displayName = 'SparklineChart'; SparklineChart.propTypes = { /** Applied as data-hook HTML attribute that can be used in the tests */ dataHook: _propTypes.default.string, /** A css class to be applied to the component's root element */ className: _propTypes.default.string, /** Sets the width of the sparkline (pixels) */ width: _propTypes.default.number, /** Sets the height of the sparkline (pixels) */ height: _propTypes.default.number, /** Chart data */ data: _propTypes.default.arrayOf(_propTypes.default.shape({ label: _propTypes.default.instanceOf(Date), value: _propTypes.default.number })).isRequired, /** Sets the color of the sparkline */ color: _propTypes.default.string, /** Indicates the starting index of the highlighted area. Default is 0 */ highlightedStartingIndex: _propTypes.default.number, /** Indicates the ending index of the highlighted area. Default is the last one */ highlightedEndingIndex: _propTypes.default.number, /** Tooltip content (JSX) getter function. */ getTooltipContent: _propTypes.default.func, /** callback when graph is hovered*/ onHover: _propTypes.default.func, /** Sets the duration of the animation in milliseconds */ animationDuration: _propTypes.default.number }; SparklineChart.defaultProps = { animationDuration: 300 }; var _default = exports.default = SparklineChart; //# sourceMappingURL=SparklineChart.js.map