lucid-ui
Version:
A UI component library from AppNexus.
464 lines (377 loc) • 18.8 kB
JavaScript
import _min from "lodash/min";
import _max from "lodash/max";
import _some from "lodash/some";
import _isUndefined from "lodash/isUndefined";
import _reduce from "lodash/reduce";
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
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; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import * as d3Axis from 'd3-axis';
import * as d3Shape from 'd3-shape';
import * as d3Drag from 'd3-drag';
import * as d3Selection from 'd3-selection';
import * as d3Transition from 'd3-transition';
import ReactDOM from 'react-dom';
import { d3Scale } from '../../index';
import * as d3Array from 'd3-array';
import { lucidClassNames } from '../../util/style-helpers';
import { getGroup, lucidXAxis } from './d3-helpers';
var cx = lucidClassNames.bind('&-DraggableLineChart');
var getAttributes = function getAttributes(selection, obj) {
return _reduce(obj, function (acc, value) {
// @ts-ignore
acc[value] = selection.attr(value);
return acc;
}, {});
};
var DraggableLineChartD3 = function DraggableLineChartD3(_selection, params) {
var _this = this;
_classCallCheck(this, DraggableLineChartD3);
_defineProperty(this, "selection", void 0);
_defineProperty(this, "params", void 0);
_defineProperty(this, "xScale", void 0);
_defineProperty(this, "yScale", void 0);
_defineProperty(this, "setMouseDown", function (isMouseDown, mouseDownStep) {
_this.params.isMouseDown = isMouseDown;
_this.params.mouseDownStep = mouseDownStep;
});
_defineProperty(this, "getIsMouseDown", function () {
return _this.params.isMouseDown;
});
_defineProperty(this, "getMouseDownStep", function () {
return _this.params.mouseDownStep || 0;
});
_defineProperty(this, "getHasRenderedPoint", function () {
return !!_this.params.hasRenderedPoint;
});
_defineProperty(this, "getHasRenderedLine", function () {
return !!_this.params.hasRenderedLine;
});
_defineProperty(this, "setHasRenderedPoint", function () {
_this.params.hasRenderedPoint = true;
});
_defineProperty(this, "setHasRenderedLine", function () {
_this.params.hasRenderedLine = true;
});
_defineProperty(this, "shouldShowPreselect", function () {
var hasUserValues = _some(_this.params.data, function (_ref) {
var y = _ref.y;
return y > 0;
});
return !!_this.params.onPreselect && !hasUserValues;
});
_defineProperty(this, "drag", function () {
var xScale = _this.xScale,
yScale = _this.yScale,
renderLine = _this.renderLine,
renderPoints = _this.renderPoints,
selection = _this.selection;
var _this$params = _this.params,
cx = _this$params.cx,
onDragEnd = _this$params.onDragEnd;
var initialPosition;
return d3Drag.drag().on('start', function () {
var activeDot = d3Selection.select(this);
initialPosition = Number(activeDot.attr('cy'));
}).on('drag', function (pointData) {
var _yScale$range = yScale.range(),
_yScale$range2 = _slicedToArray(_yScale$range, 2),
max = _yScale$range2[0],
min = _yScale$range2[1];
var activeDot = d3Selection.select(this);
var adjMouseY = initialPosition + d3Selection.event.y;
var newPointY = adjMouseY < min ? min : adjMouseY > max ? max : adjMouseY;
var lines = selection.selectAll("path.".concat(cx('&-Line')));
pointData.y = Number(yScale.invert(newPointY));
activeDot.attr('cy', newPointY);
var line = d3Shape.line().x(function (chartData) {
return xScale(chartData.x) || 0;
}).y(function (chartData) {
return yScale(chartData.y);
});
lines.attr('d', line);
}).on('end', function (d) {
if (onDragEnd) onDragEnd(d.y, d.x);
renderLine();
renderPoints();
});
});
_defineProperty(this, "renderXAxis", function () {
var _this$params2 = _this.params,
margin = _this$params2.margin,
height = _this$params2.height,
xAxisTicksVertical = _this$params2.xAxisTicksVertical,
dataIsCentered = _this$params2.dataIsCentered,
cx = _this$params2.cx,
xAxisRenderProp = _this$params2.xAxisRenderProp,
data = _this$params2.data;
var xGroup = getGroup(_this.selection, "".concat(cx('&-Axis')));
xGroup.call(function (xAxis) {
xAxis.attr('transform', "translate(".concat(0, ",", margin.top, ")")).call(lucidXAxis, {
xScale: _this.xScale,
tickSize: margin.top + margin.bottom - height,
xAxisRenderProp: xAxisRenderProp,
dataIsCentered: dataIsCentered,
data: data
});
if (xAxisTicksVertical) {
xAxis.classed('Vert', true);
} else {
xAxis.classed('NoVert', true);
}
if (dataIsCentered) {
xAxis.classed('Center', true);
}
}).call(function () {
return xGroup;
});
});
_defineProperty(this, "renderYAxis", function () {
var yGroup = getGroup(_this.selection, 'yAxisGroup');
yGroup.call(function (yAxis) {
var _this$params3 = _this.params,
margin = _this$params3.margin,
cx = _this$params3.cx;
yAxis.attr('transform', "translate(".concat(margin.left, ",", 0, ")")).classed("".concat(cx('&-Axis')), true).transition().duration(500).call(d3Axis.axisLeft(_this.yScale).ticks(10));
}).call(function () {
return yGroup;
});
});
_defineProperty(this, "renderLine", function () {
if (_this.shouldShowPreselect()) {
return;
}
var _this$params4 = _this.params,
dataIsCentered = _this$params4.dataIsCentered,
cx = _this$params4.cx;
if (!_this.getHasRenderedLine()) {
if (dataIsCentered) {
var innerXTickWidth = _this.xScale.step();
_this.selection.append('g').append('path').attr('class', "".concat(cx('&-Line'))).attr('transform', "translate(".concat(innerXTickWidth / 2, ", 0)"));
} else {
_this.selection.append('g').append('path').attr('class', "".concat(cx('&-Line')));
}
_this.setHasRenderedLine();
}
var lines = _this.selection.selectAll("path.".concat(cx('&-Line')));
lines.datum(_this.params.data).enter();
lines.transition(d3Transition.transition().duration(100)).attr('fill', 'none').attr('d', d3Shape.line().x(function (d) {
return _this.xScale(d.x) || 0;
}).y(function (d) {
return _this.yScale(d.y);
}));
});
_defineProperty(this, "renderEmptyRenderProp", function (height, width) {
var emptyRenderProp = _this.params.emptyRenderProp;
if (!emptyRenderProp || !_this.shouldShowPreselect()) {
return;
}
var emptyDataObject = _this.selection.selectAll('.emptyRender');
if (emptyDataObject.empty()) {
var emptyRender = _this.selection.selectAll('.overlayContainer').append('foreignObject').attr('height', height).attr('width', width).attr('x', _this.params.margin.left).classed('emptyRender', true);
emptyRender.html(function (value, num, node) {
ReactDOM.render(emptyRenderProp(), node[0]);
});
}
});
_defineProperty(this, "renderPoints", function () {
if (_this.shouldShowPreselect()) {
return;
}
var _this$params5 = _this.params,
data = _this$params5.data,
dataIsCentered = _this$params5.dataIsCentered;
var circle = _this.getHasRenderedPoint() ? _this.selection.selectAll('circle').data(data).join('circle') : _this.selection.append('g').selectAll('circle').data(data).join('circle');
if (dataIsCentered) {
var innerXTickWidth = _this.xScale.step();
circle.transition().duration(100).attr('cx', function (d) {
return _this.xScale(d.x) || 0;
}).attr('cy', function (d) {
return _this.yScale(d.y);
}).attr('r', 5).attr('transform', "translate(".concat(innerXTickWidth / 2, ", 0)")).style('fill', '#587EBA').style('stroke', 'white').style('stroke-width', 1);
} else {
circle.transition().duration(100).attr('cx', function (d) {
return _this.xScale(d.x) || 0;
}).attr('cy', function (d) {
return _this.yScale(d.y);
}).attr('r', 5).style('fill', '#587EBA').style('stroke', 'white').style('stroke-width', 1);
}
if (!_this.getHasRenderedPoint()) circle.call(_this.drag());
_this.setHasRenderedPoint();
});
_defineProperty(this, "reRenderDragBox", function (_ref2) {
var dragBox = _ref2.dragBox,
mouseX = _ref2.mouseX,
xLeft = _ref2.xLeft,
xRight = _ref2.xRight,
stepWidth = _ref2.stepWidth,
stepCount = _ref2.stepCount;
var isLeft = xLeft >= mouseX;
var isRight = xRight <= mouseX;
var mouseDownStep = _this.getMouseDownStep();
if (isLeft) {
var difference = _max([xLeft - mouseX, 0]) || 0;
var rawStepsSelected = Math.floor(difference / stepWidth) + 2;
var maxStepsAvailable = mouseDownStep + 1;
var stepsSelected = _min([rawStepsSelected, maxStepsAvailable]) || 1;
var activeBoxWidth = stepsSelected * stepWidth;
var nextXLeft = xRight - activeBoxWidth;
dragBox.attr('x', nextXLeft);
dragBox.attr('width', activeBoxWidth);
} else if (isRight) {
var _difference = _max([mouseX - xRight, 0]) || 0;
var _rawStepsSelected = Math.floor(_difference / stepWidth) + 2;
var _maxStepsAvailable = stepCount - mouseDownStep;
var _stepsSelected = _min([_rawStepsSelected, _maxStepsAvailable]) || 1;
var _activeBoxWidth = _stepsSelected * stepWidth;
dragBox.attr('x', xLeft);
dragBox.attr('width', _activeBoxWidth);
} else {
dragBox.attr('x', xLeft);
dragBox.attr('width', stepWidth);
}
});
_defineProperty(this, "renderHoverTracker", function () {
var _this$params6 = _this.params,
height = _this$params6.height,
_this$params6$margin = _this$params6.margin,
top = _this$params6$margin.top,
bottom = _this$params6$margin.bottom,
data = _this$params6.data,
onPreselect = _this$params6.onPreselect;
var shouldShowPreselect = _this.shouldShowPreselect,
setMouseDown = _this.setMouseDown,
getIsMouseDown = _this.getIsMouseDown,
getMouseDownStep = _this.getMouseDownStep,
reRenderDragBox = _this.reRenderDragBox,
xScale = _this.xScale,
selection = _this.selection;
if (!shouldShowPreselect()) {
selection.selectAll('.overlayContainer').remove();
return;
}
var innerHeight = height - top - bottom;
var stepWidth = xScale.step();
var stepCount = data.length;
var overlayContainer = selection.append('g').classed('overlayContainer', true).attr('transform', "translate(".concat(0, ",", top, ")"));
_this.renderEmptyRenderProp(innerHeight, stepCount * stepWidth);
var overlayTrack = overlayContainer.selectAll('rect').data(data).enter();
overlayTrack.append('rect').attr('x', function (chartData) {
return _this.xScale(chartData.x) || 0;
}).attr('y', 0).attr('width', function (chartData) {
return _this.xScale.step();
}).attr('height', innerHeight).classed(cx('&-overlayTrack'), true).on('mouseenter', function (d, i, nodes) {
if (!getIsMouseDown()) {
d3Selection.select(nodes[i]).classed('active', true);
}
}).on('mouseout', function (d, i, nodes) {
if (!getIsMouseDown()) {
d3Selection.select(nodes[i]).classed('active', false);
}
}).on('mousedown', function (d, i) {
d3Selection.selectAll('.active').classed('active', false);
var currentTarget = d3Selection.select(this);
var _getAttributes = getAttributes(currentTarget, ['x', 'y', 'width', 'height']),
x = _getAttributes.x,
y = _getAttributes.y,
width = _getAttributes.width,
height = _getAttributes.height;
setMouseDown(true, i);
var xLeft = +x;
var xRight = +x + +width; // @ts-ignore
var container = d3Selection.select(this.parentNode);
container.append('rect').attr('x', x).attr('y', y).attr('width', width).attr('height', height).classed(cx('&-overlayTrack'), true).classed('active', true).classed('dragBox', true).on('mouseout', function () {
// @ts-ignore
var _d3Selection$mouse = d3Selection.mouse(this),
_d3Selection$mouse2 = _slicedToArray(_d3Selection$mouse, 1),
mouseX = _d3Selection$mouse2[0];
var dragBox = selection.selectAll('.dragBox');
reRenderDragBox({
dragBox: dragBox,
mouseX: mouseX,
xLeft: xLeft,
xRight: xRight,
stepWidth: stepWidth,
stepCount: stepCount
});
}).on('mousemove', function () {
// @ts-ignore
var _d3Selection$mouse3 = d3Selection.mouse(this),
_d3Selection$mouse4 = _slicedToArray(_d3Selection$mouse3, 1),
mouseX = _d3Selection$mouse4[0];
var dragBox = selection.selectAll('.dragBox');
reRenderDragBox({
dragBox: dragBox,
mouseX: mouseX,
xLeft: xLeft,
xRight: xRight,
stepWidth: stepWidth,
stepCount: stepCount
});
}).on('mouseup', function () {
var clickStep = getMouseDownStep();
var activeBox = selection.selectAll('.dragBox');
var _getAttributes2 = getAttributes(activeBox, ['x', 'width']),
x = _getAttributes2.x,
width = _getAttributes2.width;
var isRight = xLeft === +x;
var steps = Math.round(+width / stepWidth);
var startingIndex = isRight ? clickStep : clickStep - steps + 1;
var endingIndex = startingIndex + steps - 1;
var updatedData = data.map(function (step, i) {
return _objectSpread(_objectSpread({}, step), {}, {
isSelected: i >= startingIndex && i <= endingIndex
});
});
!!onPreselect && onPreselect(updatedData);
setMouseDown(false);
selection.selectAll('.dragBox').remove();
selection.selectAll('.overlayContainer').remove();
});
});
});
_defineProperty(this, "renderLineChart", function () {
_this.renderXAxis();
_this.renderYAxis();
_this.renderHoverTracker();
_this.renderLine();
_this.renderPoints();
});
_defineProperty(this, "updateLineChart", function (data) {
_this.params.data = data;
_this.yScale.domain([_isUndefined(_this.params.yAxisMin) ? d3Array.min(_this.params.data, function (d) {
return d.y;
}) : _this.params.yAxisMin, d3Array.max(_this.params.data, function (d) {
return d.y;
}) || 10]);
_this.renderLineChart();
});
this.selection = _selection;
this.params = params;
if (params.dataIsCentered) {
this.xScale = d3Scale.scalePoint().domain([].concat(_toConsumableArray(this.params.data.map(function (d) {
return d.x;
})), [''])).range([this.params.margin.left, this.params.width - this.params.margin.right - this.params.margin.left]);
} else {
this.xScale = d3Scale.scalePoint().domain(this.params.data.map(function (d) {
return d.x;
})).range([this.params.margin.left, this.params.width - this.params.margin.right - this.params.margin.left]);
}
this.yScale = d3Scale.scaleLinear().domain([_isUndefined(this.params.yAxisMin) ? d3Array.min(this.params.data, function (d) {
return d.y;
}) : this.params.yAxisMin, d3Array.max(this.params.data, function (d) {
return d.y;
}) || 10]).nice().range([this.params.height - this.params.margin.bottom, this.params.margin.top]);
};
export default DraggableLineChartD3;