Transition to an isometric projection and back again.
See also Isometric Grid Slider.
| license: gpl-3.0 |
Transition to an isometric projection and back again.
See also Isometric Grid Slider.
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <style> | |
| body { | |
| margin: 0; | |
| } | |
| svg { | |
| display: table; | |
| margin: 0 auto; | |
| } | |
| line { | |
| stroke-width: 4px; | |
| } | |
| path { | |
| fill: none; | |
| } | |
| .cell { | |
| stroke: #eee; | |
| } | |
| .outline { | |
| stroke: #000; | |
| stroke-width: 2px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script type="module"> | |
| import { cross, range } from "https://cdn.skypack.dev/d3-array@3"; | |
| import { geoEquirectangular, geoPath } from "https://cdn.skypack.dev/d3-geo@3"; | |
| import { randomUniform } from "https://cdn.skypack.dev/d3-random@3"; | |
| import { interpolateSinebow } from "https://cdn.skypack.dev/d3-scale-chromatic@3"; | |
| import { select } from "https://cdn.skypack.dev/d3-selection@3"; | |
| import { interval } from "https://cdn.skypack.dev/d3-timer@3"; | |
| import { transition } from "https://cdn.skypack.dev/d3-transition@3"; | |
| const { cos, PI, round, sin } = Math; | |
| const i = 0.4; | |
| const r = range(-1, 1, i); | |
| const cells = { | |
| type: "FeatureCollection", | |
| features: cross(r, r) | |
| .map(([lon, lat]) => ({ | |
| type: "Feature", | |
| geometry: { | |
| type: "Polygon", | |
| coordinates: [ | |
| [ | |
| [lon, lat], | |
| [lon, lat + i], | |
| [lon + i, lat + i], | |
| [lon + i, lat], | |
| [lon, lat] | |
| ] | |
| ] | |
| } | |
| })) | |
| }; | |
| const x = n => round(cos(n)); | |
| const y = n => round(sin(n)); | |
| const random = randomUniform(-1, 1); | |
| const data = Array.from({ length: 250 }).map(d => ([random(), random()])); | |
| const square = [ range(1, PI * 2.5, PI * 0.25).map(n => [x(n), y(n)]) ]; | |
| const margin = {left: 1, right: 1, top: 30, bottom: 1}; | |
| const size = 400; | |
| const projection = geoEquirectangular() | |
| .reflectY(true) | |
| .fitSize([size, size], cells); | |
| const path = geoPath(projection); | |
| const svg = select("body").append("svg") | |
| .attr("width", size + margin.left + margin.right) | |
| .attr("height", size + margin.top + margin.bottom); | |
| const g = svg.append("g") | |
| .attr("transform", `translate(${[margin.left, margin.top]})`); | |
| const cell = g.selectAll(".cell") | |
| .data(cells.features) | |
| .join("path") | |
| .attr("class", "cell") | |
| .attr("d", path); | |
| const outline = g.append("path") | |
| .attr("class", "outline") | |
| .datum({ | |
| type: "Polygon", | |
| coordinates: square | |
| }) | |
| .attr("d", path); | |
| const line = g.selectAll("line") | |
| .data(data) | |
| .join("line") | |
| .attr("stroke", (d, i, e) => interpolateSinebow(i / e.length)) | |
| .attr("y2", -margin.top + 1) | |
| .attr("transform", d => `translate(${projection(d)})`); | |
| let rotation = [0, 0, 0]; | |
| draw(rotation); | |
| interval(() => { | |
| rotation = rotation[0] ? [0, 0, 0] : [30, -60, 0]; | |
| draw(rotation) | |
| }, 2e3); | |
| function draw(rotation){ | |
| projection | |
| .rotate(rotation) | |
| .fitSize([size, size], cells); | |
| cell | |
| .transition() | |
| .duration(1e3) | |
| .attr("d", path); | |
| outline | |
| .transition() | |
| .duration(1e3) | |
| .attr("d", path); | |
| line | |
| .transition() | |
| .duration(1e3) | |
| .attr("transform", d => `translate(${projection(d)})`); | |
| } | |
| </script> | |
| </body> | |
| </html> |