<html>
<head>
    <title>custom lite graph app</title>
	<meta http-equiv="X-UA-Compatible" content="chrome=1">
    <link rel="stylesheet" type="text/css" href="style.css">
	<link rel="stylesheet" type="text/css" href="litegraph.css">
    <link rel="stylesheet" type="text/css" href="litegraph-editor.css">
	<script type="text/javascript" src="litegraph.js"></script>
	<script type="text/javascript" src="litegraph-editor.js"></script>
	<style>
		#propertyPane {
			position: absolute;
			top: 10px;
			left: 10px;
			width: 200px;
			padding: 10px;
			border: 1px solid #ccc;
			background-color: #333; /* Dark gray background */
			color: #ccc; /* Light gray text */
			resize: both;
			overflow: auto;
		}
		#propertyPane input {
			background-color: #555; /* Darker gray for input background */
			color: #ccc; /* Light gray text for input */
			border: 1px solid #777; /* Border color for input */
		}
		#propertyPane label {
			color: #ccc; /* Light gray text for labels */
		}
	</style>
</head>
<body style='width:100%; height:100%'>
	<canvas id='mycanvas' width="1000" height="600"></canvas>
	</br>
	<button id="runButton">Run</button>
	<button id="stopButton">Stop</button>
	<button id="loadSoundButton">Load Sound Graph</button>
	<button id="saveButton">Save Graph JSON</button>
	<button id="loadGraphButton">Load Graph JSON</button>
	<div id="propertyPane">
		<h4>Properties</h4>
		<div id="properties"></div>
	</div>
<script>
class custom_multi_node {
	constructor(){
		this.title = "Multiplication";
		this.addInput("A","number");
		this.addInput("B","number");
		this.addOutput("A*B","number");
		this.properties = { precision: 0.1 };
		this.weightHandle = this.addWidget("slider","Weight", 100, {min: 0, max: 400, step: 10, precision: 0})
		this.addWidget("combo","flag", "on", {values: ["on","off"]})
		this.addWidget("text", "Address", "macross flashback")
		this.addWidget("button","Click me", "", function() { alert("button clicked") })
		this.serialize_widgets = true				
	}
	onExecute(){
		let a = this.getInputData(0) || 0;
		let b = this.getInputData(1) || 0;
		let result = a * b * this.properties.precision * this.weightHandle.value;
		this.setOutputData(0, result);
	}
	onPropertyChanged(name, value) {
		console.log(`Property ${name} changed to ${value}`);
		// alert(`Property ${name} changed to ${value}`);
	}
}
class custom_visual_data {
	count = 0;
	bigdata_result = null;
	constructor(){
		this.title = "custom_visual_data";
		this.addInput("A","number");
		this.addOutput("A","number");
		this.properties = { scale: 1.0 };
		this.size = [250, 70];
	}
	onExecute(){
		let a = this.getInputData(0) || 0;
		a = a * this.properties.scale;
		this.setOutputData(0, a);
		this.count++;
		this.count = this.count % 256;
		this.setDirtyCanvas(true);
		// this.trigger("onDrawBackground");
		this.bigdata_result = null;
		calculate_bigdata(this);
	}
	finish_bigdata(dataset) {
		this.bigdata_result = dataset;
		console.log("Finished async data calculation");
	}
	onAdded(){
	}
	onRemoved(){
	}
	onDropFile(file){
		alert("File dropped: " + file.name);
	}
	onDrawBackground(ctx){
		ctx.save(); // Save the current state
		ctx.beginPath();
		ctx.rect(0, 0, this.size[0], this.size[1]); // Define the clipping region
		ctx.clip();
		let blue = Math.max(0, 255 - this.count % 256);
		let orange = Math.min(255, this.count % 256);
		ctx.fillStyle = `rgb(${orange}, ${Math.floor(orange / 2)}, ${blue})`; // Smooth transition from blue to orange
		ctx.strokeStyle = "#808080"; // Gray color
		ctx.beginPath();
		ctx.arc(50,20,10,0,Math.PI*2);
		if(this.bigdata_result){
			ctx.font = "12px Arial";
			ctx.fillStyle = "orange";
			ctx.fillText("BigData: " + this.bigdata_result, 40, 50);
		}
		ctx.fill();
		ctx.stroke();
		ctx.restore(); // Restore the previous state */
	}	
}
async function processLargeData(data) {
    return new Promise((resolve) => {
        console.log("starting async data processing");
        setTimeout(() => {
            const processedData = data.map((item) => item * 2); // data double scale
            console.log("finished async data processing");
			
            resolve(processedData);
        }, 3000); // processing time estimation
    });
}
async function calculate_bigdata(node) {
	const data = [1, 2, 3, 4, 5].map(item => item + node.count); // input data for example
    const processedData = await processLargeData(data);	// async data processing
	
	console.log("Processed data:", processedData);
	node.finish_bigdata(processedData);
}
LiteGraph.registerNodeType("custom/multiply", custom_multi_node);
LiteGraph.registerNodeType("custom/VisualData", custom_visual_data);
var graph = new LGraph();
var canvas = new LGraphCanvas("#mycanvas", graph);
graph.allow_scripts = true;
canvas.allow_searchbox = true;   
/* 
update_canvas_HiPPI();
window.addEventListener("resize", function() { 
	canvas.resize();
	update_canvas_HiPPI();
});
function update_canvas_HiPPI() {
  const ratio = window.devicePixelRatio;
  if(ratio == 1) 
  	return;
  const rect = editor.canvas.parentNode.getBoundingClientRect();
  const { width, height } = rect;
  canvas.width = width * ratio;
  canvas.height = height * ratio;
  canvas.style.width = width + "px";
  canvas.style.height = height + "px";
  canvas.getContext("2d").scale(ratio, ratio);
  return canvas;
}
*/
var nodeConstA = LiteGraph.createNode("basic/const");
nodeConstA.pos = [100,200];
nodeConstA.setValue(4.5);
graph.add(nodeConstA);
var nodeConstB = LiteGraph.createNode("basic/const");
nodeConstB.pos = [100,300];
nodeConstB.setValue(1.5);
graph.add(nodeConstB);
var nodeMult = LiteGraph.createNode("custom/multiply");
nodeMult.pos = [350,200];
graph.add(nodeMult);
var nodeWatch = LiteGraph.createNode("basic/watch");
nodeWatch.pos = [600,200];
graph.add(nodeWatch);
var nodeAsync = LiteGraph.createNode("custom/VisualData");
nodeAsync.pos = [600,300];
graph.add(nodeAsync);
// All nodes must be in the graph before connections can be made.
nodeConstA.connect(0, nodeMult, 0);
nodeConstB.connect(0, nodeMult, 1);
nodeMult.connect(0, nodeWatch, 0);
nodeMult.connect(0, nodeAsync, 0);
// Add event listener to the run button
document.getElementById("runButton").addEventListener("click", function() {
	graph.start();
});
// Add event listener to the stop button
document.getElementById("stopButton").addEventListener("click", function() {
	graph.stop();
});
// Add event listener to show properties when a node is clicked
canvas.onNodeSelected = function(node) {
	var propertiesDiv = document.getElementById("properties");
	propertiesDiv.innerHTML = "";
	for (var property in node.properties) {
		if (node.properties.hasOwnProperty(property)) {
			var input = document.createElement("input");
			input.type = "text";
			input.value = node.properties[property];
			input.onchange = (function(property, node) {
				return function(event) {
					node.setProperty(property, event.target.value);
				};
			})(property, node);
			var label = document.createElement("label");
			label.innerText = property;
			propertiesDiv.appendChild(label);
			propertiesDiv.appendChild(input);
			propertiesDiv.appendChild(document.createElement("br"));
		}
	}
};
document.getElementById("loadSoundButton").addEventListener("click", function() {
	fetch('audio_delay.json') 
		.then(response => response.json())
		.then(data => {
			graph.configure(data);
			alert('Sound graph loaded');
		})
		.catch(error => console.error('Error loading JSON:', error));
});
document.getElementById("loadGraphButton").addEventListener("click", function() {
	fetch('saved_graph.json') 
		.then(response => response.json())
		.then(data => {
			graph.configure(data);
			alert('saved_graph loaded');
		})
		.catch(error => {
			console.error('Error loading JSON:', error);
			alert('Error loading JSON: ' + error.message);
		});
});
document.getElementById("saveButton").addEventListener("click", function() {
	var data = JSON.stringify(graph.serialize());
	var blob = new Blob([data], { type: 'application/json' });
	var url = URL.createObjectURL(blob);
	var a = document.createElement('a');
	a.href = url;
	a.download = 'saved_graph.json';
	a.click();
	URL.revokeObjectURL(url);
});
</script>
</body>
</html>
전체 화면에 그래프 캔버스 크기를 맞출려면, 위 소스에서 update_canvas_HiPPI() 부분 함수 리마크 처리를 해제한다.