blob: 6aa5eb4aada7042b796f411e0c70dc49282763b6 [file] [log] [blame]
import { CSSProperties } from 'react';
import { getTextDimension, Margin, Dimension } from '@superset-ui/core';
import { AxisOrient, ChannelDef, Value } from 'encodable';
import ChannelEncoderAxis from 'encodable/lib/encoders/ChannelEncoderAxis';
export interface AxisLayout {
axisWidth: number;
labelAngle: number;
labelFlush: number | boolean;
labelOffset: number;
labelOverlap: 'flat' | 'rotate';
minMargin: Partial<Margin>;
orient: AxisOrient;
tickLabelDimensions: Dimension[];
tickLabels: string[];
tickTextAnchor?: string;
}
export default function computeAxisLayout<Def extends ChannelDef<Output>, Output extends Value>(
axis: ChannelEncoderAxis<Def, Output>,
{
axisTitleHeight = 20,
axisWidth,
gapBetweenAxisLabelAndBorder = 4,
gapBetweenTickAndTickLabel = 4,
defaultTickSize = 8,
tickTextStyle = {},
}: {
axisTitleHeight?: number;
axisWidth: number;
gapBetweenAxisLabelAndBorder?: number;
gapBetweenTickAndTickLabel?: number;
defaultTickSize?: number;
tickTextStyle?: CSSProperties;
},
): AxisLayout {
const tickLabels = axis.getTickLabels();
const tickLabelDimensions = tickLabels.map((text: string) =>
getTextDimension({
style: tickTextStyle,
text,
}),
);
const {
labelAngle,
labelFlush,
labelOverlap,
labelPadding,
orient,
tickSize = defaultTickSize,
} = axis.config;
const maxWidth = Math.max(...tickLabelDimensions.map(d => d.width), 0);
// cheap heuristic, can improve
const widthPerTick = axisWidth / tickLabels.length;
const isLabelOverlap = maxWidth > widthPerTick;
const labelAngleIfOverlap = labelOverlap.strategy === 'rotate' ? labelOverlap.labelAngle : 0;
const labelAngleAfterOverlapCheck = isLabelOverlap ? labelAngleIfOverlap : 0;
const finalLabelAngle = labelAngle === 0 ? labelAngleAfterOverlapCheck : labelAngle;
const spaceForAxisTitle = axis.hasTitle() ? labelPadding + axisTitleHeight : 0;
let tickTextAnchor = 'middle';
let labelOffset = 0;
let requiredMargin =
tickSize + gapBetweenTickAndTickLabel + spaceForAxisTitle + gapBetweenAxisLabelAndBorder;
if (axis.channelEncoder.isX()) {
if (finalLabelAngle === 0) {
const labelHeight = tickLabelDimensions.length > 0 ? tickLabelDimensions[0].height : 0;
labelOffset = labelHeight + labelPadding;
requiredMargin += labelHeight;
} else {
const labelHeight = Math.ceil(
Math.abs(maxWidth * Math.sin((finalLabelAngle * Math.PI) / 180)),
);
labelOffset = labelHeight + labelPadding;
requiredMargin += labelHeight;
tickTextAnchor =
(orient === 'top' && finalLabelAngle > 0) || (orient === 'bottom' && finalLabelAngle < 0)
? 'end'
: 'start';
}
requiredMargin += 8;
} else {
labelOffset = maxWidth + spaceForAxisTitle;
requiredMargin += maxWidth;
}
return {
axisWidth,
labelAngle: finalLabelAngle,
labelFlush,
labelOffset,
labelOverlap: isLabelOverlap ? labelOverlap.strategy : 'flat',
minMargin: {
[orient]: Math.ceil(requiredMargin),
},
orient,
tickLabelDimensions,
tickLabels,
tickTextAnchor,
};
}