@nova-ui/charts
Version:
Nova Charts is a library created to provide potential consumers with solutions for various data visualizations that conform with the Nova Design Language. It's designed to solve common patterns identified by UX designers, but also be very flexible so that
778 lines • 119 kB
JavaScript
// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import { axisBottom, axisLeft, axisRight, axisTop } from "d3";
import { select } from "d3-selection";
import clone from "lodash/clone";
import each from "lodash/each";
import isEmpty from "lodash/isEmpty";
import isNil from "lodash/isNil";
import isUndefined from "lodash/isUndefined";
import uniq from "lodash/uniq";
import { takeUntil } from "rxjs/operators";
import { AXES_STYLE_CHANGE_EVENT, DESTROY_EVENT, IGNORE_INTERACTION_CLASS, SERIES_STATE_CHANGE_EVENT, } from "../../constants";
import { RenderState } from "../../renderers/types";
import { MouseInteractiveArea } from "../common/mouse-interactive-area";
import { BandScale } from "../common/scales/band-scale";
import { getAutomaticDomainWithTicks } from "../common/scales/domain-calculation/domain-with-ticks";
import { LinearScale } from "../common/scales/linear-scale";
import { isDomainWithTicksCalculator, } from "../common/scales/types";
import { InteractionLabelPlugin } from "../plugins/interaction/interaction-label-plugin";
import { InteractionLinePlugin } from "../plugins/interaction/interaction-line-plugin";
import { MouseInteractiveAreaPlugin } from "../plugins/mouse-interactive-area-plugin";
import { XYGridConfig } from "./config/xy-grid-config";
import { Grid } from "./grid";
export class XYGrid extends Grid {
static TICK_LABEL_OVERFLOW_DEBOUNCE_INTERVAL = 200;
axisX = {};
axisYLeft = {};
axisYRight = {};
gridY = {};
gridX = {};
_bottomScaleId;
_leftScaleId;
_rightScaleId;
reconcileMarginsDebounce;
// map of scale id to pending debounce timeout
handleTickLabelOverflowDebounceIndex = {};
/**
* Returns the id of the bottom axis scale
*/
get bottomScaleId() {
if (this._bottomScaleId) {
return this._bottomScaleId;
}
if (!isEmpty(this.scales)) {
return this.scales["x"].list[0].id;
}
}
/**
* Sets the id of the bottom axis scale
*/
set bottomScaleId(id) {
this._bottomScaleId = id;
}
/**
* Returns the id of the left axis scale
*/
get leftScaleId() {
if (this._leftScaleId) {
return this._leftScaleId;
}
if (!isEmpty(this.scales)) {
return this.scales["y"].list[0].id;
}
}
/**
* Sets the id of the left axis scale
*/
set leftScaleId(id) {
this._leftScaleId = id;
}
/**
* Returns the id of the right axis scale
*/
get rightScaleId() {
return this._rightScaleId;
}
/**
* Sets the id of the right axis scale
*/
set rightScaleId(id) {
this._rightScaleId = id;
}
/** @ignore */
set scales(scales) {
this._scales = scales;
if (this._scales) {
Object.keys(this._scales).forEach((scaleKey) => {
// Defaulting to left
let config = this.config().axis.left;
let axisGenerator;
if (scaleKey !== "x" && scaleKey !== "y") {
return;
}
this._scales[scaleKey]?.list
?.filter((scale) => scale instanceof LinearScale)
.forEach((scale) => {
if (scale?.id === this.leftScaleId) {
config = this.config().axis.left;
axisGenerator = axisLeft;
}
else if (scale?.id === this.rightScaleId) {
config = this.config().axis.right;
axisGenerator = axisRight;
}
else if (scale?.id === this.bottomScaleId) {
config = this.config().axis.bottom;
axisGenerator = axisBottom;
}
if (scale.domainCalculator &&
!isDomainWithTicksCalculator(scale.domainCalculator) &&
config.gridTicks) {
scale.__domainCalculatedWithTicks = true;
scale.domainCalculator =
getAutomaticDomainWithTicks(config, axisGenerator, scale.domainCalculator);
}
});
});
}
}
/** @ignore */
get scales() {
return this._scales;
}
/** See {@link IGrid#config} */
config(config) {
if (config === undefined) {
return this._config;
}
this._config = config;
return this;
}
constructor(config) {
super();
this.config(config || new XYGridConfig());
}
/** See {@link IGrid#build} */
build() {
super.build();
if (this.config().interactive) {
this.interactiveArea = this.renderingArea;
}
this.buildAxes(this.gridElementsLayer);
this.recalculateMargins(this.container);
const untilDestroy = () => takeUntil(this.eventBus.getStream(DESTROY_EVENT));
this.eventBus
.getStream(SERIES_STATE_CHANGE_EVENT)
.pipe(untilDestroy())
.subscribe((e) => {
const axesStyles = this.handleSeriesStateChange(e);
this.eventBus.getStream(AXES_STYLE_CHANGE_EVENT).next({
data: axesStyles,
});
});
return this;
}
/**
* Handle axis opacity when emphasizing/deemphasizing chart series
*
* @param e
* @private
*/
handleSeriesStateChange(e) {
if (!this.rightScaleId) {
return;
}
const axes = [
{
scaleId: this.leftScaleId,
element: this.axisYLeft.group,
},
{ scaleId: this.rightScaleId, element: this.axisYRight?.group },
];
const axesStyles = this.calculateAxesStyles(e, axes);
for (const a of axes) {
if (a.scaleId) {
a.element.attrs(axesStyles[a.scaleId]);
}
}
return axesStyles;
}
/**
* Return opacity for each axis
*
* @param e
* @param axes
* @private
*/
calculateAxesStyles(e, axes) {
const renderStates = e.data;
const emphasizedSeries = renderStates
.filter((rs) => rs.state === RenderState.emphasized && rs.series)
.map((rs) => rs.series);
if (emphasizedSeries.length > 0) {
const emphasizedYScales = uniq(emphasizedSeries
.map((s) => s?.scales["y"])
.filter((s) => !!s));
if (emphasizedYScales.length <= 0) {
return {};
}
for (const emphasizedYScale of emphasizedYScales) {
if (emphasizedYScale.id === this.leftScaleId ||
emphasizedYScale.id === this.rightScaleId) {
return axes.reduce((acc, next) => {
acc[next.scaleId] = {
opacity: emphasizedYScale.id === next.scaleId ? 1 : 0.1,
};
return acc;
}, {});
}
}
return {};
}
else {
return axes.reduce((acc, next) => {
acc[next.scaleId] = { opacity: 1 };
return acc;
}, {});
}
}
/** See {@link IGrid#buildPlugins} */
buildPlugins(chart) {
const plugins = [];
const config = this.config();
if (config.interactive) {
plugins.push(new MouseInteractiveAreaPlugin(new MouseInteractiveArea(this.getLasagna().getContainer(), this.getInteractiveArea(), config.cursor, config.dimension.margin)));
}
if (config.interactionPlugins) {
plugins.push(new InteractionLinePlugin());
plugins.push(new InteractionLabelPlugin());
}
return plugins;
}
/** See {@link IGrid#update} */
update() {
super.update();
this.updateAxes();
this.handleMarginUpdate();
return this;
}
/** @ignore */
drawTicks(config, axis, scale, axisGenerator) {
if (config.visible) {
const labelAxis = axisGenerator(scale.d3Scale)
.ticks(config.approximateTicks)
.tickSize(0);
if (scale.fixDomainValues?.length) {
labelAxis.tickValues(scale.fixDomainValues);
}
labelAxis.tickFormat(scale.formatters["tick"]);
axis.labelGroup.call(labelAxis);
let tickAxis;
if (scale instanceof BandScale) {
tickAxis = axisGenerator(scale.copyToLinear().d3Scale)
.tickSize(config.tickSize)
.tickValues(scale.bandTicks())
.tickFormat(() => "");
}
else {
tickAxis = axisGenerator(scale.d3Scale).tickSize(config.tickSize);
if (scale.fixDomainValues?.length) {
tickAxis.tickValues(scale.fixDomainValues);
}
tickAxis.tickFormat(() => "");
tickAxis.ticks(config.approximateTicks);
}
axis.tickGroup.call(tickAxis);
this.adjustAxisTicks(axis.labelGroup, scale);
}
else {
axis.labelGroup.selectAll("*").remove();
axis.tickGroup.selectAll("*").remove();
}
}
/** @ignore */
drawGrids(config, axis, axisGenerator, scale, size) {
const modifyZeroLines = (gridGroupSelection) => gridGroupSelection
.selectAll(".tick line")
.classed("nui-zero-line", (d) => d === 0);
const leftGridLines = axisGenerator(scale.d3Scale).tickSize(size);
if (scale.fixDomainValues?.length) {
leftGridLines.tickValues(scale.fixDomainValues);
}
leftGridLines.tickFormat(() => "").ticks(config.approximateTicks);
if (config.gridTicks) {
const gridSelection = axis.tickGroup.call(leftGridLines);
modifyZeroLines(gridSelection);
}
else {
axis.tickGroup.selectAll("*").remove();
}
}
/** See {@link IGrid#updateRanges} */
updateRanges() {
if (isEmpty(this.scales)) {
return this;
}
const dimension = this.config().dimension;
const padding = dimension.padding;
if (this.scales.x) {
each(this.scales.x.list, (xScale) => {
xScale.range([padding.left, dimension.width() - padding.right]);
});
}
if (this.scales.y) {
each(this.scales.y.list, (yScale) => {
yScale.range([
dimension.height() - padding.bottom,
padding.top,
]);
});
}
super.updateRanges();
return this;
}
updateXAxis() {
const xScale = this.bottomScaleId
? this.scales["x"].index[this.bottomScaleId]
: undefined;
const axis = this.config().axis;
if (!xScale) {
throw new Error("xScale is not defined");
}
this.axisX.group.attr("transform", `translate(0, ${this.config().dimension.height()})`);
// Additional transform to not overlap with ticks
this.axisX.labelGroup.attr("transform", `translate(0, ${axis.bottom.tickSize})`);
this.drawTicks(axis.bottom, this.axisX, xScale, axisBottom);
this.drawGrids(axis.bottom, this.gridX, axisTop, xScale, this.config().dimension.height());
this.gridX.tickGroup.attr("transform", `translate(0, ${this.config().dimension.height()})`);
}
updateYAxes() {
const axis = this.config().axis;
const yLeftScale = this.leftScaleId
? this.scales["y"].index[this.leftScaleId]
: undefined;
if (!yLeftScale) {
throw new Error("yLeftScale is not defined");
}
this.axisYLeft.labelGroup.attr("transform", `translate(${-axis.left.tickSize - axis.left.padding}, 0)`);
this.drawTicks(axis.left, this.axisYLeft, yLeftScale, axisLeft);
this.drawGrids(axis.left, this.gridY, axisRight, yLeftScale, this.config().dimension.width());
if (this.rightScaleId) {
const yRightScale = this.scales["y"].index[this.rightScaleId];
const rightScaleLabelX = axis.right.padding +
this.config().dimension.width() +
axis.right.tickSize;
this.axisYRight.labelGroup.attr("transform", `translate(${rightScaleLabelX}, 0)`);
this.axisYRight.tickGroup.attr("transform", `translate(${this.config().dimension.width()}, 0)`);
this.drawTicks(axis.right, this.axisYRight, yRightScale, axisRight);
}
}
updateAxes() {
if (!this.target()) {
return;
}
this.updateXAxis();
this.updateYAxes();
}
adjustAxisTicks(labelGroup, scale) {
const textOfTicks = [];
labelGroup.attr("cursor", "default");
labelGroup.selectAll("g").each(function () {
select(this)
.classed("sw-hidden", false)
.classed("tick-hidden-text", false);
textOfTicks.push(this);
});
const toRemove = this.filterRepeatedElements(textOfTicks);
toRemove.forEach((group) => {
select(group).classed("tick-hidden-text", true);
});
const allAxisLabels = this.selectAllAxisLabels(labelGroup);
allAxisLabels.forEach((group) => {
const groupSelection = select(group);
// zero-out the d3-provided x position of all labels since we're manually translating the x position of the entire group;
// add IGNORE_INTERACTION_CLASS so that mouse events used for displaying the title don't propagate from the mouse-interactive-area
groupSelection
.select("text")
.attr("x", 0)
.classed(IGNORE_INTERACTION_CLASS, true);
groupSelection.classed("pointer-events", true);
if (groupSelection.select("title").empty()) {
const datum = groupSelection.data()[0];
const titleText = (scale.formatters["tick"]
? scale.formatters["tick"](datum)
: datum);
groupSelection.append("title").text(titleText);
}
});
this.handleTickLabelOverflow(labelGroup, scale, allAxisLabels);
}
handleTickLabelOverflow(labelGroup, scale, axisLabels) {
const axisConfig = this.config().axis;
if (scale.id === this.bottomScaleId &&
(scale.isContinuous() ||
!axisConfig.bottom.tickLabel.overflowHandler)) {
const textToHide = this.getElementsToHide(axisLabels, this._config.dimension.width(), true);
textToHide.forEach((group) => {
const groupSelection = select(group);
groupSelection.classed("tick-hidden-text", true);
// disable pointer-events so the title doesn't display for hidden labels
groupSelection.classed("pointer-events", false);
});
return;
}
if (scale.id === this.rightScaleId || scale.id === this.leftScaleId) {
const textToHide = this.getElementsToHide(axisLabels, this._config.dimension.height(), false);
textToHide.forEach((group) => {
select(group).classed("sw-hidden", true);
});
}
const margin = this.config().dimension.margin;
let widthLimit = 0;
let horizontalPadding = 0;
let overflowHandler;
let fixLeftMargin = false;
let fixRightMargin = false;
const maxRightWidth = axisConfig.right.tickLabel.maxWidth;
const maxLeftWidth = axisConfig.left.tickLabel.maxWidth;
if (scale.id === this.bottomScaleId) {
const maxBottomWidth = axisConfig.bottom.tickLabel.maxWidth;
const calculatedBottomWidth = scale.bandwidth
? scale.bandwidth()
: this.getTickDistance(axisLabels);
if (!isUndefined(maxBottomWidth)) {
widthLimit =
calculatedBottomWidth > maxBottomWidth
? maxBottomWidth
: calculatedBottomWidth;
}
else {
widthLimit = calculatedBottomWidth;
}
horizontalPadding = axisConfig.bottom.tickLabel.horizontalPadding;
overflowHandler = axisConfig.bottom.tickLabel.overflowHandler;
}
else if (scale.id === this.rightScaleId && !axisConfig.right.fit) {
const calculatedRightWidth = margin.right -
axisConfig.right.padding -
axisConfig.right.tickSize;
if (!isUndefined(maxRightWidth)) {
widthLimit =
calculatedRightWidth > maxRightWidth
? maxRightWidth
: calculatedRightWidth;
}
else {
widthLimit = calculatedRightWidth;
}
horizontalPadding = axisConfig.right.tickLabel.horizontalPadding;
overflowHandler = axisConfig.right.tickLabel.overflowHandler;
}
else if (scale.id === this.leftScaleId && !axisConfig.left.fit) {
const calculatedLeftWidth = margin.left -
axisConfig.left.padding -
axisConfig.left.tickSize;
if (!isUndefined(maxLeftWidth)) {
widthLimit =
calculatedLeftWidth > maxLeftWidth
? maxLeftWidth
: calculatedLeftWidth;
}
else {
widthLimit = calculatedLeftWidth;
}
horizontalPadding = axisConfig.left.tickLabel.horizontalPadding;
overflowHandler = axisConfig.left.tickLabel.overflowHandler;
}
else if (scale.id === this.rightScaleId &&
axisConfig.right.fit &&
!isUndefined(maxRightWidth)) {
widthLimit = maxRightWidth;
horizontalPadding = axisConfig.right.tickLabel.horizontalPadding;
overflowHandler = axisConfig.right.tickLabel.overflowHandler;
fixRightMargin = true;
}
else if (scale.id === this.leftScaleId &&
axisConfig.left.fit &&
!isUndefined(maxLeftWidth)) {
widthLimit = maxLeftWidth;
horizontalPadding = axisConfig.left.tickLabel.horizontalPadding;
overflowHandler = axisConfig.left.tickLabel.overflowHandler;
fixLeftMargin = true;
}
else {
return;
}
if (!overflowHandler) {
return;
}
// sample the length of an ellipsis in the current environment
let ellipsisWidth = 0;
if (labelGroup.select(".sample-ellipsis").empty()) {
const testText = labelGroup.append("text");
const ellipsis = testText
.classed("sample-ellipsis", true)
.attr("opacity", 0)
.append("tspan")
.text("...");
ellipsisWidth = ellipsis.node()?.getComputedTextLength() || 0;
testText.remove();
}
// reset debounce if necessary
if (this.handleTickLabelOverflowDebounceIndex[scale.id]) {
clearTimeout(this.handleTickLabelOverflowDebounceIndex[scale.id]);
this.handleTickLabelOverflowDebounceIndex[scale.id] = null;
}
// hide the labels and disable pointer-events while debouncing
labelGroup.classed("tick-hidden-text", true);
axisLabels.forEach((group) => {
select(group).classed("pointer-events", false);
});
// setTimeout used for debounce when the chart is resized
this.handleTickLabelOverflowDebounceIndex[scale.id] = setTimeout(() => {
axisLabels.forEach((group) => {
const groupSelection = select(group);
// invoke the handler for each text element
groupSelection
.select("text")
.call(overflowHandler, {
widthLimit,
horizontalPadding,
ellipsisWidth,
});
// restore pointer events
groupSelection.classed("pointer-events", true);
});
const marginLocked = this._config.dimension.marginLocked;
if (marginLocked && (fixRightMargin || fixLeftMargin)) {
this.handleMarginUpdate();
marginLocked.right = fixRightMargin;
marginLocked.left = fixLeftMargin;
}
// display the labels
labelGroup.classed("tick-hidden-text", false);
}, XYGrid.TICK_LABEL_OVERFLOW_DEBOUNCE_INTERVAL);
}
selectAllAxisLabels(axisGroup) {
const actualTextElements = [];
axisGroup.selectAll("g:not(.tick-hidden-text)").each(function () {
actualTextElements.push(this);
});
return actualTextElements;
}
getOuterWidthDimensionCorrection() {
return this.config().axis.bottom.visible
? Grid.TICK_DIMENSION_CORRECTION
: 0;
}
handleMarginUpdate() {
const oldMargin = clone(this._config.dimension.margin);
this.recalculateMargins(this.container);
this.reconcileMarginsWithDebounce(oldMargin);
}
hasRightYAxis() {
return (this.config().axis.right.visible && this.rightScaleId?.length > 0);
}
buildAxes(container) {
// Grid lines: no sense to have right
this.gridY.tickGroup = container
.append("g")
.classed("sw-axis sw-axis-grid sw-axis-gridY", true);
this.gridX.tickGroup = container
.append("g")
.classed("sw-axis sw-axis-grid sw-axis-gridX", true);
// Axis groups
this.axisX.group = container
.append("g")
.classed("sw-axis sw-axis-x", true);
this.axisX.tickGroup = this.axisX.group
.append("g")
.classed("sw-axis sw-axis-x sw-axis-x-ticks", true);
this.axisX.labelGroup = this.axisX.group
.append("g")
.classed("sw-axis sw-axis-x sw-axis-x-labels", true);
this.axisYLeft.group = container
.append("g")
.classed("sw-axis sw-axis-y sw-axis-y-left", true);
this.axisYLeft.tickGroup = this.axisYLeft.group
.append("g")
.classed("sw-axis sw-axis-y sw-axis-y-ticks", true);
this.axisYLeft.labelGroup = this.axisYLeft.group
.append("g")
.classed("sw-axis sw-axis-y sw-axis-y-labels", true);
this.axisYRight.group = container
.append("g")
.classed("sw-axis sw-axis-y sw-axis-y-right", true);
this.axisYRight.tickGroup = this.axisYRight.group
.append("g")
.classed("sw-axis sw-axis-y sw-axis-y-ticks", true);
this.axisYRight.labelGroup = this.axisYRight.group
.append("g")
.classed("sw-axis sw-axis-y sw-axis-y-labels", true);
}
filterRepeatedElements(elementsToFilter) {
const arr = [elementsToFilter[0]];
elementsToFilter.reduce((prev, next) => {
const textInsideNext = select(next).text();
if (textInsideNext !== prev) {
arr.push(next);
}
return textInsideNext;
}, elementsToFilter[0] && select(elementsToFilter[0]).text());
return elementsToFilter.filter((element) => arr.indexOf(element) === -1);
}
getElementsToHide(elementsToFilter, measurement, isBottomAxis) {
const measureType = isBottomAxis ? "width" : "height";
const measurementComparison = this.getTextMeasurement(elementsToFilter, measureType) <
measurement;
const bottomAxisComparison = isBottomAxis
? this.getMaxTextWidth(elementsToFilter) <
this.getTickDistance(elementsToFilter)
: false;
const shouldNotFilter = isBottomAxis
? measurementComparison && bottomAxisComparison
: measurementComparison;
if (shouldNotFilter) {
return [];
}
const elementsToDisplay = this.elementsFiltering(elementsToFilter, measurement, measureType);
return elementsToFilter.filter((element) => elementsToDisplay.indexOf(element) === -1);
}
elementsFiltering(elementsToFilter, parameter, measureType) {
let elementsToDisplay;
let counter = 2;
const condition = (array) => measureType === "width"
? this.getMaxTextWidth(array) > this.getTickDistance(array)
: this.getTextMeasurement(array, measureType) > parameter;
do {
elementsToDisplay = elementsToFilter.filter((element, index) => index % counter === 0);
counter++;
// Break the loop when we have only 1 tick and its size is bigger than available width/height
if (elementsToDisplay.length === 1 &&
condition(elementsToDisplay)) {
break;
}
} while (condition(elementsToDisplay));
return elementsToDisplay;
}
getTextMeasurement(array, measureType) {
const textPadding = measureType === "width" ? 5 : 0;
return array.reduce((prev, next) => prev +
next.getBoundingClientRect()[measureType] +
textPadding, 0);
}
getMaxTextWidth(array) {
if (array.length === 0) {
return 0;
}
return Math.max.apply(null, array.map((tick) => tick.getBoundingClientRect().width));
}
getTickDistance(array) {
if (array.length === 0) {
return 0;
}
let smallestDiff = Infinity;
const arrayOfPositions = array.map((tick) => {
const transformVal = tick.getAttribute("transform");
if (isNil(transformVal)) {
throw new Error("tick transform is not defined");
}
return parseFloat(transformVal.slice(transformVal.indexOf("(") + 1, transformVal.indexOf(",")));
});
arrayOfPositions.reduce((p, n) => {
const diff = n - p;
if (diff < smallestDiff) {
smallestDiff = diff;
}
return n;
});
return smallestDiff;
}
recalculateMargins(container) {
if (!container) {
return;
}
const d = this._config.dimension;
const axis = this.config().axis;
const oldOuterWidth = d.outerWidth();
const oldOuterHeight = d.outerHeight();
if (!d.marginLocked?.left && axis.left.fit && axis.left.visible) {
d.margin.left =
this.getMaxTextWidth(this.selectAllAxisLabels(this.axisYLeft.labelGroup)) +
axis.left.tickSize +
axis.left.padding;
}
if (!d.marginLocked?.right && axis.right.fit && this.hasRightYAxis()) {
d.margin.right =
this.getMaxTextWidth(this.selectAllAxisLabels(this.axisYRight.labelGroup)) +
axis.right.tickSize +
axis.right.padding;
}
const bottomScale = this.bottomScaleId && this.scales
? this.scales["x"]?.index[this.bottomScaleId]
: undefined;
if (axis.bottom.fit &&
axis.bottom.visible &&
(bottomScale?.isContinuous() ||
!axis.bottom.tickLabel.overflowHandler)) {
this.fitBottomAxis(d);
}
const newTopMargin = d.margin.top; // TODO: Do the calculation, if needed
const newBottomMargin = d.margin.bottom; // TODO: Do the calculation, based on bottom Axis height
d.margin.top = newTopMargin;
d.margin.bottom = newBottomMargin;
// adjust width and height accordingly
d.outerWidth(oldOuterWidth);
d.outerHeight(oldOuterHeight);
const tx = this.config().dimension.margin.left;
const ty = this.config().dimension.margin.top;
// Note: This transform results in a firefox hack in MouseInteractiveArea in which
// the margins must be subtracted from the mouse event coordinates
container.attr("transform", `translate(${tx}, ${ty})`);
}
fitBottomAxis(d) {
const scale = this.scales?.x?.list[0].d3Scale;
if (!scale) {
return;
}
let lastTextWidth = 0;
let lastTickScaleValue = 0;
const node = this.axisX.labelGroup
.select(".tick:not(.tick-hidden-text):last-child text")
.node();
if (node) {
lastTextWidth = node.getBoundingClientRect().width;
lastTickScaleValue = scale(node.innerHTML) ?? 0;
const topOfRange = scale.range()[1];
const diff = lastTextWidth / 2 -
Math.abs(topOfRange - lastTickScaleValue) / 2;
d.margin.right = diff > 0 ? diff : 0;
}
}
/**
* This method invokes updateRanges if the margins have changed, but only after a debounce period.
* ----
* The debounce is necessary because, in the case of a very short axis, repeated attempts to
* alternately fit axis labels and recalculate ticks may conflict with each other
* causing the old and new margins to never be equal upon comparison. This scenario can cause a d3
* call stack overflow, but with a debounce, d3 can keep up with the recalculations until the chart
* is resized to consistently accommodate the width of the labels.
*/
reconcileMarginsWithDebounce(oldMargin) {
if (this.reconcileMarginsDebounce) {
clearTimeout(this.reconcileMarginsDebounce);
this.reconcileMarginsDebounce = null;
}
this.reconcileMarginsDebounce = setTimeout(() => {
if (!this.areMarginsApproximatelyEqual(oldMargin, this._config.dimension.margin)) {
this.updateChartDimensionsSubject?.next();
}
}, 100);
}
isApproximatelyEqual = (first, second) => Math.abs(first - second) < 0.1;
areMarginsApproximatelyEqual(margin1, margin2) {
return (this.isApproximatelyEqual(margin1.top, margin2.top) &&
this.isApproximatelyEqual(margin1.right, margin2.right) &&
this.isApproximatelyEqual(margin1.bottom, margin2.bottom) &&
this.isApproximatelyEqual(margin1.left, margin2.left));
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoieHktZ3JpZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9jb3JlL2dyaWQveHktZ3JpZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSx5REFBeUQ7QUFDekQsRUFBRTtBQUNGLCtFQUErRTtBQUMvRSw0RUFBNEU7QUFDNUUsOEVBQThFO0FBQzlFLCtFQUErRTtBQUMvRSw4RUFBOEU7QUFDOUUsNERBQTREO0FBQzVELEVBQUU7QUFDRiw2RUFBNkU7QUFDN0UsdURBQXVEO0FBQ3ZELEVBQUU7QUFDRiw2RUFBNkU7QUFDN0UsNEVBQTRFO0FBQzVFLCtFQUErRTtBQUMvRSwwRUFBMEU7QUFDMUUsaUZBQWlGO0FBQ2pGLDZFQUE2RTtBQUM3RSxpQkFBaUI7QUFFakIsT0FBTyxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxNQUFNLElBQUksQ0FBQztBQUM5RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxNQUFNLGNBQWMsQ0FBQztBQUNqQyxPQUFPLElBQUksTUFBTSxhQUFhLENBQUM7QUFDL0IsT0FBTyxPQUFPLE1BQU0sZ0JBQWdCLENBQUM7QUFDckMsT0FBTyxLQUFLLE1BQU0sY0FBYyxDQUFDO0FBQ2pDLE9BQU8sV0FBVyxNQUFNLG9CQUFvQixDQUFDO0FBQzdDLE9BQU8sSUFBSSxNQUFNLGFBQWEsQ0FBQztBQUMvQixPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFM0MsT0FBTyxFQUNILHVCQUF1QixFQUN2QixhQUFhLEVBQ2Isd0JBQXdCLEVBQ3hCLHlCQUF5QixHQUM1QixNQUFNLGlCQUFpQixDQUFDO0FBQ3pCLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUN4RSxPQUFPLEVBQUUsU0FBUyxFQUFF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