UNPKG

wix-style-react

Version:
426 lines (404 loc) • 15.3 kB
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import _extends from "@babel/runtime/helpers/extends"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } import React, { useState, useRef, useMemo, useEffect, memo } from 'react'; import PropTypes from 'prop-types'; import { scaleLinear } from 'd3-scale'; import { select } from 'd3-selection'; import { lineRadial, curveCardinalClosed } from 'd3-shape'; import { dataHooks } from './constants'; import { st, classes } from './RadarChart.st.css'; import { stVars as colorsStVars } from '../Foundation/stylable/colors.st.css'; import Tooltip from '../Tooltip'; var BLUE_COLOR = '#3899ec'; var DISABLED_COLOR = '#879cae'; var DOT_RADIUS = 2; var LINE_DY = 0.35; var LINE_HEIGHT = 1.4; var PADDING = 12; var AXIS_LABEL_DY = 12; var getXY = function getXY(value, index, angle, rScale) { var shift = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0; return { x: (rScale(value) + shift) * Math.cos(angle * index - Math.PI / 2), y: (rScale(value) + shift) * Math.sin(angle * index - Math.PI / 2) }; }; var CirclesSVG = /*#__PURE__*/memo(function (_ref) { var scale = _ref.scale, rScale = _ref.rScale; return scale.map(function (_ref2, index) { var value = _ref2.value; return /*#__PURE__*/React.createElement("circle", { key: index, "data-hook": dataHooks.radarChartScaleLine, className: classes.circles, r: rScale(value), cx: 0, cy: 0 }); }).reverse(); }); var WebChartSVG = /*#__PURE__*/memo(function (_ref3) { var disabled = _ref3.disabled, dots = _ref3.dots; if (disabled || dots.length === 0) { return; } return /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("linearGradient", { id: "gradient", x1: "0", x2: "1", y1: "0", y2: "0" }, /*#__PURE__*/React.createElement("stop", { offset: "0", stopColor: dots[dots.length - 1].color }), /*#__PURE__*/React.createElement("stop", { offset: "1", stopColor: dots[0].color }))); }); var OuterLabels = /*#__PURE__*/memo(function (_ref4) { var data = _ref4.data, maxScaleValue = _ref4.maxScaleValue, angle = _ref4.angle, rScale = _ref4.rScale, labelDistance = _ref4.labelDistance, disabled = _ref4.disabled, internalHoverIndex = _ref4.internalHoverIndex, width = _ref4.width, setInternalHoverIndex = _ref4.setInternalHoverIndex; var outerCircleDots = data.map(function (_ref5, index) { var label = _ref5.label; return { label: label, labelPoint: getXY(maxScaleValue, index, angle, rScale, labelDistance), linePoint: getXY(maxScaleValue, index, angle, rScale) }; }); return outerCircleDots.map(function (_ref6, index) { var label = _ref6.label, labelPoint = _ref6.labelPoint, linePoint = _ref6.linePoint; return /*#__PURE__*/React.createElement("g", { key: index, "data-hook": dataHooks.radarChartDataItem }, /*#__PURE__*/React.createElement("line", { className: classes.axis, x1: 0, y1: 0, x2: linePoint.x, y2: linePoint.y }), /*#__PURE__*/React.createElement("text", { className: st(classes.axisLabel, { hovered: index === internalHoverIndex, size: width < 360 ? 'small' : 'large', disabled: disabled }), "data-hook": "axisName".concat(index), x: labelPoint.x, y: labelPoint.y, dy: "".concat(LINE_DY, "em"), textAnchor: "middle", onMouseEnter: function onMouseEnter() { return !disabled && setInternalHoverIndex(index); }, onMouseLeave: function onMouseLeave() { return !disabled && setInternalHoverIndex(null); } }, label)); }); }); var ScaleValues = /*#__PURE__*/memo(function (_ref7) { var scale = _ref7.scale, rScale = _ref7.rScale; var scaleValues = scale.map(function (_ref8) { var value = _ref8.value, label = _ref8.label; return _objectSpread({ value: value, label: label }, getXY(value, 0, 0, rScale)); }); return scaleValues.map(function (_ref9, index) { var label = _ref9.label, y = _ref9.y; return /*#__PURE__*/React.createElement("text", { key: index, className: classes.scaleLabel, x: 0, y: y, dy: AXIS_LABEL_DY, textAnchor: "middle" }, label); }); }); var DataPoints = /*#__PURE__*/memo(function (_ref10) { var dots = _ref10.dots, disabled = _ref10.disabled, internalHoverIndex = _ref10.internalHoverIndex, setInternalHoverIndex = _ref10.setInternalHoverIndex; return dots.map(function (dot, index) { return /*#__PURE__*/React.createElement("g", { key: index }, /*#__PURE__*/React.createElement("circle", { r: !disabled && index === internalHoverIndex ? DOT_RADIUS * 2 : DOT_RADIUS, cx: dot.x, cy: dot.y, fill: disabled ? DISABLED_COLOR : dots[index].color }), /*#__PURE__*/React.createElement("circle", _extends({ className: st(classes.dataPoint, { disabled: disabled }), r: DOT_RADIUS * 4, cx: dot.x, cy: dot.y, fillOpacity: 0, onMouseEnter: function onMouseEnter() { return !disabled && setInternalHoverIndex(index); }, onMouseLeave: function onMouseLeave() { return !disabled && setInternalHoverIndex(null); } }, !disabled && { focusable: true, tabIndex: 0, onFocus: function onFocus() { return !disabled && setInternalHoverIndex(index); }, onBlur: function onBlur() { return !disabled && setInternalHoverIndex(null); } }))); }); }); var DataPointsTooltips = /*#__PURE__*/memo(function (_ref11) { var dots = _ref11.dots, containerWidth = _ref11.containerWidth, internalHoverIndex = _ref11.internalHoverIndex, disabled = _ref11.disabled; return dots.map(function (_ref12, index) { var x = _ref12.x, y = _ref12.y, tooltipContent = _ref12.tooltipContent, value = _ref12.value; return /*#__PURE__*/React.createElement("div", { key: index, style: { position: 'absolute', left: x + containerWidth / 2, top: y + containerWidth / 2 - 7 } }, /*#__PURE__*/React.createElement(Tooltip, { className: classes.tooltip, shown: index === internalHoverIndex, content: tooltipContent !== null && tooltipContent !== void 0 ? tooltipContent : value, disabled: disabled }, /*#__PURE__*/React.createElement("div", null))); }); }); var WebChartLine = /*#__PURE__*/memo(function (_ref13) { var disabled = _ref13.disabled, data = _ref13.data, angle = _ref13.angle, rScale = _ref13.rScale; var ref = useRef(null); useEffect(function () { if (ref.current && data.length) { select(ref.current).selectAll("[data-hook='curvePart']").remove(); var drawCurve = lineRadial().curve(curveCardinalClosed).radius(function (d) { return rScale(d.value); }).angle(function (_d, i) { return i * angle; }); var blob = select(ref.current).selectAll("[data-hook='curvePart']").data([data]).enter().append('g').attr('data-hook', 'curvePart'); blob.append('path').attr('d', function (d) { return drawCurve(d); }).style('stroke', BLUE_COLOR).style('stroke-width', '1px').style('stroke', disabled ? DISABLED_COLOR : 'url(#gradient)').style('fill', disabled ? DISABLED_COLOR : 'url(#gradient)').style('stroke-dasharray', disabled ? 6 : null).style('fill-opacity', 0.3); } }, [ref, data, disabled, rScale, angle]); return /*#__PURE__*/React.createElement("g", { ref: ref }); }); var RadarChart = function RadarChart(_ref14) { var dataHook = _ref14.dataHook, data = _ref14.data, scale = _ref14.scale, disabled = _ref14.disabled, width = _ref14.width, labelDistance = _ref14.labelDistance, hoverIndex = _ref14.hoverIndex, labelWidth = _ref14.labelWidth, onDataPointHover = _ref14.onDataPointHover; var _useState = useState(hoverIndex), _useState2 = _slicedToArray(_useState, 2), internalHoverIndex = _useState2[0], setInternalHoverIndex = _useState2[1]; var containerWidth = useMemo(function () { return width + labelDistance * 4 + PADDING * 2; }, [width, labelDistance]); var maxScaleValue = useMemo(function () { return Math.max.apply(Math, _toConsumableArray(scale.map(function (_ref15) { var value = _ref15.value; return value; }))); }, [scale]); var rScale = useMemo(function () { return scaleLinear().range([0, width / 2]).domain([0, maxScaleValue]); }, [width, maxScaleValue]); var angle = useMemo(function () { return Math.PI * 2 / data.length; }, [data]); var dots = useMemo(function () { return data.map(function (_ref16, index) { var value = _ref16.value, label = _ref16.label, color = _ref16.color, tooltipContent = _ref16.tooltipContent; return _objectSpread({ value: value, label: label, color: colorsStVars[color] || color || colorsStVars['A1'], tooltipContent: tooltipContent }, getXY(value, index, angle, rScale)); }); }, [data, angle, rScale]); useEffect(function () { return setInternalHoverIndex(hoverIndex); }, [hoverIndex]); useEffect(function () { if (internalHoverIndex === null) { return; } onDataPointHover(data[internalHoverIndex], internalHoverIndex); }, [internalHoverIndex, onDataPointHover, data]); useEffect(function () { data.forEach(function (_ref17, index) { var label = _ref17.label; var text = select(containerRef.current).select("[data-hook='axisName".concat(index, "']")); var words = label.split(/\s+/); var word; var line = []; var lineNumber = 0; var tspan = text.text(null).append('tspan').attr('x', text.attr('x')).attr('y', text.attr('y')).attr('dy', "".concat(LINE_DY, "em")); while (words.length) { word = words.shift(); line.push(word); tspan.text(line.join(' ')); if (tspan.node && tspan.node().getComputedTextLength && tspan.node().getComputedTextLength() > labelWidth) { line.pop(); tspan.text(line.join(' ')); line = [word]; tspan = text.append('tspan').attr('x', text.attr('x')).attr('y', text.attr('y')).attr('dy', "".concat(++lineNumber * LINE_HEIGHT + LINE_DY, "em")).text(word); } } }); }); var containerRef = useRef(null); return /*#__PURE__*/React.createElement("div", { "data-hook": dataHook, className: classes.root, style: { width: containerWidth, height: containerWidth }, "data-disabled": disabled, "data-hover-index": internalHoverIndex }, /*#__PURE__*/React.createElement("svg", { width: containerWidth, height: containerWidth, ref: containerRef }, /*#__PURE__*/React.createElement("g", { transform: "translate(".concat(containerWidth / 2, ", ").concat(containerWidth / 2, ")") }, /*#__PURE__*/React.createElement(WebChartSVG, { dots: dots, disabled: disabled }), /*#__PURE__*/React.createElement(CirclesSVG, { scale: scale, rScale: rScale }), /*#__PURE__*/React.createElement(OuterLabels, { data: data, maxScaleValue: maxScaleValue, angle: angle, rScale: rScale, labelDistance: labelDistance, disabled: disabled, internalHoverIndex: internalHoverIndex, width: width, setInternalHoverIndex: setInternalHoverIndex }), /*#__PURE__*/React.createElement(ScaleValues, { scale: scale, rScale: rScale }), /*#__PURE__*/React.createElement(WebChartLine, { disabled: disabled, data: data, angle: angle, rScale: rScale }), /*#__PURE__*/React.createElement(DataPoints, { dots: dots, disabled: disabled, internalHoverIndex: internalHoverIndex, setInternalHoverIndex: setInternalHoverIndex }))), /*#__PURE__*/React.createElement(DataPointsTooltips, { dots: dots, disabled: disabled, containerWidth: containerWidth, internalHoverIndex: internalHoverIndex, setInternalHoverIndex: setInternalHoverIndex })); }; RadarChart.displayName = 'RadarChart'; RadarChart.propTypes = { /** Applies a data-hook HTML attribute that can be used in tests */ dataHook: PropTypes.string, /** Defines data points that construct spider web-like charts. Available properties for array items:<br /> &emsp;- `value` - a number that represents value in chart<br /> &emsp;- `label` - a label that represents value description around the chart<br /> &emsp;- `color` - data point color.<br /> &emsp;- `tooltipContent` - data point tooltip content */ data: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.number, label: PropTypes.string, color: PropTypes.string, tooltipContent: PropTypes.node })), /** Defines a number of scale circles in the chart. Available properties for each scale:<br /> &emsp;- `value` - a number representing scale<br /> &emsp;- `suffix` - a string that represents value meaning (i.e. % or $) */ scale: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.number, label: PropTypes.string })), /** Specifies whether graph is disabled */ disabled: PropTypes.bool, /** Controls the width of a graph. Minimum width is 150 pixels. */ width: PropTypes.number, /** Controls label distance from a chart */ labelDistance: PropTypes.number, /** Defines the index of a data point in hover state */ hoverIndex: PropTypes.number, /** Defines maximum width of data labels */ labelWidth: PropTypes.number, /** Defines a callback function which is called every time user hovers over a data point. Includes all data point data as first argument and index as second. */ onDataPointHover: PropTypes.func }; RadarChart.defaultProps = { data: [], scale: [{ value: 50, label: '50%' }, { value: 100, label: '100%' }], width: 150, labelDistance: 50, labelWidth: 100, onDataPointHover: function onDataPointHover() {} }; export default RadarChart;