diff --git a/app/charts/combo/axis-height-linear-dual.tsx b/app/charts/combo/axis-height-linear-dual.tsx index 2fdb5d29b..5bd7526d7 100644 --- a/app/charts/combo/axis-height-linear-dual.tsx +++ b/app/charts/combo/axis-height-linear-dual.tsx @@ -10,7 +10,7 @@ import { useChartState } from "@/charts/shared/chart-state"; import { useChartTheme } from "@/charts/shared/use-chart-theme"; import { OpenMetadataPanelWrapper } from "@/components/metadata-panel"; import { theme } from "@/themes/federal"; -import { getTextWidth } from "@/utils/get-text-width"; +import { getTextHeight, getTextWidth } from "@/utils/get-text-width"; import { useAxisTitleAdjustments } from "@/utils/use-axis-title-adjustments"; import { TITLE_VPADDING } from "./combo-line-container"; @@ -65,6 +65,9 @@ export const AxisHeightLinearDual = (props: AxisHeightLinearDualProps) => { TITLE_HPADDING * (isOverlapping ? Math.floor(overlapAmount) : 2); const titleWidth = isOverlapping ? axisTitleWidth / overlapAmount : "auto"; + const axisLabelHeight = getTextHeight(axisTitle, { + fontSize: axisLabelFontSize, + }); return ( <> @@ -73,8 +76,8 @@ export const AxisHeightLinearDual = (props: AxisHeightLinearDualProps) => { width={axisTitleWidth + TITLE_HPADDING * 2} height={ (isOverlapping - ? axisLabelFontSize * Math.ceil(overlapAmount) + TITLE_VPADDING - : axisLabelFontSize + TITLE_VPADDING) * + ? axisLabelHeight * Math.ceil(overlapAmount) + TITLE_VPADDING + : axisLabelHeight + TITLE_VPADDING) * 2 + TOP_MARGIN } diff --git a/app/charts/combo/combo-line-container.tsx b/app/charts/combo/combo-line-container.tsx index 880e7a531..3551aa10b 100644 --- a/app/charts/combo/combo-line-container.tsx +++ b/app/charts/combo/combo-line-container.tsx @@ -1,6 +1,6 @@ import { ReactNode } from "react"; -import { getTextWidth } from "@/utils/get-text-width"; +import { getTextHeight, getTextWidth } from "@/utils/get-text-width"; import { TICK_PADDING } from "../shared/axis-height-linear"; import { useChartState } from "../shared/chart-state"; @@ -26,8 +26,12 @@ export const ComboLineContainer = ({ children }: { children: ReactNode }) => { const overLappingTitles = axisTitleWidth + otherAxisTitleWidth > bounds.chartWidth; + const axisLabelHeight = getTextHeight(axisTitle, { + fontSize: axisLabelFontSize, + }); + const yAdjustment = overLappingTitles - ? (axisLabelFontSize + TITLE_VPADDING) * 2 + ? (axisLabelHeight + TITLE_VPADDING) * 2 : 0; return {children}; }; diff --git a/app/charts/combo/combo-line-dual-state.tsx b/app/charts/combo/combo-line-dual-state.tsx index 90fd175c8..85562cbce 100644 --- a/app/charts/combo/combo-line-dual-state.tsx +++ b/app/charts/combo/combo-line-dual-state.tsx @@ -36,7 +36,7 @@ import { InteractionProvider } from "@/charts/shared/use-interaction"; import { ComboLineDualConfig } from "@/configurator"; import { Observation } from "@/domain/data"; import { truthy } from "@/domain/types"; -import { getTextWidth } from "@/utils/get-text-width"; +import { getTextHeight, getTextWidth } from "@/utils/get-text-width"; import { useAxisTitleAdjustments } from "@/utils/use-axis-title-adjustments"; import { useIsMobile } from "@/utils/use-is-mobile"; @@ -244,8 +244,12 @@ const ComboLineDualChartProvider = ( const overLappingTitles = axisTitleWidth + otherAxisTitleWidth > bounds.chartWidth; + const axisLabelHeight = getTextHeight(axisTitle, { + fontSize: axisLabelFontSize, + }); + if (overLappingTitles) { - bounds.height += axisLabelFontSize + TITLE_VPADDING; // Add space for the legend if titles are overlapping + bounds.height += axisLabelHeight + TITLE_VPADDING; // Add space for the legend if titles are overlapping } return ( diff --git a/app/utils/get-text-width.ts b/app/utils/get-text-width.ts index ed196da92..2ebdc5670 100644 --- a/app/utils/get-text-width.ts +++ b/app/utils/get-text-width.ts @@ -14,3 +14,39 @@ export const getTextWidth = (text: string, options: { fontSize: number }) => { return ctx.measureText(text).width; }; + +export const getTextHeight = ( + text: string, + options: { + fontSize: number; + width?: number; + lineHeight?: number | string; + } +) => { + const measureDiv = document.createElement("div"); + + measureDiv.style.font = `${options.fontSize}px ${fontFamily}`; + measureDiv.style.position = "absolute"; + measureDiv.style.visibility = "hidden"; + measureDiv.style.lineHeight = options.lineHeight + ? typeof options.lineHeight === "number" + ? `${options.lineHeight}px` + : options.lineHeight + : "normal"; + + if (options.width) { + measureDiv.style.width = `${options.width}px`; + } else { + measureDiv.style.whiteSpace = "nowrap"; + } + + measureDiv.textContent = text; + + document.body.appendChild(measureDiv); + + const height = measureDiv.offsetHeight; + + document.body.removeChild(measureDiv); + + return height; +}; diff --git a/app/utils/use-axis-title-adjustments.ts b/app/utils/use-axis-title-adjustments.ts index 1291556b3..7ab704799 100644 --- a/app/utils/use-axis-title-adjustments.ts +++ b/app/utils/use-axis-title-adjustments.ts @@ -1,7 +1,7 @@ import { TITLE_VPADDING } from "@/charts/combo/combo-line-container"; import { TICK_PADDING } from "@/charts/shared/axis-height-linear"; import { TICK_FONT_SIZE } from "@/charts/shared/use-chart-theme"; -import { getTextWidth } from "@/utils/get-text-width"; +import { getTextHeight, getTextWidth } from "@/utils/get-text-width"; export interface AxisTitleAdjustments { axisTitleAdjustment: number; @@ -33,12 +33,14 @@ export const useAxisTitleAdjustments = ({ const overlapAmount = (axisTitleWidthLeft + axisTitleWidthRight) / containerWidth; + const axisLabelHeight = getTextHeight(leftAxisTitle, { fontSize }); + const axisTitleAdjustment = (isOverlapping - ? fontSize * Math.ceil(overlapAmount) - : fontSize + TITLE_VPADDING) * + ? axisLabelHeight * Math.ceil(overlapAmount) + : axisLabelHeight + TITLE_VPADDING) * 2 - - fontSize * 2; + axisLabelHeight * 2; const topMarginAxisTitleAdjustment = 60 + axisTitleAdjustment;