Published
Edited
Jan 8, 2020
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const { w, h } = {
w: 800,
h: 800
};
const plane = cartesianPlane(w, h);
console.log(plane)

points(plane, [
{ x: -4, y: -8, color: 'red' },
{ x: 3, y: 4, color: 'blue' },
{ x: 4, y: 3, color: 'red' },
{ x: 4, y: 2, color: 'yellow' }
]);

lines(plane, [
{ gradient: 2, offset: 1.5, color: 'red', type: 'd' },
{ gradient: 1, offset: 0, color: 'blue' },
{ gradient: Infinity, offset: 0 }
]);

segment(plane, {
points: [[-1, -9], [2, -9]],
type: 'd'
});
segwitharrows(plane, {
points: [[-3, 0], [-3, 2]]
});

triangle(plane, {
name: 'Sample Triangle',
points: [[-4.0, -6.4], [2.0, 6.0], [4.0, -5.3]]
});

curves(
plane,
{
func: x => 0.5 * x * x * x + 1.5 * x * x,
domain: x => true
},
{
color: 'purple',
step: 0.01,
type: 'point',
inverse: false
}
);

parallelogram(plane, {
name: 'sample parallelogram',
point: [-1, 4],
width: 2,
height: 2,
angle: 45
});

rectangle(plane, {
name: 'sample rectangle',
point: [-1, 0],
width: 2,
height: 2
});

square(plane, {
name: 'sample square',
point: [2, 0],
length: 2
});

circle(plane, {
center: [0, 0],
radius: 2,
startAngle: 0,
endAngle: Math.PI * 90
});

return plane.svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
cartesianPlane = (width, height, { margin, axes } = {}) => {
const fixedMargin = {
left: 32,
top: 32,
right: 32,
bottom: 32,
...margin
};

const fixedAxes = {
x: {
domain: [-5, 5],
ticks: 10
},
y: {
domain: [-5, 5],
ticks: 10
},
...axes
};

const svgNode = DOM.svg(
width + fixedMargin.left + fixedMargin.right,
height + fixedMargin.top + fixedMargin.bottom
);
const svg = d3.select(svgNode);

const xscale = d3
.scaleLinear()
.domain(fixedAxes.x.domain)
.range([0, width]);

const xaxis = g => {
return g
.call(d3.axisBottom(xscale).ticks(fixedAxes.x.ticks))
.call(g => g.select('.domain').remove())
.call(g =>
g
.selectAll('.tick line')
.attr('y2', -height)
.attr('stroke-opacity', 0.1)
);
};
const yscale = d3
.scaleLinear()
.domain(fixedAxes.y.domain)
.range([height, 0]);

const yaxis = g => {
return g
.call(d3.axisLeft(yscale).ticks(fixedAxes.y.ticks))
.call(g => g.select('.domain').remove())
.call(g =>
g
.selectAll('.tick line')
.attr('x2', width)
.attr('stroke-opacity', 0.1)
);
};

const g = svg
.append('g')
.attr('transform', `translate(${fixedMargin.left}, ${fixedMargin.top})`);

g.append('g')
.attr('transform', `translate(0, ${height})`)
.call(xaxis);

g.append('g').call(yaxis);

return {
svg,
ctx: g,
scale: {
x: xscale,
y: yscale
},
domain: {
x: fixedAxes.x.domain,
y: fixedAxes.y.domain
}
};
}
Insert cell
Insert cell
Insert cell
points = (plane, data) => {
plane.ctx
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('r', 4)
.attr('cx', d => plane.scale.x(d.x))
.attr('cy', d => plane.scale.y(d.y))
.attr('fill', d => d.color || '#000000')
.append('title')
.text(d => `(${d.x}, ${d.y})`);
}
Insert cell
Insert cell
Insert cell
lines = (plane, data, type) => {
const generator = (gradient, offset) => {
if (gradient == Infinity) {
return [[offset, plane.domain.y[0]], [offset, plane.domain.y[1]]];
}

if (gradient == 0) {
return [[plane.domain.x[0], offset], [plane.domain.x[1], offset]];
}

const data = [];

if (gradient > 0) {
const left = gradient * plane.domain.x[0] + offset;
if (left < plane.domain.y[0]) {
data.push([(plane.domain.y[0] - offset) / gradient, plane.domain.y[0]]);
} else {
data.push([plane.domain.x[0], left]);
}

const right = gradient * plane.domain.x[1] + offset;
if (right > plane.domain.y[1]) {
data.push([(plane.domain.y[1] - offset) / gradient, plane.domain.y[1]]);
} else {
data.push([plane.domain.x[1], right]);
}
} else {
const left = gradient * plane.domain.x[0] + offset;
if (left > plane.domain.y[1]) {
data.push([(plane.domain.y[1] - offset) / gradient, plane.domain.y[1]]);
} else {
data.push([plane.domain.x[0], left]);
}

const right = gradient * plane.domain.x[1] + offset;
if (right < plane.domain.y[0]) {
data.push([(plane.domain.y[0] - offset) / gradient, plane.domain.y[0]]);
} else {
data.push([plane.domain.x[1], right]);
}
}

return data;
};

plane.ctx
.selectAll('path')
.data(data)
.enter()
.append('path')
.attr('d', d =>
d3.line()(
generator(d.gradient, d.offset).map(val => [
plane.scale.x(val[0]),
plane.scale.y(val[1])
])
)
)
.attr('stroke', d => d.color || 'black')
.attr('stroke-width', '1.6')
.attr('stroke-dasharray', d => (d.type === 'd' ? 4.0 : undefined))
.append('title')
.text(d => {
if (d.gradient == Infinity) {
return `x = ${d.offset}`;
}

if (d.gradient == 0) {
return `y = ${d.offset}`;
}
if (d.offset == 0) {
return `y = ${d.gradient}x`;
}

return `y = ${d.gradient}x + ${d.offset}`;
});
}
Insert cell
Insert cell
segment = (plane, data, type) => {
const color = {
point: '#9aceff',
stroke: '#9aceff',
...data.color
};
const p = data.points;
const g = plane.ctx.append('g');

g.selectAll('circles')
.data(p)
.enter()
.append('circle')
.attr('r', 2.8)
.attr('cx', d => plane.scale.x(d[0]))
.attr('cy', d => plane.scale.y(d[1]))
.attr('fill', color.point)
.append('title')
.text(d => `(${d[0]}, ${d[1]})`);

g.append('path')
.attr(
'd',
d3.line()(p.map(v => [plane.scale.x(v[0]), plane.scale.y(v[1])]))
)
.attr('stroke', color.stroke)
.attr('stroke-width', 1.6)
.attr('stroke-dasharray', data.type === 'd' ? 4.0 : undefined);
}
Insert cell
segnocir = (plane, data, type) => {
const color = {
point: '#9aceff',
stroke: '#9aceff',
...data.color
};
const p = data.points;
const g = plane.ctx.append('g');

g.selectAll('circles')
.data(p)
.enter()
.append('path')
.attr(
'd',
d3.line()(p.map(v => [plane.scale.x(v[0]), plane.scale.y(v[1])]))
)
.attr('stroke', color.stroke)
.attr('stroke-width', 1.6)
.attr('stroke-dasharray', data.type === 'd' ? 4.0 : undefined);
}
Insert cell
segwitharrows = (plane, data, type) => {
console.log('plane = ', plane);
const color = {
point: '#9aceff',
stroke: '#9aceff',
...data.color
};
const p = data.points;
const g = plane.ctx.append('g');

if (p[0][1] < p[1][1]) {
const lengthseg = (plane.domain.x[1] - plane.domain.x[0]) / 20;
const yval = data.points[1][1] - data.points[0][1];
const xval = data.points[1][0] - data.points[0][0];
const preangle = Math.atan(xval / yval);
const reangle = preangle * 180;
const angle = reangle / Math.PI;
const upangle = angle + 30;
const downangle = angle - 30;
const xi = (data.points[1][0] - lengthseg * Math.sin(upangle * Math.PI / 180));
const yi = data.points[1][1] - lengthseg * Math.cos(upangle * Math.PI / 180);
const xj = (data.points[1][0] - lengthseg * Math.sin(downangle * Math.PI / 180));
const yj = data.points[1][1] - lengthseg * Math.cos(downangle * Math.PI / 180);

const dataup = [[xi, yi], data.points[1]];

const datadown = [[xj, yj], data.points[1]];

segnocir(plane, {
points: dataup
});
segnocir(plane, {
points: datadown
});
} if (p[0][1] > p[1][1]) {
const lengthseg = (plane.domain.x[1] - plane.domain.x[0]) / 20;
const yval = data.points[1][1] - data.points[0][1];
const xval = data.points[1][0] - data.points[0][0];
const preangle = Math.atan(xval / yval);
const reangle = preangle * 180;
const angle = reangle / Math.PI;
const upangle = angle + 30;
const downangle = angle - 30;
const xi = (data.points[1][0] + lengthseg * Math.sin(upangle * Math.PI / 180));
const yi = data.points[1][1] + lengthseg * Math.cos(upangle * Math.PI / 180);
const xj = (data.points[1][0] + lengthseg * Math.sin(downangle * Math.PI / 180));
const yj = data.points[1][1] + lengthseg * Math.cos(downangle * Math.PI / 180);

const dataup = [[xi, yi], data.points[1]];

const datadown = [[xj, yj], data.points[1]];

segnocir(plane, {
points: dataup
});
segnocir(plane, {
points: datadown
});
} else if (p[0][1] == p[1][1]) {
const lengthseg = (plane.domain.x[1] - plane.domain.x[0]) / 20;
const yval = data.points[1][1] - data.points[0][1];
const xval = data.points[1][0] - data.points[0][0];
const preangle = Math.atan(xval / yval);
const reangle = preangle * 180;
const angle = reangle / Math.PI;
const upangle = angle + 30;
const downangle = angle - 30;
const xi = (data.points[1][0] + lengthseg * Math.sin(upangle * Math.PI / 180));
const yi = data.points[1][1] + lengthseg * Math.cos(upangle * Math.PI / 180);
const xj = (data.points[1][0] + lengthseg * Math.sin(downangle * Math.PI / 180));
const yj = data.points[1][1] + lengthseg * Math.cos(downangle * Math.PI / 180);

const dataup = [[xi, yi], data.points[1]];

const datadown = [[xj, yj], data.points[1]];

segnocir(plane, {
points: dataup
});
segnocir(plane, {
points: datadown
});
}
g.selectAll('circles')
.data(p)
.enter()
.append('path')
.attr(
'd',
d3.line()(p.map(v => [plane.scale.x(v[0]), plane.scale.y(v[1])]))
)
.attr('stroke', color.stroke)
.attr('stroke-width', 1.6)
.attr('stroke-dasharray', data.type === 'd' ? 4.0 : undefined);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more