Spec-Zone.ru › D3.js 5

d3-zoom

Панорамирование и масштабирование — популярные методы взаимодействия, позволяющие пользователю фокусироваться на области интереса, ограничивая просмотр. Их легко освоить благодаря непосредственному управлению: нажатие и перетаскивание для панорамирования (перевода), вращение колесика для масштабирования (масштабирования) или использование сенсорного ввода. Панорамирование и масштабирование широко используются в веб-картах, но также могут использоваться с визуализациями, такими как временные ряды и диаграммы рассеяния.

Поведение масштабирования, реализованное в d3-zoom, представляет собой удобную, но гибкую абстракцию для включения панорамирования и масштабирования в выборки. Оно обрабатывает удивительное разнообразие событий ввода и особенностей браузера. Поведение масштабирования не зависит от DOM, поэтому вы можете использовать его с SVG, HTML или Canvas.

Canvas ZoomingSVG Zooming

Поведение масштабирования также разработано для работы с d3-scale и d3-axis; см. transform.rescaleX и transform.rescaleY. Вы также можете ограничить масштабирование с помощью zoom.scaleExtent и панорамирование с помощью zoom.translateExtent.

Axis Zooming

Поведение масштабирования можно комбинировать с другими, такими как d3-drag для перетаскивания и d3-brush для фокусировки + контекста.

Drag & Zoom IIBrush & Zoom

Поведение масштабирования можно программно контролировать с помощью zoom.transform, что позволяет реализовать элементы управления пользовательским интерфейсом, которые управляют отображением или создают анимированные туры по вашим данным. Плавные переходы при масштабировании основаны на “Плавное и эффективное масштабирование и панорамирование” Ярке Дж. ван Вейка и Вима А.А. Нуйя.

Zoom Transitions

См. также d3-tile для примеров панорамирования и масштабирования карт.

Установка

Если вы используете NPM, npm install d3-zoom. В противном случае загрузите последнюю версию. Вы также можете загрузить его напрямую с d3js.org, как самостоятельную библиотеку или как часть D3 4.0. Поддерживаются AMD, CommonJS и обычные среды. В обычной среде экспортируется глобальная переменная d3:

<script src="https://d3js.org/d3-color.v1.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-ease.v1.min.js"></script>
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="https://d3js.org/d3-timer.v1.min.js"></script>
<script src="https://d3js.org/d3-transition.v1.min.js"></script>
<script src="https://d3js.org/d3-drag.v1.min.js"></script>
<script src="https://d3js.org/d3-zoom.v1.min.js"></script>
<script>

var zoom = d3.zoom();

</script>

Попробуйте d3-zoom в вашем браузере.

Справочник API

В этой таблице описано, как поведение масштабирования интерпретирует события браузера:

Событие Элемент прослушивания Событие масштабирования Предотвращение по умолчанию?
mousedown⁵ выборка начало нет¹
mousemove² окно¹ масштабирование да
mouseup² окно¹ конец да
dragstart² окно - да
selectstart² окно - да
click³ окно - да
dblclick выборка несколько⁶ да
wheel⁸ выборка масштабирование⁷ да
touchstart выборка несколько⁶ нет⁴
touchmove выборка масштабирование да
touchend выборка конец нет⁴
touchcancel выборка конец нет⁴

Распространение всех потребленных событий немедленно останавливается.

¹ Необходимо для захвата событий за пределами iframe; см. d3-drag#9.
² Применяется только во время активного жеста с помощью мыши; см. d3-drag#9.
³ Применяется только сразу после некоторых жестов с помощью мыши; см. zoom.clickDistance.
⁴ Необходимо, чтобы позволить эмуляцию щелчка при сенсорном вводе; см. d3-drag#9.
⁵ Игнорируется, если находится в пределах 500 мс после окончания жеста касания; предполагает эмуляцию щелчка.
⁶ Двойной щелчок и двойной тап инициируют переход, который излучает события начала, масштабирования и окончания.
⁷ Первое событие колесика излучает событие начала; событие окончания излучается, когда события колесика не поступают в течение 150 мс.
⁸ Игнорируется, если уже достигнут соответствующий предел предела масштаба.

d3.zoom() Исходный код

Создает новое поведение масштабирования. Возвращаемое поведение, zoom, является одновременно объектом и функцией и обычно применяется к выбранным элементам через selection.call.

zoom(selection) Исходный код

Применяет это поведение масштабирования к указанной выборке, связывая необходимые обработчики событий для панорамирования и масштабирования и инициализируя трансформацию масштабирования каждого выбранного элемента к трансформации тождества, если она еще не определена. Эта функция обычно не вызывается напрямую и вызывается через selection.call. Например, чтобы создать поведение масштабирования и применить его к выборке:

selection.call(d3.zoom().on("zoom", zoomed));

Внутри поведение масштабирования использует selection.on для привязки необходимых обработчиков событий для масштабирования. Обработчики используют имя .zoom, поэтому вы можете впоследствии отвязать поведение масштабирования следующим образом:

selection.on(".zoom", null);

Чтобы отключить только масштабирование с помощью колесика (например, чтобы не мешать стандартной прокрутке), можно удалить обработчик событий колесика поведения масштабирования после применения поведения масштабирования к выборке:

selection
    .call(zoom)
    .on("wheel.zoom", null);

В качестве альтернативы, воспользуйтесь zoom.filter для большего контроля над событиями, которые могут инициировать жесты масштабирования.

Применение поведения масштабирования также устанавливает стиль -webkit-tap-highlight-color в прозрачный, отключая выделение нажатия на iOS. Если вам нужен другой цвет выделения нажатия, удалите или повторно примените этот стиль после применения поведения перетаскивания.

zoom.transform(selection, transform) Исходный код

Если selection — выборка, устанавливает текущую трансформацию масштабирования выбранных элементов в указанный transform, мгновенно излучая события начала, масштабирования и окончания событий. Если selection — переход, определяет «масштабирование» интерполяцию к указанному transform с использованием d3.interpolateZoom, излучая событие начала при начале перехода, события масштабирования для каждого шага перехода и затем событие окончания, когда переход завершается (или прерывается). transform может быть задан либо как трансформация масштабирования, либо как функция, возвращающая трансформацию масштабирования. Если это функция, она вызывается для каждого выбранного элемента, получая текущий фрагмент данных d и индекс i, с контекстом this в качестве текущего элемента DOM.

Эта функция обычно не вызывается напрямую и вызывается через selection.call или transition.call. Например, чтобы мгновенно сбросить трансформацию масштабирования до трансформации тождества:

selection.call(zoom.transform, d3.zoomIdentity);

Чтобы плавно сбросить трансформацию масштабирования до трансформации тождества за 750 миллисекунд:

selection.transition().duration(750).call(zoom.transform, d3.zoomIdentity);

Этот метод требует, чтобы вы полностью указали новую трансформацию масштабирования и не принуждает к определённому предела масштаба и предела перевода, если таковые есть. Чтобы получить новую трансформацию из существующей трансформации и принудительно установить пределы масштабирования и перевода, см. служебные методы zoom.translateBy, zoom.scaleBy и zoom.scaleTo.

zoom.translateBy(selection, x, y) Исходный код

Если selection — выборка, перемещает текущую трансформацию масштабирования выбранных элементов на x и y, так что новый tx1 = tx0 + kx и ty1 = ty0 + ky. Если selection — переход, определяет «масштабирование» интерполяцию, перемещая текущую трансформацию. Этот метод является удобным методом для zoom.transform. Значения смещения x и y могут быть заданы как числа или как функции, возвращающие числа. Если это функция, она вызывается для каждого выбранного элемента, получая текущий фрагмент данных d и индекс i, с контекстом this в качестве текущего элемента DOM.

zoom.translateTo(selection, x, y) Исходный код

Если selection — это выборка, то переводит текущее преобразование масштабирования выбранных элементов таким образом, чтобы указанная позиция ⟨x,y⟩ оказалась в центре области просмотра. Новые значения tx = cx - kx и ty = cy - ky, где ⟨cx,cy⟩ — центр. Если selection — это переход, то определяет анимацию «масштабирования», переводящую текущее преобразование. Этот метод является удобным методом для zoom.transform. Координаты x и y могут быть заданы либо как числа, либо как функции, возвращающие числа. Если это функция, то она вызывается для каждого выбранного элемента, получая текущие данные d и индекс i, а контекст this как текущий элемент DOM.

zoom.scaleBy(selection, k) Source

Если selection — это выборка, то масштабирует текущее преобразование масштабирования выбранных элементов на k, так что новое значение k₁ = k₀k. Если selection — это переход, то определяет анимацию «масштабирования», переводящую текущее преобразование. Этот метод является удобным методом для zoom.transform. Коэффициент масштабирования k может быть задан либо как число, либо как функция, возвращающая число. Если это функция, то она вызывается для каждого выбранного элемента, получая текущие данные d и индекс i, а контекст this как текущий элемент DOM.

zoom.scaleTo(selection, k) Source

Если selection — это выборка, то масштабирует текущее преобразование масштабирования выбранных элементов до k, так что новое значение k₁ = k. Если selection — это переход, то определяет анимацию «масштабирования», переводящую текущее преобразование. Этот метод является удобным методом для zoom.transform. Коэффициент масштабирования k может быть задан либо как число, либо как функция, возвращающая число. Если это функция, то она вызывается для каждого выбранного элемента, получая текущие данные d и индекс i, а контекст this как текущий элемент DOM.

zoom.constrain([constrain]) Source

Если constrain задан, то устанавливает функцию ограничения преобразования на указанную функцию и возвращает поведение масштабирования. Если constrain не задан, возвращает текущую функцию ограничения, которая по умолчанию:

function constrain(transform, extent, translateExtent) {
  var dx0 = transform.invertX(extent[0][0]) - translateExtent[0][0],
      dx1 = transform.invertX(extent[1][0]) - translateExtent[1][0],
      dy0 = transform.invertY(extent[0][1]) - translateExtent[0][1],
      dy1 = transform.invertY(extent[1][1]) - translateExtent[1][1];
  return transform.translate(
    dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1),
    dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1)
  );
}

Функция ограничения должна вернуть преобразование, учитывая текущее преобразование, область просмотра и область перемещения. Реализация по умолчанию пытается гарантировать, что область просмотра не выходит за пределы области перемещения.

zoom.filter([filter]) Source

Если filter задан, то устанавливает фильтр на указанную функцию и возвращает поведение масштабирования. Если filter не задан, возвращает текущий фильтр, который по умолчанию:

function filter() {
  return !d3.event.button;
}

Если фильтр возвращает ложное значение, то инициирующее событие игнорируется, и жесты масштабирования не начинаются. Таким образом, фильтр определяет, какие входные события игнорируются. Фильтр по умолчанию игнорирует события mousedown на дополнительных кнопках, поскольку эти кнопки, как правило, предназначены для других целей, таких как контекстное меню.

zoom.touchable([touchable]) Source

Если touchable задан, то устанавливает детектор поддержки сенсорного управления на указанную функцию и возвращает поведение масштабирования. Если touchable не задан, возвращает текущий детектор поддержки сенсорного управления, который по умолчанию:

function touchable() {
  return "ontouchstart" in this;
}

Обработчики событий касания регистрируются только в том случае, если детектор возвращает истинное значение для соответствующего элемента, когда поведение масштабирования применено. Детектор по умолчанию хорошо работает для большинства браузеров, поддерживающих сенсорный ввод, но не для всех; например, эмулятор мобильного устройства Chrome не обнаруживает его.

zoom.wheelDelta([delta]) Source

Если delta задан, то устанавливает функцию дельты прокрутки на указанную функцию и возвращает поведение масштабирования. Если delta не задан, возвращает текущую функцию дельты прокрутки, которая по умолчанию:

function wheelDelta() {
  return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1) / 500;
}

Значение Δ, возвращаемое функцией дельты прокрутки, определяет величину масштабирования в ответ на WheelEvent. Коэффициент масштабирования transform.k умножается на 2Δ; например, Δ = +1 удваивает коэффициент масштабирования, Δ = -1 делит коэффициент масштабирования пополам.

zoom.extent([extent]) Source

Если extent задан, то устанавливает область просмотра на указанный массив точек [[x0, y0], [x1, y1]], где [x0, y0] — верхний левый угол области просмотра, а [x1, y1] — нижний правый угол области просмотра, и возвращает это поведение масштабирования. Область extent также может быть задана как функция, которая возвращает такой массив; если это функция, она вызывается для каждого выбранного элемента, получая текущие данные d и индекс i, а контекст this как текущий элемент DOM.

Если extent не задан, возвращает текущий доступ к области, который по умолчанию [[0, 0], [ширина, высота]], где ширина — ширина клиентского элемента, а высота — его высота клиентского элемента; для SVG-элементов используется ширина и высота ближайшего родительского SVG-элемента. В этом случае родительский SVG-элемент должен иметь заданные атрибуты ширины и высоты, а не (например), свойства CSS или атрибут viewBox; SVG не предоставляет программного метода для получения начального размера области просмотра. В качестве альтернативы можно использовать element.getBoundingClientRect. (В Firefox element.clientWidth и element.clientHeight равны нулю для SVG-элементов!)

Область просмотра влияет на несколько функций: центр области просмотра остается неизменным при изменении с помощью zoom.scaleBy и zoom.scaleTo; центр и размеры области просмотра влияют на выбранный путь d3.interpolateZoom; область просмотра необходима для принудительного использования необязательной области перемещения.

zoom.scaleExtent([extent]) Source

Если extent задан, то устанавливает пределы масштабирования на указанный массив чисел [k0, k1], где k0 — минимальный допустимый коэффициент масштабирования, а k1 — максимальный допустимый коэффициент масштабирования, и возвращает это поведение масштабирования. Если extent не задан, возвращает текущие пределы масштабирования, которые по умолчанию [0, ∞]. Пределы масштабирования ограничивают масштабирование. Они применяются при взаимодействии и при использовании zoom.scaleBy, zoom.scaleTo и zoom.translateBy; однако они не применяются при явном установлении преобразования с помощью zoom.transform.

Если пользователь пытается приблизить с помощью колеса мыши, когда уже достигнут соответствующий предел масштабирования, события колеса мыши будут игнорироваться и не инициируют жест масштабирования. Это позволяет пользователю прокручивать вниз мимо области масштабирования после увеличения масштаба или прокручивать вверх после уменьшения масштаба. Если вы предпочитаете всегда предотвращать прокрутку при вводе колеса мыши независимо от пределов масштабирования, зарегистрируйте обработчик событий колеса мыши для предотвращения поведения браузера по умолчанию:

selection
    .call(zoom)
    .on("wheel", function() { d3.event.preventDefault(); });
zoom.translateExtent([extent]) Source

Если extent задан, то устанавливает пределы перемещения на указанный массив точек [[x0, y0], [x1, y1]], где [x0, y0] — верхний левый угол мира, а [x1, y1] — нижний правый угол мира, и возвращает это поведение масштабирования. Если extent не задан, возвращает текущие пределы перемещения, которые по умолчанию [[-∞, -∞], [+∞, +∞]]. Пределы перемещения ограничивают панорамирование и могут вызывать перемещение при уменьшении масштаба. Они применяются при взаимодействии и при использовании zoom.scaleBy, zoom.scaleTo и zoom.translateBy; однако они не применяются при явном установлении преобразования с помощью zoom.transform.

zoom.clickDistance([distance]) Source

Если указано distance, устанавливает максимальное расстояние, на которое мышь может перемещаться между нажатием и отпусканием кнопки мыши, что вызовет последующее событие клика. Если в какой-либо момент между нажатием и отпусканием кнопки мышь находится на расстоянии, равном или большем distance, от своего положения при нажатии, событие клика, следующего за отпусканием кнопки, будет подавлено. Если distance не указан, возвращает текущий порог расстояния, который по умолчанию равен нулю. Порог расстояния измеряется в координатах клиента (event.clientX и event.clientY).

zoom.duration([duration]) Source

Если указан параметр duration, устанавливает длительность переходов при масштабировании при двойном щелчке и двойном тапе на указанное количество миллисекунд и возвращает поведение масштабирования. Если duration не указан, возвращает текущую длительность, которая по умолчанию составляет 250 миллисекунд. Если длительность не больше нуля, двойной щелчок и двойной тап вызывают мгновенные изменения преобразования масштабирования, а не инициируют плавные переходы.

Чтобы отключить переходы при двойном щелчке и двойном тапе, можно удалить обработчик события dblclick поведения масштабирования после применения поведения масштабирования к выбору:

selection
    .call(zoom)
    .on("dblclick.zoom", null);
zoom.interpolate([interpolate]) Source

Если указана функция interpolate, устанавливает фабрику интерполяции для переходов при масштабировании на указанную функцию. Если interpolate не указан, возвращает текущую фабрику интерполяции, которая по умолчанию равна d3.interpolateZoom для реализации плавного масштабирования. Чтобы применить прямую интерполяцию между двумя представлениями, используйте d3.interpolate вместо этого.

zoom.on(typenames[, listener]) Source

Если указан listener, устанавливает обработчик события для указанных typenames и возвращает поведение масштабирования. Если для того же типа и имени уже был зарегистрирован обработчик события, существующий обработчик удаляется перед добавлением нового обработчика. Если listener равен null, удаляются текущие обработчики событий для указанных typenames, если они есть. Если listener не указан, возвращает первый из текущих обработчиков, соответствующих указанным typenames, если таковой имеется. При отправке указанного события каждый listener будет вызван с тем же контекстом и аргументами, что и обработчики selection.on: текущие данные d и индекс i, с контекстом this в качестве текущего DOM-элемента.

typenames — строка, содержащая один или несколько typename, разделенных пробелами. Каждый typename — это type, необязательно после которого следует точка (.) и name, например, zoom.foo и zoom.bar; имя позволяет регистрировать несколько обработчиков для одного type. type должен быть одним из следующих:

  • start — после начала масштабирования (например, при нажатии кнопки мыши).
  • zoom — после изменения преобразования масштабирования (например, при перемещении мыши).
  • end — после окончания масштабирования (например, при отпускании кнопки мыши).

См. dispatch.on для получения дополнительной информации.

События масштабирования

Когда вызывается обработчик события масштабирования zoom event listener, d3.event устанавливается на текущее событие масштабирования. Объект event предоставляет несколько полей:

  • event.target — связанное поведение масштабирования zoom behavior.
  • event.type — строка “start”, “zoom” или “end”; см. zoom.on.
  • event.transform — текущее преобразование масштабирования.
  • event.sourceEvent — основное событие ввода, например, mousemove или touchmove.

Преобразования масштабирования

Поведение масштабирования сохраняет состояние масштабирования на элементе, к которому было применено поведение масштабирования apply, а не на самом поведении масштабирования. Это потому, что поведение масштабирования может применяться к множеству элементов одновременно, и каждый элемент может быть масштабирован независимо. Состояние масштабирования может изменяться как при взаимодействии пользователя, так и программно с помощью zoom.transform.

Чтобы получить состояние масштабирования, используйте event.transform на текущем событии масштабирования внутри обработчика события масштабирования (см. zoom.on), или используйте d3.zoomTransform для данного узла. Последнее особенно полезно для изменения состояния масштабирования программно, например, для реализации кнопок масштабирования в увеличении и уменьшении.

d3.zoomTransform(node) Source

Возвращает текущее преобразование для указанного node. Обратите внимание, что node обычно должен быть DOM-элементом, а не selection. (Выбор может состоять из нескольких узлов в различных состояниях, и эта функция возвращает только одно преобразование.) Если у вас есть выбор, сначала вызовите selection.node:

var transform = d3.zoomTransform(selection.node());

В контексте обработчика события, node обычно является элементом, получившим событие ввода (которое должно быть равно event.transform), this:

var transform = d3.zoomTransform(this);

Внутренне преобразование элемента хранится как element.__zoom; однако вы должны использовать этот метод, а не обращаться к нему напрямую. Если у данного node нет определенного преобразования, возвращает тождественное преобразование. Возвращаемое преобразование представляет собой двумерную матрицу преобразования вида:

k 0 tx
0 k ty
0 0 1

(Эта матрица способна представлять только масштабирование и сдвиги; в будущей версии может быть также разрешена поворот, хотя это, вероятно, не будет изменением, совместимым со старыми версиями.) Позиция ⟨x,y⟩ преобразуется в ⟨xk + tx,yk + ty⟩. Объект преобразования предоставляет следующие свойства:

  • transform.x — величина сдвига tx по оси x.
  • transform.y — величина сдвига ty по оси y.
  • transform.k — коэффициент масштабирования k.

Эти свойства следует рассматривать как только для чтения; вместо изменения преобразования используйте transform.scale и transform.translate для вывода нового преобразования. Также см. zoom.scaleBy, zoom.scaleTo и zoom.translateBy для удобных методов поведения масштабирования. Чтобы создать преобразование с заданными k, tx и ty:

var t = d3.zoomIdentity.translate(x, y).scale(k);

Чтобы применить преобразование к контексту 2D Canvas, используйте context.translate, за которым следует context.scale:

context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);

Аналогично, для применения преобразования к HTML-элементам через CSS:

div.style("transform", "translate(" + transform.x + "px," + transform.y + "px) scale(" + transform.k + ")");
div.style("transform-origin", "0 0");

Чтобы применить преобразование к SVG:

g.attr("transform", "translate(" + transform.x + "," + transform.y + ") scale(" + transform.k + ")");

Или проще, используя transform.toString:

g.attr("transform", transform);

Обратите внимание, что порядок преобразований важен! Сдвиг должен быть применен до масштабирования.

transform.scale(k) Source

Возвращает преобразование, масштаб k₁ которого равен k₀k, где k₀ — масштаб текущего преобразования.

transform.translate(x, y) Source

Возвращает преобразование, сдвиги tx1 и ty1 которого равны tx0 + x и ty0 + y, где tx0 и ty0 — сдвиги текущего преобразования.

transform.apply(point) Source

Возвращает преобразование указанной точки, которая представляет собой массив из двух чисел [x, y]. Возвращаемая точка равна [xk + tx, yk + ty].

transform.applyX(x) Source

Возвращает преобразование указанной координаты x, xk + tx.

transform.applyY(y) Source

Возвращает преобразование указанной координаты y, yk + ty.

transform.invert(point) Source

Возвращает обратное преобразование указанной точки, которая представляет собой массив из двух чисел [x, y]. Возвращаемая точка равна [(x - tx) / k, (y - ty) / k].

transform.invertX(x) Source

Возвращает обратное преобразование указанной координаты x, (x - tx) / k.

transform.invertY(y) Source

Возвращает обратное преобразование указанной координаты y, (y - ty) / k.

transform.rescaleX(x) Source

Возвращает копию непрерывной шкалы x, область значений которой преобразована. Реализация включает в себя сначала применение обратного преобразования по x к области значений шкалы, а затем применение обратной шкалы для вычисления соответствующей области определения:

function rescaleX(x) {
  var range = x.range().map(transform.invertX, transform),
      domain = range.map(x.invert, x);
  return x.copy().domain(domain);
}

Шкала x должна использовать d3.interpolateNumber; не используйте continuous.rangeRound, так как это снижает точность continuous.invert и может привести к неточным значениям преобразованной области определения. Этот метод не изменяет входную шкалу x; таким образом, x представляет собой не преобразованную шкалу, а возвращаемая шкала — её преобразованный вид.

transform.rescaleY(y) Source

Возвращает копию непрерывной шкалы y, область значений которой преобразована. Реализация включает в себя сначала применение обратного преобразования по y к области значений шкалы, а затем применение обратной шкалы для вычисления соответствующей области определения:

function rescaleY(y) {
  var range = y.range().map(transform.invertY, transform),
      domain = range.map(y.invert, y);
  return y.copy().domain(domain);
}

Шкала y должна использовать d3.interpolateNumber; не используйте continuous.rangeRound, так как это снижает точность continuous.invert и может привести к неточным значениям преобразованной области определения. Этот метод не изменяет входную шкалу y; таким образом, y представляет собой не преобразованную шкалу, а возвращаемая шкала — её преобразованный вид.

transform.toString() Source

Возвращает строку, представляющую преобразование SVG, соответствующее данному преобразованию. Реализовано как:

function toString() {
  return "translate(" + this.x + "," + this.y + ") scale(" + this.k + ")";
}
d3.zoomIdentity Source

Преобразование по умолчанию, где k = 1, tx = ty = 0.

© 2010–2018 Michael Bostock
Licensed under the BSD License.
https://github.com/d3/d3-zoom

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API