Last active
December 29, 2022 01:28
-
-
Save Draco18s/dae0a0d37988f058cd4f9428b801411d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Requires color.js https://gist.github.com/Draco18s/df3a75fbf965556c528061b4b014a49b | |
Graph drawing function intended for Bitburner. | |
Bar graphs should be drawn first so that their color can be used as a background color maintaing the graph when later line graphs are drawn | |
StockPosition should be drawn last so that it can use special characters when intersecting with line graphs. | |
StockPosition should also not take Red or Green as its line color as it uses those to colorize buy and sell entries. Black is also used for position == 0 along with a dashed character. | |
Maximum width is approximately 120 before linewrapping occurs. | |
Examples: | |
Bar and Line | |
▁ ▁⎼ | |
█ ▁⎼ | |
█⎼ | |
▁███ | |
▁⎼▁███▁ | |
▁⎼ ▁█████▁ | |
▁⎼ ▁███████▁ | |
▁⎼ ▁▅█████████▅▁ | |
▁⎼▃▄▅▇█████████████▇▅▄ | |
Stock position | |
╒══ | |
│ | |
│ | |
═══════════╕ │ | |
╘┈┈┈┈┈┈┈┈═══════════╛ | |
*/ | |
/** @param {NS} ns */ | |
import { Color } from "/functions/color.js"; | |
//import { Lerp } from "/functions/util.js"; | |
//Lerp can be referenced via an import instead | |
function Lerp(v0, v1, t) { | |
return v0*(1-t)+v1*t | |
} | |
// Windows character combining and font widths sometimes don't work right | |
// If not on Windows, set this to false and things'll probably look nicer | |
// If things still don't line up, go to line 218 and adjust with the | |
// available characters on 219-223. | |
// Characters on 219 may not be in increasing hight on any given OS. | |
const WindowsOS = true; | |
export const DrawType = { | |
Default: 0, | |
FullCharacterLine: 1, | |
SubcharacterLine: 2, | |
Bar: 3, | |
StockOptions: 4, | |
} | |
export class GraphOptions { | |
/** | |
* @type {Number} | |
* @public | |
*/ | |
height = 10; //number of lines | |
/** | |
* @type {Number} | |
* @public | |
*/ | |
width = 115; | |
/** | |
* @type {Number} | |
* @public | |
*/ | |
fidelity = 1; //lerping between samples | |
/** | |
* | |
*/ | |
shareScale = true; | |
} | |
/** | |
* @class | |
*/ | |
export class GraphData { | |
/** | |
* @type {Number} | |
* @public | |
*/ | |
sortOrder; | |
/** | |
* @type {Number[]} | |
* @public | |
*/ | |
data; | |
/** | |
* @type {Color} | |
* @public | |
*/ | |
color; | |
/** | |
* @type {DrawType} | |
* @public | |
*/ | |
drawType = DrawType.FullCharacterLine; | |
Min() { | |
return Math.min(...this.data); | |
} | |
Max() { | |
return Math.max(...this.data); | |
} | |
} | |
/** | |
* @param {GraphData[]} dataStreams | |
* @param {GraphOptions} options | |
* @returns {string[][]} | |
*/ | |
export function DrawGraph(dataStreams, options, NS = null) { | |
dataStreams = dataStreams.sort((a, b) => a.sortOrder > b.sortOrder ? 1 : -1); | |
var graph = []; | |
for (var h = 0; h < options.height; h++) { | |
var p = []; | |
graph.push(p); | |
for (var w = 0; w < options.width; w++) { | |
graph[h].push(" "); | |
} | |
} | |
var minVal; | |
var maxVal; | |
if (options.shareScale) { | |
if (NS) NS.print(dataStreams.map(x => x.Min())); | |
minVal = Math.min(...dataStreams.map(x => x.Min())); | |
maxVal = Math.max(...dataStreams.map(x => x.Max())); | |
} | |
var dataSize = Math.floor(options.width / options.fidelity); | |
for (var d in dataStreams) { | |
var data = dataStreams[d]; | |
if (!options.shareScale) { | |
minVal = data.Min(); | |
maxVal = data.Max(); | |
} | |
if (minVal == maxVal && data.drawType == DrawType.StockOptions) { | |
minVal = 0; | |
} | |
else if(minVal == maxVal) { | |
var t = maxVal*0.1; | |
minVal -= t; | |
maxVal += t; | |
} | |
var step = Math.abs((maxVal - minVal) / (options.height-1)); | |
var bigstep = Math.abs((maxVal - minVal) / (options.height)); | |
if(step <= 0) step = 1; | |
if(bigstep <= 0) bigstep = 1; | |
var x = 0; | |
for (var i = Math.max(1, data.data.length - dataSize); i < data.data.length; i++) { | |
var p1 = data.data[i - 1]; | |
var p2 = data.data[i]; | |
if (p1 === '' || p2 === '') { | |
x++; | |
continue; | |
} | |
for (var slice = 0; slice < options.fidelity; slice++) { | |
var y = (Lerp(p1, p2, (slice) / options.fidelity) - minVal) / (data.drawType == DrawType.StockOptions ? step : bigstep); | |
var f = (y - Math.floor(y)); | |
y = Math.floor(y); | |
var slope = (p2 - p1) / step; | |
if (step == 0) { | |
y = 0; | |
slope = 0; | |
f = 0; | |
} | |
if(y >= graph.length) { | |
y--; | |
f = 1 - Number.EPSILON; | |
} | |
if(y >= graph.length || Number.isNaN(y)) { | |
if(NS) | |
NS.print(`Lerp(${p1}, ${p2}, (${slice}/${options.fidelity})-${minVal}) / ${step}`); | |
continue; | |
} | |
var v = graph[y]; | |
v[x * options.fidelity + slice] = GetCharForFrac(data.drawType, f, data.color, v[x * options.fidelity + slice]); | |
while (data.drawType == DrawType.Bar && --y >= 0) { | |
graph[y][x * options.fidelity + slice] = GetCharForFrac(data.drawType, .99, data.color, p2); | |
} | |
if (data.drawType == DrawType.StockOptions) { | |
y = (p1 - minVal) / step; | |
y = Math.floor(y); | |
var tbm = 3; | |
if (slope > 0) tbm = tbmBot; | |
if (slope < 0) tbm = tbmTop; | |
var strrr = ''; | |
try { | |
while (data.drawType == DrawType.StockOptions && y >= 0 && y < graph.length) { | |
strrr = y; | |
graph[y][x * options.fidelity + slice] = GetCharForStock(data.color, slope, slice, options.fidelity, p1, p2, minVal, step, tbm, graph[y][x * options.fidelity + slice], NS); | |
tbm = tbmMid; | |
if (y == Math.floor((p2 - minVal) / step) || y < 0 || y >= graph.length) break; | |
if (Math.sign(Math.trunc(slope)) == 0) { | |
if (Math.floor((p1 - minVal) / step) == Math.floor((p2 - minVal) / step)) | |
break; | |
} | |
y += Math.sign(slope); | |
if (y == Math.floor((p2 - minVal) / step)) tbm = (slope > 0) ? tbmTop : tbmBot; | |
} | |
} | |
catch (e) { | |
return strrr + ': ' + graph.length + '\n' + e; | |
} | |
} | |
} | |
x++; | |
} | |
} | |
var output = []; | |
graph = graph.reverse(); | |
for (var g in graph) { | |
output.push(graph[g].join('')); | |
} | |
return '\n' + output.join('\n'); | |
} | |
const lineSegments = WindowsOS ? '▁_⚊―—━▔‾' : '▁⎯⚊⎽⎼─⎻⎺'; | |
//▁_⎽⚊―—–⎼‒━─⎯⎻⎺▔‾ | |
//⎽⎼⎻⎺ too short on Windows 5:6 | |
//ᐨ too short on Windows 3:4 | |
//㇐ too long on Windows 10:6 | |
//ꣻ too long on Windows 9:10 | |
const barSegments = '▁▂▃▄▅▆▇█'; | |
function GetCharForFrac(type, frac, color, prevStr) { | |
var resetNeed = ''; | |
var prevChar = prevStr[prevStr.length - 1]; | |
frac = Math.floor(frac * 8); | |
if (prevChar == '█') { | |
//prevStr = prevStr.substring(0,2) + prevStr[3] + 'm'; | |
color = prevStr.substring(0, 2) + '4' + prevStr[3] + ';3' + color[3] + 'm'; //color[0] + '[3' + color[3] + ';4' + prevStr[3] + 'm'; | |
resetNeed = Color.reset; | |
//[37m | |
} | |
if (type == DrawType.SubcharacterLine) { | |
if (frac >= 0 && frac <= 8) return color + lineSegments[frac] + resetNeed; | |
return color + '-' + resetNeed; | |
} | |
if (type == DrawType.FullCharacterLine) { | |
return color + '#' + resetNeed;//*-+=.# | |
} | |
if (type == DrawType.Bar) { | |
if (frac >= 0 && frac <= 8) return color + barSegments[frac] + resetNeed; | |
return color + '-' + resetNeed; | |
} | |
return prevStr + resetNeed; | |
} | |
let tbmTop = 2; | |
let tbmMid = 1; | |
let tbmBot = 0; | |
function GetCharForStock(color, slope, slice, numSlices, inVal, finVal, minv, step, tbm, prevStr) { | |
var prevChar = prevStr[prevStr.length - 1]; | |
var approxFrac = lineSegments.indexOf(prevChar); | |
var useUnderlineInstead = approxFrac < 3; | |
var useOverlineInstead = approxFrac > 4; | |
var ret = GetCharForStockInner(color, slope, slice, numSlices, inVal, finVal, minv, step, tbm, prevStr); | |
return ret; | |
} | |
function GetCharForStockInner(color, slope, slice, numSlices, inVal, finVal, minv, step, tbm, prevStr) { | |
var bar = '';//Color.reset; | |
var prevChar = prevStr[prevStr.length - 1]; | |
var prevIndex = lineSegments.indexOf(prevChar); | |
var prevIsLine = prevIndex >= 0; | |
var underline = false; | |
if(prevIsLine && prevIndex >= 3 && prevIndex <= 5) { | |
bar = WindowsOS ? '' : '̶'; | |
prevIsLine = false; | |
} | |
//`\x1b[3${color};4m${newchar}${Color.reset}` | |
if(prevIsLine && prevIndex <= 3) { | |
prevIsLine = false; | |
underline = true; | |
} | |
if(prevIsLine && prevIndex >= 6) { | |
prevIsLine = false; | |
bar = '̄'; | |
bar = `${bar}${bar}`; | |
underline = false; | |
} | |
var sl = Math.sign(slope); | |
slope = Math.sign(Math.trunc(slope)); | |
if (slope == 0 && Math.floor((inVal - minv) / step) != Math.floor((finVal - minv) / step)) { | |
slope = sl; | |
} | |
//return slope; | |
if (slice == Math.floor(numSlices / 2)) { | |
if (tbm == tbmBot && slope >= 1) { | |
var color2 = Color.green; | |
return (underline ? `\x1b[3${color2[3]};4m` : color2) + (WindowsOS ? bar : '') + (prevIsLine ? '┴' : '╛') + (WindowsOS ? '' : bar) + (underline ? Color.reset : '');//'╛'; | |
} | |
if (tbm == tbmTop && slope >= 1) { | |
var color2 = Color.green; | |
return (underline ? `\x1b[3${color2[3]};4m` : color2) + (WindowsOS ? bar : '') + (prevIsLine ? '┬' : '╒') + (WindowsOS ? '' : bar) + (underline ? Color.reset : '');//'╒'; | |
} | |
if (tbm == tbmTop && slope <= -1) { | |
var color2 = Color.red; | |
return (underline ? `\x1b[3${color2[3]};4m` : color2) + (WindowsOS ? bar : '') + (prevIsLine ? '┬' : '╕') + (WindowsOS ? '' : bar) + (underline ? Color.reset : '');//'╕'; | |
} | |
if (tbm == tbmBot && slope <= -1) { | |
var color2 = Color.red; | |
return (underline ? `\x1b[3${color2[3]};4m` : color2) + (WindowsOS ? bar : '') + (prevIsLine ? '┴' : '╘') + (WindowsOS ? '' : bar) + (underline ? Color.reset : '');//'╘'; | |
} | |
if (tbm == tbmMid) { | |
if (slope >= 1) { | |
var color2 = Color.green; | |
return (underline ? `\x1b[3${color2[3]};4m` : color2) + (WindowsOS ? bar : '') + (prevIsLine ? '┼' : '│') + (WindowsOS ? '' : bar) + (underline ? Color.reset : ''); | |
} | |
if (slope <= -1) { | |
var color2 = Color.red; | |
return (underline ? `\x1b[3${color2[3]};4m` : color2) + (WindowsOS ? bar : '') + (prevIsLine ? '┼' : '│') + (WindowsOS ? '' : bar) + (underline ? Color.reset : ''); | |
} | |
} | |
} | |
if (sl == 0) //return prevIsLine>=0 ? prevStr : (finVal < 1 ? (underline ? `\x1b[30;4m` : Color.black) + '┈' : (underline ? `\x1b[3${color[3]};4m` : color) + '═'+bar); | |
{ | |
/* | |
(WindowsOS ? bar : '') + (orig) + (WindowsOS ? '' : bar) | |
*/ | |
if (finVal < 1) { | |
return prevIndex >= 0 ? prevStr : (underline ? `\x1b[30;4m` : Color.black) + '┈'; | |
} | |
return prevIsLine ? prevStr : ((WindowsOS ? bar : '') + (underline ? `\x1b[3${color[3]};4m` : color) + '═' + (WindowsOS ? '' : bar)) + (underline ? Color.reset : '');// | |
} | |
if (slice < Math.floor(numSlices / 2) && tbm == tbmBot && slope >= 1) { | |
var color2 = inVal < 1 ? Color.black : Color.green; | |
return (underline ? `\x1b[3${color2[3]};4m` : color2) + (WindowsOS ? bar : '') + (prevIsLine ? prevChar : '═') + (WindowsOS ? '' : bar) + (underline ? Color.reset : ''); | |
} | |
if (slice > Math.floor(numSlices / 2) && tbm == tbmTop && slope >= 1) { | |
var color2 = Color.green; | |
return (underline ? `\x1b[3${color2[3]};4m` : color2) + (WindowsOS ? bar : '') + (prevIsLine ? prevChar : '═') + (WindowsOS ? '' : bar) + (underline ? Color.reset : ''); | |
} | |
if (slice < Math.floor(numSlices / 2) && tbm == tbmTop && slope <= -1) { | |
var color2 = Color.red; | |
return (underline ? `\x1b[3${color2[3]};4m` : color2) + (WindowsOS ? bar : '') + (prevIsLine ? prevChar : '═') + (WindowsOS ? '' : bar) + (underline ? Color.reset : ''); | |
} | |
if (slice > Math.floor(numSlices / 2) && tbm == tbmBot && slope <= -1) { | |
var color2 = finVal < 1 ? Color.black : Color.red; | |
return (underline ? `\x1b[3${color2[3]};4m` : color2) + (WindowsOS ? bar : '') + (prevIsLine ? prevChar : '═') + (WindowsOS ? '' : bar) + (underline ? Color.reset : ''); | |
} | |
var color2 = sl < 1 ? (finVal < 1 && slice == numSlices-1 ? Color.black : Color.red) : (inVal < 1 && slice == 0 ? Color.black : Color.green); | |
if (slope == 0) return prevIsLine ? prevStr : ((WindowsOS ? bar : '')+(underline ? `\x1b[3${color2[3]};4m` : color2) + '═') + (WindowsOS ? '' : bar) + (underline ? Color.reset : ''); | |
return prevStr; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment