wix-style-react
Version:
wix-style-react
496 lines (494 loc) • 16.6 kB
JavaScript
"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