Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
SOMDataset |
|
| 3.0;3 | ||||
SOMDataset$SOMDatasetIterator |
|
| 3.0;3 |
1 | /* ======================================================================= | |
2 | * A visualisation library extension for JFreeChart. Please see JFreeChart | |
3 | * for further information. | |
4 | * ======================================================================= | |
5 | * Copyright (C) 2006 University of Helsinki, Department of Computer Science | |
6 | * | |
7 | * This library is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU Lesser General Public | |
9 | * License as published by the Free Software Foundation; either | |
10 | * version 2.1 of the License, or (at your option) any later version. | |
11 | * | |
12 | * This library is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * Lesser General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU Lesser General Public | |
18 | * License along with this library; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | * ----------------------------- | |
21 | * Contact: ohtu@cs.helsinki.fi | |
22 | * ----------------------------- | |
23 | * | |
24 | */ | |
25 | ||
26 | ||
27 | package org.jfree.data.som; | |
28 | ||
29 | import java.util.*; | |
30 | import java.awt.Color; | |
31 | import java.io.Serializable; | |
32 | ||
33 | import org.jfree.data.general.AbstractDataset; | |
34 | ||
35 | ||
36 | /** | |
37 | * A dataset for dealing with SOM-map-cells. | |
38 | * | |
39 | * @author viski-project, Department of Computer Science, Univ. Helsinki | |
40 | */ | |
41 | public class SOMDataset extends AbstractDataset implements Cloneable, Serializable { | |
42 | ||
43 | /** Datastorage. */ | |
44 | protected SOMDataItem[][] data; | |
45 | ||
46 | /** | |
47 | * Creates a new SOMDataset with given columns and rows. | |
48 | * | |
49 | * @param columns the number of columns. | |
50 | * @param rows the number of rows. | |
51 | * @throws IllegalArgumentException If columns <= 0 or rows <= 0 | |
52 | */ | |
53 | 65 | public SOMDataset(int columns, int rows) throws IllegalArgumentException { |
54 | 65 | if (columns <= 0) |
55 | 5 | throw new IllegalArgumentException("Non-positive column count given to SOMDataset"); |
56 | 60 | if (rows <= 0) |
57 | 2 | throw new IllegalArgumentException("Non-positive row count given to SOMDataset"); |
58 | 58 | data = new SOMDataItem[columns][rows]; |
59 | 58 | } |
60 | ||
61 | /** | |
62 | * Returns the value at a specific (x,y) point in the datamatrix. | |
63 | * | |
64 | * @return A SOMDataItem. | |
65 | * @throws IndexOutOfBoundsException | |
66 | */ | |
67 | public SOMDataItem getValue(int x, int y) throws IndexOutOfBoundsException { | |
68 | 446 | return data[x][y]; |
69 | } | |
70 | ||
71 | /** | |
72 | * Returns the number of columns in a SOMDataset. | |
73 | * | |
74 | * @return The number of columns. | |
75 | */ | |
76 | public int getColumnCount() { | |
77 | 305 | return data.length; |
78 | } | |
79 | ||
80 | /** | |
81 | * Returns the number of rows in a SOMDataset. | |
82 | * | |
83 | * @return The number of rows. | |
84 | */ | |
85 | public int getRowCount() { | |
86 | 327 | return data[0].length; |
87 | } | |
88 | ||
89 | /** | |
90 | * Creates a new SOMDataItem from the params and adds it to the dataset. | |
91 | * | |
92 | * @param x the x-coordinate to add to in the matrix. | |
93 | * @param y the y-coordinate to add to in the matrix. | |
94 | * @param color the color of the dataitem. | |
95 | * @param description the textual description of the dataitem. | |
96 | * @param neuronWeights the numerical data of the dataitem. | |
97 | * | |
98 | * @throws IndexOutOfBoundsException | |
99 | */ | |
100 | public void addValue(int x, int y, Color color, String[] description, double[] neuronWeights) throws IndexOutOfBoundsException { | |
101 | 142 | data[x][y] = new SOMDataItem(color, description, neuronWeights); |
102 | 133 | } |
103 | ||
104 | /** | |
105 | * Adds a SOMDataItem to the dataset. | |
106 | * | |
107 | * @param x the x-coordinate to add to in the matrix. | |
108 | * @param y the y-coordinate to add to in the matrix. | |
109 | * @param item the SOMDataItem to add. | |
110 | * | |
111 | * @throws IndexOutOfBoundsException | |
112 | * @throws NullPointerException | |
113 | */ | |
114 | public void addValue(int x, int y, SOMDataItem item) throws IndexOutOfBoundsException { | |
115 | 4 | if (item == null) |
116 | 1 | throw new NullPointerException("item given to addValue was null."); |
117 | 3 | data[x][y] = item; |
118 | 3 | } |
119 | ||
120 | /** | |
121 | * Returns the neighbours of a dataitem in a {@link List}-object. | |
122 | * The distances are Euclidean distances | |
123 | * between the RGB-values of this data item and all other data items. | |
124 | * | |
125 | * @param x the x-coordinate in the matrix to start the search from. | |
126 | * @param y the y-coordinate in the matrix to start the search from. | |
127 | * @param maxDistance the distance between a neighbour (ie. similar color) and a non-neighbour | |
128 | * @param includeCenter Include the item at (x,y) in the list | |
129 | * | |
130 | * @return The list of neighbours. | |
131 | * @throws IndexOutOfBoundsException | |
132 | * @throws IllegalArgumentException If maxDistance < 0 | |
133 | */ | |
134 | public List getNeighbors(int x, int y, int maxDistance, boolean includeCenter) | |
135 | throws IndexOutOfBoundsException, IllegalArgumentException { | |
136 | 22 | SOMDataItem center = getValue(x, y); |
137 | ||
138 | 22 | return getNeighbors(center, maxDistance, includeCenter); |
139 | } | |
140 | ||
141 | /** | |
142 | * Returns the neighbours of a dataitem in a {@link List}-object. | |
143 | * The distances are Euclidean distances | |
144 | * between the RGB-values of this data item and all other data items. | |
145 | * | |
146 | * @param center the cell to which all other cells are compared | |
147 | * @param maxDistance the distance between a neighbour (ie. similar color) and a non-neighbour | |
148 | * @param includeCenter Include center in the list | |
149 | * | |
150 | * @return The list of neighbours. | |
151 | * @throws NullPointerException If center is null. | |
152 | * @throws IllegalArgumentException If maxDistance < 0. | |
153 | */ | |
154 | public List getNeighbors(SOMDataItem center, int maxDistance, boolean includeCenter) | |
155 | throws IllegalArgumentException, NullPointerException { | |
156 | 24 | if (center == null) |
157 | 1 | throw new NullPointerException("center given to getNeighbors() was null"); |
158 | 23 | if (maxDistance < 0) |
159 | 2 | throw new IllegalArgumentException("maxDistance given to getNeighbors() was negative."); |
160 | 21 | Color centerColor = center.getColor(); |
161 | 21 | LinkedList list = new LinkedList(); |
162 | ||
163 | 21 | Iterator i = new SOMDatasetIterator(this); |
164 | 107 | while (i.hasNext()) { |
165 | 86 | SOMDataItem item = (SOMDataItem) i.next(); |
166 | 86 | if (includeCenter || item != center) { |
167 | 74 | double diff = colorDistance(item.getColor(), centerColor); |
168 | 74 | if (diff <= maxDistance) |
169 | 43 | list.add(item); |
170 | } | |
171 | 86 | } |
172 | ||
173 | 21 | return list; |
174 | } | |
175 | ||
176 | /** | |
177 | * This method calculates the distance between two sets of integer color-values. | |
178 | * | |
179 | * @return The difference in color-values. | |
180 | */ | |
181 | private double colorDistance(Color color1, Color color2) { | |
182 | 74 | int r1 = color1.getRed(); |
183 | 74 | int g1 = color1.getGreen(); |
184 | 74 | int b1 = color1.getBlue(); |
185 | ||
186 | 74 | int r2 = color2.getRed(); |
187 | 74 | int g2 = color2.getGreen(); |
188 | 74 | int b2 = color2.getBlue(); |
189 | ||
190 | 74 | return Math.sqrt((r1-r2)*(r1-r2)+(g1-g2)*(g1-g2)+(b1-b2)*(b1-b2)); |
191 | } | |
192 | ||
193 | /** | |
194 | * Returns a {@link List} of {@link SOMDataItem} objects the are contained | |
195 | * inside the rectangle defined by two {@link SOMDataItem} objects. | |
196 | * | |
197 | * @param item1 corner of the rectangle | |
198 | * @param item2 corner of the rectangle | |
199 | * | |
200 | * @return The list of {@link SOMDataItem}s inside the area. | |
201 | * @throws NullPointerException If item1 == null or item2 == null | |
202 | * @throws IllegalArgumentException If item1 or item2 do not belong to this dataset. | |
203 | */ | |
204 | public List getArea(SOMDataItem item1, SOMDataItem item2) | |
205 | throws IllegalArgumentException, NullPointerException { | |
206 | 12 | if (item1 == null || item2 == null) |
207 | 2 | throw new NullPointerException("item1 or item2 given to getArea() was null"); |
208 | 10 | int[] xy1 = getCoordinates(item1); |
209 | 10 | int[] xy2 = getCoordinates(item2); |
210 | ||
211 | 10 | if (xy1 == null || xy2 == null) |
212 | 2 | throw new IllegalArgumentException(); |
213 | ||
214 | 8 | int width = Math.abs(xy1[0] - xy2[0]) + 1; |
215 | 8 | int height = Math.abs(xy1[1] - xy2[1]) + 1; |
216 | 8 | int startX = Math.min(xy1[0], xy2[0]); |
217 | 8 | int startY = Math.min(xy1[1], xy2[1]); |
218 | 8 | LinkedList list = new LinkedList(); |
219 | ||
220 | 23 | for (int y=0; y < height; ++y) { |
221 | 41 | for (int x=0; x < width; ++x) { |
222 | 26 | list.add(this.data[startX + x][startY + y]); |
223 | } | |
224 | } | |
225 | ||
226 | 8 | return list; |
227 | } | |
228 | ||
229 | /** | |
230 | * Returns the coordinates of a SOMDataItem. | |
231 | * | |
232 | * @param item the item whose coordinates we want. | |
233 | * | |
234 | * @return The coordinates. | |
235 | */ | |
236 | private int[] getCoordinates(SOMDataItem item) { | |
237 | 20 | int[] xy = new int[2]; |
238 | ||
239 | 33 | for (int y=0; y < this.data[0].length; ++y) { |
240 | 66 | for (int x=0; x < this.data.length; ++x) { |
241 | 53 | if (item == this.data[x][y]) { |
242 | 18 | xy[0] = x; |
243 | 18 | xy[1] = y; |
244 | 18 | return xy; |
245 | } | |
246 | } | |
247 | } | |
248 | ||
249 | 2 | return null; |
250 | } | |
251 | ||
252 | /** | |
253 | * This method changes the color hue of all dataitems in a dataset for | |
254 | * given amount, negative or positive. The color-values of each dataitem | |
255 | * are retrieved, changed and new rgb-values are calculated and set. | |
256 | * | |
257 | * @param angle the amount to increase the hue angle. | |
258 | * @throws IllegalArgumentException If Math.abs(angle) >= 360. | |
259 | */ | |
260 | public void changeHueValues(int angle) | |
261 | throws IllegalArgumentException { | |
262 | 12 | if (Math.abs(angle) > 360) |
263 | 2 | throw new IllegalArgumentException("angle value given to changeHueValues() was not in [-360,360]."); |
264 | 10 | Iterator i = new SOMDatasetIterator(this); |
265 | 54 | while (i.hasNext()) { |
266 | 44 | SOMDataItem item = (SOMDataItem)i.next(); |
267 | ||
268 | 44 | if (item != null) { |
269 | 44 | Color c = item.getColor(); |
270 | 44 | float[] hsb = Color.RGBtoHSB( |
271 | c.getRed(), | |
272 | c.getGreen(), | |
273 | c.getBlue(), | |
274 | null); | |
275 | ||
276 | 44 | hsb[0] += angle/360.0 + 1.0; |
277 | 44 | hsb[0] %= 1.0; |
278 | 44 | item.setColor(Color.getHSBColor(hsb[0], hsb[1], hsb[2])); |
279 | } | |
280 | 44 | } |
281 | 10 | } |
282 | ||
283 | /** | |
284 | * This method returns an iterator that iterates all it's dataitems | |
285 | * | |
286 | * @return iterator | |
287 | */ | |
288 | public Iterator iterator() { | |
289 | 24 | return new SOMDatasetIterator(this); |
290 | } | |
291 | ||
292 | /** | |
293 | * Deselects all the dataitems in this SOMDataset. | |
294 | */ | |
295 | public void deselectAll() { | |
296 | 12 | Iterator i = iterator(); |
297 | 76 | while (i.hasNext()) { |
298 | 65 | SOMDataItem item = (SOMDataItem)i.next(); |
299 | 65 | item.setSelected(false); |
300 | 64 | } |
301 | 11 | } |
302 | ||
303 | /** | |
304 | * An inner class to deal with SOMDataItem-objects in the SOMDataset. This | |
305 | * class implements the {@link Iterator} interface | |
306 | */ | |
307 | private class SOMDatasetIterator implements Iterator { | |
308 | private SOMDataset dataset; | |
309 | private int x; | |
310 | private int y; | |
311 | ||
312 | /** | |
313 | * Creates a new SOMDatasetIterator for a dataset. | |
314 | * | |
315 | * @param dataset the SOMDataset to be iterated. | |
316 | */ | |
317 | 55 | public SOMDatasetIterator(SOMDataset dataset) { |
318 | 55 | this.dataset = dataset; |
319 | 55 | this.x = 0; |
320 | 55 | this.y = 0; |
321 | 55 | } |
322 | ||
323 | /** | |
324 | * Returns false, if the pointer already is at the last item. | |
325 | * | |
326 | * @return A boolean. | |
327 | */ | |
328 | public boolean hasNext() { | |
329 | 266 | return !((this.y == this.dataset.getRowCount()) && (this.x == 0)); |
330 | } | |
331 | ||
332 | /** | |
333 | * Moves the pointer to the next item. | |
334 | * | |
335 | * @return The SOMDataItem. | |
336 | * @throws NoSuchElementException | |
337 | */ | |
338 | public Object next() { | |
339 | SOMDataItem item; | |
340 | 230 | item = this.dataset.getValue(this.x++, this.y); |
341 | // if (item == null) | |
342 | // throw new NoSuchElementException(); | |
343 | ||
344 | 230 | this.x %= this.dataset.getColumnCount(); |
345 | 230 | if (this.x == 0) |
346 | 116 | this.y++; |
347 | ||
348 | 230 | return item; |
349 | } | |
350 | ||
351 | /** | |
352 | * Should remove an item from the iteration list. | |
353 | * This method does nothing. | |
354 | */ | |
355 | public void remove() throws UnsupportedOperationException { | |
356 | 1 | throw new UnsupportedOperationException("SOMDataset iterators do not implement remove()."); |
357 | } | |
358 | ||
359 | } | |
360 | ||
361 | /** | |
362 | * Tests if this object is equal to another. | |
363 | * | |
364 | * @param obj the other object. | |
365 | * | |
366 | * @return A boolean. | |
367 | */ | |
368 | public boolean equals(Object obj) { | |
369 | 12 | if (obj == this) { |
370 | 5 | return true; |
371 | } | |
372 | ||
373 | 7 | if (!(obj instanceof SOMDataset)) { |
374 | 1 | return false; |
375 | } | |
376 | 6 | SOMDataset that = (SOMDataset) obj; |
377 | 6 | int cols = this.getColumnCount(); |
378 | 6 | int rows = this.getRowCount(); |
379 | 6 | if (that.getColumnCount() != cols || |
380 | that.getRowCount() != rows) { | |
381 | 2 | return false; |
382 | } | |
383 | ||
384 | 4 | Iterator i1 = this.iterator(); |
385 | 4 | Iterator i2 = that.iterator(); |
386 | 14 | while (i1.hasNext()) { |
387 | 11 | SOMDataItem item1 = (SOMDataItem)i1.next(); |
388 | 11 | SOMDataItem item2 = (SOMDataItem)i2.next(); |
389 | 11 | if (item1 == null && item2 == null) { |
390 | ; | |
391 | 2 | } |
392 | 9 | else if (item1 == null || item2 == null || |
393 | item1.equals(item2) == false) | |
394 | 1 | return false; |
395 | 10 | } |
396 | ||
397 | 3 | return true; |
398 | } | |
399 | ||
400 | /** | |
401 | * Returns a clone of the dataset. | |
402 | * | |
403 | * @return A clone. | |
404 | * | |
405 | * @throws CloneNotSupportedException This class will not throw this | |
406 | * exception, but subclasses (if any) might. | |
407 | */ | |
408 | public Object clone() throws CloneNotSupportedException { | |
409 | 1 | SOMDataset clone = (SOMDataset) super.clone(); |
410 | 1 | clone.data = (SOMDataItem[][]) this.data.clone(); |
411 | 1 | return clone; |
412 | } | |
413 | ||
414 | } | |
415 |