You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

145 lines
4.0 KiB
TypeScript

const MAX_LINE_SIZE = 50
/**
* Draw a semi-plane delimited by the equation `a1 x + a2 y <= b`
*/
export function drawSemiplane(
g: CanvasRenderingContext2D,
a1: number,
a2: number,
b: number,
{
gradientAccent,
gradientTransparent,
gradientSize,
lineColor,
lineWidth,
}: {
gradientAccent?: string
gradientTransparent?: string
gradientSize?: number
lineColor?: string
lineWidth?: number
} = {}
) {
gradientAccent ??= '#ffa90066'
gradientTransparent ??= '#ffa90000'
lineColor ??= '#9c6700'
lineWidth ??= 2
gradientSize ??= 1 / 1.25
// The gradient is perpendicular to the line, first generate a point on the line
let [p1, p2] = [0, 0]
if (a2 === 0) {
p1 = b / a1
p2 = 0
} else {
p1 = 0
p2 = b / a2
}
const normalize = Math.sqrt(a1 ** 2 + a2 ** 2) / gradientSize
const gradient = g.createLinearGradient(p1, p2, p1 - a1 / normalize, p2 - a2 / normalize)
gradient.addColorStop(0, gradientAccent)
gradient.addColorStop(1, gradientTransparent)
g.fillStyle = gradient
// g.fillStyle = 'rgba(0, 0, 0, 0.1)'
g.beginPath()
if (a2 === 0) {
if (a1 > 0) {
g.moveTo(b / a1, -MAX_LINE_SIZE)
g.lineTo(-MAX_LINE_SIZE, -MAX_LINE_SIZE)
g.lineTo(-MAX_LINE_SIZE, MAX_LINE_SIZE)
g.lineTo(b / a1, MAX_LINE_SIZE)
} else {
g.moveTo(b / a1, -MAX_LINE_SIZE)
g.lineTo(MAX_LINE_SIZE, -MAX_LINE_SIZE)
g.lineTo(MAX_LINE_SIZE, MAX_LINE_SIZE)
g.lineTo(b / a1, MAX_LINE_SIZE)
}
} else {
if (a2 > 0) {
g.moveTo(-MAX_LINE_SIZE, (b - a1 * -MAX_LINE_SIZE) / a2)
g.lineTo(-MAX_LINE_SIZE, MAX_LINE_SIZE)
g.lineTo(-MAX_LINE_SIZE, -MAX_LINE_SIZE)
g.lineTo(MAX_LINE_SIZE, (b - a1 * MAX_LINE_SIZE) / a2)
} else {
g.moveTo(MAX_LINE_SIZE, (b - a1 * MAX_LINE_SIZE) / a2)
g.lineTo(MAX_LINE_SIZE, -MAX_LINE_SIZE)
g.lineTo(MAX_LINE_SIZE, MAX_LINE_SIZE)
g.lineTo(-MAX_LINE_SIZE, (b - a1 * -MAX_LINE_SIZE) / a2)
}
}
g.fill()
// Draw the line
g.strokeStyle = lineColor
g.lineWidth = (lineWidth * 10) / (g.canvas.width / window.devicePixelRatio)
g.beginPath()
if (a2 === 0) {
g.moveTo(b / a1, -MAX_LINE_SIZE)
g.lineTo(b / a1, MAX_LINE_SIZE)
} else {
g.moveTo(-MAX_LINE_SIZE, (b - a1 * -MAX_LINE_SIZE) / a2)
g.lineTo(MAX_LINE_SIZE, (b - a1 * MAX_LINE_SIZE) / a2)
}
g.stroke()
}
export function drawSimpleArrow(
g: CanvasRenderingContext2D,
x1: number,
y1: number,
x2: number,
y2: number,
size: number,
color: string = '#333',
lineDash: number[] = []
) {
const arrowLength = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
const actualSize = (500 * size) / g.canvas.offsetWidth
g.save()
g.strokeStyle = color
g.fillStyle = color
g.setLineDash(lineDash)
g.beginPath()
g.translate(x1, y1)
g.rotate(Math.atan2(y2 - y1, x2 - x1))
g.moveTo(0, 0)
g.lineTo(arrowLength - actualSize / 2, 0)
g.stroke()
g.beginPath()
g.moveTo(arrowLength, 0)
g.lineTo(arrowLength - actualSize, -actualSize * 0.75)
g.lineTo(arrowLength - actualSize, +actualSize * 0.75)
g.lineTo(arrowLength, 0)
g.fill()
g.restore()
}
export function fillDot(g: CanvasRenderingContext2D, x: number, y: number, radius: number) {
g.beginPath()
g.arc(x, y, radius, 0, 2 * Math.PI)
g.fill()
}
export function strokeDot(g: CanvasRenderingContext2D, x: number, y: number, radius: number) {
g.beginPath()
g.arc(x, y, radius, 0, 2 * Math.PI)
g.stroke()
}
export function strokeInfiniteLine(g: CanvasRenderingContext2D, x1: number, y1: number, angle: number) {
g.beginPath()
g.moveTo(x1 - Math.cos(angle) * MAX_LINE_SIZE, y1 - Math.sin(angle) * MAX_LINE_SIZE)
g.lineTo(x1 + Math.cos(angle) * MAX_LINE_SIZE, y1 + Math.sin(angle) * MAX_LINE_SIZE)
g.stroke()
}