1 //<script type="text/javascript">
11 Pman.Tab.BuilderErm = {
27 this.embed = el.createChild({
30 codebase: 'http://www.adobe.com/svg/viewer/install/',
31 classid: 'clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2',
32 pluginspage: 'http://www.adobe.com/svg/viewer/install/',
38 this.wrapper = this.embed;
45 this.svg = this.wrapper.createChild( {
47 xmlns: 'http://www.w3.org/2000/svg',
53 this.grp = this.svg.createChild( {
57 //this.paper.circle(320, 240, 60).animate({fill: "#223fa3", stroke: "#000", "stroke-width": 80, "stroke-opacity": 0.5}, 2000);
59 this.currentX = this.START_X;
60 this.currentY = this.START_Y;
64 url: baseURL+'/Builder/ERM.php',
66 success: function (data) {
67 _this.tables = data.tables;
68 _this.links = data.links;
77 // el.dom.addEventListener("DOMMouseScroll", function(e) { _this.mouseScroll(e); }, false);
78 // el.dom.addEventListener("mousewheel", function(e) { _this.mouseScroll(e); }, false);
79 el.dom.addEventListener("scroll", function(e) { _this.redrawTables(); }, false);
80 //document.addEventListener("keypress", function(e) { _this.keyPress(e); }, false);
81 //this.grp.on("mousedown", function(e) { _this.mouseDown(e); }, false);
83 this.svg.on("mouseup", function(e) { _this.mouseUp(e); } , false);
84 this.svg.on("mousemove", function(e) { _this.mouseMove(e); }, false);
85 //document.addEventListener("mouseout", mouseUp, false);
112 drawTables : function() {
113 this.el.mask("Drawing");
114 this.tableLocations = [];
115 for (var i in this.tables) {
116 var data = this.drawTable( i, this.tables[i], this.groupId++ );
117 this.tableLocations.push(data);
118 this.tableMap[data.name] = data;
119 //if (this.groupId > 10) { break; }
127 redrawTables : function()
129 console.log('redraw');
130 Roo.each(this.tableLocations , this.renderTable , this);
134 renderTable : function ( data)
138 if (!data.grp) { // always make the group..
139 data.grp = this.grp.createChild({
145 data.grp.dom.tdata = data;
152 right: data.rectX + data.rectW,
153 bottom: data.rectY + data.rectH
157 //console.log('can Not see : ' + data.name);
159 if (!data.rect) { // already deleted!!!
162 // delete everythting..
163 while (data.grp.dom.childNodes.length) {
164 data.grp.dom.removeChild(data.grp.dom.firstChild);
167 data.rectHead = false;
168 data.rectText = false;
170 for (var i =0; i < data.cols.length; i++) {
171 data.cols[i].textH = false;
172 data.cols[i].textD = false;
181 data.rect = data.grp.createChild({
188 r:"0", rx:"0", ry:"0",
189 fill:"none", stroke:"#000",
191 'class' : 'b-erm-rect'
193 data.rect.on("mousedown", function(e) { _this.mouseDown(e); }, false);
196 if (!data.rectHead) {
197 data.rectHead = data.grp.createChild({
203 height: this.TEXT_PAD + this.FONT_SIZE+ 8,
205 'class' : 'b-erm-recthead'
210 if (!data.rectText) {
212 data.rectText = data.grp.createChild({
215 x: this.TEXT_PAD + 10,
216 y: this.TEXT_PAD + this.FONT_SIZE+4,
217 'class' : 'b-erm-tblname',
224 var drawHowMuch = this.drawHowMuch();
230 for (var i =0; i < data.cols.length; i++) {
231 var c = data.cols[i];
232 if (drawHowMuch < 1 && c.textH) {
236 if (drawHowMuch < 2 && c.textD) {
241 if (drawHowMuch < 1) {
242 continue; // dont draw..
247 c.textH = data.grp.createChild({
252 'class' : 'b-erm-colname',
263 if (drawHowMuch < 2) {
267 c.textD = c.textH.createChild({
270 'class' : 'b-erm-coltype',
271 cn : [ '\u00a0' + c.desc ]
279 console.log('translate(' + data.rectX + "," + data.rectY + ')');
280 data.grp.dom.setAttribute('transform', 'translate(' + data.rectX + "," + data.rectY + ')');
285 drawHowMuch : function ()
287 if (this.currentScale > 0.9) {
290 if (this.currentScale > 0.5) {
296 canSee: function (box)
298 // work out limits of screen!!!
299 var scroll = this.el.getScroll(); // perhasp we should cache these!!!!
300 var outersize = this.el.getSize();
301 // get width / height from svg element..
302 // diviede by the scale to calc position?
303 var scale = 1.0/this.currentScale;
306 top: scale * scroll.top,
307 left: scale * scroll.left,
308 bottom: scale * (scroll.top + outersize.height),
309 right: scale * (scroll.left + outersize.width)
312 //console.log('check See: ---');
313 //console.log('window: ' + vbox.toSource());
314 //console.log('tablebox: ' + box.toSource());
315 //console.log('---');
316 /// option a) - any of the points are inside the box..
320 (vbox.bottom > box.top && box.top > vbox.top ) &&
321 (vbox.right > box.left && box.left > vbox.left )
325 // box.bottom / box right
327 (vbox.bottom > box.bottom && box.bottom > vbox.top ) &&
328 (vbox.right > box.right && box.right > vbox.left )
332 // points outside!, but body inside..
334 (box.right > vbox.left && box.left < vbox.right) &&
335 (box.bottome > vbox.top && box.top < vbox.bottom )
348 drawTable : function ( name, cols, groupId )
350 //alert(table.toSource());
354 var ret = { links: [] };
359 //tabGrp.node.setAttribute("id", "G" + tableId);
360 //draw the box - attempt some layout
364 for (var i in cols ) {
365 var sl = i.length + cols[i].length + 1;
366 maxWidth = maxWidth > sl ? maxWidth : sl;
371 var tableWidth = (maxWidth*this.FONT_SIZE *0.5) + (2*this.TEXT_PAD) + 10;
372 //height of the box is table name+num columns*font height + padding
374 var tableHeight = ((length+1)*(this.FONT_SIZE*1.2)) +
375 ((length +1)*this.TEXT_PAD) + 5;
377 this.maxTableHeight = (tableHeight > this.maxTableHeight)? tableHeight : this.maxTableHeight;
379 // preserve the existing position of the table
380 // obviously this will look rubbish if there are new tables and
384 if (typeof(this.positions[name]) == "undefined") {
391 //store position, may be handy later
392 this.positions[name] = box;
394 box = this.positions[name];
395 this.currentX = box.x;
396 this.currentY = box.y;
399 ret.rectX = this.currentX;
400 ret.rectY = this.currentY;
401 ret.rectW = tableWidth;
402 ret.rectH = tableHeight;
405 var textY = this.TEXT_PAD + this.FONT_SIZE+4;
411 textY += (this.TEXT_PAD*2) + this.FONT_SIZE;
413 //add the column name and type text
418 for (var i in cols) {
419 //String[] colAndType : ) {
423 textX : this.TEXT_PAD + 10,
435 this.lpositions[name + '.' + i] = {
442 textY += this.TEXT_PAD + this.FONT_SIZE;
446 this.currentX += tableWidth + 10;
447 this.maxX = (this.maxX<this.currentX) ? this.currentX : this.maxX;
448 if (this.currentX > this.widthLimit) {
449 this.currentX = this.START_X;
450 this.currentY += this.maxTableHeight + 10;
452 //alert(this.currentY);
453 this.maxY = ((this.currentY + tableHeight) > this.maxY) ? this.currentY + tableHeight : this.maxY;
456 //tabGrp.node.setAttribute("tableName", name);
460 this.renderTable(ret);
463 zoom : function(factor)
465 var outersize = this.el.getSize();
466 var de = this.grp.dom;
467 this.currentScale = this.currentScale || 1.0;
468 // var oldTranslate = { x: de.currentTranslate.x, y: de.currentTranslate.y };
471 this.currentScale *= factor;
473 de.setAttribute('transform','scale('+this.currentScale+')');
475 // correct currentTranslate so zooming is to the center of the viewport:
476 //sessionStorage.zoom = "" + de.currentScale ;
478 width: Math.max(this.maxX * this.currentScale, outersize.width),
479 height: Math.max(this.maxY * this.currentScale, outersize.height)
488 var vp_width, vp_height;
490 vp_width = de.viewport.width;
491 vp_height = de.viewport.height;
494 // work around difficiency in moz ('viewport' property not implemented)
495 vp_width = window.innerWidth;
496 vp_height = window.innerHeight;
498 //de.currentTranslate.x = mouseX - (de.currentScale/oldScale) * (mouseX - oldTranslate.x);
499 //de.currentTranslate.y = mouseY - (de.currentScale/oldScale) * (mouseY - oldTranslate.y);
501 //de.currentTranslate.x = vp_width/2 - (de.currentScale/oldScale) * (vp_width/2 - oldTranslate.x + (mouseX - vp_width/2));
502 //de.currentTranslate.y = vp_height/2 - (de.currentScale/oldScale) * (vp_height/2 - oldTranslate.y + (mouseY - vp_height/2));
508 // mouse down applied to elements (other mouse events are handled by doc)
509 mouseDown : function (e) {
510 this.draggingElement = e.getTarget().parentNode;
512 //var tdata = this.draggingElement.tdata;
518 // console.log('set drag element');
519 var p = this.svg.dom.createSVGPoint();
523 //var m = this.getScreenCTM(this.svg.dom);
525 //p = p.matrixTransform(m.inverse());
526 this.nMouseOffsetX = p.x - parseInt(this.draggingElement.getAttribute("dragx") || 0);
527 this.nMouseOffsetY = p.y - parseInt(this.draggingElement.getAttribute("dragy") || 0);
530 mouseUp : function (evt) {
533 if (!this.draggingElement ) {
536 var tdata = this.draggingElement.tdata;
537 tdata.rectX = this.lastX;
538 tdata.rectY = this.lastY;
540 this.nMouseOffsetX = 0;
541 this.nMouseOffsetY = 0;
542 this.draggingElement = null;
547 //console.log('unset drag element');
550 mouseMove : function (e) {
551 if(!this.draggingElement ) {
556 var tdata = this.draggingElement.tdata;
557 var x = this.currentScale * (xy[0] - this.startX);
558 var y = this.currentScale * (xy[1] - this.startY);
559 this.lastX = (tdata.rectX + x);
560 this.lastY = (tdata.rectY + y);
564 this.draggingElement.setAttribute("transform",
565 "translate(" + this.lastX + "," + this.lastY + ")");
570 var p = this.svg.dom.createSVGPoint();
574 var m = this.getScreenCTM(this.svg.dom);
576 p = p.matrixTransform(m.inverse());
577 p.x -= this.nMouseOffsetX;
578 p.y -= this.nMouseOffsetY;
579 // console.log(' drag ' + p.x + ',' + p.y );
581 this.draggingElement.setAttribute("dragx", p.x);
582 this.draggingElement.setAttribute("dragy", p.y);
583 this.draggingElement.setAttribute("transform", "translate(" + p.x + "," + p.y + ")");
584 //this.draggingElement.moveCallback(
585 // Pman.Tab.BuilderErm.Movable.draggingElement.obj);
593 getScreenCTM : function (doc){
594 if(doc.getScreenCTM) {
595 return doc.getScreenCTM();
599 var sCTM= root.createSVGMatrix()
601 var tr= root.createSVGMatrix()
602 var par=root.getAttribute("preserveAspectRatio")
603 if (par==null || par=="") par="xMidYMid meet"//setting to default value
604 parX=par.substring(0,4) //xMin;xMid;xMax
605 parY=par.substring(4,8)//YMin;YMid;YMax;
607 mos=ma[1] //meet;slice
609 //get dimensions of the viewport
616 w=root.getAttribute("width")
617 if (w==null || w=="") w=innerWidth
619 h=root.getAttribute("height")
620 if (h==null || h=="") h=innerHeight
622 // Jeff Schiller: Modified to account for percentages - I'm not
623 // absolutely certain this is correct but it works for 100%/100%
624 if(w.substr(w.length-1, 1) == "%") {
625 w = (parseFloat(w.substr(0,w.length-1)) / 100.0) * innerWidth;
627 if(h.substr(h.length-1, 1) == "%") {
628 h = (parseFloat(h.substr(0,h.length-1)) / 100.0) * innerHeight;
632 vba=root.getAttribute("viewBox")
633 if(vba==null) vba="0 0 "+w+" "+h
634 var vb=vba.split(" ")//get the viewBox into an array
636 //--------------------------------------------------------------------------
637 //create a matrix with current user transformation
638 tr.a= this.currentScale;
639 tr.d=this.currentScale;
640 tr.e= root.currentTranslate.x;
641 tr.f=root.currentTranslate.y;
656 //preserveAspectRatio="none"
660 sCTM.e=- vb[0]*sx //translateX
661 sCTM.f=- vb[0]*sy //translateY
662 sCTM=tr.multiply(sCTM)//taking user transformations into acount
670 //-------------------------------------------------------
673 sCTM.e=((w-vb[2]*s)/2) - vb[0]*s //translateX
677 sCTM.e=- vb[0]*s//translateX
681 sCTM.e=(w-vb[2]*s)- vb[0]*s //translateX
685 //------------------------------------------------------------
688 sCTM.f=(h-vb[3]*s)/2 - vb[1]*s //translateY
692 sCTM.f=- vb[1]*s//translateY
696 sCTM.f=(h-vb[3]*s) - vb[1]*s //translateY
700 sCTM=tr.multiply(sCTM)//taking user transformations into acount
717 keyPress: function (evt) {
718 //alert(evt.charCode);
720 if (evt.charCode != 115) { // s = save!
723 var w = window.open("about:blank", "default.js");
724 w.document.write("<H1> Save this as default.js</H1><code>");
725 w.document.write("var positions = " + sessionStorage.positions + ";");
726 w.document.write("</code>");
734 drawConnections : function()
736 //alert(this.positions.toSource());
737 for(var tn in this.links) {
739 for (var linkfrom in this.links[tn]) {
742 ontable : this.tableMap[tn],
744 totable: this.tableMap[this.links[tn][0]],
745 tocol: this.links[tn][1]
747 link.fromtable.links.push(link);
748 link.totable.links.push(link);
760 Pman.Tab.BuilderErm.Movable = function(obj, eobj, moveCallback)
763 this.nMouseOffsetX = 0;
764 this.nMouseOffsetY = 0;
769 eobj.addEventListener("mousedown", function(e) { _this.mouseDown(e); }, false);
770 if (!Pman.Tab.BuilderErm.Movable.loaded) {
771 document.addEventListener("mouseup", function(e) { _this.mouseUp(e); } , false);
772 document.addEventListener("mousemove", function(e) { _this.mouseMove(e); }, false);
773 //document.addEventListener("mouseout", mouseUp, false);
774 Pman.Tab.BuilderErm.Movable.loaded = true;
777 obj.setAttribute('dragx', '0');
778 obj.setAttribute('dragy', '0');
780 this.moveCallback = moveCallback;
783 Pman.Tab.BuilderErm.Movable.draggingElement = false;
784 Pman.Tab.BuilderErm.Movable.loaded = false;
786 Roo.apply(Pman.Tab.BuilderErm.Movable.prototype, {
789 // Following is from Holger Will since ASV3 and O9 do not support getScreenTCM()
790 // See http://groups.yahoo.com/group/svg-developers/message/50789
791 getScreenCTM : function (doc){
792 if(doc.getScreenCTM) {
793 return doc.getScreenCTM();
797 var sCTM= root.createSVGMatrix()
799 var tr= root.createSVGMatrix()
800 var par=root.getAttribute("preserveAspectRatio")
801 if (par==null || par=="") par="xMidYMid meet"//setting to default value
802 parX=par.substring(0,4) //xMin;xMid;xMax
803 parY=par.substring(4,8)//YMin;YMid;YMax;
805 mos=ma[1] //meet;slice
807 //get dimensions of the viewport
814 w=root.getAttribute("width")
815 if (w==null || w=="") w=innerWidth
817 h=root.getAttribute("height")
818 if (h==null || h=="") h=innerHeight
820 // Jeff Schiller: Modified to account for percentages - I'm not
821 // absolutely certain this is correct but it works for 100%/100%
822 if(w.substr(w.length-1, 1) == "%") {
823 w = (parseFloat(w.substr(0,w.length-1)) / 100.0) * innerWidth;
825 if(h.substr(h.length-1, 1) == "%") {
826 h = (parseFloat(h.substr(0,h.length-1)) / 100.0) * innerHeight;
830 vba=root.getAttribute("viewBox")
831 if(vba==null) vba="0 0 "+w+" "+h
832 var vb=vba.split(" ")//get the viewBox into an array
834 //--------------------------------------------------------------------------
835 //create a matrix with current user transformation
836 tr.a= root.currentScale
837 tr.d=root.currentScale
838 tr.e= root.currentTranslate.x
839 tr.f=root.currentTranslate.y
854 //preserveAspectRatio="none"
858 sCTM.e=- vb[0]*sx //translateX
859 sCTM.f=- vb[0]*sy //translateY
860 sCTM=tr.multiply(sCTM)//taking user transformations into acount
868 //-------------------------------------------------------
871 sCTM.e=((w-vb[2]*s)/2) - vb[0]*s //translateX
875 sCTM.e=- vb[0]*s//translateX
879 sCTM.e=(w-vb[2]*s)- vb[0]*s //translateX
883 //------------------------------------------------------------
886 sCTM.f=(h-vb[3]*s)/2 - vb[1]*s //translateY
890 sCTM.f=- vb[1]*s//translateY
894 sCTM.f=(h-vb[3]*s) - vb[1]*s //translateY
898 sCTM=tr.multiply(sCTM)//taking user transformations into acount
905 mouseDown : function (evt) {
907 Pman.Tab.BuilderErm.Movable.draggingElement = _this;
909 var p = document.documentElement.createSVGPoint();
913 var m = getScreenCTM(document.documentElement);
915 p = p.matrixTransform(m.inverse());
916 _this.nMouseOffsetX = p.x - parseInt(Pman.Tab.BuilderErm.Movable.draggingElement.obj.getAttribute("dragx"));
917 _this.nMouseOffsetY = p.y - parseInt(Pman.Tab.BuilderErm.Movable.draggingElement.obj.getAttribute("dragy"));
920 mouseUp : function (evt) {
922 if (!Pman.Tab.BuilderErm.Movable.draggingElement ) {
925 Pman.Tab.BuilderErm.Movable.draggingElement.nMouseOffsetX = 0;
926 Pman.Tab.BuilderErm.Movable.draggingElement.nMouseOffsetY = 0;
927 Pman.Tab.BuilderErm.Movable.draggingElement = null;
930 mouseMove : function (evt) {
931 var p = document.documentElement.createSVGPoint();
935 var m = getScreenCTM(document.documentElement);
936 if(Pman.Tab.BuilderErm.Movable.draggingElement ) {
937 p = p.matrixTransform(m.inverse());
938 p.x -= Pman.Tab.BuilderErm.Movable.draggingElement.nMouseOffsetX;
939 p.y -= Pman.Tab.BuilderErm.Movable.draggingElement.nMouseOffsetY;
942 Pman.Tab.BuilderErm.Movable.draggingElement.obj.setAttribute("dragx", p.x);
943 Pman.Tab.BuilderErm.Movable.draggingElement.obj.setAttribute("dragy", p.y);
944 Pman.Tab.BuilderErm.Movable.draggingElement.obj.setAttribute("transform", "translate(" + p.x + "," + p.y + ")");
945 Pman.Tab.BuilderErm.Movable.draggingElement.moveCallback(Pman.Tab.BuilderErm.Movable.draggingElement.obj);
958 Pman.Tab.BuilderErm.SVGDiagram = function()
962 this.lpositions = {};
967 Roo.apply(Pman.Tab.BuilderErm.SVGDiagram.Prototype, {
984 savePositions :function() {
986 sessionStorage.positions = this.positions.toSource();
990 loadPositions : function () {
991 if (sessionStorage.getItem("positions")) {
992 eval ("this.positions = " + sessionStorage.positions);
997 this.currentX += tableWidth + 10;
998 this.maxX = (this.maxX<this.currentX) ? this.currentX : this.maxX;
999 if (this.currentX > this.widthLimit) {
1000 this.currentX = this.START_X;
1001 this.currentY += this.maxTableHeight + 10;
1003 //alert(this.currentY);
1004 this.maxY = ((this.currentY + tableHeight) > this.maxY) ? this.currentY + tableHeight : this.maxY;
1007 var _cb = function(e) {
1008 _t.positions[name].x = parseInt(rect.getAttribute("x")) + parseInt(tabGrp.getAttribute("dragx"));
1009 _t.positions[name].y = parseInt(rect.getAttribute("y")) + parseInt(tabGrp.getAttribute("dragy"));
1011 _t.drawConnections(e.getAttribute("tableName"));
1014 new Pman.Tab.BuilderErm.Movable(tabGrp, rect, _cb);
1015 //new Pman.Tab.BuilderErm.Movable(tabGrp, rect2, _cb);
1016 tabGrp.setAttribute("tableName", name);
1020 drawConnections : function(tablematch)
1022 //alert(this.positions.toSource());
1023 for(var tn in this.links) {
1024 for (var linkfrom in this.links[tn]) {
1025 if (typeof(tablematch) != "undefined") {
1026 if ((tn != tablematch) && (this.links[tn][linkfrom][0] != tablematch)) {
1033 this.drawConnection(tn, linkfrom, this.links[tn][linkfrom][0], this.links[tn][linkfrom][1]);
1038 drawConnections : function(tablematch)
1040 //alert(this.positions.toSource());
1041 for(var tn in this.links) {
1042 for (var linkfrom in this.links[tn]) {
1043 if (typeof(tablematch) != "undefined") {
1044 if ((tn != tablematch) && (this.links[tn][linkfrom][0] != tablematch)) {
1051 this.drawConnection(tn, linkfrom, this.links[tn][linkfrom][0], this.links[tn][linkfrom][1]);
1056 drawConnection : function( tablefrom, colfrom, tableto, colto )
1059 var id = 'LINK-' + tablefrom +'-' + colfrom +'-' + tableto +'-' + colto;
1061 var pfrom = this.lpositions[tablefrom + '.' + colfrom];
1062 var pto = this.lpositions[tableto+ '.' + colto];
1063 if (!pfrom || !pto) {
1067 //alert(pfrom.toSource());
1068 //alert(pto.toSource());
1069 // decide how to join.
1071 fobj = document.getElementById('GT~' + tablefrom);
1072 tobj = document.getElementById('GT~' + tableto);
1074 froml = pfrom.l + parseInt(fobj.getAttribute("dragx"));
1075 fromr = pfrom.r + parseInt(fobj.getAttribute("dragx"));
1077 tol = pto.l + parseInt(tobj.getAttribute("dragx"));
1078 tor = pto.r + parseInt(tobj.getAttribute("dragx"));
1081 fx = froml > tol ? froml : fromr;
1082 tx = froml > tol ? tor : tol;
1084 var line = document.getElementById(id);
1088 line = document.createElementNS(SVGNS, 'line');
1091 line.setAttribute('id', id);
1092 line.setAttribute('x1', fx);
1093 line.setAttribute('y1', doffset + pfrom.y + parseInt(fobj.getAttribute("dragy")));
1094 line.setAttribute('x2', tx);
1095 line.setAttribute('y2', doffset + pto.y + parseInt(tobj.getAttribute("dragy")));
1096 line.setAttribute('stroke', 'red');
1098 document.rootElement.appendChild(line);
1101 // draw a line on the target!
1103 var id = 'LINKL-' + tablefrom +'-' + colfrom;
1104 if (!document.getElementById(id)) {
1105 var line = document.createElementNS(SVGNS, 'line');
1106 line.setAttribute('id', id);
1107 line.setAttribute('x1', pfrom.l);
1108 line.setAttribute('y1', pfrom.y+doffset );
1109 line.setAttribute('x2', pfrom.r);
1110 line.setAttribute('y2', pfrom.y+doffset );
1111 line.setAttribute('stroke', 'red');
1112 var tb = document.getElementById("GT~" + tablefrom);
1113 tb.insertBefore(line, tb.getElementsByTagName('text')[0]);
1116 var id = 'LINKL-' + tableto +'-' + colto;
1117 if (!document.getElementById(id)) {
1118 var line = document.createElementNS(SVGNS, 'line');
1119 line.setAttribute('id', id);
1120 line.setAttribute('x1', pto.l);
1121 line.setAttribute('y1', pto.y+doffset );
1122 line.setAttribute('x2', pto.r);
1123 line.setAttribute('y2', pto.y+doffset );
1124 var tb = document.getElementById("GT~" + tableto);
1125 tb.insertBefore(line, tb.getElementsByTagName('text')[0]);
1126 line.setAttribute('stroke', 'red');