function Tapestry(donnees, parametres) {

//	Initialisation des attributs
	var mesDonnees = donnees;
	this.zoomlvl = 0;
	this.beginIndexs=[0,5,0,0];
	this.decalageParent = [0,10,10,1];
	this.listePosition=[[],[]];


	this.parametres = {
			name: null,
			hauteur: 330,
			largeur: 1100,	
			selector: "#tapestry",
			selectorHaut : null,
			selectorBas : null,
			relations: null,
			contenu: null,
			template: null
	};
	
	this.etendre(parametres);
	
	
	/* -- Gestion des valeurs par defaut -- */
	
	if (null==this.parametres.name) {
		this.parametres.name = this.parametres.selector;
	}
	if (null==this.parametres.hauteur) {
		this.parametres.hauteur = 400;
	}
	if (null==this.parametres.largeur) {
		this.parametres.largeur = 900;
	}
	if (null==this.parametres.selectorBas) {
		this.parametres.selectorBas = "#timelinebasse";
	}
	if (null==this.parametres.selectorHaut) {
		this.parametres.selectorHaut = "#timelinehaute";
	}
	if (null==this.parametres.contenu) {
		if (null!=this.parametres.template) {
			this.template = jQuery(this.parametres.template.selector);
			this.template.remove();
			this.parametres.contenu = this.renderContent;
		}
		else {
			this.parametres.contenu = this.defaultContent;
		}
	}
	
	
	
	//----------------------------------------------------------------------
	// INITIALISATION : on calcule les positions / dimensions des futurs objets
	this.listePosition=this.getPosition();
	var object = this;
	var larg,haut;
	larg = String(this.parametres.largeur/5) + "px";
	larg2=String(this.parametres.largeur/10) + "px";
	larg3= String(9*this.parametres.largeur/100) + "px";
	haut = String(this.parametres.hauteur/2 -1) + "px" ;
	haut2 = String(this.parametres.hauteur) + "px" ;
	this.lastIndexs = [9,18,160,1441];


	//----------------------------------------------------------------------
	// FENETRE DE FOND
	this.globalTapestry = d3.select(object.parametres.selector);
	this.globalTapestry.append('div').attr('id','timelinehaute');
	this.globalTapestry.append('div').attr('id','timelinebasse');

	this.tapestryHtml = d3.select(object.parametres.selectorHaut)
	.style('position', 'relative')
	.style('left','200px')
	.style('width', object.parametres.largeur + 'px')
	.style('height', object.parametres.hauteur + 'px')
	.style('background-color', 'black');	
	//.style('overflow','hidden');

	//----------------------------------------------------------------------
	// BOUTON DEZOOM
	var left = this.parametres.largeur/2 +100+"px";
	this.boutonZoom = d3.select(object.parametres.selectorBas).append('button')
	.attr('id',"dezoom")
	.style('position', 'relative')
	.style('top','5px')
	.style('left',left)
	.style('width', "200px")
	.style('height',"20px")
	.style('background-color', 'grey')	
	.style('margin-top', '5px')
	.style('stroke-width','2px')
	.style('stroke','black')
	.style('display','none')
	.html("<p class = 'title' style = \" width:200px;\"> Dezoom </p>")
	.on('mouseover', function(){d3.select(this).style('opacity', 0.8);})
	.on('mouseout', function(){d3.select(this).style('opacity', 1);})
	.on('click',function(){object.agencerDezoom(mesDonnees);});

	//----------------------------------------------------------------------
	// BOUTON SLIDELEFT
	var html = "<img class = 'img' src = \"data/boutongauche.png\" style=\" width:"+larg3+"; height:"+haut+"; margin-top: 0px; margin-left: 0px; stroke-width:0px\" />";
	this.boutonSlideGauche = d3.select(object.parametres.selectorHaut).append('button')
	.attr('id',"sliderLeft")
	.style('position', 'absolute')
	.style('z-index',1)
	.style('width', larg2)
	.style('height',haut)
	.style('background-color', 'black')	
	.style('opacity',0.2)
	.style('stroke','black')
	.html(html)
	.on('mouseover', function(){
		if (object.beginIndexs[object.zoomlvl]!=0){
		d3.select(this).style('opacity', 0.8);}})
	.on('mouseout', function(){
		var lvl = object.zoomlvl;
		if (object.beginIndexs[lvl]!=0){
		d3.select(this).style('opacity', 1);}})
	.on('click',function(){object.sliderLeft(mesDonnees);});

	//----------------------------------------------------------------------
	// BOUTON SLIDERIGHT
	left = this.parametres.largeur*9/10 + "px";
	this.boutonSlideDroite = d3.select(this.parametres.selectorHaut).append('button')
	.attr('id',"sliderRight")
	.style('position', 'absolute')
	.style('z-index',1)
	.style('top',-5)
	.style('left',left)
	.style('width', larg2)
	.style('height',haut)
	.style('background-color', 'black')	
	.style('margin-top', '5px')
	.style('stroke-width','2px')
	.style('stroke','black')
	.style('opacity',0.2)
	.html("<img class = 'img' src = \"data/boutondroite.png\" style=\" width:"+larg3+"; height:"+haut+"; margin-top: 0px; margin-left: 0px; stroke-width:0px\" />")
	.on('mouseover', function(){
		var lvl = object.zoomlvl;
		if (object.beginIndexs[lvl]!=object.lastIndexs[lvl]-9){
		d3.select(this).style('opacity', 0.8);}})
	.on('mouseout', function(){
		var lvl = object.zoomlvl;
		if (object.beginIndexs[lvl]!=object.lastIndexs[lvl]-9){
			d3.select(this).style('opacity', 1);}})
	.on('click',function(){object.sliderRight(mesDonnees);});

	
	//----------------------------------------------------------------------
	// INIT DES PETITES FENETRES

	d3.select(this.parametres.selectorHaut).selectAll()
	.data(mesDonnees[0].slice(0, 9)).enter().append("div")
	.attr('class', 'cell')
	.attr('id', "middle")
	.attr('indexInTap',function(index){return index;})
	.on('mouseover', function(){if (object.zoomlvl!=3){d3.select(this)
		.transition()
		.duration(120)
		.style('opacity', 0.8)
		.style('stroke', 'black')
		.style('stroke-width', '3px');}})
	.on('mouseout', function(){d3.select(this)
		.transition()
		.duration(120)
		.style('opacity', 1)
		.style('stroke', 'white')
		.style('stroke-width', '0px');})
	.on('click',function(datum,index){object.agencerZoom(datum,index,mesDonnees);})
	.on('scroll',function(datum,index){object.agencerZoom(datum,index);})
	.style('position', 'absolute')
	.style('width', larg)
	.style('height',haut)
	.style('background-color', 'black')
	.style('text-align','center')
	.html(function(datum,index){return object.parametres.contenu.call(object, this,datum);})
	.style('left', function(datum, index) {
		return object.listePosition[index][0];
			})
	.style('top', function(datum, index) {
			return object.listePosition[index][1];
			});
	if (null!=this.parametres.relations) {
		this.parametres.relations.add(this.parametres.name, this);
		
		d3.select(this.parametres.selectorHaut).selectAll("div")
			.each(function(datum) {
				var item = d3.select(this);
				datum["relationName"] = object.parametres.name +'.'+ datum.name;
				object.parametres.relations.add(datum.relationName, item);
			});
	}
//	d3.select("#timelinehaute").selectAll("#middle")
//				.append('button')
//				.attr('class', 'dezoom-butoon')
//				.attr('id', "dezoom")
//				.style('width','180px')
//				.style('height','20px')
//				.style('position','absolute')
//				.style('top','180')
//				.style('opacity',' 0,8')
//				.style('background','dark-grey')
//				.style('z-index',10)
//				.attr('onmouseover',"this.style(\"stroke\",\"white\");" );			
;
}

Tapestry.prototype = {		
		sleep: function(ms, args, obj) {
		    /* Called at the top of a function to invoke a delay in processing that
		       function

		       Returns true if the function should be executed, and false if the sleep
		       timer is activated. 

		       At the end of the sleep tiemout, the calling function is re-called by
		       calling it with "apply (obj, args || [])".
		    */

		    var caller = sleep.caller;
		    if (caller.sleepTimer) { 
		      /* if invoked from timeout function, delete timer and return false */
		      delete caller.sleepTimer;
		      return true;
		    }},
		    
		etendre: function(parametres) {
			if (null==parametres || "object"!=typeof(parametres)) {
				return;
			}
			
			for (var cle in parametres) {
				this.parametres[cle] = parametres[cle];
			}
		},

		getPosition: function(){	// renvoie une liste contenant la position(x,y) absolute des fenêtres de la tapestry
			var l,h;
			l= this.parametres.largeur;
			h= this.parametres.hauteur;
			var listposition=[];
			for (var i=0;i<9;i++){listposition.push([(i*0.5)*l/5,h/2*((i+1)%2)]);}
			//listposition.push([4.5*l/5,0]);
			return listposition;
		},

		
		
		updateLayout: function(lvl,sliceIndex, isSlide,mesDonnees){		// met a jour la tapestry avec les données[lvl] à l'indice sliceIndex
			var object = this;
			var duration = isSlide? 40 : 160;
			this.tapestryHtml.selectAll("#middle")
			
			.transition()
			.duration(duration)
			//.delay(10)
			//.delay(function(index){return 10*index;})
			.style("opacity", 0);
			
			var timeout = window.setTimeout(function() {

			
			
			object.tapestryHtml.selectAll("#middle")
			.data(mesDonnees[lvl].slice(sliceIndex,sliceIndex+9))
			.html(function(datum,index){return object.parametres.contenu.call(object, this,datum);});
			
			object.tapestryHtml.selectAll("#middle")
			.transition()
			.duration(2*duration)
			.delay(10)
			//.delay(function(index){return 10*index;})
			.style("opacity", 1);
			
			object.tapestryHtml.select("#sliderRight")
				.transition()
				.duration(duration)
				.delay(10)
				.style('opacity',function(){
					if (object.beginIndexs[lvl]+9==object.lastIndexs[lvl]){
						return 0.2;
					}
					else{return 1;}				});
			
			object.tapestryHtml.select("#sliderLeft")
				.transition()
				.duration(duration)
				.delay(10)
				.style('opacity',function(){
					if (object.beginIndexs[lvl]==0){
						return 0.2;
					}
					else{return 1;}				});
			
			object.boutonZoom
			.style('display',function(){
				if (lvl==0){return 'none';}
				else {return 'block';}
//				result = (lvl==0) ? 'none' : 'block';
//				return result;
			});
			}, 3*duration);
		},

		
		
		sliderLeft: function(mesDonnees){
			if (this.zoomlvl!=0){
			var lvl= this.zoomlvl;
			var father = this.beginIndexs[lvl];
			if (father!=0){ 
				var coef= (father == 1) ? 1 :2;	
				this.beginIndexs[lvl]-=coef;
				this.updateLayout(lvl, father-coef,true,mesDonnees);
				
				
				// Gestion des decalages des niveau superieurs
				//-------------------------------------------------------------------
				if (lvl!=0){
					this.decalageParent[lvl-1]-=2;
					var decal = this.decalageParent[lvl-1];
					if (decal<2){
						if (decal==0){
							this.decalageParent[lvl-1]=9;
							if(this.beginIndexs[lvl-1]!=0){
								this.beginIndexs[lvl-1]--;
								if (lvl==3){
									this.decalageParent[1]--;
									if (this.decalageParent[1]==1){	
										this.decalageParent[1]=10;
										if(this.beginIndexs[1]!=0){this.beginIndexs[1]--;}							
									}}}}
						else{
							this.decalageParent[lvl-1]=10;
							if(this.beginIndexs[lvl-1]!=0){
								this.beginIndexs[lvl-1]--;
								if (lvl==3){
									this.decalageParent[1]--;
									if (this.decalageParent[1]==1){	
										this.decalageParent[1]=10;
										if(this.beginIndexs[1]!=0){this.beginIndexs[1]--;}							
									}}}}}}}

		}},
		sliderRight: function(mesDonnees){
			if (this.zoomlvl!=0){
			//var last = donnees1[this.zoomlvl].length-9;
			var lvl=this.zoomlvl;
			var last = this.lastIndexs[lvl]-9;
			var father = this.beginIndexs[lvl];
			if (father!=last){
				var coef= (father == last-1) ? 1 :2;	
				this.beginIndexs[lvl]+=coef;
				this.updateLayout(lvl, father+coef,true,mesDonnees);

				// Gestion des decalages des niveau superieurs
				//-------------------------------------------------------------------
				if (this.zoomlvl!=1){
					//var lastFather = donnees1[lvl-1].length-9;
					var lastFather = this.lastIndexs[lvl-1]-9;
					this.decalageParent[lvl-1]+=2;
					decal =this.decalageParent[lvl-1];
					if (decal>18){
						if (decal==20){
							this.decalageParent[lvl-1]=11;
							if(this.beginIndexs[lvl-1]!=lastFather){
								this.beginIndexs[lvl-1]++;
								if (lvl==3){
									this.decalageParent[1]++;
									if (this.decalageParent[1]==19){	
										this.decalageParent[1]=10;
										if(this.beginIndexs[1]!=this.lastIndexs-9){this.beginIndexs[1]++;}							
									}}}}
						else{
							this.decalageParent[this.zoomlvl-1]=10;
							if(this.beginIndexs[this.zoomlvl-1]!=lastFather){
								this.beginIndexs[this.zoomlvl-1]++;
								if (lvl==3){
									this.decalageParent[0]++;
									if (this.decalageParent[1]==19){	
										this.decalageParent[1]=10;
										if(this.beginIndexs[1]!=mesDonnees[1].length-9){this.beginIndexs[1]++;}							
									}}}}}}}
		}},
		
		agencerZoom: function(element,indexx,mesDonnees){
			var lvl=element.zoomlvl;
			if (lvl !=3){
				this.beginIndexs[lvl+1]=element.child-indexx;
				this.updateLayout(lvl+1, element.child-indexx,false,mesDonnees);
				this.decalageParent[lvl]+=4-indexx;
				this.zoomlvl = lvl +1;
			}},

		agencerDezoom: function(mesDonnees){
			var lvl = this.zoomlvl;
			if (lvl!=0) {
				this.updateLayout(lvl-1, this.beginIndexs[lvl-1],false,mesDonnees);
				this.zoomlvl--;
				this.decalageParent[lvl-1]=10;
			}},
			
		getDataTapestry: function(json){
			
		},
		renderContent: function(cellule, element) {
			
			var html;
			var img = new Image();
			indexx = element.indexx;
			img.src= images[indexx];
			var wc, hc;
			wc = cellule.offsetWidth  ;
			hc = cellule.offsetHeight ;
			
			var data = {
				'urlImage': img.src,
				'widhtImage': wc +"px",
				'heightImage': hc +"px",
				'margin-top': hc/2+'px',
				'cssDimensionImage': "width:"+ wc + "px; ",
				'cssDimensionTitre':" width:" + wc + "px; height:" +hc/10 +"px;",
				'titreImage': "image " + indexx
			};
			
			return this.template
				.render(data, this.parametres.template.directives).html();
		},
		
		defaultContent: function(cellule, element) {	// cellule contient la div et element ses données
			
				var html;
				var img = new Image();
				
				indexx = element.indexx;
				img.src= images[indexx];
				var wc, hc;
				wc = cellule.offsetWidth  ;
				hc = cellule.offsetHeight ;
				html = "<img class = 'img' src = \"" + img.src + "\" style=\"width:"+ wc + "px; height:" +hc + "px;  margin-top:5px; margin-left: 0px;\" />";
				html += "<p class = 'title' style = \" width:" + wc + "px; height:" +hc/10 +"px;\"> Image "+String(indexx)+"</p>";
				return html;
		
		}


};


