Absolute top position and flow width
For building charts with a scale I’ve been struggling for a while to find a way to make ticks in a vertical scale react to label sizes (have a width following normal flow, pushing their container) and be at the right height (absolutely positioning at the height they need to be). The horizontal scale has the same problem with flow height and absolute left.
My workaround was for a long time to set a fixed width and hope no label was wider. Relative container for the whole scale. Then each tick takes parent width (this is my issue), is absolutely positioned with a top set to a percentage (linear from minimum value = 100% and maximum value = 0%), and finally centered around actual value using translation transform.
<!-- parent grid: has to define scales width (vertical) or height (horizontal) -->
<div style="border: 1px solid #BBB; padding: 5pt; display: grid; grid-template-columns: 100pt auto 1fr; grid-template-rows: minmax(200px, auto) auto 30pt;">
<!-- vertical scale, relative for ticks to be positioned against it -->
<div style="grid-area: 1 / 1; position: relative; text-align: right; margin-right: 4px;">
<!-- vertical ticks, take parent width and absolute top position -->
<div style="position: absolute; top: calc((10 - 8.2) / (10 - 0) * 100%); width: 100%; transform: translate(0, -50%);">8.2/10</div>
<div style="position: absolute; top: calc((10 - 4.4) / (10 - 0) * 100%); width: 100%; transform: translate(0, -50%);">4.4/10</div>
</div>
<div style="grid-area: 1 / 2; width: 4px; background-color: #444; border-radius: 2px;"></div>
<div style="grid-area: 2 / 3; height: 4px; background-color: #444; border-radius: 2px;"></div>
<!-- horizontal scale, relative for ticks to be positioned against it -->
<div style="grid-area: 3 / 3; position: relative; margin-top: 4px;">
<!-- horizontal ticks, take parent height and absolute left position -->
<div style="position: absolute; left: calc((34 - 0) / (100 - 0) * 100%); height: 100%; transform: translate(-50%, 0);">34%</div>
<div style="position: absolute; left: calc((72 - 0) / (100 - 0) * 100%); height: 100%; transform: translate(-50%, 0);">72%</div>
</div>
</div>
But I found a way with grid: by dropping all ticks the same cell of a one-by-one grid, they can use relative vertical position instead of absolute, and this means they will contribute to defining the width of their parent.
This removes the need for the vertical scale to pre-define a width, if the font increases or a label is wider than expected that will just push chart area.
Container can still define minimum and maximum to ensure the chart remains pretty under all circumstances.
Horizontal scale needs an additional safeguard as every tick can take full parent width, large labels may overlap.
This can be prevented by by setting a maximum width that ensures every tick has less than the space between two ticks (eg 100%/(N+1)
).
<!-- parent grid: this time not defining scales width/height but leaving auto -->
<div style="border: 1px solid #BBB; padding: 5pt; display: grid; grid-template-columns: auto auto 1fr; grid-template-rows: minmax(200px, auto) auto auto;">
<!-- vertical scale, 1x1 grid with every element placed at the start -->
<div style="grid-area: 1 / 1; display: grid; place-items: start; text-align: right; margin-right: 4px;">
<!-- vertical ticks, as cell elements with a relative top -->
<div style="grid-area: 1 / 1; position: relative; top: calc((10 - 8.2) / (10 - 0) * 100%); transform: translate(0, -50%);">8.2/10</div>
<div style="grid-area: 1 / 1; position: relative; top: calc((10 - 4.4) / (10 - 0) * 100%); transform: translate(0, -50%);">4.4/10</div>
</div>
<div style="grid-area: 1 / 2; width: 4px; background-color: #444; border-radius: 2px;"></div>
<div style="grid-area: 2 / 3; height: 4px; background-color: #444; border-radius: 2px;"></div>
<!-- horizontal scale, 1x1 grid with every element placed at the start -->
<div style="grid-area: 3 / 3; display: grid; place-items: start; text-align: center; margin-top: 4px;">
<!-- horizontal ticks, as cell elements with a relative left -->
<div style="grid-area: 1 / 1; position: relative; left: calc((34 - 0) / (100 - 0) * 100%); max-width: calc((1 / 3) * 100%); transform: translate(-50%, 0);">34%</div>
<div style="grid-area: 1 / 1; position: relative; left: calc((72 - 0) / (100 - 0) * 100%); max-width: calc((1 / 3) * 100%); transform: translate(-50%, 0);">72%</div>
</div>
</div>
Compared to the first chart for which I needed to put a lot of margin, this one gives as much space as possible to the actual chart area with no risk of truncated label.