//19.12.19, 18:00
//version 9.3
var 
// --- user globals ---
margin = 100,//min Margin on all four sides
Zvalue = 178, // the grid constant
gridColor = "red",

swatterRadius = 200, //px
swatterColor = "#8066ff00",
focusRad = 20,  //px
backgroundColor="none", // use colors like "none" or "yellow" or "#66ffee00"


// --- internal globals ---
frameLeft, frameRight, frameTop, frameBottom, frameWidth, frameHeight,
xPairs = 0, yLines = 0,
xPoints, yPoints,

nGridLines,
totalGridPoints,
currentID = 0,
disty = Zvalue *sqrt(3)/2,//equilateral triangular network 
gridID = false,
stampTime = 0,
gridID = 0,//image where grid sas painted
oldCX, oldCY,

tol33 = Zvalue/3, //tolerances
tol50 = Zvalue/2,
keyTime,
tens, ones,
keyTable="",
jokerCode = NaN,
repeatCode = NaN,
keyCodes, keyItems, keyLabels, keyColors, panelID, 	
panelLineHeight = 26,
//running = false,
markTime = 0,

function defineKeyTable(){
	if(keyTable != "")
		return;//already defined
	keyTable = "";
	//       code, item, label

	keyTable += "#1,  A, PV-1";
	keyTable += "#2,  B, PV-2";
	keyTable += "#3,  C, PV-3";
	keyTable += "#4,  D, PV-4a";
	keyTable += "#5,  E, PV-4b";
	keyTable += "#66, F, PV-4c";
	keyTable += "#67, F, CA";
	keyTable += "#68, F, EVTO";
	keyTable += "#69, A, VTO";
	keyTable += "#70, B, MN";
	keyTable += "#71, A, HY";
	keyTable += "#72, F, EGG";
	keyTable += "#73, A, POF";
	keyTable += "#74, B, e-Alph";
	keyTable += "#75, C, l-Alph";
	keyTable += "#76, D, Beta";
	keyTable += "#77, E, blood";
	keyTable += "#78, E, Tissue";
	keyTable += "#79, E, Space";
	keyTable += "#80, E, Out*";

	keyTableAr = split(keyTable, "#");
	itmColors = split(ojGetItemColors());
	itmNames = split(ojGetItemNames());
	len = keyTableAr.length;
	keyCodes = newArray(len);
	keyColors = newArray(len);
	keyItems = newArray(len);
	keyLabels = newArray(len);
	for (jj = 0; jj < len; jj++){
		parts = split(keyTableAr[jj], ", \t");
		if (parts.length != 3)
			exit("Error in KeyTable: \n" + keyTableAr[jj]);
		keyCodes[jj] = parseInt(parts[0]);
		keyItems[jj] = parts[1];
		keyLabels[jj] = parts[2];
		keyColors[jj] = "gray";
		if (startsWith(toLowerCase(keyLabels[jj]), "empty"))
			exit("Label cannot start with 'empty'");
		
		for (kk = 0; kk < itmNames.length; kk++){
			if(parts[1] == itmNames[kk])
				keyColors[jj] = itmColors[kk];
		}
	}
	//check ambiguity
	for (jj = 0; jj < len; jj++){
		code1 = keyCodes[jj];
		label1 = keyLabels[jj];
		//if(code1 < 10){
			for (kk = 0; kk < len; kk++){
				if(kk != jj){
				code2 = keyCodes[kk];
				label2 = keyLabels[kk];
				if(code2 >= code1 * 10 && code2 <= code1 * 10 +9)
					exit("Ambiguity: \n\nkey #" + code1 +" conflicts with: \nkey #" + code2);
				if(code2 == code1 || label2 == label1)
					exit("Ambiguity : \n" + code1 + ": " + label1 + "\n" + code2 +": " + label2);
			}
		}
	
	}
}



macro "Initialize Grid [F3]"{
	initializeGrid();
}

macro "Set Joker [I]"{
	prevID = getImageID;
	defineKeyTable();
	index = -1;
	code = getNumber("Joker code:", jokerCode);
	for(jj = 0; jj < keyCodes.length; jj++){
		if(code == keyCodes[jj])
			index = jj;
	}
	if (index == -1)
		exit("Code does not exist: "+ code);

	jokerCode = keyCodes[index];
	showStatus("Joker key 'J', code= " + jokerCode + ", Label=" + keyLabels[index]);


	
	showPanel();
	setForegroundColor(0,0,0);
	fillRect(0, 0, 24, getHeight);

	selectImage(panelID);
	setForegroundColor(0xfefefe);
	fillRect(3, index * panelLineHeight + 4, 18, 20);
	fillRect(2, index * panelLineHeight + 5, 20, 18);//rounded rect
	setForegroundColor(25,0,0);
	setFont("Monospaced", 18, "antiliased");
	drawString("J", 6, index * panelLineHeight + 25);
	run("Select None");
	setOption("Changes", false);
	selectImage(prevID);
}


function isSingleKey(key){
	for(jj = 0; jj < keyCodes.length; jj++)
		if (key == keyCodes[jj])
			return true;
	return false;
}

function initializeGrid(){
	link = ojImageLink();
	prepare();
	ojSetColumnProperty("Cat", "label background=" + backgroundColor, 1);
	Overlay.clear;
	gridToManager();
	roiManager("Show All without labels");
	//run("Labels...", "   ");//avoid IJ labels
	run("From ROI Manager");
	frameToOverlay();
	myUpdate();
	//run("ROI Manager...");//for closing
	//selectWindow("ROI Manager"); 
	//run("Close");19.12.19, 11:59
	close("ROI Manager");
	selectWindow("ImageJ");
	//if(link > 0)
	//	ojShowImage(link);

}

macro "-"{}


// calculate parameters for current image size
// sets "Require Command key for macros"
function prepare(){
	ojRequires("1.03v2");
	defineKeyTable();
	img = ojImageLink();
	if(img == 0)
		exit("Linked Image is not in front");
	if (gridID != getImageID){
		run("Misc...", "require run");
		setOption("QueueMacros" , true);
		gridID = getImageID;
		labels = replace(labels, " ", "\n");
	}
	if(nImages == 0) exit;
	if (currentID == getImageID)
		return;
	currentID = getImageID;
	
	calcFrame(img);
	if (xPairs < 1 || yLines < 1){ 
		currentID = 0;
		exit(" Image is too small");
	}
	if(!isOpen(panelID))
		close("Panel");
}

//Sets global frame parameters for a linked image
function calcFrame(img){
	ww = ojGetImageValue(img, "width");
	hh = ojGetImageValue(img, "height");

	frameWidth = ww - 2 * margin;
	xPairs = floor(frameWidth/Zvalue/2);
	frameWidth = xPairs * Zvalue * 2;
	frameLeft = round((ww - frameWidth)/2);
	frameRight = ww - frameLeft;

	frameHeight = hh - 2 * margin;
	yLines = floor(frameHeight/disty);
	frameHeight = disty * yLines ;
	frameTop = round((hh - frameHeight)/2);
	frameBottom = hh - frameTop;
}

//adds gridlines to roiManager
function gridToManager(){
	xPoints = newArray(50000);
	yPoints = newArray(50000);
	
	gridID = getImageID;
	pt = 0;
	offx = 0.25 * Zvalue;
	for (y = frameTop + 0.5 * disty; y < (frameTop + frameHeight); y += disty){
		showStatus("!y= " + round(y));
		for (pair = 0; pair < xPairs; pair++){
			x = frameLeft + offx + pair*2 * Zvalue;
			xPoints[pt] = round(x);
			xPoints[pt + 1] = round(x + Zvalue);
			yPoints[pt] = round(y);
			yPoints[pt + 1] = round(y);
			pt += 2;
		}
		if (offx < (0.5 * Zvalue)) 
			offx = 0.75 * Zvalue;
		else 
			offx = 0.25 * Zvalue;
	}
	nGridLines = pt/2;
	run("Select None");
	run("Show Overlay");
	xPoints = Array.trim(xPoints, pt);
	yPoints = Array.trim(yPoints, pt);
	xPoints2 = Array.trim(xPoints, pt);
	yPoints2 = Array.trim(yPoints, pt);
	cnt = roiManager("count");

	if (cnt > 0 && cnt == nGridLines) {//dont recalc grid if last point is correct

		roiManager("select", nGridLines-1);
		getSelectionCoordinates(xx, yy);
		lastXLine = xPoints[pt -2];
		lastXArr = xx[0];
		lastYLine = yPoints[pt -2];
		lastYArr = yy[0];
		if (lastXLine == lastXArr && lastYLine == lastYArr)
			return;
	}		
	roiManager("Reset");
	roiManager("Show None");
	time = getTime;
	thisID = getImageID;
	newImage("Dummy", "8-bit White", 2, 2, 1);
	//wait(100);
	showStatus ("!Creating " + nGridLines + " Lines");

	for (jj = 0; jj < nGridLines; jj++){
		makeLine(xPoints[jj*2], yPoints[jj*2], xPoints[jj*2] + Zvalue, yPoints[jj*2]);
		roiManager("Add", gridColor);
		//showProgress(jj/nGridLines);
	}
	close("Dummy");;
	selectImage(thisID);
	showStatus ("!" + nGridLines +  " Lines;  "  + (getTime - time)/1000 + "  sec. ");
	//wait(100);
}

//adds frame to Overlay
function frameToOverlay() {
		makeSelection("polyline", newArray(frameLeft,frameRight,frameRight), newArray(frameTop,frameTop,getHeight));
		run("Add Selection...", "stroke=green width=2");
		makeSelection("polyline", newArray(frameRight,frameLeft,frameLeft), newArray(frameBottom,frameBottom,0));
		run("Add Selection...", "stroke=red width=2");
		run("Select None");
	}
	run("Show Overlay");
}


//check if position is already occupied by a marker
function isFree(xx, yy, tolerance){
	img = ojImageLink();
	if (img == 0)
		return false;
	for (obj = ojFirstObject(img); obj <= ojLastObject(img); obj++){
		ojSelectObject(obj);
		dd = sqrt(pow(ojXPos(1) - xx, 2)  + pow(ojYPos(1) - yy, 2));
		ojSelectObject(0);
		if (dd < tolerance) return false;
	}
	return true; 
}

//return index of closest grid point, otherwise -1
function closestPoint(xx, yy, tolerance){
	if(ojImageLink() == 0)
		exit("First bring linked image to front");
	if(gridID != getImageID) {
		selectWindow("ImageJ");//19.12.19, 12:20
		if(getBoolean("First Initialize Grid?"))
			initializeGrid();
		exit;
	}
	index = -1;
	minDist = 1e6;
	for(jj = 0; jj < xPoints.length; jj++){
		dd = sqrt(pow(xPoints[jj] - xx, 2)  + pow(yPoints[jj] - yy, 2));
		if (dd < tolerance && dd <minDist) {
			index = jj;
			minDist = dd;
		}
	}
	return index;
}

//macro "<Category>"{}
macro "<Key>1 [1]"{ doKey(1); }
macro "<Key>2 [2]"{ doKey(2); }
macro "<Key>3 [3]"{ doKey(3); }
macro "<Key>4 [4]"{ doKey(4); }
macro "<Key>5 [5]"{ doKey(5); }
macro "<Key>6 [6]"{ doKey(6); }
macro "<Key>7 [7]"{ doKey(7); }
macro "<Key>8 [8]"{ doKey(8); }
macro "<Key>9 [9]"{ doKey(9); }
macro "<Key>0 [0]"{ doKey(0); }
macro "<Key>Joker [J]"{ doKey("-1"); }
macro "<Key>Repeat [R]"{ doKey("-2"); }


//if in grid, doMark is called 
//if in panel, joker is changed
function doKey(key){
	setOption("QueueMacros" , true);
	if(ojImageLink() == 0)
		exit("First bring linked image to front");

	inPanel = ((getImageID == panelID) && key == -1);
	if((keyTable == "" || (gridID != getImageID)) && !inPanel){
		if(getBoolean("First Initialize Grid?"))
			initializeGrid();
		exit;
	}
	cmd = getInfo("command.name");
	if (!startsWith(cmd, "^"))
		exit("Command must be invoked via shortcut key, while cursor is at desired position");
	if (key == -1){//joker
		if (getImageID == panelID){
			beep;
			return;
		}
		if(isNaN(jokerCode))
			exit("Joker is not defined yet \n(choose 'ObjectJ>Set Joker [I]')"); 		
		doMark(-1);
		return;		
	}
	
	if (key == -2){ //repeat
		doMark(-2);
		return;
	}
	
	timeOut = getTime-keyTime > 1500;//1.5 sec time between dual keys
	if (isSingleKey(key) && timeOut){
			tens = 0;
			ones = key;
			doMark(tens *10 + ones);
			return;
	}
	if (!timeOut && tens > 0){
			ones = key;
			doMark(tens *10 + ones);
			return;
	}
	if (timeOut && key >= 6 && key <=9){
		tens = key;
		keyTime = getTime;
		return;
	}
}

function getFocusCenter(){
   	if (selectionType == 1){//oval
		getSelectionBounds(left, top, ww, hh);
		if (ww !=focusRad *2)
			return newArray(-1, -1);
		x = left + focusRad;
		y = top + focusRad;
		return newArray(x, y);
	}
	return newArray(-1, -1);

}


function doMark(code){
	if(getTime - markTime < 2500){
		showStatus("Too fast");
		markTime = 0;//18.12.19, 18:51
		beep;
		exit;
	}
	markTime = getTime;
	
	if (code == -1)
		code =  jokerCode;
	if (code == -2)
		code =  repeatCode;
	if(!(code > 0)){
		showStatus("Code does not exist: " + code);
		beep;
		return;
	}
	index = -1;
	for (jj = 0; jj < keyCodes.length; jj++)
		if(keyCodes[jj] == code)
			index = jj;
	if(index == -1) {beep; exit;}
	itemType = keyItems[index];
	label = keyLabels[index];
	getCursorLoc(x, y, z, flags);
	if(x <0 || x >= getWidth || y < 0 || y > getHeight)
		exit;
	toBeMarked = newArray(x, y);
	arr = getFocusCenter();
	focusExists = arr[0]>=0;
	if (focusExists)
		toBeMarked = newArray(arr[0], arr[1]);
	keyTime = 0;
	tens = 0;
	
	
	if (!focusExists && !isSwatter()){
		if(!isFree(x, y, tol50)){
			beep;
			exit;
		}
	}
	if (isSwatter())
		toBeMarked = getSwatterPoints(true);
	else{
		showStatus("code=" + code);
		index = closestPoint(toBeMarked[0], toBeMarked[1], tol50);//18.12.19, 17:51
		if (index <0){
			beep;
			showStatus("Could not snap to Grid-point");
			wait(200);
			running = false;
			exit;
		}
		toBeMarked[0] = xPoints[index];
		toBeMarked[1] = yPoints[index];
	}
	ojUpdateMarkers(false);

	for(mm = 0; mm < toBeMarked.length; mm += 2){
		ojSwitchToItem(itemType);
		//currentPt = ojNObjects(); 04.12.16 22:27
		ojSetMarker(toBeMarked[mm], toBeMarked[mm+1]);
		ojCloseObject();
		obj = ojNewestObject();
		if (obj > 0){
			ojSetResult("Cat", obj, label);
			ojSetResult("Code", obj, code);
		}
	}
	repeatCode = code;
	ojUpdateMarkers(true);
	ojSetTool(0);
	if (focusExists)
		advanceFocus("next");
	myUpdate();
	markTime = 0;
}

function isSwatter(){
	if(selectionType <0)
		return false;
	a = Roi.getFillColor;
	b = swatterColor;
	if(a==b)
		return true;
	return false;
}

macro "First Focus [F]"{
	advanceFocus("first");
}
macro "Next Focus [G]"{
	advanceFocus("next");
}

function createOccupyList(){
	List.clear;
	img = ojImageLink();
	if (img == 0) 
		exit;
	for (obj = ojFirstObject(img); obj <= ojLastObject(img); obj++){
		ojSelectObject(obj);
		val = "" + obj;
		key = "" + (round(ojYPos(1)) * 10000) + round(ojXPos(1));
		List.set(key, val);
	}
}

function advanceFocus(s){
	if(gridID != getImageID) {
		if(getBoolean("First Initialize Grid?"))
			initializeGrid();
		exit;
	}
	arr = getFocusCenter();
	nextIndex = -1;
	inc = 1;
	thisIndex = closestPoint(arr[0], arr[1], 1000);
	if (arr[0] == xPoints[thisIndex] && arr[1] == yPoints[thisIndex])
		thisIndex += inc;//if it was centered, choose next
	createOccupyList();
	if(s=="first")
		thisIndex = 0;
	for (kk = thisIndex; kk < xPoints.length; kk+= inc){
		key = "" + (10000 * yPoints[kk] + xPoints[kk]);
		val = List.get(key);
		if (val == ""){
			nextIndex = kk;
			break;
		}
	
	}	
	
	if(nextIndex == -1){
		beep;
		showStatus("No empty Weibel position found");
		wait(500);
		exit;
	}
		
	x = xPoints[nextIndex];
	y = yPoints[nextIndex] ;
	setTool("oval");
	makeOval(x- focusRad, y - focusRad, focusRad*2, focusRad*2);
	run("Properties... ", "  stroke=magenta width=4");
	getDisplayedArea(left, top, w, h);
	zoom = getZoom;
	centerX = round(left + w/2) & 0xfffffe;
	centerY = round(top + h/2) & 0xfffffe;
	if (x<left+margin || x > left + w - margin)
		centerX =  round(x + w/2 -margin) & 0xfffffe;
		
	if(y<top+margin ||y > top + h - margin)
		centerY =  round(y + h/2 -margin) & 0xfffffe;
	if (centerX != oldCX || centerY != oldCY){
		ojZoom(zoom, centerX, centerY);
		oldCX = centerX;
		oldCY = centerY;
	}
	myUpdate();
}


macro "Start/Stop Swatter [S]"{
	if(ojImageLink() == 0)
		exit("Linked Image is not in front");
	getCursorLoc(x, y, z, modifiers);
	if(x <0 || x >= getWidth || y < 0 || y > getHeight)
		exit;
	if(selectionType == 1){
		run("Select None");
		setTool(0);
		return;
	}
	if (gridID != getImageID){
		if(getBoolean("First Initialize Grid?"))
			initializeGrid();
		exit;
	}
	ojSetTool(0);
	setTool("brush");
	call("ij.gui.Toolbar.setBrushSize", swatterRadius * 2); 
	makeOval(x - swatterRadius, y-swatterRadius, swatterRadius * 2, swatterRadius * 2);
	run("Properties... ", "name=[] stroke=magenta width=1 fill=" + swatterColor);
}

macro "Swatter All/None [A]"{
	if (gridID != getImageID){
		if(getBoolean("First Initialize Grid?"))
			initializeGrid();
		exit;
	}
	if(isSwatter()){//brush or rect
		run("Select None");
		setTool(0);
		return;
	}
	makeRectangle( frameLeft, frameTop, frameWidth, frameHeight);
	run("Make Inverse");
	run("Make Inverse");
	run("Properties... ", "name=[] stroke=magenta width=1 fill=" + swatterColor);
}

macro "Kill [K]"{
	swatterExists = isSwatter();
	getCursorLoc(x, y, z, modifiers);
	if(x <0 || x >= getWidth || y < 0 || y > getHeight)
		exit;

	img = ojImageLink();
	if (img == 0) {beep; exit; }
	ojUpdateMarkers(false);
	closestObj = 0;
	minDist = 1e6;
	for (obj = ojLastObject(img); obj >= ojFirstObject(img); obj--){
		ojSelectObject(obj);
		if (!swatterExists){
			dx = x - ojXPos(1);
			dy = y - ojYPos(1);
			dd = sqrt(dx * dx + dy * dy);
			if (dd < tol33 && dd <minDist) {
				closestObj = obj;
				minDist = dd;
			}
		}
		if (swatterExists)
			if (selectionContains(ojXPos(1), ojYPos(1)))
				ojDeleteObject(obj);
	}
	if (!swatterExists && closestObj > 0)
		ojDeleteObject(closestObj);
	ojUpdateMarkers(true);
	myUpdate();
}

//checks if markers are on exactly the same place and allows to kill them sequentially
macro "Kill Duplicates"{
	foundObjects = newArray(4999);
	found = 0;
	for(img = 1; img<= ojNImages(); img++){
		title = ojGetImageName(img);
		showProgress(ojNImages()/img);
		locs = "";
		for(objA = ojFirstObject(img); objA <= ojLastObject(img); objA++){
			ojSelectObject(objA);
			xA = ojXPos(1);
			yA = ojYPos(1);
			locs += "" + xA + "_" + yA + "_" + objA +"\n";
		}
		locsN = split(locs, "\n");
		locsN = Array.sort(locsN);
		for (jj = 1; jj < locsN.length; jj++){
			end = lastIndexOf(locsN[jj-1], "_");
			locA=substring( locsN[jj-1], 0, end);
			locB=substring( locsN[jj], 0, end);
			if(locA == locB){
				secondObj = parseInt(substring( locsN[jj], end +1));
				foundObjects[found] = secondObj;
				found++;
				jj++; //don't kill both
			}
		}
	}
	foundObjects = Array.trim(foundObjects, found);
	if(found == 0)
		exit("No duplicates were found");
	Array.sort(foundObjects);
	for(jj = foundObjects.length-1; jj >= 0; jj--){
		ojShowObject(foundObjects[jj]);
		x = ojXPos(1);
		y = ojYPos(1);
		makeOval(x - 60, y - 60 , 120, 120);
		run("Set... ", "zoom=100 x=&x y=&y width=800 height=800");
		run("Properties... ", "  fill=#80ff0000");
		b = getBoolean("" + (jj+1) + " duplicates found;  Kill This One?");
		if(b) 
			ojDeleteObject(foundObjects[jj]);	
	}
}

//returns array with alternating x and y locations
function getSwatterPoints(freeOnes){
	xyPoints = newArray(99999);
	ff = 0;
	for (pt = 0; pt < xPoints.length; pt++){
		if (selectionContains(xPoints[pt], yPoints[pt])){
			showProgress(pt/xPoints.length);
			x = xPoints[pt];
			y = yPoints[pt];
			free = isFree(x, y, tol50);
			if (free && freeOnes || !free && !freeOnes ){
				xyPoints[ff++] = xPoints[pt];
				xyPoints[ff++] = yPoints[pt];
			}
		}
	}
	xyPoints = Array.trim(xyPoints, ff);
	return xyPoints;
}

function 	myUpdate(){
	ojHideResults();
	//run("Hide Overlay");
	run("Show Overlay");
	run("Labels...", "   ");//avoid IJ labels
}


macro "Show Panel [P]"{
	showPanel();
}

function showPanel(){
	if(isOpen(panelID)){
		selectImage(panelID);
		return;
	}
	close("Panel");
	defineKeyTable();
	hh = keyCodes.length * panelLineHeight + panelLineHeight; 
	newImage("Panel", "RGB black", 256, hh, 1);
	panelID = getImageID;
	setFont("SansSerif", 18, " antialiased");
	y = 0;
	for (jj = 0; jj < keyCodes.length; jj++){
 		setColor(keyColors[jj]);
		drawString(keyCodes[jj], 30, y+24);
		drawString(keyLabels[jj], 60, y+24);
		y+= panelLineHeight;
	} 
	run("Select None");
	setOption("Changes", false);
}

macro "Show Frequency Table [T]"{
	while(isOpen("Frequency Table.xls")){
		selectWindow("Frequency Table.xls");
		run("Close");
	}
	totalGridPoints = 0;
//debug;
	for (img = 1; img <= ojNImages(); img++){
		calcFrame(img);
		totalGridPoints +=  xPairs * 2 *yLines;	
	}
	currentID = 0;
	emptyPoints = totalGridPoints - ojNObjects();
	close("Frequency Graph.tif");
	defineKeyTable();
	
//extend three arrays by one "empty" element
	oneElement = newArray(1);
	nLabels = keyLabels.length;
	Code = Array.concat(keyCodes, oneElement);
	Code[nLabels] = "";
	Cat = Array.concat(keyLabels, oneElement);
	Cat[nLabels] = "Empty*";
	keyColors1 = Array.concat(keyColors, oneElement);
	keyColors1[nLabels] = "gray";
	
	
	percent = newArray(nLabels +1);
	count = newArray(nLabels + 1);
	nMarked = 0;
	for (obj = 1; obj <= ojNObjects(); obj++){
		cat = ojResultString("Cat", obj);
		for (jj = 0; jj < nLabels; jj++){
			if(cat == keyLabels[jj]){
				count[jj]++;
				if(!endsWith(cat, "*"))
					nMarked ++;
				break;
			}
		}
	}

	for (jj = 0; jj < nLabels; jj++)
		if(!endsWith(Cat[jj], "*"))
			percent[jj] = count[jj] / nMarked * 100;//percent
 	count[nLabels] = emptyPoints; 	
	
	Array.show("Frequency Table.xls", Code, Cat, count, percent);
	newImage("Frequency Graph.tif", "RGB white", 500, nLabels * 24 + 36, 1);
	x1 = 150;
	x2 = x1 + 80;
	y = 6;
	setJustification("right");
	setFont("SansSerif", 18, " antialiased");
	setLineWidth(1);
	for (jj = 0; jj < nLabels +1; jj++){
		val = percent[jj];
		setColor(keyColors1[jj]);
		drawString(Cat[jj], x1, y + 20);
		s1 = "" + d2s(val, 0) + " %";
		flag = endsWith(Cat[jj], "*") || Cat[jj] =="Empty*";
		if (flag)
			s1 = "[" + count[jj] + "]";
		drawString(s1 , x2-4, y + 20);
		if(!flag){
			for (kk = 0; kk < 20; kk++) 
				drawLine(x2, y+kk, x2 + val * 2, y+kk);//one percent = 2 pixels
		}
		y += 24;
	}
	setOption("Changes", false);
}
