Login   Register  
PHP Classes
elePHPant
Icontem

File: heatclass.php

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  Classes of Greg Bulmash  >  Brainhandles Heatmap  >  heatclass.php  >  Download  
File: heatclass.php
Role: Class source
Content type: text/plain
Description: Main Class File
Class: Brainhandles Heatmap
Generate heatmap images from a set of points
Author: By
Last change:
Date: 2011-08-16 21:02
Size: 11,838 bytes
 

Contents

Class file image Download
<?php

/*============================================================
           The Brainhandles.com PHP HeatMap Class
                      by Greg Bulmash
http://www.brainhandles.com/techno-thoughts/the-brainhandles-heatmap-php-class
			   Copyright © 2011 Greg Bulmash
			   
       Licensed for use under Creative Commons 3.0 By
	     http://creativecommons.org/licenses/by/3.0/
    Must be attributed to Greg Bulmash of Brainhandles.com
=============================================================*/

class bhHeatmap 
{
	
	private $dataset = array();
	private $overlaypath = "";
	private $overlayfile = "";
	private $imagex = 0;
	private $imagey = 0;
	private $mainim = null; //placeholder, will become GD image object
	private $dotimage = null; //placeholder will become GD image object
	private $dotholder = array(); //placeholder will hold dot data for overlays
	private $imagespath = "/path/to/dir/with/dot.png";
	
	private $dimsokay = false;
	private $arrayokay = false;
	private $overlayokay = true;

	public function makeMap(){
	
		// after all the data is imported and set, this gets called 
		// to process it and create the image
	
		if(!$this->allokay()) return false;

		// make blank white image with imagex/imagey dimensions;
		if(!$this->makeBaseImage()) return false; 
		
		//overlay all the dots
		foreach ($this->dataset["points"] as $count => $points){		
			$this->setIntensity($count);
			foreach ($points as $notimportant => $point) {
				$this->overlayDot($point);
			}		
		}
		
		// save base image
		imagejpeg($this->mainim, $this->overlaypath . "bw." . $this->overlayfile, 90);		

		// colorize and save image
		$this->colorize();
	}
	

	private function allokay(){
		//returns true if the dimensions, overlay path, incoming array, and path to root images are okay.
		//throws errors if these data elements have not been properly specified

			
		
			if(!$this->dimsokay) trigger_error("Dimensons of output image were not properly specified. Heatmap image cannot be generated.",E_USER_ERROR); 

			if(!$this->arrayokay) trigger_error("The array of data points was not properly imported. Image cannot be generated.",E_USER_ERROR); 
	
			if(!$this->overlayokay) trigger_error("A viable path for saving the output image was not properly specified. Heatmap image cannot be generated.",E_USER_ERROR); 

			if(!file_exists($this->imagespath . "dot.png")) trigger_error("The path to dot.png was not properly specified. Heatmap image cannot be generated.",E_USER_ERROR);
			
			return true;
	}
			
	public function setDims($x,$y){
	
		// Set the dimensions of the output image in pixels. 
		// Must be bigger than 50 x 50, but currently no upper limit.
		// Of course, the bigger the image, the bigger the memory and CPU use.
	
		$this->dimsokay = false;
	
		$isx = false;
		$isy = false;
	
		if(intval($x)>50){
			$this->imagex = intval($x);
			$isx = true;
			}
	
		if(intval($y)>50){
			$this->imagey = intval($y);
			$isy = true;
			}

		if(($isx)&&($isy)) $this->dimsokay = true;
	}
	
	public function setImagesPath($path){

		// A dummy value is set in the object initialization.
		// You can hardcode the value above and never call this function
		// or you can pass the value from your script. The reason not to 
		// hardcode it would be if you want to use this class with multiple
		// color schemes or dot shapes.
	
		if(!is_readable($path)) return false;
		if(!file_exists($path . "dot.png")) return false;
		$this->imagespath = $path;		
	}
	
	public function setDimsFromPath($path){
	
	// check that path resolves to a local image and set imagex, imagey for dimensions 
		$this->dimsokay = false;	
		$info = getimagesize($path);
		if(!info) return;
		if(($info[0]>50)&&($info[1]>50)){
			$this->imagex = $info[0];
			$this->imagey = $info[0];
			$this->dimsokay = true;			
		}
	}

	
	public function setOverlay($path, $filename){
	
		// set the path and filename for the file you want
		// to write the heatmap overlay image to.
	
		$this->overlayokay = false;
		// check if directory exists and is writeable
		if(!is_writeable($path)) return false;
		
		//check if filename is okay
		if(!preg_match("/^[a-zA-Z0-9\-\.]+$/",$filename)) return false;
	
		$this->overlaypath = $path;
		$this->overlayfile = $filename;
		$this->overlayokay = true;
	}
			
	public function importData($datarray){
	
		// does basic validation and processing of a clickstream: 
		// input is an array of subarrays with the 
		// subarrays having "x" and "y" integer values. It counts occurrences.
	
		$this->arrayokay = false;
		if(gettype($datarray) != "array") return false;
		if((!isset($datarray[0]["x"]))||(!isset($datarray[0]["y"]))) return false;
		
		$point = array("x"=>0,"y"=>0,"count"=>0);
		$pointset = array(); // reset array to empty
				
		foreach($datarray as $num=>$points){
			// first ensure the point is within the boundaries of the image dimensions
				if(intval($points["x"]) < -14) continue;
				if(intval($points["x"]) > ($this->imagex + 14)) continue;
				if(intval($points["y"]) < -14) continue;
				if(intval($points["y"]) > ($this->imagey + 14)) continue;			
		
			// create an entry for the point, if one does not exist, increment the count
			$wholepoint = intval($points["x"])."x".intval($points["y"]);
			if(!isset($pointset[$wholepoint])) {
				$pointset[$wholepoint] = $point;
				$pointset[$wholepoint]["x"] = intval($points["x"]);
				$pointset[$wholepoint]["y"] = intval($points["y"]);
				}
			$pointset[$wholepoint]["count"] += 1;	
		}

			//Get max & min for intensity adjustments
		
		$this->generateMinMax($pointset);
		
		$this->arrayokay = true;
		return true;		
	}
	
	public function setArray($datarray){
		 /* if you've already got your points and their relative values
			you structure an array like this....
			
			$array["20x40"]["x"] = 20;
			$array["20x40"]["y"] = 40;
			$array["20x40"]["count"] = 18;
		*/
		
		// validate the array
		$this->arrayokay = false;
		$arrayisgood = true;
		foreach($datarray as $point=>$values){
		
			// make sure $point is in the form of "20x40"
		
			if(!preg_match("/^[0-9]+x[0-9]+$/",$point)){
				$arrayisgood = false;
				break;
				}

			// make sure $values contains x, y, and count values

			if((!isset($values["x"]))||(!isset($values["y"]))||(!isset($values["count"]))){
				$arrayisgood = false;
				break;
				}	

			// make sure the x, y, and count values are greater than 0
				
			if((!intval($values["x"])>0)||(!intval($values["y"])>0)||(!intval($values["count"])>0)){
				$arrayisgood = false;
				break;
				}
			}
		if($arrayisgood) {
			$this->arrayokay = true;
		} else {
			return false;
		}

		$this->generateMinMax($datarray);

	}

	private function generateMinMax($datarray) {
	
			$this->dataset = array("min"=>8000000000,"max"=>-8000000000,"points"=>array());
			foreach($datarray as $point => $info){
				$count = $info["count"];
				if($this->dataset["min"] > $count) $this->dataset["min"] = $count;
				if($this->dataset["max"] < $count) $this->dataset["max"] = $count;

				//after determining if the incidence of the point should expand the min or max, we group the points by incidence in $dataset["points"]

				if(!isset($this->dataset["points"][$count])) $this->dataset["points"][$count] = array();
				$this->dataset["points"][$count][] = array("x"=>$info["x"],"y"=>$info["y"]);
			}
	
	}
	
	
	
	
	private function setIntensity($density){
	
		$top = $this->dataset["max"];
		$bottom = $this->dataset["min"];
		$range = 1 + ($top - $bottom);
		//range is (max - min) + 1 to get the true number of points in the range;
		
		$rangeplier = $range/55;
		if($range < 25) $rangeplier = $rangeplier * (55/($range*2));
		$maxintensity = 70 - (58 - ceil(($range - 1)/$rangeplier));
		$adjuster = ($maxintensity < 68) ? 67 - $maxintensity : 0;
	
		$realadjuster = ceil(($adjuster + ($adjuster * (1/(($range + 1) - ($density - $bottom)))))/2);
		$intensity =  $realadjuster + (70 - (58 - ceil(($density - $bottom)/$rangeplier)));	
	
		// now make the dot

		$im = imagecreatetruecolor(32,32);
		$white = imagecolorallocate($im, 255, 255, 255);
		imagefill($im,0,0,$white);

		$bol = imagecreatefrompng($this->imagespath . 'dot.png');
		imagecopymergegray($im, $bol, 0, 0, 0, 0, 32, 32, $intensity);
		
		$dot = array ("r"=>0,"g"=>0,"b"=>0);
		for($y = 0; $y < 32; $y++){
			$this->dotholder[$y] = array();
			for($x = 0; $x < 32; $x++) {
				$this->dotholder[$y][$x] = $dot;
				$rgb = imagecolorat($im,$x,$y);
				$this->dotholder[$y][$x]["r"] = ($rgb >> 16) & 0xFF;
				$this->dotholder[$y][$x]["g"] = ($rgb >> 8) & 0xFF;
				$this->dotholder[$y][$x]["b"] = $rgb & 0xFF;		
				}
		}	
		
	
	}
	
	private function makeBaseImage(){
		//creates a blank white image using the dimensions 
		if(!$this->dimsokay) return false;
		$this->mainim = imagecreatetruecolor($this->imagex,$this->imagey);
		$white = imagecolorallocate($this->mainim, 255, 255, 255);
		imagefill($this->mainim, 0, 0, $white);
		return true;
	}

	private function overlayDot($points){
		$dotsize = 32; // in future we can make this a settable value
		$xmax = $this->imagex;
		$ymax = $this->imagey;
		
	
		$topx = $points["x"] - intval($dotsize/2);
		$topy = $points["y"] - intval($dotsize/2);
		
		for($y = 0; $y < $dotsize; $y++){
			for($x = 0; $x < $dotsize; $x++){
				$inx = $topx + $x;
				$iny = $topy + $y;
				
				if(($inx < 0)||($iny < 0)) continue;
				if(($inx > ($xmax-1))||($iny > ($ymax-1))) continue;			
				
				//get the color for the pixel
				$rgb = imagecolorat($this->mainim,$inx,$iny);
					$rbase = ($rgb >> 16) & 0xFF;
					$gbase = ($rgb >> 8) & 0xFF;
					$bbase = $rgb & 0xFF;	
				
				// let's multiply this pixel by the corresponding dot pixel
					$rbase = intval(($rbase * $this->dotholder[$y][$x]["r"])/255);
					$gbase = intval(($gbase * $this->dotholder[$y][$x]["g"])/255);
					$bbase = intval(($bbase * $this->dotholder[$y][$x]["b"])/255);

				if(isset($rebase)) imagecolordeallocate($this->mainim, $rebase); 
				// deallocate the $rebase color if it was allocated previously
				$rebase = imagecolorallocate($this->mainim, $rbase, $gbase, $bbase); 
				// allocate the $rebase color with the multiplied value
				imagesetpixel($this->mainim, $inx, $iny, $rebase); 
				// set the pixel to the newly multiplied value
				}
			}
	}
	
	private function colorize(){	
		$palcolors = $this->importColors($this->imagespath . 'colors.png');		

		$im = imagecreatefromjpeg($this->overlaypath . "bw." . $this->overlayfile);

		$height = imagesy($im);
		$width = imagesx($im);

		for($x = 0; $x < $width; $x++){
			for($y = 0; $y < $height; $y++) {
				
				$rgb = imagecolorat($im,$x,$y);
				$r = ($rgb >> 16) & 0xFF;
				$g = ($rgb >> 8) & 0xFF;
				$b = $rgb & 0xFF;
				
				$col = 255 - intval(($r + $g + $b)/3);
				if(isset($newcol)) imagecolordeallocate($im, $newcol);
				$newcol = imagecolorallocate($im, $palcolors[$col]["r"], $palcolors[$col]["g"], $palcolors[$col]["b"]);
				imagesetpixel($im, $x, $y, $newcol);
			}
		}
		
		imagejpeg($im,$this->overlaypath . $this->overlayfile);

		}	

	private function importColors($image){

	
		$dotpallette = array();
		$pixel = array("r"=>0,"g"=>0,"b"=>0);
		$sim = imagecreatefrompng($image);
		
		for($i = 0; $i < 256; $i++){
			$dotpallette[$i] = $pixel;

				$rgb = imagecolorat($sim,16,$i);
				$dotpallette[$i]["r"] = ($rgb >> 16) & 0xFF;
				$dotpallette[$i]["g"] = ($rgb >> 8) & 0xFF;
				$dotpallette[$i]["b"] = $rgb & 0xFF;
		}

		return $dotpallette;

	}
	
	
	
}
		

?>