commit 3176e4fdf61239e1129aafa11f5bf94ee60ee4ab Author: linarphy Date: Sat Dec 30 21:47:55 2023 +0100 First commit 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();