@semcore/chart
Version:
Semrush Chart Component
429 lines (428 loc) • 18.4 kB
JavaScript
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized";
import _inherits from "@babel/runtime/helpers/inherits";
import _createSuper from "@babel/runtime/helpers/createSuper";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import { sstyled as _sstyled2 } from "@semcore/core";
import { sstyled as _sstyled } from "@semcore/core";
var _excluded = ["className", "width", "height", "style"];
import React from 'react';
import { Surface } from 'recharts';
import { findAllByType, findChildByType, getPresentationAttributes, isChildrenEqual, validateWidthHeight } from "recharts/es6/util/ReactUtils";
import { intersectionArea, intersectionAreaPath, normalizeSolution, scaleSolution, venn } from '@upsetjs/venn.js';
import { Tooltip } from '../../Tooltip';
import VennArea from './VennArea';
import VennIntersection from './VennIntersection';
import fire from '@semcore/utils/lib/fire';
import assignProps from '@semcore/utils/lib/assignProps';
import { sstyled } from '@semcore/core';
import cn from 'classnames';
/*__reshadow-styles__:"./style/venn-tooltip-label.shadow.css"*/
var styles = ( /*__reshadow_css_start__*/_sstyled.insert( /*__inner_css_start__*/".___STooltipLabel_1te07_gg_{display:inline-flex;justify-content:space-between;width:100%;padding-bottom:var(--intergalactic-spacing-2x, 8px);border-bottom:1px solid var(--intergalactic-border-primary, #c4c7cf);color:var(--intergalactic-text-primary, #191b23);font-size:var(--intergalactic-fs-100, 12px);line-height:var(--intergalactic-lh-100, 133%)}.___STooltipLabelTitle_1te07_gg_{margin-right:var(--intergalactic-spacing-4x, 16px)}.___STooltipLabelPercentage_1te07_gg_{margin-right:var(--intergalactic-spacing-3x, 12px);color:var(--intergalactic-text-secondary, #6c6e79)}.___STooltipLabelValue_1te07_gg_{font-weight:var(--intergalactic-bold, Bold)}" /*__inner_css_end__*/, "1te07_gg_") /*__reshadow_css_end__*/, {
"__STooltipLabel": "___STooltipLabel_1te07_gg_",
"__STooltipLabelTitle": "___STooltipLabelTitle_1te07_gg_",
"__STooltipLabelPercentage": "___STooltipLabelPercentage_1te07_gg_",
"__STooltipLabelValue": "___STooltipLabelValue_1te07_gg_"
});
/*__reshadow-styles__:"../../style/chart.shadow.css"*/
var chartStyles = ( /*__reshadow_css_start__*/_sstyled2.insert( /*__inner_css_start__*/".___SChart_z2wr8_gg_ .recharts-label{text-anchor:middle;fill:var(--intergalactic-text-hint, #6c6e79)}.___SChart_z2wr8_gg_ .recharts-cartesian-axis-tick-value{fill:var(--intergalactic-chart-grid-text-label, #6c6e79);font-size:12px;line-height:1.1}.___SChart_z2wr8_gg_ .recharts-default-tooltip{padding:12px;margin:0;background-color:var(--intergalactic-bg-primary-neutral, #ffffff);white-space:nowrap;border:1px solid var(--intergalactic-border-secondary, #e0e1e9);box-shadow:var(--intergalactic-box-shadow-popper, 0px 1px 12px 0px rgba(25, 27, 35, 0.15));border-radius:var(--intergalactic-popper-rounded, 6px)}.___SChart_z2wr8_gg_ .recharts-tooltip-item{display:flex;align-items:center;justify-content:space-between;font-size:12px;color:var(--intergalactic-text-primary, #191b23);padding:4px 0}.___SChart_z2wr8_gg_ .recharts-tooltip-label{font-size:12px;color:var(--intergalactic-text-secondary, #6c6e79);margin-top:0;margin-bottom:var(--intergalactic-spacing-2x, 8px)}.___SChart_z2wr8_gg_ .recharts-tooltip-label:empty{margin-bottom:0}.___SChart_z2wr8_gg_ .recharts-tooltip-item-value-wrapper{margin-left:var(--intergalactic-spacing-3x, 12px)}.___SChart_z2wr8_gg_ .recharts-tooltip-item-value{font-weight:var(--intergalactic-bold, Bold)}.___SChart_z2wr8_gg_ .recharts-tooltip-item-color{display:inline-block;margin-right:var(--intergalactic-spacing-2x, 8px);width:8px;height:8px;border-radius:50%}.___SChart_z2wr8_gg_ .recharts-area-dot{fill-opacity:1}" /*__inner_css_end__*/, "z2wr8_gg_") /*__reshadow_css_end__*/, {
"__SChart": "___SChart_z2wr8_gg_"
});
/**
* @deprecated Please, use package `@semcore/ui/d3-chart` instead. Package `@semcore/chart` is deprecated.
*/
var VennChart = /*#__PURE__*/function (_React$PureComponent) {
_inherits(VennChart, _React$PureComponent);
var _super = _createSuper(VennChart);
function VennChart(props) {
var _this;
_classCallCheck(this, VennChart);
_this = _super.call(this, props);
_defineProperty(_assertThisInitialized(_this), "circles", void 0);
_defineProperty(_assertThisInitialized(_this), "circlesLayout", void 0);
_defineProperty(_assertThisInitialized(_this), "handleMouseEnter", function (nodeProps) {
return function (e) {
var children = _this.state.children;
var tooltip = findChildByType(children, Tooltip);
_this.setState({
activeNode: nodeProps,
isTooltipActive: !!tooltip
}, function () {
return fire(_assertThisInitialized(_this), 'onMouseEnter', e, _this.generatePayload());
});
};
});
_defineProperty(_assertThisInitialized(_this), "handleMouseLeave", function (e) {
_this.setState({
activeNode: null,
isTooltipActive: false
}, function () {
fire(_assertThisInitialized(_this), 'onMouseLeave', e, _this.generatePayload());
});
});
_defineProperty(_assertThisInitialized(_this), "handleClick", function (e) {
fire(_assertThisInitialized(_this), 'onClick', e, _this.generatePayload());
});
_defineProperty(_assertThisInitialized(_this), "dataToCirclesLayoutObj", function () {
var actualData = _this.state.actualData;
var _this$props = _this.props,
width = _this$props.width,
height = _this$props.height,
padding = _this$props.padding,
orientation = _this$props.orientation,
orientationOrder = _this$props.orientationOrder,
minAreaRadius = _this$props.minAreaRadius;
if (actualData.length === 0) {
_this.circles = {};
return null;
}
var circles = venn(actualData);
var normalisedCircles = normalizeSolution(circles, orientation, orientationOrder);
var scaledCircles = scaleSolution(normalisedCircles, width, height, padding);
Object.keys(scaledCircles).forEach(function (key) {
var circleRadius = scaledCircles[key].radius;
scaledCircles[key].radius = Math.max(minAreaRadius, circleRadius);
//@ts-ignore
scaledCircles[key].data = getElementDataByKey(actualData, key);
});
//@ts-ignore
_this.circles = scaledCircles;
});
_defineProperty(_assertThisInitialized(_this), "renderCircle", function (circleProps) {
var activeNode = _this.state.activeNode;
var x = circleProps.x,
y = circleProps.y,
radius = circleProps.radius,
data = circleProps.data,
name = circleProps.name,
node = circleProps.node;
var isNodeActive = activeNode && name === activeNode.name;
var nodeProps = _objectSpread(_objectSpread({}, node.props), {}, {
cx: x,
cy: y,
r: radius,
key: "venn-area-".concat(name),
size: data.size,
active: isNodeActive,
data: data
});
var nodeHandlers = {
onMouseEnter: _this.handleMouseEnter(nodeProps),
onMouseLeave: _this.handleMouseLeave,
onClick: _this.handleClick
};
return /*#__PURE__*/React.cloneElement(node, assignProps(nodeProps, nodeHandlers));
});
_defineProperty(_assertThisInitialized(_this), "renderIntersection", function (intersectionProps) {
var sets = intersectionProps.sets;
var activeNode = _this.state.activeNode;
var isNodeActive = (activeNode === null || activeNode === void 0 ? void 0 : activeNode.sets) && stringArrayIsEqual(sets, activeNode.sets);
var nodeHandlers = {
onMouseEnter: _this.handleMouseEnter(intersectionProps),
onMouseLeave: _this.handleMouseLeave,
onClick: _this.handleClick
};
var nodeProps = _objectSpread({
active: isNodeActive,
key: "venn-intersection-".concat(sets.join('-'))
}, assignProps(intersectionProps, nodeHandlers));
return /*#__PURE__*/React.createElement(VennIntersection, nodeProps);
});
_defineProperty(_assertThisInitialized(_this), "formatTooltipLabel", function (label, payload) {
var _ref;
var tooltipLabelIntersectionSizeFormatter = _this.props.tooltipLabelIntersectionSizeFormatter;
if (!payload || payload.length < 2) {
return null;
}
var totalValue = payload.reduce(function (acc, item) {
return acc + item.payload.size;
}, 0);
var sets = payload.reduce(function (acc, item) {
return acc.concat(item.payload.sets);
}, []);
var _payload$0$data$find = payload[0].data.find(function (dataItem) {
return sets.every(function (set) {
return dataItem.sets.includes(set);
});
}),
size = _payload$0$data$find.size;
var percent = size / (totalValue / 100);
var finalPercentage = percent < 1 ? '< 1' : percent.toFixed(0);
var STooltipLabel = 'span';
var STooltipLabelTitle = 'span';
var STooltipLabelPercentage = 'span';
var STooltipLabelValue = 'span';
return _ref = sstyled(styles), /*#__PURE__*/React.createElement(STooltipLabel, _ref.cn("STooltipLabel", {}), /*#__PURE__*/React.createElement(STooltipLabelTitle, _ref.cn("STooltipLabelTitle", {}), label), /*#__PURE__*/React.createElement(STooltipLabelPercentage, _ref.cn("STooltipLabelPercentage", {}), finalPercentage, "%"), /*#__PURE__*/React.createElement(STooltipLabelValue, _ref.cn("STooltipLabelValue", {}), tooltipLabelIntersectionSizeFormatter(size)));
});
var _data = props.data,
_children = props.children;
_this.state = {
data: _data,
children: _children,
actualData: VennChart.getActualData(props),
activeNode: null,
isTooltipActive: false
};
return _this;
}
_createClass(VennChart, [{
key: "getCirclesLayout",
value: function getCirclesLayout() {
var children = this.state.children;
// tslint:disable-next-line:no-this-assignment
var circles = this.circles;
var circleItems = findAllByType(children, VennArea);
this.circlesLayout = Object.keys(circles).map(function (circle) {
var circleName = circles[circle].data.name;
var circleNode = circleItems.find(function (_ref3) {
var props = _ref3.props;
return circleName === props.name;
});
return _objectSpread({
name: circleName,
node: circleNode
}, circles[circle]);
});
}
}, {
key: "renderCircles",
value: function renderCircles() {
return this.circlesLayout.map(this.renderCircle);
}
}, {
key: "getIntersectionsLayout",
value: function getIntersectionsLayout() {
var _this2 = this;
var actualData = this.state.actualData;
var circlesWithIntersections = actualData.filter(function (item) {
return item.sets.length > 1;
});
return circlesWithIntersections.map(function (intersection) {
var sets = intersection.sets,
name = intersection.name,
size = intersection.size;
var circleNodes = sets.map(function (set) {
return _this2.circlesLayout.find(function (circle) {
return circle.data.sets[0] === set;
});
});
var path = intersectionAreaPath(circleNodes);
return {
d: path,
circles: circleNodes,
name: name,
size: size,
sets: sets
};
});
}
}, {
key: "renderIntersections",
value: function renderIntersections() {
var intersections = this.getIntersectionsLayout();
return intersections.map(this.renderIntersection);
}
}, {
key: "getAreaTooltipCoordinate",
value: function getAreaTooltipCoordinate(activeNode) {
if (!activeNode || !activeNode.cx || !activeNode.cy) {
return null;
}
return {
x: activeNode.cx + activeNode.r,
y: activeNode.cy + activeNode.r / 2
};
}
}, {
key: "getIntersectionTooltipCoordinate",
value: function getIntersectionTooltipCoordinate(activeNode) {
if (!activeNode || !activeNode.circles) {
return null;
}
var stats = {};
intersectionArea(activeNode.circles, stats);
// @ts-ignore
var innerPoints = stats.innerPoints;
return innerPoints.reduce(function (acc, _ref4) {
var x = _ref4.x,
y = _ref4.y;
if (x > acc.x) {
acc.x = x;
}
if (y > acc.y) {
acc.y = y;
}
return acc;
}, {
x: 0,
y: 0
});
}
}, {
key: "generatePayload",
value: function generatePayload() {
var data = this.props.data;
var activeNode = this.state.activeNode;
if (!activeNode) {
return [];
}
return activeNode.name ? [{
payload: activeNode.data,
name: activeNode.name,
value: activeNode.size,
fill: activeNode.fill,
data: data
}] : activeNode.circles.map(function (circle) {
return {
payload: circle.data,
// @ts-ignore
name: circle.name,
value: circle.data.size,
// @ts-ignore
fill: circle.node.props.fill,
data: data
};
});
}
}, {
key: "renderTooltip",
value: function renderTooltip() {
var children = this.props.children;
var tooltipItem = findChildByType(children, Tooltip);
if (!tooltipItem) {
return null;
}
var _this$props2 = this.props,
width = _this$props2.width,
height = _this$props2.height;
var _this$state = this.state,
isTooltipActive = _this$state.isTooltipActive,
activeNode = _this$state.activeNode;
var viewBox = {
x: 0,
y: 0,
width: width,
height: height
};
var coordinate = this.getAreaTooltipCoordinate(activeNode) || this.getIntersectionTooltipCoordinate(activeNode);
var payload = isTooltipActive ? this.generatePayload() : [];
return /*#__PURE__*/React.cloneElement(tooltipItem, _objectSpread(_objectSpread({
label: 'Overlap',
labelFormatter: this.formatTooltipLabel
}, tooltipItem.props), {}, {
viewBox: viewBox,
active: isTooltipActive,
coordinate: coordinate,
payload: payload
}));
}
}, {
key: "render",
value: function render() {
var _ref2;
if (!validateWidthHeight(this)) {
return null;
}
this.dataToCirclesLayoutObj(); // elegant crutch 🤷
this.getCirclesLayout();
var _this$props3 = this.props,
className = _this$props3.className,
width = _this$props3.width,
height = _this$props3.height,
style = _this$props3.style,
other = _objectWithoutProperties(_this$props3, _excluded);
var attrs = getPresentationAttributes(other);
var SChart = 'div';
return _ref2 = sstyled(chartStyles), /*#__PURE__*/React.createElement(SChart, _ref2.cn("SChart", {
"className": cn('recharts-wrapper', className),
"style": _objectSpread({
position: 'relative',
cursor: 'default',
width: width,
height: height
}, style)
}), /*#__PURE__*/React.createElement(Surface, _ref2.cn("Surface", _objectSpread(_objectSpread({}, attrs), {}, {
"width": width,
"height": height
})), this.renderCircles(), this.renderIntersections()), this.renderTooltip());
}
}], [{
key: "getActualData",
value: function getActualData(props) {
var data = props.data,
children = props.children;
var circleItems = findAllByType(children, VennArea);
// Mapping data to children to define actual sets
var actualSets = circleItems.reduce(function (acc, circle) {
if (circle.props.hidden) {
return acc;
}
var name = circle.props.name;
var circleDataItem = data.find(function (item) {
return item.name === name;
});
if (!circleDataItem || circleDataItem.sets.length > 1) {
return acc;
}
return [].concat(_toConsumableArray(acc), _toConsumableArray(circleDataItem.sets));
}, []);
// filtering data by actual sets
return data.filter(function (item) {
return item.sets.every(function (set) {
return actualSets.includes(set);
});
});
}
}, {
key: "getDerivedStateFromProps",
value: function getDerivedStateFromProps(props, state) {
var children = props.children,
data = props.data;
var activeNode = state.activeNode,
isTooltipActive = state.isTooltipActive;
if (data !== state.data || !isChildrenEqual(children, state.children)) {
var actualData = VennChart.getActualData(props);
return {
data: data,
children: children,
actualData: actualData,
activeNode: activeNode,
isTooltipActive: isTooltipActive
};
}
return null;
}
}]);
return VennChart;
}(React.PureComponent);
_defineProperty(VennChart, "displayName", 'VennChart');
_defineProperty(VennChart, "defaultProps", {
padding: 2,
orientation: Math.PI / 2,
orientationOrder: function orientationOrder(c1, c2) {
return c2.radius - c1.radius;
},
minAreaRadius: 6,
tooltipLabelIntersectionSizeFormatter: function tooltipLabelIntersectionSizeFormatter(v) {
return v;
}
});
export { VennChart as default };
function stringArrayIsEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
return arr1.every(function (item) {
return arr2.includes(item);
});
}
function getElementDataByKey(data, key) {
return data.find(function (el) {
return el.sets.length === 1 && el.sets[0] === key;
});
}
//# sourceMappingURL=VennChart.js.map