Небольшое знание сетки CSS позволяет нам построить диаграмму, которая заполняет доступное пространство в обоих направлениях.

Chart.js — это полнофункциональная, но простая в использовании библиотека Javascript для создания многих типов диаграмм и графиков, которые она рисует на элементе холста. Поскольку холст похож на изображение в том смысле, что он имеет как собственный (рендеринговый) размер в пикселях, так и размер экрана, сделать его адаптивным — непростая задача. Документы Chart.js кратко описывают проблему:

Когда дело доходит до изменения размера диаграммы в зависимости от размера окна, основным ограничением является то, что размер холста рендеринг (canvas.width и .height) не может не выражаться относительными значениями, вопреки размеру отображаемого (canvas.style.width и .height). Кроме того, эти размеры не зависят друг от друга, поэтому размер рендеринга холста не подстраивается автоматически в зависимости от размера отображения, что делает визуализацию неточной.

Более подробное объяснение и общие решения по изменению размера холста см. в разделе Изменение размера холста в WebGL.

Если вы удосужились прочитать эту статью, вы знаете, что изменение размера холста требует определенных усилий. К счастью, Chart.js делает большую часть этой сложной работы за нас! Диаграмма Chart.js по умолчанию адаптивна, но с одним ограничением: она сохраняет соотношение сторон. Вот пример, практически без применения стилей:

Вы можете подумать: «Если холст похож на изображение, разве это не именно то, что нам нужно?». В конце концов, если вы волей-неволей изменили соотношение сторон изображения, оно выглядело бы искаженным и странным:

Но с диаграммой или графиком часто наши данные могут быть представлены разборчиво в различных соотношениях сторон:

В ситуации, над которой я работал, в интерфейсе есть несколько панелей данных и элементов управления, окружающих диаграмму. Мы не знаем, какого размера будет браузер, изменит ли пользователь размер своего браузера или даже свернет одну из панелей. Но (почти) что бы ни случилось, мы хотим, чтобы диаграмма занимала все доступное ей место:

Изменение размера диаграммы в двух измерениях

При поведении Chart.js по умолчанию размер диаграммы изменяется в зависимости от доступной ширины, но не высоты, всегда сохраняя исходное соотношение сторон.

Так как же заставить нашу диаграмму изменять размер в двух измерениях? Мы определенно не хотим заниматься выяснением пространства, доступного для диаграммы, и вручную устанавливать соотношение сторон. Даже если бы нам это удалось, chart.options.aspectRatio нельзя было бы изменить после того, как диаграмма была нарисована.

Придав нашему макету немного больше структуры с помощью сетки CSS, например:

.grid {
 display: grid;
 gap: 1em;
 grid-template-rows: min-content 1fr min-content;
 grid-template-columns: 1fr 3fr;
 grid-template-areas: "a h"
                      "a c"
                      "a f";
 height: calc(100vh - 2em);
 width: calc(100vw - 2em); /* not necessary, but just to be explicit */
 margin: 1em;
}

вот снова поведение диаграммы по умолчанию:

Вы заметите, что размеры диаграммы (с сохранением соотношения сторон) изменяются, когда окно просмотра становится больше, но не когда оно становится меньше. Мы хотим, чтобы диаграмма:

  • заполнить как вертикальное, так и горизонтальное пространство
  • изменить размер, если область просмотра становится меньше в любом направлении

Первая задача проста. Если мы установим maintainAspectRatio в false, диаграмма заполнит доступное пространство по горизонтали и вертикали при первой загрузке. И он изменяет размер с переменным соотношением сторон. Это немного приближает нас к тому, что мы хотим:

Но опять же, вы заметите, что диаграмма станет только больше! Это связано с тем, что Chart.js перерисовывает холст, когда содержащийся в нем элемент меняет размер:

Обнаружение изменения размера холста не может быть выполнено непосредственно из элемента холста. Chart.js использует свой родительский контейнер для обновления рендеринга холста и размеров дисплея.

С нашей текущей настройкой сетки CSS элемент-контейнер диаграммы (который является элементом сетки) не изменяет размер, когда окно просмотра становится меньше; сама диаграмма препятствует этому. Таким образом, повторный рендер никогда не запускается. Такое поведение может показаться вам несколько неожиданным. В конце концов, мы устанавливаем высоту и ширину нашей сетки, а элемент сетки, содержащий нашу диаграмму, имеет высоту дорожки 1fr и ширину 3fr. Это 3 «доли доступной ширины», верно? Вроде, как бы, что-то вроде.

3fr на самом деле сокращение от minmax(auto, 3fr). И размер элемента сетки auto определяется его содержимым. Элемент сетки не станет меньше своего содержимого, если:

  • у него есть переполнение, установленное на что-то, кроме видимого
  • он или его сетка-дорожка имеет определенную (не дробную) ширину
  • минимальная ширина дорожки сетки задана явно

Мы можем использовать любой из вышеперечисленных вариантов для решения нашей проблемы, но самым простым является последний вариант. Вместо 3fr мы можем использовать minmax(0, 3fr). Теперь у нас есть:

.grid {
 display: grid;
 gap: 1em;
 grid-template-rows: min-content minmax(0, 1fr) min-content;
 grid-template-columns: 1fr minmax(0, 3fr);
 grid-template-areas: "a h"
                      "a c"
                      "a f";
 height: calc(100vh - 2em);
 width: calc(100vw - 2em); /* not necessary, but just to be explicit */
 margin: 1em;
}

Это позволяет сузить дорожку сетки до нулевой ширины. Таким образом, родитель диаграммы (в данном случае элемент figure, который является элементом сетки) изменяет размер и запускает повторную визуализацию диаграммы:

Заключение

Это довольно специфическая проблема, но отслеживание решения привело меня к лучшему пониманию того, как определяются размеры элементов сетки и почему это иногда приводит к неожиданному поведению с переполнением. Подробнее об этом читайте в статье Дейва Руперта Breaking the Grid и в этой ручке с кучей примеров сетки.