feat(chart): add configurable chart presentation
This commit is contained in:
88
src/components/core/title.ts
Normal file
88
src/components/core/title.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
export const TITLE_AREA_MIN_HEIGHT = 44
|
||||
export const TITLE_AREA_MAX_HEIGHT = 160
|
||||
export const TITLE_AREA_HORIZONTAL_PADDING = 24
|
||||
export const TITLE_AREA_VERTICAL_PADDING = 18
|
||||
|
||||
const TRIGONOMETRY_EPSILON = 1e-6
|
||||
|
||||
export interface RotatedTitleLayout {
|
||||
textWidth: number
|
||||
textHeight: number
|
||||
visualWidth: number
|
||||
visualHeight: number
|
||||
areaHeight: number
|
||||
scale: number
|
||||
wrapped: boolean
|
||||
}
|
||||
|
||||
export interface RotatedTitleLayoutOptions {
|
||||
naturalWidth: number
|
||||
naturalHeight: number
|
||||
availableWidth: number
|
||||
rotation: number
|
||||
}
|
||||
|
||||
function clampPositive(value: number): number {
|
||||
return Number.isFinite(value) ? Math.max(1, value) : 1
|
||||
}
|
||||
|
||||
export function calculateRotatedTitleLayout({
|
||||
naturalWidth,
|
||||
naturalHeight,
|
||||
availableWidth,
|
||||
rotation,
|
||||
}: RotatedTitleLayoutOptions): RotatedTitleLayout {
|
||||
const safeNaturalWidth = clampPositive(naturalWidth)
|
||||
const safeNaturalHeight = clampPositive(naturalHeight)
|
||||
const safeAvailableWidth = clampPositive(availableWidth)
|
||||
const safeRotation = Number.isFinite(rotation) ? rotation : 0
|
||||
const radians = (safeRotation * Math.PI) / 180
|
||||
const absoluteCosine = Math.abs(Math.cos(radians))
|
||||
const absoluteSine = Math.abs(Math.sin(radians))
|
||||
const normalizedRotation = ((safeRotation % 360) + 360) % 360
|
||||
const isRotated =
|
||||
normalizedRotation > TRIGONOMETRY_EPSILON &&
|
||||
Math.abs(normalizedRotation - 360) > TRIGONOMETRY_EPSILON
|
||||
|
||||
if (!isRotated) {
|
||||
const lineCount = Math.max(1, Math.ceil(safeNaturalWidth / safeAvailableWidth))
|
||||
const textWidth = safeAvailableWidth
|
||||
const textHeight = safeNaturalHeight * lineCount
|
||||
return {
|
||||
textWidth,
|
||||
textHeight,
|
||||
visualWidth: textWidth,
|
||||
visualHeight: textHeight,
|
||||
areaHeight: Math.max(TITLE_AREA_MIN_HEIGHT, textHeight + TITLE_AREA_VERTICAL_PADDING),
|
||||
scale: 1,
|
||||
wrapped: lineCount > 1,
|
||||
}
|
||||
}
|
||||
|
||||
const maximumVisualHeight = TITLE_AREA_MAX_HEIGHT - TITLE_AREA_VERTICAL_PADDING
|
||||
const naturalVisualWidth =
|
||||
safeNaturalWidth * absoluteCosine + safeNaturalHeight * absoluteSine
|
||||
const naturalVisualHeight =
|
||||
safeNaturalWidth * absoluteSine + safeNaturalHeight * absoluteCosine
|
||||
const scale = Math.min(
|
||||
1,
|
||||
safeAvailableWidth / naturalVisualWidth,
|
||||
maximumVisualHeight / naturalVisualHeight,
|
||||
)
|
||||
const visualWidth = naturalVisualWidth * scale
|
||||
const visualHeight = naturalVisualHeight * scale
|
||||
const areaHeight = Math.min(
|
||||
TITLE_AREA_MAX_HEIGHT,
|
||||
Math.max(TITLE_AREA_MIN_HEIGHT, visualHeight + TITLE_AREA_VERTICAL_PADDING),
|
||||
)
|
||||
|
||||
return {
|
||||
textWidth: safeNaturalWidth,
|
||||
textHeight: safeNaturalHeight,
|
||||
visualWidth,
|
||||
visualHeight,
|
||||
areaHeight,
|
||||
scale,
|
||||
wrapped: false,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user