1: using System;
2: using System.Collections.Generic;
3: using System.Configuration;
4: using System.Drawing;
5: using System.IO;
6: using System.Linq;
7: using System.Web;
8: using System.Xml.Linq;
9:
10: using BlogEngine.Core;
11:
12: public class HeatMap
13: {
14: #region Variables
15: private const int RADIUS = 8; // Radius of click blob in image
16: private static int[][] _coordsArray; // Contains the # of Clicks for each Coordinate pair
17: private static int _maxClick = 0; // Max # Clicks anywhwere
18: private static int _width = 0;
19: private static int _height = 0;
20: private static List<HeatMapClick> _clicks; // Actual data retrieved from XML data source
21:
22: private static string _pageName;
23: private static bool _allDates = true;
24: private static DateTime _from = DateTime.MinValue;
25: private static DateTime _to = DateTime.MinValue;
26: private static int _transparency;
27: private static string _imageName; // FileName of Generated HeatMap Image
28: #endregion
29:
30: public static void GenerateHeatMap(string pageName,
31: bool allDates,
32: DateTime from,
33: DateTime to,
34: int transparency,
35: string imageName)
36: {
37: _allDates = allDates;
38: _from = from;
39: _to = to;
40: _transparency = transparency;
41: _pageName = pageName;
42: _imageName = imageName;
43:
44: Generate();
45: }
46:
47: #region Internal
48: private static int[] ColourSequence(long value)
49: {
50: // _transparency is a percentage value to establish the base colour
51: // 100% = 235 for rgb
52: // 0% = completely transparent as shown in the alpha
53:
54:
55: int baseColour = 235;
56: int alpha = 0;
57: int red = 1;
58: int green = 2;
59: int blue = 3;
60:
61: // the alpha is the transparency
62: int[] argb = new int[4];
63: argb[alpha] = (int)((Double)235 * (Double)((Double)_transparency / 100));
64:
65: if (value == 0)
66: {
67: argb[red] = baseColour;
68: argb[green] = baseColour;
69: argb[blue] = baseColour;
70: }
71: else if (0 < value && value < 255) // from white to blue (255 colours)
72: {
73: argb[red] = (int)(baseColour - value);
74: argb[green] = (int)(baseColour - value);
75:
76: if (argb[red] < 0) argb[red] = 0;
77: if (argb[green] < 0) argb[green] = 0;
78:
79: argb[blue] = (int)(235 + (double)((double)(value) * 20 / 255));
80: }
81: else if (255 <= value && value < 510) // from blue to cyan (255 colours)
82: {
83: argb[red] = 0;
84: argb[green] = (int)(value - 255);
85: argb[blue] = 255;
86: }
87: else if (510 <= value && value < 765) // from cyan to green (255 colours)
88: {
89: argb[red] = 0;
90: argb[green] = 255;
91: argb[blue] = 255 - (int)(value - 510);
92: }
93: else if (765 <= value && value < 1020) // from green to yellow (255 colours)
94: {
95: argb[red] = (int)(value - 765);
96: argb[green] = 255;
97: argb[blue] = 0;
98: }
99: else if (1020 <= value && value < 1275) // from yellow to red (255 colours)
100: {
101: argb[red] = 255;
102: argb[green] = 255 - (int)(value - 1020);
103: argb[blue] = 0;
104: }
105: else if (value >= 1275) // return red
106: {
107: argb[red] = 255;
108: argb[green] = 0;
109: argb[blue] = 0;
110: }
111: return argb;
112: }
113: /// <summary>
114: /// Creates the Actual HeatMap Image from the Processed _coordsArray
115: /// </summary>
116: /// <param name="pageName"></param>
117: /// <returns></returns>
118: private static byte[] CreateImage()
119: {
120: System.IO.MemoryStream ms = null;
121: Bitmap b = new Bitmap(_width, _height);
122: Graphics g = Graphics.FromImage(b);
123: Pen p = null;
124:
125: try
126: {
127: for (int x = 0; x < _width; x++)
128: {
129: for (int y = 0; y < _height; y++)
130: {
131: if (_coordsArray[x][y] >= 0)
132: {
133: p = new Pen(GetColor(_maxClick, _coordsArray[x][y], 5));
134: g.DrawLine(p, x, y, x + 1, y + 1);
135: }
136: }
137: }
138:
139: ms = new MemoryStream();
140: b.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
141: }
142: catch (Exception)
143: {
144: throw;
145: }
146: finally
147: {
148: ms.Dispose();
149: b.Dispose();
150: g.Dispose();
151: }
152: return ms.GetBuffer();
153: }
154: private static Double Distance(int x1, int y1, int x2, int y2)
155: {
156: return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2));
157: }
158: /// <summary>
159: /// Fetches all HeatMap Clicks based on user criteria
160: /// </summary>
161: /// <returns></returns>
162: private static List<HeatMapClick> FetchHeatMapClicks()
163: {
164: string settings = ConfigurationManager.AppSettings["HeatMap_Settings"];
165: string path = string.Concat(HttpContext.Current.Request.PhysicalApplicationPath,
166: "App_Data/" + settings);
167:
168: XElement heatmapClicks = null;
169: try
170: {
171: heatmapClicks = XElement.Load(path);
172: }
173: catch { }
174:
175: if ((heatmapClicks != null) && (heatmapClicks.Elements().Count() > 0))
176: {
177:
178: if (_allDates)
179: {
180: var clicks = from c in heatmapClicks.Elements()
181: where c.Element("Page").Value == _pageName
182: select new HeatMapClick()
183: {
184: Date = Convert.ToDateTime(c.Element("Date").Value),
185: Page = c.Element("Page").Value,
186: CoordX = Convert.ToInt32(c.Element("CoordX").Value),
187: CoordY = Convert.ToInt32(c.Element("CoordY").Value)
188: };
189: return clicks.ToList();
190: }
191: else
192: {
193: var clicks = from c in heatmapClicks.Elements()
194: where c.Element("Page").Value == _pageName
195: && Convert.ToDateTime(c.Element("Date").Value) >= _from
196: && Convert.ToDateTime(c.Element("Date").Value) <= _to
197: select new HeatMapClick()
198: {
199: Date = Convert.ToDateTime(c.Element("Date").Value),
200: Page = c.Element("Page").Value,
201: CoordX = Convert.ToInt32(c.Element("CoordX").Value),
202: CoordY = Convert.ToInt32(c.Element("CoordY").Value)
203: };
204: return clicks.ToList();
205: }
206: }
207: else
208: return null;
209: }
210: /// <summary>
211: /// Corrdinates the building of the HeatMap image.
212: /// Saves the Image to the current Theme Images folder
213: /// </summary>
214: private static void Generate()
215: {
216: _clicks = FetchHeatMapClicks();
217:
218: if (_clicks == null || _clicks.Count == 0)
219: return;
220:
221: int[] wh = GetWidthHeight();
222: _width = wh[0] + RADIUS;
223: _height = wh[1] + RADIUS;
224:
225: _coordsArray = new int[_width][];
226: for (int i = 0; i < _width; i++)
227: {
228: _coordsArray[i] = new int[_height];
229: for (int j = 0; j < _height; j++)
230: {
231: _coordsArray[i][j] = 0;
232: }
233: }
234:
235: GenerateClickBlobs();
236: GetMaxClick();
237:
238: byte[] imgBytes = CreateImage();
239:
240: string query = HttpContext.Current.Request.QueryString["theme"];
241: string theme = !string.IsNullOrEmpty(query) ? query : BlogSettings.Instance.Theme;
242: string path = string.Concat(HttpContext.Current.Request.PhysicalApplicationPath,
243: "themes/",
244: theme,
245: "/images/Heatmap/",
246: _imageName);
247:
248: try
249: {
250: FileStream fs = File.Create(path);
251: fs.Write(imgBytes, 0, imgBytes.Length);
252: fs.Close();
253: }
254: catch (Exception)
255: {
256: throw;
257: }
258: }
259: private static void GenerateClickBlobs()
260: {
261: try
262: {
263: foreach (HeatMapClick c in _clicks)
264: {
265: RootedIncrementOnPoint(c.CoordX, c.CoordY);
266: // OR use
267: //LinearSlideIncrementOnPoint(hc.CoordX, hc.CoordY);
268: }
269: }
270: catch (Exception)
271: {
272: throw;
273: }
274: }
275: private static Color GetColor(long upperBound, long value, int error)
276: {
277: int[] argb = new int[4];
278: argb = ColourSequence(value * ((1275 * (100 + error) / 100) / upperBound));
279: return Color.FromArgb(argb[0], argb[1], argb[2], argb[3]);
280: }
281: private static void GetMaxClick()
282: {
283: _maxClick = 0;
284: for (int x = 0; x < _width; x++)
285: {
286: for (int y = 0; y < _height; y++)
287: {
288: if (_coordsArray[x][y] > _maxClick)
289: _maxClick = _coordsArray[x][y];
290: }
291: }
292: }
293: /// <summary>
294: /// Determines the Width and Height of the Image to be processed
295: /// by obtaining the largest X-coord and Y-coord values
296: /// </summary>
297: /// <returns></returns>
298: private static int[] GetWidthHeight()
299: {
300: int[] wh = { 0, 0 };
301:
302: try
303: {
304: foreach (HeatMapClick c in _clicks)
305: {
306: if (c.CoordX > wh[0])
307: wh[0] = c.CoordX;
308: if (c.CoordY > wh[1])
309: wh[1] = c.CoordY;
310: }
311: }
312: catch (Exception)
313: {
314: throw;
315: }
316: return wh;
317: }
318: private static void LinearSlideIncrementOnPoint(int x, int y)
319: {
320: for (int i = x - RADIUS; i < x + RADIUS; i++)
321: {
322: for (int j = y - RADIUS; j < y + RADIUS; j++)
323: {
324: if (((i > 0) && (j > 0)) && ((i < _width) && (j < _height)))
325: {
326: if (Distance(x, y, i, j) < RADIUS)
327: {
328: _coordsArray[i][j] += (int)(RADIUS - Distance(x, y, i, j));
329: }
330: }
331: }
332: }
333: }
334: private static void RootedIncrementOnPoint(int x, int y)
335: {
336: for (int i = x - RADIUS; i < x + RADIUS; i++)
337: {
338: for (int j = y - RADIUS; j < y + RADIUS; j++)
339: {
340: if (((i >= 0) && (j >= 0)) && ((i < _width) && (j < _height)))
341: {
342: if (Distance(x, y, i, j) < RADIUS)
343: {
344: _coordsArray[i][j] += (int)Math.Sqrt(RADIUS - Distance(x, y, i, j));
345: }
346: }
347: }
348: }
349: }
350: #endregion
351:
352: private class HeatMapClick
353: {
354: public DateTime Date { get; set; }
355: public string Page { get; set; }
356: public int CoordX { get; set; }
357: public int CoordY { get; set; }
358: }
359: }