Add theming & Update README.md

This commit is contained in:
linarphy 2024-01-30 23:31:36 +01:00
parent ac9cf007bc
commit 63357cf82b
No known key found for this signature in database
GPG key ID: 0610ABB68DAA7B65
3 changed files with 398 additions and 176 deletions

View file

@ -29,6 +29,7 @@ This project is licensied under the GPL 3 license - see the
*v 0.1.6* *v 0.1.6*
- Add i18n feature - Add i18n feature
- Add named argument - Add named argument
- Add theming
*v 0.1.5* *v 0.1.5*
- Add state interface with jsplot (pyplot procedural style) - Add state interface with jsplot (pyplot procedural style)

View file

@ -1,3 +1,130 @@
/**
* font object
*
* @property size int font-size (px)
* @property family string font-family
* @property color string font color
*/
class Font
{
/**
* @param size int font-size (px)
* @param family string font-family
* @param color string font color
*/
constructor({
size ,
family,
color ,
})
{
this.size = size;
this.family = family;
this.color = color;
}
/**
* Convert font to css font proprety's syntax
*
* @return String
*/
toString()
{
return this.size.toString() + 'px ' + self.family;
}
}
var LightTheme = {
line : {
style: 'solid',
color: 'black',
} ,
marker: {
style: '' ,
color: 'black',
size : 5 ,
} ,
title : {
font : new Font({
size : 20 ,
family: 'sans-serif',
color : 'black' ,
}) ,
margin: 10,
} ,
bordercolor: 'black' ,
axis : {
font : new Font({
size : 15 ,
family: 'sans-serif',
color : 'black' ,
}) ,
ticks : {
x: {
number: 4,
} ,
y: {
number: 4,
} ,
size : 5,
} ,
margin: 15,
} ,
};
var DarkTheme = {
line : {
style: 'solid',
color: 'white',
} ,
marker: {
style: '' ,
color: 'white',
size : 5 ,
} ,
title : {
font : new Font({
size : 20 ,
family: 'sans-serif',
color : 'white' ,
}) ,
margin: 10,
} ,
bordercolor: 'white' ,
axis : {
font : new Font({
size : 15 ,
family: 'sans-serif',
color : 'white' ,
}) ,
ticks : {
x: {
number: 4,
} ,
y: {
number: 4,
} ,
size : 5,
} ,
margin: 15,
} ,
};
var DefaultTheme = LightTheme;
if (
window.matchMedia(
'(prefers-color-scheme: dark)'
).matches
)
{
DefaultTheme = DarkTheme;
}
else
{
DefaultTheme = LightTheme;
}
/** /**
* a manager allows to manipulate multiple canvas * a manager allows to manipulate multiple canvas
* *
@ -6,6 +133,7 @@
* *
* @property Element parent_element DOM element containing every canvas * @property Element parent_element DOM element containing every canvas
* of this manager * of this manager
* @property Theme theme Theme of managed canvas
* @property 1darray<Canvas> list_canvas List of canvas held by this * @property 1darray<Canvas> list_canvas List of canvas held by this
* manager * manager
*/ */
@ -13,10 +141,15 @@ class Manager
{ {
/** /**
* @param Element parent_element Element holding the canvas * @param Element parent_element Element holding the canvas
* @param Theme theme Theme of managed canvas
*/ */
constructor(parent_element) constructor({
parent_element ,
theme = DefaultTheme,
})
{ {
this.parent_element = parent_element; this.parent_element = parent_element;
this.theme = theme;
this.list_canvas = []; this.list_canvas = [];
} }
@ -34,11 +167,11 @@ class Manager
}) })
{ {
let element_canvas = document.createElement('canvas'); let element_canvas = document.createElement('canvas');
let text = document.createTextNode( let text = document.createTextNode(_(
'Your browser doesn\'t seem to support canvas, or you ' + 'Your browser doesn\'t seem to support canvas, or you ' +
'deactivated them. This is the canvas where the chart ' + 'deactivated them. This is the canvas where the chart ' +
'should be displayed' 'should be displayed'
); ));
element_canvas.appendChild(text); element_canvas.appendChild(text);
if (this.parent_element.childElementCount === 0) if (this.parent_element.childElementCount === 0)
{ {
@ -54,7 +187,12 @@ class Manager
ctx.canvas.width = width ; ctx.canvas.width = width ;
ctx.canvas.height = height; ctx.canvas.height = height;
let figure = new Figure({}); let figure = new Figure({});
let canvas = new Canvas(ctx, figure); let canvas = new Canvas({
manager: this ,
context: ctx ,
figure : figure,
});
canvas.figure.canvas = canvas;
this.list_canvas.splice(position, 0, canvas); this.list_canvas.splice(position, 0, canvas);
return canvas; return canvas;
} }
@ -85,18 +223,21 @@ class Manager
/** /**
* a canvas allows to render a figure * a canvas allows to render a figure
* *
* @property Manager manager Manager of this canvas
* @property CanvasRenderingContext2D ctx Canvas API interface * @property CanvasRenderingContext2D ctx Canvas API interface
* @property Figure figure Figure held by this canvas * @property Figure figure Figure held by this canvas
*/ */
class Canvas class Canvas
{ {
/** /**
* @param Manager manager Manager of this canvas
* @param CanvasRenderingContext2D ctx Canvas API interface * @param CanvasRenderingContext2D ctx Canvas API interface
* @param Figure figure Figure held by this canvas * @param Figure figure Figure held by this canvas
*/ */
constructor(ctx, figure) constructor({manager, context , figure})
{ {
this.ctx = ctx ; this.manager = manager;
this.ctx = context;
this.figure = figure ; this.figure = figure ;
} }
@ -109,19 +250,25 @@ class Canvas
{ {
throw _('no_figure') throw _('no_figure')
} }
this.ctx.clearRect(
0 ,
0 ,
this.ctx.canvas.width ,
this.ctx.canvas.height,
);
let start_x = 0; let start_x = 0;
for (const index_axes in this.figure.list_axes) for (const index_axes in this.figure.list_axes)
{ {
let axes = this.figure.list_axes[index_axes]; let axes = this.figure.list_axes[index_axes];
let x_proportion = axes.width/this.figure.width; let x_proportion = axes.width/this.figure.width;
let end_x = start_x + this.ctx.canvas.width * x_proportion; let end_x = start_x + this.ctx.canvas.width * x_proportion;
axes.draw( axes.draw({
this.ctx, ctx: this.ctx,
[ box: [
[start_x, 0 ], [start_x, 0 ],
[end_x , this.ctx.canvas.height], [end_x , this.ctx.canvas.height],
] , ] ,
); });
start_x = end_x; start_x = end_x;
} }
} }
@ -130,6 +277,7 @@ class Canvas
/** /**
* a figure hold axes and every plot elements * a figure hold axes and every plot elements
* *
* @property Canvas canvas Canvas of this figure
* @property int width Total width of this figure (related to each * @property int width Total width of this figure (related to each
* axes) * axes)
* @property int height Total height of this figure (related to each * @property int height Total height of this figure (related to each
@ -138,8 +286,9 @@ class Canvas
*/ */
class Figure class Figure
{ {
constructor() constructor({})
{ {
this.canvas = undefined;
this.width = 0 ; this.width = 0 ;
this.height = 0 ; this.height = 0 ;
this.list_axes = [] ; this.list_axes = [] ;
@ -161,6 +310,7 @@ class Figure
this.width += relative_width ; this.width += relative_width ;
this.height += relative_height; this.height += relative_height;
let axes = new Axes({ let axes = new Axes({
figure: this ,
width : relative_width , width : relative_width ,
height: relative_height, height: relative_height,
}); });
@ -173,7 +323,7 @@ class Figure
* *
* @param int position Position of the axes in the figure to delete * @param int position Position of the axes in the figure to delete
*/ */
remove_axes(position = 0) remove_axes({position = 0})
{ {
this.list_axes.splice(position, 1); this.list_axes.splice(position, 1);
} }
@ -182,6 +332,7 @@ class Figure
/** /**
* an axes represents one plot in a figure. * an axes represents one plot in a figure.
* *
* @property Figure figure Figure of this Axes
* @property int width Width of the axes * @property int width Width of the axes
* @property int height Height of the axes * @property int height Height of the axes
* @property String title Title of the axes * @property String title Title of the axes
@ -190,18 +341,22 @@ class Figure
class Axes class Axes
{ {
/** /**
* @param Figure figure Figure of this Axes
* @param int width Width of the axes * @param int width Width of the axes
* @param int height Height of the axes * @param int height Height of the axes
* @param String title Title of the axes
*/ */
constructor({ constructor({
figure ,
width = 100, width = 100,
height = 100, height = 100,
}) })
{ {
this.figure = figure;
this.width = width ; this.width = width ;
this.height = height; this.height = height;
this.axis = new Axis({}); this.axis = new Axis({
axes: this,
}) ;
this.lines = [] ; this.lines = [] ;
} }
@ -226,14 +381,15 @@ class Axes
plot({ plot({
x , x ,
y , y ,
linestyle = 'solid' , linestyle ,
linecolor = 'black' , linecolor ,
markerstyle = '' , markerstyle,
markercolor = 'black' , markercolor,
markersize = 5 , markersize ,
}) })
{ {
let line = new Line({ let line = new Line({
axes : this ,
x : x , x : x ,
y : y , y : y ,
linestyle : linestyle , linestyle : linestyle ,
@ -249,9 +405,9 @@ class Axes
* render the axes to the canvas * render the axes to the canvas
* *
* @param ctx CanvasRenderingContext2D Context of the canvas * @param ctx CanvasRenderingContext2D Context of the canvas
* @param view 2darray<Number> Window where to draw the canvas * @param box 2darray<Number> Window where to draw the canvas
*/ */
draw(ctx, view) draw({ctx, box})
{ {
const x_min = get_ext_array( const x_min = get_ext_array(
this.lines.map( this.lines.map(
@ -289,37 +445,37 @@ class Axes
) )
) )
); );
let n_view = view; let n_box = box;
if (this.title !== undefined) if (this.title !== undefined)
{ {
n_view = this.title.draw( n_box = this.title.draw({
ctx , ctx: ctx,
view, box: box,
); });
} }
n_view = this.axis.draw( n_box = this.axis.draw({
ctx , ctx : ctx ,
n_view, box : n_box ,
[ [x_min, y_min], [x_max, y_max] ], view : [ [x_min, y_min], [x_max, y_max] ],
); });
for (const index_line in this.lines) for (const index_line in this.lines)
{ {
this.lines[index_line].draw( this.lines[index_line].draw({
ctx , ctx : ctx ,
n_view, box : n_box ,
[ view: [
[x_min, y_min], [x_min, y_min],
[x_max, y_max], [x_max, y_max],
] , ] ,
); });
} }
ctx.beginPath(); ctx.beginPath();
ctx.strokeStyle = 'black'; ctx.strokeStyle = this.figure.canvas.manager.theme.bordercolor;
ctx.setLineDash([]); ctx.setLineDash([]);
ctx.strokeRect( ctx.strokeRect(
n_view[0][0], n_view[0][1], n_box[0][0], n_box[0][1],
n_view[1][0] - n_view[0][0], n_box[1][0] - n_box[0][0],
n_view[1][1] - n_view[0][1], n_box[1][1] - n_box[0][1],
); );
} }
@ -330,7 +486,10 @@ class Axes
*/ */
set title(content) set title(content)
{ {
this._title = new Title({content: content}); this._title = new Title({
axes : this ,
content: content,
});
} }
/** /**
@ -345,6 +504,7 @@ class Axes
/** /**
* a line, with a color and a line style * a line, with a color and a line style
* *
* @property Axes axes Axes of this line
* @property 1darray<float> x xdata * @property 1darray<float> x xdata
* @property 1darray<float> y ydata * @property 1darray<float> y ydata
* @property String linestyle style of the line (can be "solid", * @property String linestyle style of the line (can be "solid",
@ -363,31 +523,59 @@ class Axes
class Line class Line
{ {
/** /**
* @param Axes axes Axes of this Line
* @param 1darray<float> x xdata * @param 1darray<float> x xdata
* @param 1darray<float> y ydata * @param 1darray<float> y ydata
* @param string linestyle style of the line (can be "solid", * @param String linestyle style of the line (can be "solid",
* "dotted", "dashdot", "dashed" or "" * "dotted", "dashdot", "dashed" or ""
* for no line) * for no line)
* @param string linecolor color of the line (can be "#abcdef", * @param String linecolor color of the line (can be "#abcdef",
* "rgb(x,y,z)" or a name of a color. * "rgb(x,y,z)" or a name of a color.
* @param string markerstyle style of the marker used for each * @param String markerstyle style of the marker used for each
* point (can be "circle", "point", * point (can be "circle", "point",
* "small", "cross" or "" for no * "small", "cross" or "" for no
* marker) * marker)
* @param string markercolor color of the marker (same * @param String markercolor color of the marker (same
* definition than linecolor) * definition than linecolor)
* @param Number markersize size of the marker * @param Number markersize size of the marker
*/ */
constructor({ constructor({
axes ,
x , x ,
y , y ,
linestyle = 'solid' , linestyle ,
linecolor = 'black' , linecolor ,
markerstyle = '' , markerstyle,
markercolor = 'black' , markercolor,
markersize = 5 , markersize ,
}) })
{ {
this.axes = axes ;
if (linestyle === undefined)
{
linestyle = this.axes.figure.canvas.manager.theme.line
.style;
}
if (linecolor === undefined)
{
linecolor = this.axes.figure.canvas.manager.theme.line
.color;
}
if (markerstyle === undefined)
{
markerstyle = this.axes.figure.canvas.manager.theme.marker
.style;
}
if (markercolor === undefined)
{
markercolor = this.axes.figure.canvas.manager.theme.marker
.color;
}
if (markersize === undefined)
{
markersize = this.axes.figure.canvas.manager.theme.marker
.size;
}
this.x = x ; this.x = x ;
this.y = y ; this.y = y ;
this.linestyle = linestyle ; this.linestyle = linestyle ;
@ -405,7 +593,7 @@ class Line
* @param 2dlist<float> view scale of the box (link value <-> * @param 2dlist<float> view scale of the box (link value <->
* coordinate * coordinate
*/ */
draw(ctx, box, view) draw({ctx, box, view})
{ {
const x_delta = view[1][0] - view[0][0]; const x_delta = view[1][0] - view[0][0];
const y_delta = view[1][1] - view[0][1]; const y_delta = view[1][1] - view[0][1];
@ -432,19 +620,19 @@ class Line
); );
if (this.linestyle != '') if (this.linestyle != '')
{ {
this.drawLine( this.drawLine({
ctx, ctx : ctx ,
x_coordinates, x_coordinates: x_coordinates,
y_coordinates, y_coordinates: y_coordinates,
); });
} }
if (this.markerstyle != '') if (this.markerstyle != '')
{ {
this.drawPoints( this.drawPoints({
ctx , ctx : ctx ,
x_coordinates, x_coordinates: x_coordinates,
y_coordinates, y_coordinates: y_coordinates,
); });
} }
} }
@ -455,7 +643,7 @@ class Line
* @param 1darray<Number> x_coordinates X coordinates of the point * @param 1darray<Number> x_coordinates X coordinates of the point
* @param 1darray<Number> y_coordinates Y coordinates of the point * @param 1darray<Number> y_coordinates Y coordinates of the point
*/ */
drawLine(ctx, x_coordinates, y_coordinates) drawLine({ctx, x_coordinates, y_coordinates})
{ {
ctx.beginPath(); ctx.beginPath();
ctx.strokeStyle = this.linecolor; ctx.strokeStyle = this.linecolor;
@ -500,7 +688,7 @@ class Line
* @param 1darray<Number> x_coordinates x coordinate of the point * @param 1darray<Number> x_coordinates x coordinate of the point
* @param 1darray<Number> y_coordinates y coordinate of the point * @param 1darray<Number> y_coordinates y coordinate of the point
*/ */
drawPoints(ctx, x_coordinates, y_coordinates) drawPoints({ctx, x_coordinates, y_coordinates})
{ {
for (const index in x_coordinates) for (const index in x_coordinates)
{ {
@ -581,65 +769,39 @@ class Line
} }
} }
/**
* font object
*
* @property size int font-size (px)
* @property family string font-family
* @property color string font color
*/
class Font
{
/**
* @param size int font-size (px)
* @param family string font-family
* @param color string font color
*/
constructor({
size = 20 ,
family = 'sans-serif',
color = 'black' ,
})
{
this.size = size;
this.family = family;
this.color = color;
}
/**
* Convert font to css font proprety's syntax
*
* @return String
*/
toString()
{
return this.size.toString() + 'px ' + self.family;
}
}
/** /**
* title of an axes * title of an axes
* *
* @property content String Content of a title * @property Axes axes Axes of this title
* @property font Font Font of a title * @property String content Content of the title
* @property Font font Font of the title
* @property Number margin Margin of the title
*/ */
class Title class Title
{ {
/** /**
* @param content Content Content of a title * @param Axes axes Axes of this title
* @param font Font Font of a title * @param String content Content of the title
* @param Font font Font of the title
* @param Number margin Margin of the title
*/ */
constructor({ constructor({
content = '' , axes ,
font = undefined, content ,
margin = 5 , font ,
margin ,
}) })
{ {
this.content = content; this.axes = axes ;
if (font === undefined) if (font === undefined)
{ {
font = new Font({}); font = this.axes.figure.canvas.manager.theme.title.font;
} }
if (margin === undefined)
{
margin = this.axes.figure.canvas.manager.theme.title.margin;
}
this.content = content;
this.font = font ; this.font = font ;
this.margin = margin ; this.margin = margin ;
} }
@ -652,11 +814,10 @@ class Title
* *
* @return plot_box int<array> New windows for the plot * @return plot_box int<array> New windows for the plot
*/ */
draw(ctx, box) draw({ctx, box})
{ {
ctx.font = this.font.toString(); ctx.font = this.font.toString();
ctx.fillStyle = 'black'; ctx.fillStyle = this.font.color;
ctx.strokeStyle = 'black';
let width = ctx.measureText(this.content).width; let width = ctx.measureText(this.content).width;
if (width > box[1][0] - box[0][0] + 2 * this.margin) if (width > box[1][0] - box[0][0] + 2 * this.margin)
{ {
@ -691,40 +852,70 @@ class Title
/** /**
* Axis (x and y) of the axes (not separated xaxis and yaxis) * Axis (x and y) of the axes (not separated xaxis and yaxis)
* *
* @property int number_x_tick Number of x axis tick * @property Axes axes Axes of the Axis
* @property int number_y_tick Number of y axis tick * @property Number number_x_tick Number of x axis tick
* @property int size_tick Size of the tick * @property Number number_y_tick Number of y axis tick
* @property int margin Size of the margin * @property Number size_tick Size of the tick
* @property Number margin Size of the margin
* @property Font font Font of tick labels * @property Font font Font of tick labels
*/ */
class Axis class Axis
{ {
/** /**
* @param number_x_tick int Number of x axis tick * @param Axes axes Axes of the Axis
* @param number_y_tick int Number of y axis tick * @param Number number_x_tick Number of x axis tick
* @param size_tick int Size of the tick * @param Number number_y_tick Number of y axis tick
* @param margin int Size of the margin * @param Number size_tick Size of the tick
* @param text_height int Height of the text label * @param Number margin Size of the margin
* @param Number text_height Height of the text label
* @param Font font Font of tick labels * @param Font font Font of tick labels
*/ */
constructor({ constructor({
number_x_tick = 4 , axes ,
number_y_tick = 4 , number_x_tick,
size_tick = 5 , number_y_tick,
margin = 15 , size_tick ,
text_height = 15 , margin ,
font = undefined, text_height ,
font ,
}) })
{ {
this.axes = axes ;
if (number_x_tick === undefined)
{
number_x_tick = this.axes.figure.canvas.manager.theme.axis
.ticks.x.number;
}
if (number_y_tick === undefined)
{
number_y_tick = this.axes.figure.canvas.manager.theme.axis
.ticks.y.number;
}
if (size_tick === undefined)
{
size_tick = this.axes.figure.canvas.manager.theme.axis
.ticks.size;
}
if (margin === undefined)
{
margin = this.axes.figure.canvas.manager.theme.axis
.margin;
}
if (text_height === undefined)
{
text_height = this.axes.figure.canvas.manager.theme.axis
.font.size;
}
if (font === undefined)
{
font = this.axes.figure.canvas.manager.theme.axis
.font;
}
this.number_x_tick = number_x_tick; this.number_x_tick = number_x_tick;
this.number_y_tick = number_y_tick; this.number_y_tick = number_y_tick;
this.size_tick = size_tick ; this.size_tick = size_tick ;
this.margin = margin ; this.margin = margin ;
this.text_height = text_height ; this.text_height = text_height ;
if (font === undefined)
{
font = new Font({});
}
this.font = font ; this.font = font ;
} }
@ -737,11 +928,11 @@ class Axis
* real value * real value
* @return 2darray<Number> New window where to draw the rest of the plot * @return 2darray<Number> New window where to draw the rest of the plot
*/ */
draw(ctx, box, view) draw({ctx, box, view})
{ {
ctx.font = this.font.toString(); ctx.font = this.font.toString();
ctx.strokeStyle = 'black'; ctx.strokeStyle = this.font.color;
ctx.fillStyle = 'black'; ctx.fillStyle = this.font.color;
let index = 0; let index = 0;
let start_pos = box[0][1]; let start_pos = box[0][1];
let padding = ( let padding = (
@ -824,12 +1015,14 @@ class jsplot
{ {
/** /**
* @param Manager manager Current managed manager * @param Manager manager Current managed manager
* @param Theme theme Theme for managed canvas
*/ */
constructor() constructor(theme)
{ {
this.manager = new Manager( this.manager = new Manager({
document.getElementsByTagName('main')[0] parent_element: document.getElementsByTagName('main')[0],
); theme : theme ,
});
} }
/** /**
@ -918,12 +1111,12 @@ class jsplot
this.manager.add_canvas({ this.manager.add_canvas({
width: 500, width: 500,
height: 500, height: 500,
position: this.manager.list_canvas - 1, position: 0 ,
}); });
} }
else else
{ {
this.manager.list_canvas[0].draw(); this.manager.draw();
this.manager.add_canvas({ this.manager.add_canvas({
width: 500 , width: 500 ,
height: 500 , height: 500 ,

View file

@ -1 +1,29 @@
:root
{
--background-color: white;
--color : black;
}
@media (prefers-color-scheme: light)
{
:root
{
--background-color: white;
--color : black;
}
}
@media (prefers-color-scheme: dark)
{
:root
{
--background-color: black;
--color : white;
}
}
body
{
background-color: var(--background-color);
color : var(--color );
}