fybdp-d3-kg
Version:
Knowledge Graph using React and D3.js
1,276 lines (1,240 loc) • 1.11 MB
JavaScript
/**
* fybdp-d3-kg@1.0.1 is a Data visualization library for React based on D3.
* Copyright©2020 FUYUN DATA All Rights Reserved.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('classnames'), require('memoize-one'), require('ellipsize'), require('d3-array'), require('calculate-size'), require('human-format'), require('d3-shape'), require('transformation-matrix'), require('d3-scale'), require('memoize-bind'), require('react-sizeme'), require('big-integer'), require('chroma-js'), require('rdk'), require('framer-motion'), require('is-equal'), require('d3-interpolate'), require('d3-geo'), require('d3-sankey'), require('react-countup'), require('d3'), require('d3-interpolate/src/transform/parse'), require('font-awesome/css/font-awesome.css'), require('sweetalert2')) :
typeof define === 'function' && define.amd ? define(['exports', 'react', 'classnames', 'memoize-one', 'ellipsize', 'd3-array', 'calculate-size', 'human-format', 'd3-shape', 'transformation-matrix', 'd3-scale', 'memoize-bind', 'react-sizeme', 'big-integer', 'chroma-js', 'rdk', 'framer-motion', 'is-equal', 'd3-interpolate', 'd3-geo', 'd3-sankey', 'react-countup', 'd3', 'd3-interpolate/src/transform/parse', 'font-awesome/css/font-awesome.css', 'sweetalert2'], factory) :
(global = global || self, factory(global['d3-kg'] = {}, global.React, global.classNames, global.memoize, global.ellipsize, global.d3Array, global.calculateSize, global.humanFormat, global.d3Shape, global.transformationMatrix, global.d3Scale, global.bind, global.reactSizeme, global.bigInt, global.chroma, global.rdk, global.framerMotion, global.isEqual, global.d3Interpolate, global.d3Geo, global.d3Sankey, global.CountUp, global.d3, global.parse, null, global.Swal));
}(this, (function (exports, React, classNames, memoize, ellipsize, d3Array, calculateSize, humanFormat, d3Shape, transformationMatrix, d3Scale, bind, reactSizeme, bigInt, chroma, rdk, framerMotion, isEqual, d3Interpolate, d3Geo, d3Sankey, CountUp, d3, parse, fontAwesome_css, Swal) { 'use strict';
var React__default = 'default' in React ? React['default'] : React;
classNames = classNames && classNames.hasOwnProperty('default') ? classNames['default'] : classNames;
memoize = memoize && memoize.hasOwnProperty('default') ? memoize['default'] : memoize;
ellipsize = ellipsize && ellipsize.hasOwnProperty('default') ? ellipsize['default'] : ellipsize;
calculateSize = calculateSize && calculateSize.hasOwnProperty('default') ? calculateSize['default'] : calculateSize;
humanFormat = humanFormat && humanFormat.hasOwnProperty('default') ? humanFormat['default'] : humanFormat;
bind = bind && bind.hasOwnProperty('default') ? bind['default'] : bind;
bigInt = bigInt && bigInt.hasOwnProperty('default') ? bigInt['default'] : bigInt;
chroma = chroma && chroma.hasOwnProperty('default') ? chroma['default'] : chroma;
isEqual = isEqual && isEqual.hasOwnProperty('default') ? isEqual['default'] : isEqual;
CountUp = CountUp && CountUp.hasOwnProperty('default') ? CountUp['default'] : CountUp;
Swal = Swal && Swal.hasOwnProperty('default') ? Swal['default'] : Swal;
class LinearAxisTickLabel extends React.Component {
getAlign() {
const { align, half } = this.props;
if ((align === 'inside' || align === 'outside') && half === 'center') {
return 'center';
}
if (align === 'inside') {
return half === 'start' ? 'end' : 'start';
}
if (align === 'outside') {
return half === 'start' ? 'start' : 'end';
}
return align;
}
getTickLineSpacing() {
const { line } = this.props;
if (!line) {
return [0, 0];
}
const size = line.props.size;
const position = line.props.position;
if (position === 'start') {
return [size * -1, 0];
}
else if (position === 'end') {
return [0, size];
}
else {
return [size * -0.5, size * 0.5];
}
}
getOffset() {
const { padding, position, rotation, orientation } = this.props;
const adjustedPadding = typeof padding === 'number'
? {
fromAxis: padding,
alongAxis: padding
}
: padding;
const spacing = this.getTickLineSpacing();
const offset1 = position === 'start'
? spacing[0] - adjustedPadding.fromAxis
: position === 'end'
? spacing[1] + adjustedPadding.fromAxis
: 0;
const align = this.getAlign();
let offset2 = rotation === true ? -5 : 0;
offset2 +=
align === 'center'
? 0
: align === 'start'
? -adjustedPadding.alongAxis
: adjustedPadding.alongAxis;
const horz = orientation === 'horizontal';
return {
[horz ? 'x' : 'y']: offset2,
[horz ? 'y' : 'x']: offset1
};
}
getTextPosition() {
const { angle, orientation, position } = this.props;
let transform = '';
let textAnchor = '';
let alignmentBaseline = 'middle';
if (angle !== 0) {
transform = `rotate(${angle})`;
textAnchor = 'end';
}
else {
const align = this.getAlign();
if (orientation === 'horizontal') {
textAnchor =
align === 'center' ? 'middle' : align === 'start' ? 'end' : 'start';
if (position === 'start') {
alignmentBaseline = 'baseline';
}
else if (position === 'end') {
alignmentBaseline = 'hanging';
}
}
else {
alignmentBaseline =
align === 'center'
? 'middle'
: align === 'start'
? 'baseline'
: 'hanging';
if (position === 'start') {
textAnchor = 'end';
}
else if (position === 'end') {
textAnchor = 'start';
}
else {
textAnchor = 'middle';
}
}
}
return {
transform,
textAnchor: this.props.textAnchor || textAnchor,
alignmentBaseline
};
}
render() {
const { fill, text, fullText, fontSize, fontFamily, className } = this.props;
const { x, y } = this.getOffset();
const textPosition = this.getTextPosition();
return (React__default.createElement("g", { transform: `translate(${x}, ${y})`, fontSize: fontSize, fontFamily: fontFamily },
React__default.createElement("title", null, fullText),
React__default.createElement("text", Object.assign({}, textPosition, { fill: fill, className: className }), text)));
}
}
LinearAxisTickLabel.defaultProps = {
fill: '#8F979F',
fontSize: 11,
fontFamily: 'sans-serif',
rotation: true,
padding: 0,
align: 'center'
};
class LinearAxisTickLine extends React.PureComponent {
positionTick() {
const { size, position, orientation } = this.props;
const isVertical = orientation === 'vertical';
const tickSize = size || 0;
const start = position === 'start' ? tickSize * -1 : position === 'center' ? tickSize * -0.5 : 0;
const end = start + tickSize;
return {
x1: isVertical ? end : 0,
x2: isVertical ? start : 0,
y1: isVertical ? 0 : start,
y2: isVertical ? 0 : end
};
}
render() {
const { strokeColor, strokeWidth, className } = this.props;
const path = this.positionTick();
return React__default.createElement("line", Object.assign({ className: className, strokeWidth: strokeWidth, stroke: strokeColor }, path));
}
}
LinearAxisTickLine.defaultProps = {
strokeColor: '#8F979F',
strokeWidth: 1,
size: 5
};
// https://stackoverflow.com/questions/673905/best-way-to-determine-users-locale-within-browser
const getNavigatorLanguage = () => {
if (typeof window === 'undefined') {
return 'en';
}
if (navigator.languages && navigator.languages.length) {
return navigator.languages[0];
}
if (navigator.userLanguage ||
navigator.language ||
navigator.browserLanguage) {
return 'en';
}
};
const locale = getNavigatorLanguage();
const options = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour12: true,
formatMatcher: 'best fit'
};
/**
* Format a value based on type.
*/
function formatValue(value) {
if (value !== undefined) {
if (value instanceof Date) {
return value.toLocaleDateString(locale, options);
}
else if (typeof value === 'number') {
return value.toLocaleString();
}
return value;
}
return 'No value';
}
const ONE_DAY = 60 * 60 * 24;
const DURATION_TICK_STEPS = [
0.001,
0.005,
0.01,
0.05,
0.1,
0.5,
1,
5,
10,
15,
60,
60 * 15,
60 * 30,
60 * 60,
60 * 60 * 2,
60 * 60 * 4,
60 * 60 * 6,
60 * 60 * 8,
60 * 60 * 12,
ONE_DAY // 24 h
];
/**
* Reduce the ticks to the max number of ticks.
*/
function reduceTicks(ticks, maxTicks) {
if (ticks.length > maxTicks) {
const reduced = [];
const modulus = Math.floor(ticks.length / maxTicks);
for (let i = 0; i < ticks.length; i++) {
if (i % modulus === 0) {
reduced.push(ticks[i]);
}
}
ticks = reduced;
}
return ticks;
}
/**
* Determine the max ticks for the available width.
*/
function getMaxTicks(size, dimension) {
const tickWidth = Math.max(size, 0);
return Math.floor(dimension / tickWidth);
}
/**
* Formats the ticks in a duration format.
*/
function getDurationTicks(domain, maxTicks) {
const domainWidth = domain[1] - domain[0];
let tickStep = null;
for (const s of DURATION_TICK_STEPS) {
if (domainWidth / s < maxTicks) {
tickStep = s;
break;
}
}
if (tickStep === null) {
const numDayTicks = domainWidth / ONE_DAY;
const dayStep = Math.ceil(numDayTicks / maxTicks);
tickStep = ONE_DAY * dayStep;
}
const ticks = [domain[0]];
while (ticks[ticks.length - 1] + tickStep <= domain[1]) {
ticks.push(ticks[ticks.length - 1] + tickStep);
}
return ticks;
}
/**
* Get the tick values from the scale.
*/
function getTicks(scale, tickValues, type, maxTicks = 100, interval) {
let result;
if (tickValues) {
result = tickValues;
}
else {
if (scale.ticks) {
if (type === 'duration') {
result = getDurationTicks(scale.domain(), maxTicks);
}
else if (interval) {
result = scale.ticks(interval);
}
else {
if (type === 'time') {
// If its time, we need to handle the time count
// manually because d3 does this odd rounding
result = scale.ticks();
result = reduceTicks(result, maxTicks);
}
else {
result = scale.ticks(maxTicks);
}
}
}
else {
tickValues = scale.domain();
result = reduceTicks(tickValues, maxTicks);
}
}
return result;
}
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
function __rest(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;
}
/**
* CloneElement is a wrapper component for createElement function.
* This allows you to describe your cloning element declaratively
* which is a more natural API for React.
*/
class CloneElement extends React.PureComponent {
constructor() {
super(...arguments);
this.getProjectedProps = memoize(props => {
const childProps = this.props.element.props;
return Object.keys(props).reduce((acc, key) => {
const prop = props[key];
const childProp = childProps[key];
if (typeof prop === 'function' && typeof childProp === 'function') {
acc[key] = args => {
prop(args);
childProp(args);
};
}
else if (key === 'className') {
acc[key] = classNames(prop, childProp);
}
else {
acc[key] = prop;
}
return acc;
}, {});
});
}
render() {
const _a = this.props, { element, children } = _a, rest = __rest(_a, ["element", "children"]);
if (element === null) {
return children;
}
const newProps = this.getProjectedProps(rest);
return React.cloneElement(element, Object.assign(Object.assign(Object.assign({}, element.props), newProps), { children }));
}
}
const calculateDimensions = (text, fontFamily, fontSize) => {
// SSR Rendering doesn't support canvas measurements
// we have to make a guess in this case...
if (typeof document === 'undefined') {
return {
width: text.length * 8,
height: 25
};
}
return calculateSize(text, {
font: fontFamily,
fontSize: `${fontSize}px`
});
};
class LinearAxisTickSeries extends React.Component {
/**
* Gets the adjusted scale given offsets.
*/
getAdjustedScale() {
const { scale } = this.props;
if (scale.bandwidth) {
let offset = scale.bandwidth() / 2;
if (scale.round()) {
offset = Math.round(offset);
}
return d => +scale(d) + offset;
}
else {
return d => +scale(d);
}
}
/**
* Gets the x/y position for a given tick.
*/
getPosition(scaledTick) {
const { orientation } = this.props;
if (orientation === 'horizontal') {
return { x: scaledTick, y: 0 };
}
else {
return { x: 0, y: scaledTick };
}
}
/**
* Gets the dimension (height/width) this axis is calculating on.
*/
getDimension() {
const { height, width, orientation } = this.props;
return orientation === 'vertical' ? height : width;
}
/**
* Calculates the rotation angle that the ticks need to be shifted to.
* This equation will measure the length of the text in a external canvas
* object and determine what the longest label is and rotate until they fit.
*/
getRotationAngle(ticks) {
if (!this.props.label) {
return 0;
}
const label = this.props.label.props;
const dimension = this.getDimension();
const maxTicksLength = d3Array.max(ticks, tick => tick.width);
let angle = 0;
if (label.rotation) {
if (label.rotation === true) {
let baseWidth = maxTicksLength;
const maxBaseWidth = Math.floor(dimension / ticks.length);
while (baseWidth > maxBaseWidth && angle > -90) {
angle -= 30;
baseWidth = Math.cos(angle * (Math.PI / 180)) * maxTicksLength;
}
}
else {
angle = label.rotation;
}
}
return angle;
}
/**
* Gets the formatted label of the tick.
*/
getLabelFormat() {
const { label, scale } = this.props;
if (label && label.props.format) {
return label.props.format;
}
else if (scale.tickFormat) {
return scale.tickFormat.apply(scale, [5]);
}
else {
return v => formatValue(v);
}
}
/**
* Gets the ticks given the dimensions and scales and returns
* the text and position.
*/
getTicks() {
const { scale, tickSize, tickValues, interval, axis, label } = this.props;
const dimension = this.getDimension();
const maxTicks = getMaxTicks(tickSize, dimension);
const ticks = getTicks(scale, tickValues, axis.type, maxTicks, interval);
const adjustedScale = this.getAdjustedScale();
const format = this.getLabelFormat();
const midpoint = dimension / 2;
return ticks.map(tick => {
const fullText = format(tick);
const scaledTick = adjustedScale(tick);
const position = this.getPosition(scaledTick);
const text = ellipsize(fullText, 18);
const size = label
? calculateDimensions(text, label.props.fontFamily, label.props.fontSize.toString())
: {};
return Object.assign(Object.assign(Object.assign({}, position), size), { text,
fullText, half: scaledTick === midpoint
? 'center'
: scaledTick < midpoint
? 'start'
: 'end' });
});
}
render() {
const { label, line, height, width, orientation } = this.props;
const ticks = this.getTicks();
const angle = this.getRotationAngle(ticks);
return (React__default.createElement(React.Fragment, null, ticks.map((tick, i) => (React__default.createElement("g", { key: i, transform: `translate(${tick.x}, ${tick.y})` },
line && (React__default.createElement(CloneElement, { element: line, height: height, width: width, orientation: orientation })),
label && (React__default.createElement(CloneElement, { element: label, text: tick.text, fullText: tick.fullText, half: tick.half, angle: angle, orientation: orientation, line: line })))))));
}
}
LinearAxisTickSeries.defaultProps = {
line: React__default.createElement(LinearAxisTickLine, null),
label: React__default.createElement(LinearAxisTickLabel, null),
tickSize: 30
};
const humanFormatScale = new humanFormat.Scale({
k: 1000,
M: 1000000,
B: 1000000000
});
const humanFormatMillionScale = new humanFormat.Scale({
M: 1,
B: 1000,
T: 1000000
});
const ONE_MILLION = 1000000;
const ONE_BILLION = 1000000000;
const humanFormatBigInteger = bigInteger => {
if (bigInteger.greater(ONE_BILLION)) {
return humanFormat(bigInteger.divide(ONE_MILLION).toJSNumber(), {
scale: humanFormatMillionScale
});
}
return humanFormat(bigInteger.toJSNumber(), { scale: humanFormatScale });
};
const bigIntegerToLocaleString = bigInteger => {
let i = 0;
let formattedString = '';
for (const c of bigInteger
.toString()
.split('')
.reverse()) {
if (i > 0 && i % 3 === 0) {
formattedString = ',' + formattedString;
}
formattedString = c + formattedString;
i++;
}
return formattedString;
};
/**
* Given a margins object, returns the top/left/right/bottom positions.
*/
function parseMargins(margins) {
let top = 0;
let right = 0;
let bottom = 0;
let left = 0;
if (Array.isArray(margins)) {
if (margins.length === 2) {
top = margins[0];
bottom = margins[0];
left = margins[1];
right = margins[1];
}
else if (margins.length === 4) {
top = margins[0];
right = margins[1];
bottom = margins[2];
left = margins[3];
}
}
else if (margins !== undefined) {
top = margins;
right = margins;
bottom = margins;
left = margins;
}
return {
top,
right,
bottom,
left
};
}
/**
* Calculates the margins for the chart.
*/
function calculateMarginOffsets(height, width, margins) {
const { left, right, bottom, top } = margins;
const newHeight = height - top - bottom;
const newWidth = width - left - right;
return {
height: newHeight,
width: newWidth
};
}
/**
* Calculates the dimensions for the chart.
*/
function getDimension({ xOffset, yOffset, height, width, margins }) {
const parsedMargins = parseMargins(margins);
const marginDims = calculateMarginOffsets(height, width, parsedMargins);
const chartWidth = marginDims.width - xOffset;
const chartHeight = marginDims.height - yOffset;
return {
xOffset,
yOffset,
height,
width,
chartWidth,
chartHeight,
xMargin: xOffset + parsedMargins.left,
yMargin: parsedMargins.top
};
}
/**
* Gets the min/max values handling nested arrays.
*/
function extent(data, attr) {
const accessor = (val, fn) => {
if (Array.isArray(val.data)) {
return fn(val.data, vv => vv[attr]);
}
return val[attr];
};
const minVal = d3Array.min(data, d => accessor(d, d3Array.min));
const maxVal = d3Array.max(data, d => accessor(d, d3Array.max));
return [minVal, maxVal];
}
/**
* Get the domain for the Y Axis.
*/
function getYDomain({ data, scaled = false, isDiverging = false }) {
const [startY, endY] = extent(data, 'y');
const [startY1, endY1] = extent(data, 'y1');
// If dealing w/ negative numbers, we should
// normalize the top and bottom values
if (startY < 0 || isDiverging) {
const posStart = -startY;
const maxNum = Math.max(posStart, endY);
return [-maxNum, maxNum];
}
// Scaled start scale at non-zero
if (scaled) {
return [startY1, endY1];
}
// Start at 0 based
return [0, endY1];
}
/**
* Get the domain for the X Axis.
*/
function getXDomain({ data, scaled = false, isDiverging = false }) {
const [startX, endX] = extent(data, 'x');
const [startX0] = extent(data, 'x0');
// Histograms use dates for start/end
if (typeof startX === 'number' && typeof endX === 'number') {
// If dealing w/ negative numbers, we should
// normalize the top and bottom values
if (startX0 < 0 || isDiverging) {
const posStart = -startX0;
const maxNum = Math.max(posStart, endX);
return [-maxNum, maxNum];
}
// If not scaled, return 0/max domains
if (!scaled) {
return [0, endX];
}
}
// Scaled start scale at non-zero
return [startX, endX];
}
/**
* Helper function for interpolation.
*/
function interpolate(type) {
if (type === 'smooth') {
return d3Shape.curveMonotoneX;
}
else if (type === 'step') {
return d3Shape.curveStep;
}
else {
return d3Shape.curveLinear;
}
}
/**
* Add ability to calculate scale band position.
* Reference: https://stackoverflow.com/questions/38633082/d3-getting-invert-value-of-band-scales
*/
const scaleBandInvert = (scale) => {
const domain = scale.domain();
const paddingOuter = scale(domain[0]);
const eachBand = scale.step();
return (value) => {
const index = Math.floor((value - paddingOuter) / eachBand);
return domain[Math.max(0, Math.min(index, domain.length - 1))];
};
};
/**
* Given a point position, get the closes data point in the dataset.
*/
function getClosestPoint(pos, scale, data, attr = 'x') {
// If we have a band scale, handle that special
const domain = scale.invert ? scale.invert(pos) : scaleBandInvert(scale)(pos);
// Select the index
const bisect = d3Array.bisector((d) => d[attr]).right;
const index = bisect(data, domain);
// Determine min index
const minIndex = Math.max(0, index - 1);
const before = data[minIndex];
// Determine max index
const maxIndex = Math.min(data.length - 1, index);
const after = data[maxIndex];
// Determine which is closest to the point
let beforeVal = before[attr];
let afterVal = after[attr];
beforeVal = domain - beforeVal;
afterVal = afterVal - domain;
return beforeVal < afterVal ? before : after;
}
/**
* Given an event, get the parent svg element;
*/
function getParentSVG(event) {
// set node to targets owner svg
let node = event.target.ownerSVGElement;
// find the outermost svg
if (node) {
while (node.ownerSVGElement) {
node = node.ownerSVGElement;
}
}
return node;
}
/**
* Given an event, get the relative X/Y position for a target.
*/
function getPositionForTarget({ target, clientX, clientY }) {
const { top, left } = target.getBoundingClientRect();
return {
x: clientX - left - target.clientLeft,
y: clientY - top - target.clientTop
};
}
/**
* Gets the point from q given matrix.
*/
function getPointFromMatrix(event, matrix) {
const parent = getParentSVG(event);
if (!parent) {
return null;
}
// Determines client coordinates relative to the editor component
const { top, left } = parent.getBoundingClientRect();
const x = event.clientX - left;
const y = event.clientY - top;
// Transforms the coordinate to world coordinate (in the SVG/DIV world)
return transformationMatrix.applyToPoint(transformationMatrix.inverse(matrix), { x, y });
}
/**
* Get the start/end matrix.
*/
function getLimitMatrix(height, width, matrix) {
return transformationMatrix.applyToPoints(matrix, [
{ x: 0, y: 0 },
{ x: width, y: height }
]);
}
/**
* Constrain the matrix.
*/
function constrainMatrix(height, width, matrix) {
const [min, max] = getLimitMatrix(height, width, matrix);
if (max['x'] < width || max['y'] < height) {
return true;
}
if (min['x'] > 0 || min['y'] > 0) {
return true;
}
return false;
}
/**
* Determine if scale factor is less than allowed.
*/
function lessThanScaleFactorMin(value, scaleFactor) {
return value.scaleFactorMin && value.d * scaleFactor <= value.scaleFactorMin;
}
/**
* Determine if scale factor is larger than allowed.
*/
function moreThanScaleFactorMax(value, scaleFactor) {
return value.scaleFactorMax && value.d * scaleFactor >= value.scaleFactorMax;
}
/**
* Determine if both min and max scale fctors are going out of bounds.
*/
function isZoomLevelGoingOutOfBounds(value, scaleFactor) {
const a = lessThanScaleFactorMin(value, scaleFactor) && scaleFactor < 1;
const b = moreThanScaleFactorMax(value, scaleFactor) && scaleFactor > 1;
return a || b;
}
/**
* Toggle the text selection of the body.
*/
function toggleTextSelection(allowSelection) {
const style = allowSelection ? '' : 'none';
[
'-webkit-touch-callout',
'-webkit-user-select',
'-khtml-user-select',
'-moz-user-select',
'-ms-user-select',
'user-select'
].forEach(prop => (document.body.style[prop] = style));
}
/**
* Calculates whether the stroke should be shown.
*/
function calculateShowStroke(current, data) {
const i = data.indexOf(current);
let showLine = false;
const prev = data[i - 1];
if (i > 0 && prev.y) {
showLine = true;
}
const cur = data[i];
if (cur.y) {
showLine = true;
}
const next = data[i + 1];
if (i < data.length - 1 && next.y) {
showLine = true;
}
return showLine;
}
/**
* Get the angle from a radian.
*/
const getDegrees = (radians) => (radians / Math.PI) * 180 - 90;
const functionProps = (prop, val, data) => {
if (typeof val === 'function') {
return val(data);
}
else if (prop === 'className') {
return classNames(val);
}
else if (val !== undefined || val !== null) {
return val;
}
return {};
};
const constructFunctionProps = (props, data) => ({
className: functionProps('className', props.className, data),
style: functionProps('style', props.style, data)
});
/**
* Given a dataset and a list of accessors, returns a unique collection.
*/
function uniqueBy(data, ...accessors) {
const result = [];
const ittr = (arr, depth) => {
for (const a of arr) {
const acc = accessors[depth];
if (acc === undefined) {
throw new Error(`Accessor not found for depth: ${depth}`);
}
const val = acc(a);
if (Array.isArray(val)) {
ittr(val, depth + 1);
}
else if (!result.includes(val)) {
result.push(val);
}
}
};
ittr(data, 0);
return result;
}
let axisLineId = 0;
class LinearAxisLine extends React.Component {
constructor() {
super(...arguments);
this.state = {
id: (axisLineId++).toString()
};
}
render() {
const { strokeColor, strokeGradient, scale, orientation, className } = this.props;
const { id } = this.state;
const [range0, range1] = scale.range();
return (React__default.createElement(React.Fragment, null,
React__default.createElement("line", { className: className, x1: orientation === 'vertical' ? 0 : range0,
// Workaround for a Chrome/Firefox bug where it won't render gradients for straight lines
x2: orientation === 'vertical' ? 0.00001 : range1, y1: orientation === 'vertical' ? range0 : 0, y2: orientation === 'vertical' ? range1 : 0.00001, strokeWidth: 1, stroke: strokeGradient ? `url(#axis-gradient-${id})` : strokeColor }),
strokeGradient && (React__default.createElement(CloneElement, { element: strokeGradient, id: `axis-gradient-${id}` }))));
}
}
LinearAxisLine.defaultProps = {
strokeColor: '#8F979F',
strokeWidth: 1
};
class LinearAxis extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
this.state = {
height: props.height,
width: props.width
};
}
componentDidMount() {
this.updateDimensions();
}
componentDidUpdate(prevProps) {
const { height, width, scale } = this.props;
if (width !== prevProps.width ||
height !== prevProps.height ||
scale !== prevProps.scale) {
this.updateDimensions();
}
}
updateDimensions() {
const { onDimensionsChange, orientation, position } = this.props;
const shouldOffset = position !== 'center';
let height;
let width;
if (shouldOffset) {
const dims = this.ref.current.getBoundingClientRect();
width = Math.floor(dims.width);
height = Math.floor(dims.height);
}
if (orientation === 'vertical') {
if (this.state.width !== width) {
this.setState({ width });
onDimensionsChange({ width });
}
}
else {
if (this.state.height !== height) {
this.setState({ height });
onDimensionsChange({ height });
}
}
}
getPosition() {
const { position, width, height, orientation } = this.props;
let translateY = 0;
let translateX = 0;
if (position === 'end' && orientation === 'horizontal') {
translateY = height;
}
else if (position === 'center' && orientation === 'horizontal') {
translateY = height / 2;
}
else if (position === 'end' && orientation === 'vertical') {
translateX = width;
}
else if (position === 'center' && orientation === 'vertical') {
translateX = width / 2;
}
return { translateX, translateY };
}
render() {
const { scale, height, width, orientation, axisLine, tickSeries } = this.props;
const { translateX, translateY } = this.getPosition();
return (React__default.createElement("g", { transform: `translate(${translateX}, ${translateY})`, ref: this.ref },
axisLine && (React__default.createElement(CloneElement, { element: axisLine, height: height, width: width, scale: scale, orientation: orientation })),
(tickSeries.props.line || tickSeries.props.label) && (React__default.createElement(CloneElement, { element: tickSeries, height: height, width: width, scale: scale, orientation: orientation, axis: this.props }))));
}
}
LinearAxis.defaultProps = {
axisLine: React__default.createElement(LinearAxisLine, null),
tickSeries: React__default.createElement(LinearAxisTickSeries, null),
scaled: false,
roundDomains: false,
onDimensionsChange: () => undefined
};
class LinearXAxisTickLabel extends React.Component {
render() {
return React__default.createElement(LinearAxisTickLabel, Object.assign({}, this.props));
}
}
LinearXAxisTickLabel.defaultProps = Object.assign(Object.assign({}, LinearAxisTickLabel.defaultProps), { rotation: true, position: 'end', align: 'center' });
class LinearXAxisTickLine extends React.Component {
render() {
return React__default.createElement(LinearAxisTickLine, Object.assign({}, this.props));
}
}
LinearXAxisTickLine.defaultProps = Object.assign(Object.assign({}, LinearAxisTickLine.defaultProps), { position: 'end' });
class LinearXAxisTickSeries extends React.Component {
render() {
return React__default.createElement(LinearAxisTickSeries, Object.assign({}, this.props));
}
}
LinearXAxisTickSeries.defaultProps = Object.assign(Object.assign({}, LinearAxisTickSeries.defaultProps), { tickSize: 75, line: React__default.createElement(LinearXAxisTickLine, null), label: React__default.createElement(LinearXAxisTickLabel, null) });
class LinearXAxis extends React.Component {
render() {
return React__default.createElement(LinearAxis, Object.assign({}, this.props));
}
}
LinearXAxis.defaultProps = Object.assign(Object.assign({}, LinearAxis.defaultProps), { position: 'end', roundDomains: false, scaled: false, type: 'value', orientation: 'horizontal', tickSeries: React__default.createElement(LinearXAxisTickSeries, null) });
class LinearYAxisTickLabel extends React.Component {
render() {
return React__default.createElement(LinearAxisTickLabel, Object.assign({}, this.props));
}
}
LinearYAxisTickLabel.defaultProps = Object.assign(Object.assign({}, LinearAxisTickLabel.defaultProps), { rotation: false, position: 'start', align: 'center' });
class LinearYAxisTickLine extends React.Component {
render() {
return React__default.createElement(LinearAxisTickLine, Object.assign({}, this.props));
}
}
LinearYAxisTickLine.defaultProps = Object.assign(Object.assign({}, LinearAxisTickLine.defaultProps), { position: 'start' });
class LinearYAxisTickSeries extends React.Component {
render() {
return React__default.createElement(LinearAxisTickSeries, Object.assign({}, this.props));
}
}
LinearYAxisTickSeries.defaultProps = Object.assign(Object.assign({}, LinearAxisTickSeries.defaultProps), { tickSize: 30, line: React__default.createElement(LinearYAxisTickLine, null), label: React__default.createElement(LinearYAxisTickLabel, null) });
class LinearYAxis extends React.Component {
render() {
return React__default.createElement(LinearAxis, Object.assign({}, this.props));
}
}
LinearYAxis.defaultProps = Object.assign(Object.assign({}, LinearAxis.defaultProps), { orientation: 'vertical', scaled: false, roundDomains: false, type: 'value', position: 'start', tickSeries: React__default.createElement(LinearYAxisTickSeries, null) });
/**
* Returns whether the axis has a visual element or not.
*/
const isAxisVisible = (axis) => !!axis.tickSeries.props.label || !!axis.tickSeries.props.line;
class RadialAxisTickLine extends React.PureComponent {
render() {
const { stroke, size, position, innerRadius, outerRadius } = this.props;
const x1 = position === 'outside' ? size : -(outerRadius - innerRadius);
return (React__default.createElement("line", { x1: x1, x2: 0, stroke: stroke, style: { pointerEvents: 'none' } }));
}
}
RadialAxisTickLine.defaultProps = {
stroke: 'rgba(113, 128, 141, .5)',
size: 10,
position: 'inside'
};
const rad2deg = (angle) => (angle * 180) / Math.PI;
class RadialAxisTickLabel extends React.PureComponent {
getPosition() {
const { point, autoRotate, rotation, padding } = this.props;
let textAnchor;
let transform;
if (autoRotate) {
const l = point >= Math.PI;
const r = point < 2 * Math.PI;
// TODO: This centers the text, determine better way later
if ((rotation >= 85 && rotation <= 95) ||
(rotation <= -85 && rotation >= -95)) {
textAnchor = 'middle';
}
else if (l && r) {
textAnchor = 'end';
}
else {
textAnchor = 'start';
}
transform = `rotate(${90 - rad2deg(point)}, ${padding}, 0)`;
}
else {
const shouldRotate = rotation > 100 && rotation;
const rotate = shouldRotate ? 180 : 0;
const translate = shouldRotate ? -30 : 0;
textAnchor = shouldRotate ? 'end' : 'start';
transform = `rotate(${rotate}) translate(${translate})`;
}
return {
transform,
textAnchor
};
}
render() {
const { data, fill, fontFamily, fontSize, format, lineSize, index } = this.props;
const text = format ? format(data, index) : formatValue(data);
const { transform, textAnchor } = this.getPosition();
return (React__default.createElement("g", { transform: transform },
React__default.createElement("title", null, text),
React__default.createElement("text", { dy: "0.35em", x: lineSize + 5, textAnchor: textAnchor, fill: fill, fontFamily: fontFamily, fontSize: fontSize }, text)));
}
}
RadialAxisTickLabel.defaultProps = {
fill: '#71808d',
fontSize: 11,
padding: 15,
fontFamily: 'sans-serif',
autoRotate: true
};
class RadialAxisTick extends React.Component {
render() {
const { line, label, scale, outerRadius, data, index, padding, innerRadius } = this.props;
const point = scale(data);
const rotation = (point * 180) / Math.PI - 90;
const transform = `rotate(${rotation}) translate(${outerRadius +
padding},0)`;
const lineSize = line ? line.props.size : 0;
return (React__default.createElement("g", { transform: transform },
line && (React__default.createElement(CloneElement, { element: line, innerRadius: innerRadius, outerRadius: outerRadius })),
label && (React__default.createElement(CloneElement, { element: label, index: index, point: point, rotation: rotation, lineSize: lineSize, data: data }))));
}
}
RadialAxisTick.defaultProps = {
outerRadius: 0,
padding: 0,
line: React__default.createElement(RadialAxisTickLine, null),
label: React__default.createElement(RadialAxisTickLabel, null)
};
class RadialAxisTickSeries extends React.Component {
render() {
const { scale, count, outerRadius, tick, tickValues, innerRadius, interval } = this.props;
const ticks = getTicks(scale, tickValues, 'time', count, interval || count);
return (React__default.createElement(React.Fragment, null, ticks.map((data, i) => (React__default.createElement(CloneElement, { element: tick, key: i, index: i, scale: scale, data: data, innerRadius: innerRadius, outerRadius: outerRadius })))));
}
}
RadialAxisTickSeries.defaultProps = {
count: 12,
tick: React__default.createElement(RadialAxisTick, null)
};
class RadialAxisArc extends React.Component {
render() {
const { index, stroke, strokeDasharray, scale } = this.props;
const r = scale(index);
const strokeColor = typeof stroke === 'string' ? stroke : stroke(index);
const strokeDash = typeof strokeDasharray === 'string'
? strokeDasharray
: strokeDasharray(index);
return (React__default.createElement("circle", { fill: "none", strokeDasharray: strokeDash, stroke: strokeColor, style: { pointerEvents: 'none' }, cx: "0", cy: "0", r: r }));
}
}
RadialAxisArc.defaultProps = {
stroke: '#71808d',
strokeDasharray: '1,4'
};
class RadialAxisArcSeries extends React.Component {
render() {
const { count, innerRadius, outerRadius, arc } = this.props;
const scale = d3Scale.scaleLinear()
.domain([0, count])
.range([innerRadius, outerRadius]);
const arcs = scale.ticks(count);
return (React__default.createElement(React.Fragment, null, arcs.map(d => (React__default.createElement(CloneElement, { element: arc, key: d, index: d, scale: scale })))));
}
}
RadialAxisArcSeries.defaultProps = {
count: 12,
arc: React__default.createElement(RadialAxisArc, null)
};
class RadialAxis extends React.Component {
render() {
const { arcs, ticks, xScale, height, width, innerRadius } = this.props;
const outerRadius = Math.min(height, width) / 2;
return (React__default.createElement(React.Fragment, null,
arcs && (React__default.createElement(CloneElement, { element: arcs, outerRadius: outerRadius, innerRadius: innerRadius })),
ticks && (React__default.createElement(CloneElement, { element: ticks, scale: xScale, innerRadius: innerRadius, outerRadius: outerRadius }))));
}
}
RadialAxis.defaultProps = {
innerRadius: 10,
arcs: React__default.createElement(RadialAxisArcSeries, null),
ticks: React__default.createElement(RadialAxisTickSeries, null)
};
class Move extends React.Component {
constructor() {
super(...arguments);
this.started = false;
this.deltaX = 0;
this.deltaY = 0;
this.prevXPosition = 0;
this.prevYPosition = 0;
this.onMouseMove = event => {
event.preventDefault();
event.stopPropagation();
const { movementX, movementY } = event;
this.deltaX = this.deltaX + movementX;
this.deltaY = this.deltaY + movementY;
if (this.checkThreshold()) {
this.disableText(true);
this.setCursor(true);
this.deltaX = 0;
this.deltaY = 0;
this.started = true;
this.props.onMoveStart({
nativeEvent: event,
type: 'mouse'
});
}
else {
this.rqf = requestAnimationFrame(() => {
this.props.onMove({
nativeEvent: event,
type: 'mouse',
x: movementX,
y: movementY
});
});
}
};
this.onMouseUp = event => {
event.preventDefault();
event.stopPropagation();
this.disposeHandlers();
if (this.started) {
this.props.onMoveEnd({
nativeEvent: event,
type: 'mouse'
});
}
else {
this.props.onMoveCancel({
nativeEvent: event,
type: 'mouse'
});
}
};
this.onTouchMove = (event) => {
event.preventDefault();
event.stopPropagation();
// Calculate delta from previous position and current
const { clientX, clientY } = this.getTouchCoords(event);
const deltaX = clientX - this.prevXPosition;
const deltaY = clientY - this.prevYPosition;
// Track the delta
this.deltaX = this.deltaX + deltaX;
this.deltaY = this.deltaY + deltaY;
if (this.checkThreshold()) {
this.disableText(true);
this.setCursor(true);
this.deltaX = 0;
this.deltaY = 0;
this.started = true;
this.props.onMoveStart({
// TODO: Come back and clean this up...
nativeEvent: Object.assign(Object.assign({}, event), { clientX,
clientY }),
type: 'touch'
});
}
else {
this.rqf = requestAnimationFrame(() => {
this.props.onMove({
// TODO: Come back and clean this up...
nativeEvent: Object.assign(Object.assign({}, event), { clientX,
clientY }),
type: 'touch',
x: deltaX,
y: d