Published unlisted
Edited
Jun 17, 2021
1 star
Insert cell
md`# Vector data model`
Insert cell
import {table} from "@tmcw/tables/2"
Insert cell
pointCoords = [
{number: 1, name: "Point 1", x: 21, y: 35},
{number: 2, name: "Point 2", x: 43, y: 16},
{number: 3, name: "Point 3", x: 33, y: 21}
]
Insert cell
Insert cell
pointsPlot = {
// While width is given, we need to define the height of our drawing "canvas"
// Define our svg
const svg = d3.select(DOM.svg(width, height))
// Make some empty space around
// Try to set the margins to zeros and see what happens!
const margin = { left: 10, top: 10, right: 10, bottom: 10 }
const xScale = d3.scaleLinear()
// Instead of mapping (0 - 40) to (0 - 100), we fill the width
.range([margin.left, width - margin.right])
.domain(d3.extent(pointCoords.map(d => d.x)))
// Append it to svg
// svg.append('g').call(d3.axisBottom(xScale))
const yScale = d3.scaleLinear()
// For y-axis, we map (0 - 40) to (580 - 10) to fill the height
// If you wonder why we are mapping the values reversely, 0 -> 580 and 40 -> 10,
// as in the coordination system of our screens, the top left corner is (0, 0)
// and bottom left corner is (0, 600)
.range([height - margin.bottom, margin.top])
.domain(d3.extent(pointCoords.map(d => d.y)))
// Append it to svg
// svg.append('g')
// .call(d3.axisLeft(yScale))
// // Need to shift the axis a little bit in order to show the tick labels
// // See more about "transform" in the cell below
// .attr('transform', `translate(${margin.left},0)`)
svg.selectAll('circle')
.data(pointCoords)
.enter()
.append('circle')
// Circles are distributed across x-axis
.attr('cx', d => xScale(d.x))
// Across y-axis as well, and it becomes two dimensional
.attr('cy', d => yScale(d.y))
.attr('r', 5)
.attr('fill', 'SteelBlue')
// Return it, and Observable will show it as output
return svg.node()
}
Insert cell
pointsTable = Inputs.table(pointCoords, {columns: ["name", "x", "y"]})
Insert cell
lineCoords = [
{number: 1, name: "Vertex 1", x: 12, y: 7},
{number: 3, name: "Vertex 3", x: 28, y: 20},
{number: 3, name: "Vertex 3", x: 33, y: 41},
{number: 2, name: "Vertex 2", x: 48, y: 26}
]
Insert cell
lineGenerator = d3.line()
Insert cell
lineData = lineCoords.map(d => [d.x * 100, 200 - d.y])
Insert cell
lineGenerator(lineData)
Insert cell
linesTitle = md`### Lines`
Insert cell
linesPlot = {
// Define our svg
const svg = d3.select(DOM.svg(width, height))
// Make some empty space around
// Try to set the margins to zeros and see what happens!
const margin = { left: 10, top: 10, right: 10, bottom: 10 }
const xScale = d3.scaleLinear()
.range([margin.left, width - margin.right])
.domain(d3.extent(lineCoords.map(d => d.x)))
const yScale = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain(d3.extent(lineCoords.map(d => d.y)))

// Line generator, scaling x and y to fill the width and height
const line = d3.line()
.x(d => xScale(d.x))
.y(d => yScale(d.y))

svg.selectAll('path')
// .data() expects an array of multiple series, each series with multiple data points
// We have only one line to plot, so we pass in an array with only one series, and this
// series has many data points in it
.data([lineCoords])
.enter()
.append('path')
// Reminded that d is a series of data points, that is, an array of values
// And the line function call takes in this array of data points
.attr('d', d => line(d))
// Remove the default value of color fill, try to comment out this line and see what happens
.attr('fill', 'none')
// The default of stroke is none, while we disabled the color fill, we make the line visible
// by setting the stroke color
.attr('stroke', 'SteelBlue')
.attr('stroke-width', 3)

return svg.node()
}
Insert cell
linesTable = Inputs.table(lineCoords, {columns: ["name", "x", "y"]})
Insert cell
polyCoords = [
{number: 1, name: "Vertex 1", x: 47, y: 43},
{number: 3, name: "Vertex 2", x: 22, y: 40},
{number: 3, name: "Vertex 3", x: 26, y: 34},
{number: 2, name: "Vertex 4", x: 21, y: 5},
{number: 2, name: "Vertex 5", x: 40, y: 15},
{number: 2, name: "Vertex 4", x: 46, y: 31}
]
Insert cell
polysTitle = md`### Polygons`
Insert cell
embedPolys = {
const ctx = DOM.context2d(width, height);
drawPolygon(polyCoords, ctx, width, height);
return ctx.canvas;
}
Insert cell
// How much of this is necessary?

generateVectors = (polyCoords) => {
// Divide the interior points of the coordinates into two chains & extract the vector components
const xVec = [];
const yVec = [];
const n = polyCoords.xPool.length;
let lastTop = polyCoords.minX, lastBot = polyCoords.minX;
for (let i = 1; i < n - 1; i++) {
let x = polyCoords.xPool[i];

if (Math.random() < 0.5) {
xVec.push(x - lastTop);
lastTop = x;
} else {
xVec.push(lastBot - x);
lastBot = x;
}
}

xVec.push(polyCoords.maxX - lastTop);
xVec.push(lastBot - polyCoords.maxX);

let lastLeft = polyCoords.minY, lastRight = polyCoords.minY;

for (let i = 1; i < n - 1; i++) {
let y = polyCoords.yPool[i];

if (Math.random() < 0.5) {
yVec.push(y - lastLeft);
lastLeft = y;
} else {
yVec.push(lastRight - y);
lastRight = y;
}
}

yVec.push(polyCoords.maxY - lastLeft);
yVec.push(lastRight - polyCoords.maxY);
// Randomly pair up the X- and Y-components
shuffle(yVec);

// Combine the paired up components into vectors
const vec = [];

for (let i = 0; i < n; i++) {
vec.push([xVec[i], yVec[i]]);
}
return vec;
}
Insert cell
shuffle = (a) => {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
Insert cell
drawPolygon = (polygon, ctx) => {
ctx.beginPath();
ctx.moveTo(polygon[0][0], polygon[0][1]);
for(let i=0; i<polygon.length; i++) {
ctx.lineTo(polygon[i][0], polygon[i][1]);
}
ctx.lineTo(polygon[0][0], polygon[0][1]);
ctx.closePath();
ctx.stroke();
}
Insert cell
// generateCoordinates = (n) => {
// // Generate two lists of random X and Y coordinates
// const xPool = [];
// const yPool = [];

// for (let i = 0; i < n; i++) {
// xPool.push(Math.random());
// yPool.push(Math.random());
// }

// // Sort them
// xPool.sort();
// yPool.sort();

// // Isolate the extreme points
// const minX = xPool[0], maxX = xPool[n - 1];
// const minY = yPool[0], maxY = yPool[n - 1];
// return {xPool, yPool, minX, maxX, minY, maxY};
// }
Insert cell
width = 200
Insert cell
height = 200
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more