GithubHelp home page GithubHelp logo

wegiangb / threeg.js Goto Github PK

View Code? Open in Web Editor NEW

This project forked from hofk/threeg.js

0.0 2.0 0.0 83 KB

three. js addon to create special or extended geometries. The addon generates indexed or non indexed BufferGeometries.

HTML 35.95% JavaScript 64.05%

threeg.js's Introduction

THREEg.js

A three.js addon to create special or extended geometries.

The addon generates indexed or non indexed BufferGeometries. Non indexed allows an explosion representation.

@author hofk / http://sandbox.threejs.hofk.de/ or http://sandboxthreeg.threejs.hofk.de/

For more efficiency.

Each single geometry section between ............................. name ............................. can be deleted.


..................................... Magic Box ................................................

An enlarged box. Non indexed BufferGeometry. It is made up of 26 parts. Multi material is supported.

	geometry = new THREE.BufferGeometry();
	geometry.createMagicBox = THREEg.createMagicBox; // insert the methode from THREEg.js
	geometry.createMagicBox( parameters ); // apply the methode
	
	// mesh
	mesh = new THREE.Mesh( geometry, materials );
        scene.add( mesh );

Include: <script src="THREEg.js"></script>

Example:

geometry = new THREE.BufferGeometry();
geometry.createMagicBox = THREEg.createMagicBox; // insert the methode from THREEg.js
geometry.createMagicBox( {
  
	smoothness: 1,
	radius: function( t ){ return 0.08 + 0.08 * Math.sin( 0.5 * t ) },
	materials: [ 3, 3, 4, 4, 5, 5, 9, 9, 9, 9, 9 ],
	width: function( t ){ return 0.7 + 0.3 * Math.sin( 0.5 * t ) },
	height: function( t ){ return 0.7 + 0.3 * Math.cos( 0.5 * t ) },
	depth: function( t ){ return 0.7 + 0.3 * Math.cos( 0.5 * t ) * Math.sin( 0.5 * t ) },
	
 } ); 
	  

Parameters briefly explained in THREEg.js:

/*	parameter overview	--- all parameters are optional ---
   p = {
   	
   		// simple properties
   		
   	widthSegments,
   	heightSegments,
   	depthSegments,
   	smoothness,
   	
   	uvmode,
   	contourmode,
   	explodemode,
   	
   		// arrays (sides order +x, -x, +y, -y, +z, -z)
   		
   	sides, // values: no side: 0, complete: 1, in addition: no plane: 2, edgeless: 3, no corners: 4
   	lidHinges // values for direction are 0: first, 1: second following axis (order x, y, z, x, y)
   	materials, // index for 6 sides and for 3 edges (parallel to axis), 2 corners (+y, -y)		
   	
   		// functions with  parameter time   // function ( t )
   
   	width,
   	height,
   	depth,
   	radius,
   	
   	waffleDeep,		// depth of center vertex	
   	rounding,
   	profile,		// y = f( x, t ),  0 < x < PI / 2, 0 < y < 1
   	pointX,			// normally interval 0 to PI / 2
   	pointY,			// normally interval 0 to 1
   	
   	lidAngle0,		// lid angle to side
   	lidAngle1,
   	lidAngle2,
   	lidAngle3,
   	lidAngle4,
   	lidAngle5,
   	
   	explode,
   }	
   
*/
/* defaults and values

   widthSegments: 2,
   heightSegments: 2,
   depthSegments: 2,	
   smoothness: 4,
   
   uvmode: 0,
   contourmode: 'rounding', // 'profile' 'bezier' 'linear' 
   explodemode: 'center', // 'normal'
   
   sides: 		[ 1, 1, 1, 1, 1, 1 ],
   lidHinges:		[ 0, 0, 0, 0, 0, 0 ],
   materials: 		[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], // material index
   
   width: 		function ( t ) { return 1 },
   height: 		function ( t ) { return 1 },
   depth: 		function ( t ) { return 1 },
   radius: 		function ( t ) { return 0.1 },
   
   waffleDeep:		function ( t ) { return 0 },		
   rounding:		function ( t ) { return 1 },		
   profile: 		function ( x, t ) { return Math.sin( x ) },	
   pointX: 		function ( t ) { return 0.001 },
   pointY: 		function ( t ) { return 0.999 },
   
   lidAngle0:		function ( t ) { return 0 },
   lidAngle1:		function ( t ) { return 0 },
   lidAngle2:		function ( t ) { return 0 },
   lidAngle3:		function ( t ) { return 0 },
   lidAngle4:		function ( t ) { return 0 },
   lidAngle5:		function ( t ) { return 0 },
   
   explode:		function ( t ) { return 0 },	

*/
// box with 6 planes + 12 edges + 8 corners = 26 parts:
  
  var sideEdge = [        // edges    _____2_______
                          //         |            /          sites (planes)
  [  4,  7,  8,  9 ],     //     10/ |          9/|            ____________
  [  5,  6, 10, 11 ],     //      / 5|          / |           |            /
  [  2,  3,  9, 10 ],     //     /______3_____ / 4|         / |      |5|  /|
  [  0,  1,  8, 11 ],     //    |    |_____1__|___|        /  |   /2/    / |
  [  0,  3,  6,  7 ],     //    | 11/         |   /       /____________ /  |
  [  1,  2,  4,  5 ]      //   6|  /         7|  /8      |  1 |________|_0_|
                          //    | /           | /        |   /         |   /
  ];                      //    | ______0____ |/         |  /    /3/   |  /
                          //                             | /  |4|      | /
                          //                             | ___________ |/
  var sideCorner = [      // corners
                          //         1____________0
  [ 0, 3, 4, 7 ],         //         |            /
  [ 1, 2, 5, 6 ],         //       / |           /|
  [ 0, 1, 2, 3 ],         //      /  |          / |
  [ 4, 5, 6, 7 ],         //    2/____________3/  |
  [ 2, 3, 6, 7 ],         //    |    |________|___|
  [ 0, 1, 4, 5 ]          //    |   /5        |   4
                          //    |  /          |  /
  ];                      //    | /           | /
                          //    | ___________ |/
                          //    6             7
  		    

further information:

// contourmode 'rounding'

var y1 = 2 / pi * Math.asin( Math.sin( x ) );
var y2 = Math.sin( x );	
return y1 + g.rounding( t ) * ( y2 - y1 );

// contourmode 'profile', 'linear', 'bezier' implemented with function sinuslike ( x ) { ...

/* Extension of a function y=f(x), 0 < x < pi2, 0 < y < 1 with f(0) = 0, f(pi2) = 1
	to a function with period 2*pi.
	// pi = Math.PI // pi2 = Math.PI / 2  
	// pX = pointX( t ) // pY = pointY( t )
*/	

// 'linear'

var m = ( 1 - pY ) / ( pi2 - pX );  
return x > pX ? m * x + 1 - pi2 * m : pY / pX * x;

// 'bezier'

var a = pi2 - 2 * pX;		
if( a === 0 ) {				
	tm = x / ( 2 * pX );				
} else {				
	var tp = pX / a;
	var tr = tp * tp  + x / a;				
	tm =  - tp + ( pX < pi2 / 2 ? 1 : -1 ) * Math.sqrt( tr );	
}			
return ( 1 - 2 * pY ) * tm * tm + 2 * pY * tm;	

}


..................................... Magic Sphere .............................................

An enlarged sphere. Non indexed BufferGeometry. It is made up of eight parts. Multi material is supported.

	geometry = new THREE.BufferGeometry();
	geometry.createMagicSphere = THREEg.createMagicSphere; // insert the methode from THREEg.js
	geometry.createMagicSphere( parameters ); // apply the methode
	
	// mesh
	mesh = new THREE.Mesh( geometry, materials );
        scene.add( mesh );

Include: <script src="THREEg.js"></script>

Example:

geometry = new THREE.BufferGeometry();
geometry.createMagicSphere = THREEg.createMagicSphere; // insert the methode from THREEg.js
geometry.createMagicSphere( {
  
	equator: 8,
	contourmode: 'bezier',
	parts: [ 1, 1, 1, 0, 0, 1, 1, 1 ],
	radius: function( t ){ return  1.2 * ( 0.3 + Math.abs( Math.sin( 0.4 * t ) ) ) },
	pointX: function( t ){ return  0.6 * ( 1 + Math.sin( 0.4 * t) ) },
	pointY: function( t ){ return  0.4 * ( 1 + Math.sin( 0.3 * t) ) },
  
 } ); 
	  

Parameters briefly explained in THREEg.js:

   /*	parameter overview	--- all parameters are optional ---
   
   p = {
   	
   		// simple properties
   	
   	equator,
   	uvmode,
   	contourmode,
   	explodemode,
   	
   		// array, value 1 for octant, otherwise arbitrary - upper counterclockwise, lower clockwise
   	parts, 
   	
   		// functions with  parameter time   // function ( t )
   
   	radius,
   	rounding,
   	profile,		// y = f( x, t ),  0 < x < PI / 2, 0 < y < 1
   	pointX,			// normally interval 0 to PI / 2
   	pointY,			// normally interval 0 to 1
   	driftX,
   	driftY,
   	driftZ,
   	explode,		// factor for exploded view - non indexed BufferGeometry
   	
   }	
   
   */
/* defaults and // values
	
	equator: 6,
	uvmode: 0, //1
	contourmode: 'rounding'; // 'profile'  'bezier' 'linear'
	explodemode: 'center'; // 'normal'
	
	parts: [ 1, 1, 1, 1, 1, 1, 1, 1 ],
	radius: function ( t ) { return 1 },
	rounding: function ( t ) { return 1 },
	profile: function ( x, t ) { return Math.sin( x ) },
	pointX: function ( t ) { return 0.001 },
	pointY: function ( t ) { return 0.999 },
	driftX: function ( t ) { return 0 },
	driftY: function ( t ) { return 0 },
	driftZ: function ( t ) { return 0 },
	explode: function ( t ) { return 0 },
*/

..................................... Labyrinth-3D-2D ..........................................

Easy to design 3D and 2D Labyrinth Geometry. Non indexed BufferGeometry. It is realized as one non-indexed BufferGeometry. Multi material is supported.

	geometry = new THREE.BufferGeometry();
	geometry.createLabyrinth = THREEg.createLabyrinth; // insert the methode from THREEg.js
	geometry.createLabyrinth(  dim, design, m ); // apply the methode
	
	// mesh
	mesh = new THREE.Mesh( geometry, materials );
        scene.add( mesh );

parameters:

dim: '2D' or '3D'
design: arrays as in the examples
m: arrays for material index as in the examples

Include: <script src="THREEg.js"></script>

// Description of the design in THREEg.js section Labyrinth-3D-2D 

// ..................................... Labyrinth-3D-2D .......................................

/*
	icons design 3D
	The characters on the keyboard have been chosen so that they roughly reflect the form.
	
	wall description
	sides l f r b is left front right back, with floor and roof
	
	char sides
	G	l f r b   can only be achieved by beaming
	M	l f r
	C	b l f
	3	f r b
	U	l b r
	H	l r
	:	f b
	F	l f
	7	f r
	L	l b
	J	b r
	I	l 
	1	r
	-	f
	.	b
	
	without walls
	since extra character not possible on the wall
	* roof and floor
	^ roofless
	v floorless
	x roofless and floorless
	
	with four side walls but roofless and floorless
	#
	
//--------------------------------------------------------------
	
	design 2D 
	only icon + 
	All the neighboring boxes are connected. There's no way out!
	
	var design2D = [  // will be converted to design 3D
	' ++++++++++   ',
	' +++  ++  ++  ',
	
	];
		
*/

EXAMPLES:

 
var design3D = [
	// upper storey first
	//23456789.......
	[
	'     M         G', // 1
	'     H          ', // 2
	'     H          ', // 3
	'   F-*--7       ', // 4
	'   I*7**1       ', // 5
	' C:v*L.**:::7   ', // 6
	'   L*...J   U   ', // 7
	'    H           ', // 8
	'    L::::3      '  // 9	
	],[
	'                ', // 1
	'                ', // 2
	'          G     ', // 3
	'                ', // 4
	'                ', // 5
	'   #            ', // 6
	'                ', // 7
	'                ', // 8
	'                '  // 9	
	],[
	'F::3            ', // 1
	'H    F:::::7    ', // 2
	'H    H     H    ', // 3
	'H  F-*-7   H    ', // 4
	'H  I****:::1    ', // 5
	'L::x***1   H    ', // 6
	'   I...J   H    ', // 7
	'   H   F:7 L:::7', // 8
	'   L:::J L:::::J'  // 9	
	]];
	
	// labyrinth material index
	var materialIndex3D = [
	// upper storey first
	// px, nx, py, ny, pz, nz 
	[ 0, 1, 2, 3, 4, 5 ], 
	[ 0, 0, 1, 1, 2, 2 ],
	[ 6, 7, 6, 7, 6, 7 ],
	];

//---------------------------------------------------------
		
	var design2D = [  // will be converted to design 3D
	' ++++++++++   ',
	' +++  ++  ++  ',
	' +++  +++     ',
	'++ ++ ++++++++',
	' +++++ + +   +',
	'   +  ++ +++++',
	'   ++++  +    '
	];
	
	var materialIndex2D = [
	
	// px, nx, py, ny, pz, nz 
	0, 1, 2, 3, 4, 5 
	
	];

..................................... Line Grid ...................................................

Easy to design Line Grid Geometry. Non indexed BufferGeometry.

The line grid can either be created in the xy-plane, or you can design grids on the sides of a box.

var gridGeometry = new THREE.BufferGeometry( );
gridGeometry.createLineGrid = THREEg.createLineGrid;
gridGeometry.createLineGrid( designGrid, multi material, width, height, depth );
var grid = new THREE.LineSegments( gridGeometry, materials );

parameters:

designGrid: arrays as in the examples
If the design results in double lines at one position, the surplus line is eliminated.

optional are

multi material:  mode 'align' (2 materials) or 'side' (default, up to 6 materials)
width, height, depth:

For a box.
The size of the box is determined by the design by default, but can also be specified as required.
The length of the first line in the design of each side is decisive for the centering of the design.
You can easily change the centering by using blanks at the beginning and end.

Include: <script src="THREEg.js"></script>

// Description of the design in THREEg.js section Line Grid.

// ..................................... Line Grid ...................................................

/*
	design: array :
	
	icons
	The characters on the keyboard have been chosen so that they roughly reflect the form.
	
	description
	lines l f r b is left front right back
	
	char lines
	G	l f r b		// compatible to labyrinth design
	M	l f r
	C	b l f
	3	f r b
	U	l b r
	H	l r
	:	f b
	F	l f
	7	f r
	L	l b
	J	b r
	I	l 
	1	r
	-	f
	.	b
	
	special signs
	#	Grid with complete squares, equals G
	
	+	All the neighboring squares are connected.
*/

EXAMPLES:

// Note: Half the length of the first string on each plane determines the center of the design.
	
	var designPlaneNo1 =[	// is created on the x-y plane
	'        +        ',// spaces after + -->  length of first row, center of design
	'        +  ',
	'    .F7.-.F7. ', 
	'    I.  G  .1 ',
	'    I L. .J 1 ',
	'    I   H   1 ',
	'## : U C 3 M : ## ',
	'    I   H   1 ',
	'    I F- -7 1 ',
	'    I-  H  -1 ',
	'    -LJ-.-LJ- ',
	'        +',
	'        +'
	];
	
	var designBoxNo2 = [
	
	[], // no px
	[], // no nx
	[   // py
	'+++++++',
	'++   ++',
	'+ 7 F +',
	'  1 I  ',
	'+ J L +',
	'++   ++',
	'++   ++'
	],
	[  // ny
	'### + ###',
	'## +++  #',
	'#  + +  #',
	'#   H   #',
	'   : :   ',
	'#   H   #',
	'#  L.J  #',
	'##     ##',
	'###   ###'
	],
	[   // pz
	'+++++++++',
	'+++###+++',
	'##     ##',
	'##  +  ##',
	'##     ##',
	'+++ H +++',
	'+++###+++'
	]   // no nz
	];

..................................... Profiled Contour Geometry MultiMaterial ...................................................

The geometry is realized as indexed BufferGeometry and supports two MultiMaterial modes. Each an array with the 2D coordinates of the profile shape and the frame contour is required.

	geometry = new THREE.BufferGeometry();	
	geometry.createProfiledContourMMgeometry = THREEg.createProfiledContourMMgeometry;
	geometry.createProfiledContourMMgeometry( profileShape, contour, contourClosed, openEnded, profileMaterial );
	// mesh
	mesh = new THREE.Mesh( geometry, materials );
        scene.add( mesh );

parameters:

profileShape: array with coordinate pairs
contour: array  with coordinate pairs

optional are

contourClosed: if true (default) the last point is connected to the first one
openEnded: if true the ends are not closed, default is false
profileMaterial: true - each section of the profile has an increased material index,
false (default) - each contour surface has an increased material index

Include: <script src="THREEg.js"></script>

EXAMPLE:

var profileShape = [];
for ( var i = 0; i < 8; i ++ ){	
   profileShape.push ( 0.5 * Math.cos( i / detail * Math.PI * 2 ), 0.5 * Math.sin( i / detail * Math.PI * 2 ) ); }
var contour = [-3,4, 0,4, 4,4, 2,1, 4,-2, 0,-3, -4,-3,	-4,0 ];
var materials = [ // rainbow-colored	
	new THREE.MeshPhongMaterial( { color: 0xfa0001, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0xff7b00, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0xf9f901, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0x008601, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0x01bbbb, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0x250290, side: THREE.DoubleSide } ),	
	new THREE.MeshPhongMaterial( { color: 0xfc4ea5, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0x83058a, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0x83058a, side: THREE.DoubleSide } )	
]
var geometry = new THREE.BufferGeometry( );
geometry.createProfiledContourMMgeometry = THREEg.createProfiledContourMMgeometry;
geometry.createProfiledContourMMgeometry( profileShape, contour, false, false, true );
// mesh
var fullProfile = new THREE.Mesh( geometry, materials );
scene.add( fullProfile );

.................................................................... Road / Wall ..............................................................................

The geometry is realized as indexed BufferGeometry and supports MultiMaterial.

	geometry = new THREE.BufferGeometry();
	
	g.createRoad = THREEg.createRoad;
	g.createRoad( curvePoints, lengthSegments, trackDistances );	
   // or
	g.createWall = THREEg.createWall;
	g.createWall( curvePoints, lengthSegments, sidesDistances, widthDistance, hightDistance );
		
	// mesh
	mesh = new THREE.Mesh( geometry, materials );
        scene.add( mesh );

parameters:

(all optional)

curvePoints: Array  with groups of 3 coordinates each for CatmullRomCurve3, are converted to Vector3.   
lengthSegments: Number of segments for the length of the road.

trackDistances: Array with distances to the center of the street (curve).

sidesDistances: Array of 4 arrays with distances for sides left, top, right, bottom. If [] the side is not created.
widthDistance: Distance between left and right side. If not specified, the width of the top page is used.
hightDistance: Distance between top and bottom side. If not specified, the height of the left page is used.

Include: <script src="THREEg.js"></script>

EXAMPLE: road

var curvePoints =  [
 -25, 0.2, -25,
 -24, 0.2, -24,
 -4, 2, -9,
 4, 1, -6,
 6, 0, 0,
 -3, 1, 1,
 -11, 0, 6,
 -12, 1, 1,
 -7, 1, -3,
 7, 8, -9,
 13, 2, -12,
];
var lengthSegments = 200;
var trackDistances = [ -0.62, -0.6, -0.02, 0.02, 0.6, 0.62 ];

var g = new THREE.BufferGeometry( );
g.createRoad = THREEg.createRoad;
g.createRoad( curvePoints, lengthSegments, trackDistances );

tex = new THREE.TextureLoader().load( 'CentralMarking.png' );
tex.wrapS = THREE.RepeatWrapping;
tex.repeat.set( lengthSegments / 2 );
var material = [	
	new THREE.MeshBasicMaterial( { color: 0xffffff, side: THREE.DoubleSide, wireframe: true} ),
	new THREE.MeshBasicMaterial( { color: 0x000000, side: THREE.DoubleSide, wireframe: true} ),
	new THREE.MeshBasicMaterial( { map: tex, side: THREE.DoubleSide, wireframe: true } ),
	new THREE.MeshBasicMaterial( { color: 0x000000, side: THREE.DoubleSide} ),
	new THREE.MeshBasicMaterial( { color: 0xffffff, side: THREE.DoubleSide} ),	
];
// mesh
var mesh = new THREE.Mesh( g, material );
scene.add( mesh );

CentralMarking CentralMarking.png

g.t = []; // tangents
g.n = []; // normals
g.b = []; // binormals

are stored in the geometry, see example RoadRace.html

EXAMPLE: wall

var curvePoints =  [
 -16, 0,  24,
  -2, 0,  24,
  10, 3,  16,
  14, 2,   6,
  28, 0,   4,
  22, 1,  -8,
  10, 2, -14,
   0, 5, -12,
  -4, 2,  -6,
 -16, 0,   0,
 -20, 3,   8,
 -24, 0,  24,
 -16, 0,  24  
];

var lengthSegments = 400;
var sidesDistances = [ [ -0.9, -0.3, 0.3, 0.9 ], [ -0.4, 0, 0.4 ], [ -0.9, -0.3, 0.3, 0.9 ], [ -0.9, 1.1 ] ];

var gWall = new THREE.BufferGeometry( );
gWall.createWall = THREEg.createWall;
gWall.createWall( curvePoints, lengthSegments, sidesDistances );
//gWall.createWall( ); // all parameters default

tex = new THREE.TextureLoader().load( 'brick.jpg' );
tex.wrapS = THREE.RepeatWrapping;
tex.wrapT = THREE.RepeatWrapping;
tex.repeat.set( lengthSegments / 2, 4 );

var material = [
		
	new THREE.MeshBasicMaterial( { map: tex, side: THREE.DoubleSide } ),
	new THREE.MeshBasicMaterial( { color: 0xff8844, side: THREE.DoubleSide, wireframe: true } ),
	new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide, wireframe: true } ),
	new THREE.MeshBasicMaterial( { color: 0x000000, side: THREE.DoubleSide } ),
	new THREE.MeshBasicMaterial( { color: 0xffffff, side: THREE.DoubleSide } ),
	new THREE.MeshBasicMaterial( { color: 0x128821, side: THREE.DoubleSide } ),
	new THREE.MeshBasicMaterial( { map: tex, side: THREE.DoubleSide } ),
	new THREE.MeshBasicMaterial( { color: 0x0000ff, side: THREE.DoubleSide, wireframe: true } ),
	new THREE.MeshBasicMaterial( { color: 0x004400, side: THREE.DoubleSide } ),
	
];

var mesh = new THREE.Mesh( gWall, material );
scene.add( mesh );

threeg.js's People

Contributors

hofk avatar

Watchers

James Cloos avatar Gordon avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.