From 3176e4fdf61239e1129aafa11f5bf94ee60ee4ab Mon Sep 17 00:00:00 2001 From: linarphy Date: Sat, 30 Dec 2023 21:47:55 +0100 Subject: [PATCH] First commit --- README.md | 32 ++++++ favicon.svg | 87 ++++++++++++++++ humans.txt | 9 ++ index.html | 29 ++++++ lichartee.js | 281 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.css | 1 + main.js | 58 +++++++++++ test.js | 13 +++ 8 files changed, 510 insertions(+) create mode 100644 README.md create mode 100644 favicon.svg create mode 100644 humans.txt create mode 100644 index.html create mode 100644 lichartee.js create mode 100644 main.css create mode 100644 main.js create mode 100644 test.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..f71f818 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# lichartee + +Clone of [matplotlib](https://matplotlib.org) in javascript + +## Getting started + +Download or clone this repo, then open [index.html](index.html) in your +favorite browser. + +[test.js](test.js) present an example use of this library. + +## Documentation + +WIP + +## Author + +- linarphy - *initial work* + +## Versioning + +We use [Semver](http://semver.org) for versioning. + +## License + +This project is licensied under the Chocolate-Ware license - see the +[LICENSE](LICENSE) file for more information. + +## Changelog + +*v.0.1* +- creation of a basic layout diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 0000000..543442c --- /dev/null +++ b/favicon.svg @@ -0,0 +1,87 @@ + + + + + + + + lichartee logo + + + + + + + + + lichartee logo + 2023/12/30 + + + linarphy + + + + + logo + char + lichartee + + + + + ArtLibre + + + + + + + + + + + + + + + diff --git a/humans.txt b/humans.txt new file mode 100644 index 0000000..b75a996 --- /dev/null +++ b/humans.txt @@ -0,0 +1,9 @@ +/* TEAM */ + Dev: linarphy + Fediverse: @linarphy@linarphy.net + +/* SITE */ + Last update: 2023/12/30 + Language: English + Doctype: HTML5 + IDE: neovim diff --git a/index.html b/index.html new file mode 100644 index 0000000..7a5b5a0 --- /dev/null +++ b/index.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + Chart demo + + + + +
+ Multiple plot +
+
+
+ + + diff --git a/lichartee.js b/lichartee.js new file mode 100644 index 0000000..64ab951 --- /dev/null +++ b/lichartee.js @@ -0,0 +1,281 @@ +class Manager +{ + constructor(parent_element) + { + this.parent_element = parent_element; + this.list_canvas = []; + } + + /** + * add a canvas + * + * @param int width Width of the added canvas + * @param int height Height of the added canvas + * @param int position Subjective position of the added canvas + */ + add_canvas(width = 100, height = 100, position = 0) + { + let element_canvas = document.createElement('canvas'); + let text = document.createTextNode( + 'Your browser doesn\'t seem to support canvas, or you ' + + 'deactivated them. This is the canvas where the chart ' + + 'should be displayed' + ); + element_canvas.appendChild(text); + if (this.parent_element.childElementCount === 0) + { + this.parent_element.appendChild(element_canvas); + } + else + { + this.parent_element.children[position].before( + element_canvas + ); + } + let ctx = element_canvas.getContext('2d'); + ctx.canvas.width = width ; + ctx.canvas.height = height; + let figure = new Figure(); + let canvas = new Canvas(ctx, figure); + this.list_canvas.splice(position, 0, canvas); + return canvas; + } + + draw() + { + for (const index in this.list_canvas) + { + this.list_canvas[index].draw(); + } + } + + remove_canvas(position = 0) + { + document.removeChild(this.list_canvas[position].ctx.canvas); + this.list_canvas.splice(position, 1); + } +} + +class Canvas +{ + constructor(ctx, figure) + { + this.ctx = ctx ; + this.figure = figure; + } + + draw() + { + if (this.figure instanceof Figure.constructor) + { + throw _('this canvas does not possess figure to draw') + } + let start_x = 0; + for (const index_axes in this.figure.list_axes) + { + let axes = this.figure.list_axes[index_axes]; + let x_proportion = axes.width/this.figure.width + this.ctx.beginPath(); + this.ctx.moveTo( + start_x, + 0 + ); + this.ctx.lineTo( + start_x , + this.ctx.canvas.height + ); + let end_x = start_x + this.ctx.canvas.width * x_proportion + this.ctx.lineTo( + end_x , + this.ctx.canvas.height + ); + this.ctx.lineTo( + end_x, + 0 + ); + this.ctx.lineTo( + start_x, + 0 + ); + this.ctx.strokeStyle = 'rgb(0,0,0)'; + this.ctx.stroke(); + const x_min = get_ext_array( + axes.lines.map( + (element) => element.x.reduce( + (a, b) => Math.min(a, b), + Infinity + ) + ) , + Math.min, + 1 + ); + const y_min = get_ext_array( + axes.lines.map( + (element) => element.y.reduce( + (a, b) => Math.min(a, b), + Infinity + ) + ) , + Math.min, + 1 + ); + const x_max = get_ext_array( + axes.lines.map( + (element) => element.x.reduce( + (a, b) => Math.max(a, b), + -Infinity + ) + ) + ); + const y_max = get_ext_array( + axes.lines.map( + (element) => element.y.reduce( + (a, b) => Math.max(a, b), + -Infinity + ) + ) + ); + for (const index_line in axes.lines) + { + axes.lines[index_line].draw( + this.ctx , + [[start_x, 0],[end_x, this.ctx.canvas.height]], + [[x_min, y_min], [x_max, y_max]] + ); + } + start_x = end_x; + } + } +} + +class Figure +{ + constructor() + { + this.width = 0 ; + this.height = 0 ; + this.list_axes = []; + } + + add_axes(relative_width = 50, relative_height = 50, position = 0) + { + this.width += relative_width ; + this.height += relative_height; + let axes = new Axes(relative_width, relative_height); + this.list_axes.splice(position, 0, axes); + return axes; + } + + remove_axes(position) + { + this.list_axes.splice(position, 1); + } +} + +class Axes +{ + constructor( + width = 100 , + height = 100 , + position = 0 , + title = '' , + ) + { + this.width = width ; + this.height = height ; + this.position = position; + this.title = title ; + this.lines = [] ; + } + + plot(x, y, linestyle = undefined, label = '') + { + if (linestyle === undefined) + { + linestyle = new Style(); + } + let line = new Line(x, y, linestyle, label); + this.lines.push(line); + } +} + +class Line +{ + constructor( + x , + y , + linestyle = undefined , + label = '' + ) + { + this.x = x ; + this.y = y ; + this.linestyle = linestyle ; + this.label = label ; + } + + /** + * draw the line in the box coordinate + * + * @param ctx ctx Context of the canvas + * @param 2dlist box Box where to draw the line + * @param 2dlist view Scale of the box (link value <-> + * coordinate + */ + draw(ctx, box, view) + { + const x_delta = view[1][0] - view[0][0]; + const y_delta = view[1][1] - view[0][1]; + const box_width = box[1][0] - box[0][0]; + const box_height = box[1][1] - box[0][1]; + + let x_coordinates = this.x.map( + function (element, index, array) + { + return box[0][0] + box_width * ( + element - view[0][0] + ) / x_delta; + } + ); + let y_coordinates = this.y.map( + function (element, index, array) + { + return box[0][1] + box_height - ( // starting top left + box_height * ( + element - view[0][1] + ) / y_delta + ); + } + ); + ctx.moveTo( + x_coordinates[0], + y_coordinates[0] + ); + for (const index in x_coordinates) + { + ctx.lineTo( + x_coordinates[index], + y_coordinates[index] + ); + } + ctx.strokeStyle = this.linestyle.color; + ctx.stroke(); + } +} + +class Style +{ + constructor(color = "rgb(0,0,0)", style = 'solid') + { + this.color = color; + this.style = style; + } +} + +function get_ext_array(array, f = Math.max, sign = -1) +{ + return array.reduce( + (a, b) => f(a, b), + sign * Infinity + ); +} diff --git a/main.css b/main.css new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/main.css @@ -0,0 +1 @@ + diff --git a/main.js b/main.js new file mode 100644 index 0000000..0c91fde --- /dev/null +++ b/main.js @@ -0,0 +1,58 @@ +/* + * GLOBAL VARIABLE! + * list of loaded scripts (only itself at start) +**/ +var __scripts = [ 'main.js' ]; +//var __lang = 'en'; + +/* i18n small function */ +function _(key) +{ + return key; + //return LANG[__lang][key] == undefined ? key : LANG[__lang][key]; +} + +/* include other scripts to the html file */ +async function include(src, script_list) +{ + /* DOM operations */ + let script = document.createElement('script'); + script.setAttribute('src' , src ); + document.getElementsByTagName('head')[0].appendChild(script); + + /* check if the element is added to the DOM */ + let script_loaded = new Promise( (resolve, reject) => { + if (script !== undefined) + { + script.addEventListener('load', () => { + resolve(0); + }); + } + else + { + reject( + _( + 'an error occured when adding the script element' + + 'for ${src}' + ) + ); + } + } ); + + script_list.push(src); // the script is considered as loaded + + return script_loaded.then( + (value) => script_list, + (error) => { + throw error; + } + ); +} + +async function launch() +{ + __scripts = await include('./lichartee.js', __scripts); + __scripts = await include('./test.js', __scripts); +} + +launch() diff --git a/test.js b/test.js new file mode 100644 index 0000000..a62f716 --- /dev/null +++ b/test.js @@ -0,0 +1,13 @@ +let manager = new Manager( + document.getElementsByTagName('main')[0] +); +manager.add_canvas(500, 500); +manager.add_canvas(500, 500).figure.add_axes().plot( + [0,1,2,3,4,5] , + [0,1,4,9,16,25], +); +manager.list_canvas[0].figure.list_axes[0].plot( + [0,1,2,3,4,5], + [0,2,4,6,8,10] +); +manager.draw();