<h1>CSS</h1>

		<wc-graph-css points='[[10,10], [20,20], [30,30], [40,40], [10,20,"blue"], [20,30,"blue"], [70,95,"blue"],[-20,-20,"magenta", 2.5, "square"]]'></wc-graph-css>
		<wc-graph-css points='[[10,10], [20,20], [30,30], [40,40], [10,20,"blue"], [20,30,"blue"], [70,95,"blue"]]'>
		</wc-graph-css>
		<wc-graph-css id="test4"></wc-graph-css>
<script>
document.addEventListener("DOMContentLoaded", () => {
	document.querySelector("#test4").points = [[-10, -10, "green"], [-20, -20, "green"], [-30, -30, "green"], [-40, -40, "green"]];
});
</script>

		<wc-graph-css func="return 75 * Math.sin(5 * x)" xmin="-3" xmax="3" step="0.01">
		</wc-graph-css>
		
		<wc-graph-css func="return 75 * Math.sin(5 * x)" xmin="-3" xmax="3" step="0.05">
		</wc-graph-css>
		
		<wc-graph-css func="return 75 * Math.sin(5 * x)" xmin="-3" xmax="3" step="0.01" width="1280" height="720" continuous
			default-size="4" default-color="blue" thickness="2">
		</wc-graph-css>
function windowValue(v, vmin, vmax, flipped = false) {
	v = flipped ? -v : v;
	return (v - vmin) / (vmax - vmin);
}
function hyphenCaseToCamelCase(text) {
	return text.replace(/-([a-z])/g, g => g[1].toUpperCase());
}

function createShape(shape, [x, y], size, color, value, previousY){
	const td = document.createElement("td");
	td.style.setProperty("--y", y + "px");
	td.style.setProperty("--prev-y", previousY + "px");
	td.style.setProperty("--size", size + "px");
	td.style.setProperty("--color", color);
	td.textContent = value;

	switch(shape){
		case "circle": {
			td.classList.add("circle");
		}
	}

	return td;
}

class WcGraphCss extends HTMLElement {
	#points = [];
	#width = 320;
	#height = 240;
	#xmax = 100;
	#xmin = -100;
	#ymax = 100;
	#ymin = -100;
	#func;
	#step = 1;
	#thickness = 1;
	#continuous = false;

	#defaultShape = "circle";
	#defaultSize = 2;
	#defaultColor = "#F00"

	static observedAttributes = ["points", "func", "step", "width", "height", "xmin", "xmax", "ymin", "ymax", "default-shape", "default-size", "default-color", "continuous", "thickness"];
	constructor() {
		super();
		this.bind(this);
	}
	bind(element) {
		element.attachEvents.bind(element);
	}
	render() {
		if (!this.shadowRoot) {
			this.attachShadow({ mode: "open" });
		}
		this.shadowRoot.innerHTML = "";
		
		let points;
		if (this.#func) {
			points = [];
			for (let x = this.#xmin; x < this.#xmax; x += this.#step) {
				const y = this.#func(x);
				points.push({ x, y, color: this.#defaultColor, size: this.#defaultSize, shape: this.#defaultShape });
			}
		} else {
			points = this.#points;
		}

		points = points.map((p, i, arr) => ({
			x: windowValue(p.x, this.#xmin, this.#xmax) * this.#width,
			y: windowValue(p.y, this.#ymin, this.#ymax, true) * this.#height,
			value: p.y,
			color: p.color,
			size: p.size,
			shape: p.shape
		}));

		points.sort((a,b) => b.x - a.x).reverse();

		points = points.map((p, i, arr) => ({
			...p,
			...{
				width: p.x - (arr[i - 1]?.x ?? 0),
				previousY: (arr[i - 1]?.y ?? 0)
			}
		}));

		const style = document.createElement("style");
    style.textContent = `
    table {
	display: inline-block;
	background: linear-gradient(90deg, transparent, transparent calc(50% - 1px), black 50%, black 50%, transparent calc(50% + 1px), transparent), linear-gradient(0deg, transparent, transparent calc(50% - 1px), black 50%, black 50%, transparent calc(50% + 1px), transparent);
	font-size: 0;
}
tbody {
	display: grid;
	height: 100%;
}
tr {
	display: block;
	height: 100%;
	position: relative;
}
td {
	display: block;
	position: relative;
	padding: 0;
	height: 100%;
	width: 100%;
}
table.continuous td::before {
	content: "";
	display: block;
	height: 100%;
	width: 100%;
	background-color: var(--default-color);
	clip-path: polygon(
		0 calc(var(--prev-y) - var(--size) - var(--thickness) / 2), 
		100% calc(var(--y) - var(--size) - var(--thickness) / 2), 
		100% calc(var(--y) - var(--size) + var(--thickness) / 2), 
		0 calc(var(--prev-y) - var(--size) + var(--thickness) / 2)
	);
}
tr:first-child td::before {
	content: none;
}
td::after {
	content: "";
	display: block;
	position: absolute;
	top: calc(var(--y) - var(--size));
	height: calc(var(--size) * 2);
	width: calc(var(--size) * 2);
	background-color: var(--color);
	right: calc(var(--size) * -1);
}
td.circle::after {
	border-radius: var(--size);
}
    `;
		this.shadowRoot.append(style);

		const table = document.createElement("table");
		table.style.setProperty("--default-color", this.#defaultColor);
		table.style.setProperty("--thickness", this.#thickness + "px");
		if(this.#continuous){
			table.classList.add("continuous");
		}
		table.style.width = this.#width + "px";
		table.style.height = this.#height + "px";
		const tbody = document.createElement("tbody");
		tbody.style.gridTemplateColumns = points.map(p => p.width + "px").join(" ");
		table.append(tbody);
		for (const point of points) {
			const tr = document.createElement("tr");
			const td = createShape(point.shape, [point.x, point.y], point.size, point.color, point.value, point.previousY);
			tr.append(td);
			tbody.append(tr);
		}
		table.append(tbody);
		this.shadowRoot.append(table);
	}
	attachEvents() {

	}
	connectedCallback() {
		this.render();
		this.attachEvents();
	}
	attributeChangedCallback(name, oldValue, newValue) {
		this[hyphenCaseToCamelCase(name)] = newValue;
	}
	set points(value) {
		if (typeof (value) === "string") {
			value = JSON.parse(value);
		}

		value = value.map(p => ({
			x: p[0],
			y: p[1],
			color: p[2] ?? this.#defaultColor,
			size: p[3] ?? this.#defaultSize,
			shape: p[4] ?? this.#defaultShape
		}));

		this.#points = value;

		this.render();
	}
	get points() {
		return this.#points;
	}
	set width(value) {
		this.#width = parseFloat(value);
	}
	get width() {
		return this.#width;
	}
	set height(value) {
		this.#height = parseFloat(value);
	}
	get height() {
		return this.#height;
	}
	set xmax(value) {
		this.#xmax = parseFloat(value);
	}
	get xmax() {
		return this.#xmax;
	}
	set xmin(value) {
		this.#xmin = parseFloat(value);
	}
	get xmin() {
		return this.#xmin;
	}
	set ymax(value) {
		this.#ymax = parseFloat(value);
	}
	get ymax() {
		return this.#ymax;
	}
	set ymin(value) {
		this.#ymin = parseFloat(value);
	}
	get ymin() {
		return this.#ymin;
	}
	set func(value) {
		this.#func = new Function(["x"], value);
		this.render();
	}
	set step(value) {
		this.#step = parseFloat(value);
	}
	set defaultSize(value) {
		this.#defaultSize = parseFloat(value);
	}
	set defaultShape(value) {
		this.#defaultShape = value;
	}
	set defaultColor(value) {
		this.#defaultColor = value;
	}
	set continuous(value) {
		this.#continuous = value !== undefined;
	}
	set thickness(value) {
		this.#thickness = parseFloat(value);
	}
}

customElements.define("wc-graph-css", WcGraphCss);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.