UNPKG

fybdp-d3-kg

Version:

Knowledge Graph using React and D3.js

1,276 lines (1,240 loc) 1.11 MB
/** * 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