1. <?php
2. /**
3. * File containing the abstract ezcGraphAxisLabelRenderer class
4. *
5. * @package Graph
6. * @version 1.4.2
7. * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
8. * @license http://ez.no/licenses/new_bsd New BSD License
9. */
10. /**
11. * Abstract class to render labels and grids on axis. Will be extended to
12. * make it possible using different algorithms for rendering axis labels.
13. *
14. * Implements basic methods to render the grid and steps on a axis.
15. *
16. * @property bool $majorStepCount
17. * Count of major steps.
18. * @property bool $minorStepCount
19. * Count of minor steps.
20. * @property int $majorStepSize
21. * Size of major steps.
22. * @property int $minorStepSize
23. * Size of minor steps.
24. * @property bool $innerStep
25. * Indicates if steps are shown on the inner side of axis.
26. * @property bool $outerStep
27. * Indicates if steps are shown on the outer side of axis.
28. * @property bool $outerGrid
29. * Indicates if the grid is shown on the outer side of axis.
30. * @property bool $showLables
31. * Indicates if the labels should be shown
32. * @property int $labelPadding
33. * Padding of labels.
34. *
35. * @version 1.4.2
36. * @package Graph
37. */
38. abstract class ezcGraphAxisLabelRenderer extends ezcBaseOptions
39. {
40. /**
41. * Driver to render axis labels
42. *
43. * @var ezcGraphDriver
44. */
45. protected $driver;
46.
47. /**
48. * Constructor
49. *
50. * @param array $options Default option array
51. * @return void
52. * @ignore
53. */
54. public function __construct( array $options = array() )
55. {
56. $this->properties['majorStepCount'] = false;
57. $this->properties['minorStepCount'] = false;
58. $this->properties['majorStepSize'] = 3;
59. $this->properties['minorStepSize'] = 1;
60. $this->properties['innerStep'] = true;
61. $this->properties['outerStep'] = false;
62. $this->properties['outerGrid'] = false;
63. $this->properties['showLabels'] = true;
64. $this->properties['labelPadding'] = 2;
65.
66. parent::__construct( $options );
67. }
68.
69. /**
70. * __set
71. *
72. * @param mixed $propertyName
73. * @param mixed $propertyValue
74. * @throws ezcBaseValueException
75. * If a submitted parameter was out of range or type.
76. * @throws ezcBasePropertyNotFoundException
77. * If a the value for the property options is not an instance of
78. * @return void
79. * @ignore
80. */
81. public function __set( $propertyName, $propertyValue )
82. {
83. switch ( $propertyName )
84. {
85. case 'driver':
86. if ( $propertyValue instanceof ezcGraphDriver )
87. {
88. $this->properties['driver'] = $propertyValue;
89. }
90. else
91. {
92. throw new ezcGraphInvalidDriverException( $propertyValue );
93. }
94. break;
95. case 'majorStepCount':
96. if ( ( $propertyValue !== false ) &&
97. !is_numeric( $propertyValue ) ||
98. ( $propertyValue < 0 ) )
99. {
100. throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' );
101. }
102.
103. $this->properties['majorStepCount'] = (int) $propertyValue;
104. break;
105. case 'minorStepCount':
106. if ( ( $propertyValue !== false ) &&
107. !is_numeric( $propertyValue ) ||
108. ( $propertyValue < 0 ) )
109. {
110. throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' );
111. }
112.
113. $this->properties['minorStepCount'] = (int) $propertyValue;
114. break;
115. case 'majorStepSize':
116. if ( !is_numeric( $propertyValue ) ||
117. ( $propertyValue < 0 ) )
118. {
119. throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' );
120. }
121.
122. $this->properties['majorStepSize'] = (int) $propertyValue;
123. break;
124. case 'minorStepSize':
125. if ( !is_numeric( $propertyValue ) ||
126. ( $propertyValue < 0 ) )
127. {
128. throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' );
129. }
130.
131. $this->properties['minorStepSize'] = (int) $propertyValue;
132. break;
133. case 'innerStep':
134. if ( !is_bool( $propertyValue ) )
135. {
136. throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' );
137. }
138.
139. $this->properties['innerStep'] = (bool) $propertyValue;
140. break;
141. case 'outerStep':
142. if ( !is_bool( $propertyValue ) )
143. {
144. throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' );
145. }
146.
147. $this->properties['outerStep'] = (bool) $propertyValue;
148. break;
149. case 'outerGrid':
150. if ( !is_bool( $propertyValue ) )
151. {
152. throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' );
153. }
154.
155. $this->properties['outerGrid'] = (bool) $propertyValue;
156. break;
157. case 'showLabels':
158. if ( !is_bool( $propertyValue ) )
159. {
160. throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' );
161. }
162.
163. $this->properties['showLabels'] = (bool) $propertyValue;
164. break;
165. case 'labelPadding':
166. if ( !is_numeric( $propertyValue ) ||
167. ( $propertyValue < 0 ) )
168. {
169. throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' );
170. }
171.
172. $this->properties['labelPadding'] = (int) $propertyValue;
173. break;
174. default:
175. throw new ezcBasePropertyNotFoundException( $propertyName );
176. }
177. }
178.
179. /**
180. * Checks for the cutting point of two lines.
181. *
182. * The lines are given by a start position and the direction of the line,
183. * both as instances of {@link ezcGraphCoordinate}. If no cutting point
184. * could be calculated, because the lines are parallel the function will
185. * return false. Otherwise the factor returned can be used to calculate the
186. * cutting point using the following equatation:
187. * point = $aStart + $factor * $aDir;
188. *
189. * We return the factor instead of the resulting point because it can be
190. * easily determined from the factor if the cutting point is in "behind"
191. * the line starting point, or if the distance to the cutting point is
192. * bigger then the direction vector is long ( $factor > 1 ).
193. *
194. * @param ezcGraphCoordinate $aStart
195. * @param ezcGraphCoordinate $aDir
196. * @param ezcGraphCoordinate $bStart
197. * @param ezcGraphCoordinate $bDir
198. * @return mixed
199. */
200. public function determineLineCuttingPoint( ezcGraphCoordinate $aStart, ezcGraphCoordinate $aDir, ezcGraphCoordinate $bStart, ezcGraphCoordinate $bDir )
201. {
202. // Check if lines are parallel
203. if ( ( ( abs( $aDir->x ) < .000001 ) && ( abs( $bDir->x ) < .000001 ) ) ||
204. ( ( abs( $aDir->y ) < .000001 ) && ( abs( $bDir->y ) < .000001 ) ) ||
205. ( ( abs( $aDir->x * $bDir->x * $aDir->y * $bDir->y ) > .000001 ) &&
206. ( abs( ( $aDir->x / $aDir->y ) - ( $bDir->x / $bDir->y ) ) < .000001 )
207. )
208. )
209. {
210. return false;
211. }
212.
213. // Use ? : to prevent division by zero
214. $denominator =
215. ( abs( $aDir->y ) > .000001 ? $bDir->y / $aDir->y : .0 ) -
216. ( abs( $aDir->x ) > .000001 ? $bDir->x / $aDir->x : .0 );
217.
218. // Solve equatation
219. if ( abs( $denominator ) < .000001 )
220. {
221. return - (
222. ( abs( $aDir->y ) > .000001 ? $bStart->y / $aDir->y : .0 ) -
223. ( abs( $aDir->y ) > .000001 ? $aStart->y / $aDir->y : .0 ) -
224. ( abs( $aDir->x ) > .000001 ? $bStart->x / $aDir->x : .0 ) +
225. ( abs( $aDir->x ) > .000001 ? $aStart->x / $aDir->x : .0 )
226. );
227. }
228. else
229. {
230. return - (
231. ( abs( $aDir->y ) > .000001 ? $bStart->y / $aDir->y : .0 ) -
232. ( abs( $aDir->y ) > .000001 ? $aStart->y / $aDir->y : .0 ) -
233. ( abs( $aDir->x ) > .000001 ? $bStart->x / $aDir->x : .0 ) +
234. ( abs( $aDir->x ) > .000001 ? $aStart->x / $aDir->x : .0 )
235. ) / $denominator;
236. }
237. }
238.
239. /**
240. * Draw single step on a axis
241. *
242. * Draws a step on a axis at the current position
243. *
244. * @param ezcGraphRenderer $renderer Renderer to draw the step with
245. * @param ezcGraphCoordinate $position Position of step
246. * @param ezcGraphCoordinate $direction Direction of axis
247. * @param int $axisPosition Position of axis
248. * @param int $size Step size
249. * @param ezcGraphColor $color Color of axis
250. * @return void
251. */
252. public function drawStep( ezcGraphRenderer $renderer, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, $axisPosition, $size, ezcGraphColor $color )
253. {
254. if ( ! ( $this->innerStep || $this->outerStep ) )
255. {
256. return false;
257. }
258.
259. $drawStep = false;
260. if ( ( ( $axisPosition === ezcGraph::CENTER ) && $this->innerStep ) ||
261. ( ( $axisPosition === ezcGraph::BOTTOM ) && $this->outerStep ) ||
262. ( ( $axisPosition === ezcGraph::TOP ) && $this->innerStep ) ||
263. ( ( $axisPosition === ezcGraph::RIGHT ) && $this->outerStep ) ||
264. ( ( $axisPosition === ezcGraph::LEFT ) && $this->innerStep ) )
265. {
266. // Turn direction vector to left by 90 degrees and multiply
267. // with major step size
268. $stepStart = new ezcGraphCoordinate(
269. $position->x + $direction->y * $size,
270. $position->y - $direction->x * $size
271. );
272. $drawStep = true;
273. }
274. else
275. {
276. $stepStart = $position;
277. }
278.
279. if ( ( ( $axisPosition === ezcGraph::CENTER ) && $this->innerStep ) ||
280. ( ( $axisPosition === ezcGraph::BOTTOM ) && $this->innerStep ) ||
281. ( ( $axisPosition === ezcGraph::TOP ) && $this->outerStep ) ||
282. ( ( $axisPosition === ezcGraph::RIGHT ) && $this->innerStep ) ||
283. ( ( $axisPosition === ezcGraph::LEFT ) && $this->outerStep ) )
284. {
285. // Turn direction vector to right by 90 degrees and multiply
286. // with major step size
287. $stepEnd = new ezcGraphCoordinate(
288. $position->x - $direction->y * $size,
289. $position->y + $direction->x * $size
290. );
291. $drawStep = true;
292. }
293. else
294. {
295. $stepEnd = $position;
296. }
297.
298. if ( $drawStep )
299. {
300. $renderer->drawStepLine(
301. $stepStart,
302. $stepEnd,
303. $color
304. );
305. }
306. }
307.
308. /**
309. * Draw non-rectangular grid lines grid
310. *
311. * Draws a grid line at the current position, for non-rectangular axis.
312. *
313. * @param ezcGraphRenderer $renderer Renderer to draw the grid with
314. * @param ezcGraphBoundings $boundings Boundings of axis
315. * @param ezcGraphCoordinate $position Position of step
316. * @param ezcGraphCoordinate $direction Direction of axis
317. * @param ezcGraphColor $color Color of axis
318. * @return void
319. */
320. protected function drawNonRectangularGrid( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, ezcGraphColor $color )
321. {
322. // Direction of grid line is direction of axis turned right by 90
323. // degrees
324. $gridDirection = new ezcGraphCoordinate(
325. $direction->y,
326. - $direction->x
327. );
328.
329. $cuttingPoints = array();
330. foreach ( array( // Bounding lines
331. array(
332. 'start' => new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ),
333. 'dir' => new ezcGraphCoordinate( 0, $boundings->y1 - $boundings->y0 )
334. ),
335. array(
336. 'start' => new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ),
337. 'dir' => new ezcGraphCoordinate( $boundings->x1 - $boundings->x0, 0 )
338. ),
339. array(
340. 'start' => new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ),
341. 'dir' => new ezcGraphCoordinate( 0, $boundings->y0 - $boundings->y1 )
342. ),
343. array(
344. 'start' => new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ),
345. 'dir' => new ezcGraphCoordinate( $boundings->x0 - $boundings->x1, 0 )
346. ),
347. ) as $boundingLine )
348. {
349. // Test for cutting points with bounding lines, where cutting
350. // position is between 0 and 1, which means, that the line is hit
351. // on the bounding box rectangle. Use these points as a start and
352. // ending point for the grid lines. There should *always* be
353. // exactly two points returned.
354. $cuttingPosition = $this->determineLineCuttingPoint(
355. $boundingLine['start'],
356. $boundingLine['dir'],
357. $position,
358. $gridDirection
359. );
360.
361. if ( $cuttingPosition === false )
362. {
363. continue;
364. }
365.
366. $cuttingPosition = abs( $cuttingPosition );
367.
368. if ( ( $cuttingPosition >= 0 ) &&
369. ( $cuttingPosition <= 1 ) )
370. {
371. $cuttingPoints[] = new ezcGraphCoordinate(
372. $boundingLine['start']->x + $cuttingPosition * $boundingLine['dir']->x,
373. $boundingLine['start']->y + $cuttingPosition * $boundingLine['dir']->y
374. );
375. }
376. }
377.
378. if ( count( $cuttingPoints ) < 2 )
379. {
380. // This should not happpen
381. return false;
382. }
383.
384. // Finally draw grid line
385. $renderer->drawGridLine(
386. $cuttingPoints[0],
387. $cuttingPoints[1],
388. $color
389. );
390. }
391.
392. /**
393. * Draw rectangular grid
394. *
395. * Draws a grid line at the current position for rectangular directed axis.
396. *
397. * Method special for rectangularly directed axis to minimize the floating
398. * point calculation inaccuracies. Those are not necessary for rectangles,
399. * while for non-rectangular directed axis.
400. *
401. * @param ezcGraphRenderer $renderer Renderer to draw the grid with
402. * @param ezcGraphBoundings $boundings Boundings of axis
403. * @param ezcGraphCoordinate $position Position of step
404. * @param ezcGraphCoordinate $direction Direction of axis
405. * @param ezcGraphColor $color Color of axis
406. * @return void
407. */
408. protected function drawRectangularGrid( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, ezcGraphColor $color )
409. {
410. if ( abs( $direction->x ) < .00001 )
411. {
412. $renderer->drawGridLine(
413. new ezcGraphCoordinate(
414. $boundings->x0,
415. $position->y
416. ),
417. new ezcGraphCoordinate(
418. $boundings->x1,
419. $position->y
420. ),
421. $color
422. );
423. }
424. else
425. {
426. $renderer->drawGridLine(
427. new ezcGraphCoordinate(
428. $position->x,
429. $boundings->y0
430. ),
431. new ezcGraphCoordinate(
432. $position->x,
433. $boundings->y1
434. ),
435. $color
436. );
437. }
438. }
439.
440. /**
441. * Draw grid
442. *
443. * Draws a grid line at the current position
444. *
445. * @param ezcGraphRenderer $renderer Renderer to draw the grid with
446. * @param ezcGraphBoundings $boundings Boundings of axis
447. * @param ezcGraphCoordinate $position Position of step
448. * @param ezcGraphCoordinate $direction Direction of axis
449. * @param ezcGraphColor $color Color of axis
450. * @return void
451. */
452. protected function drawGrid( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, ezcGraphColor $color )
453. {
454. // Check if the axis direction is rectangular
455. if ( ( abs( $direction->x ) < .00001 ) ||
456. ( abs( $direction->y ) < .00001 ) )
457. {
458. return $this->drawRectangularGrid( $renderer, $boundings, $position, $direction, $color );
459. }
460. else
461. {
462. return $this->drawNonRectangularGrid( $renderer, $boundings, $position, $direction, $color );
463. }
464. }
465.
466. /**
467. * Modify chart boundings
468. *
469. * Optionally modify boundings of chart data
470. *
471. * @param ezcGraphBoundings $boundings Current boundings of chart
472. * @param ezcGraphCoordinate $direction Direction of the current axis
473. * @return ezcGraphBoundings Modified boundings
474. */
475. public function modifyChartBoundings( ezcGraphBoundings $boundings, ezcGraphCoordinate $direction )
476. {
477. return $boundings;
478. }
479.
480. /**
481. * Modify chart data position
482. *
483. * Optionally additionally modify the coodinate of a data point
484. *
485. * @param ezcGraphCoordinate $coordinate Data point coordinate
486. * @return ezcGraphCoordinate Modified coordinate
487. */
488. public function modifyChartDataPosition( ezcGraphCoordinate $coordinate )
489. {
490. return $coordinate;
491. }
492.
493. /**
494. * Get axis space values
495. *
496. * Get axis space values, depending on passed parameters. If
497. * $innerBoundings is given it will be used to caclulat the axis spaces
498. * available for label rendering. If not given the legacy method will be
499. * used, which uses the xAxisSpace and yAxisSpace values calcualted by the
500. * renderer.
501. *
502. * Returns an array( $xSpace, $ySpace ), containing the irespective size in
503. * pixels. Additionally calculates the grid boundings passed by reference.
504. *
505. * @param ezcGraphRenderer $renderer
506. * @param ezcGraphBoundings $boundings
507. * @param mixed $innerBoundings
508. * @return array
509. */
510. protected function getAxisSpace( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphChartElementAxis $axis, $innerBoundings, &$gridBoundings )
511. {
512. if ( $innerBoundings !== null )
513. {
514. $gridBoundings = clone $innerBoundings;
515. $xSpace = abs( $axis->position === ezcGraph::LEFT ? $innerBoundings->x0 - $boundings->x0 : $boundings->x1 - $innerBoundings->x1 );
516. $ySpace = abs( $axis->position === ezcGraph::TOP ? $innerBoundings->y0 - $boundings->y0 : $boundings->y1 - $innerBoundings->y1 );
517. }
518. else
519. {
520. $gridBoundings = new ezcGraphBoundings(
521. $boundings->x0 + ( $xSpace = abs( $renderer->xAxisSpace ) ),
522. $boundings->y0 + ( $ySpace = abs( $renderer->yAxisSpace ) ),
523. $boundings->x1 - $xSpace,
524. $boundings->y1 - $ySpace
525. );
526. }
527.
528. if ( $this->outerGrid )
529. {
530. $gridBoundings = $boundings;
531. }
532.
533. return array( $xSpace, $ySpace );
534. }
535.
536. /**
537. * Render Axis labels
538. *
539. * Render labels for an axis.
540. *
541. * @param ezcGraphRenderer $renderer Renderer used to draw the chart
542. * @param ezcGraphBoundings $boundings Boundings of the axis
543. * @param ezcGraphCoordinate $start Axis starting point
544. * @param ezcGraphCoordinate $end Axis ending point
545. * @param ezcGraphChartElementAxis $axis Axis instance
546. * @return void
547. */
548. abstract public function renderLabels(
549. ezcGraphRenderer $renderer,
550. ezcGraphBoundings $boundings,
551. ezcGraphCoordinate $start,
552. ezcGraphCoordinate $end,
553. ezcGraphChartElementAxis $axis
554. );
555. }
556.
557. ?>