xref: /trunk/main/extensions/source/scanner/grid.cxx (revision 2a97ec55)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_extensions.hxx"
26 #include <grid.hrc>
27 #include <cstdio>
28 #include <math.h> // for M_LN10 and M_E
29 
30 #define _USE_MATH_DEFINES
31 #include <cmath>
32 #undef _USE_MATH_DEFINES
33 
34 #include <grid.hxx>
35 
36 // for ::std::sort
37 #include <algorithm>
38 
39 ResId SaneResId( sal_uInt32 );
40 
41 /***********************************************************************
42  *
43  *	GridWindow
44  *
45  ***********************************************************************/
46 
47 // ---------------------------------------------------------------------
48 
GridWindow(double * pXValues,double * pYValues,int nValues,Window * pParent,sal_Bool bCutValues)49 GridWindow::GridWindow(double* pXValues, double* pYValues, int nValues, Window* pParent, sal_Bool bCutValues )
50 :	ModalDialog( pParent, SaneResId( GRID_DIALOG ) ),
51 	m_aGridArea( 50, 15, 100, 100 ),
52 	m_pXValues( pXValues ),
53 	m_pOrigYValues( pYValues ),
54 	m_nValues( nValues ),
55 	m_pNewYValues( NULL ),
56 	m_bCutValues( bCutValues ),
57 	m_aHandles(),
58 	m_nDragIndex( 0xffffffff ),
59 	m_aMarkerBitmap( Bitmap( SaneResId( GRID_DIALOG_HANDLE_BMP ) ), Color( 255, 255, 255 ) ),
60 	m_aOKButton( this, SaneResId( GRID_DIALOG_OK_BTN ) ),
61 	m_aCancelButton( this, SaneResId( GRID_DIALOG_CANCEL_BTN ) ),
62 	m_aResetTypeBox( this, SaneResId( GRID_DIALOG_TYPE_BOX ) ),
63 	m_aResetButton( this, SaneResId( GRID_DIALOG_RESET_BTN ) )
64 {
65 	sal_uInt16 nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_LINEAR_ASCENDING ) ) );
66 	m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_LINEAR_ASCENDING );
67 
68 	nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_LINEAR_DESCENDING ) ) );
69 	m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_LINEAR_DESCENDING );
70 
71 	nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_RESET ) ) );
72 	m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_RESET );
73 
74 	nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_EXPONENTIAL ) ) );
75 	m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_EXPONENTIAL );
76 
77 	m_aResetTypeBox.SelectEntryPos( 0 );
78 
79 	m_aResetButton.SetClickHdl( LINK( this, GridWindow, ClickButtonHdl ) );
80 
81 	SetMapMode( MapMode( MAP_PIXEL ) );
82 	Size aSize = GetOutputSizePixel();
83 	Size aBtnSize = m_aOKButton.GetOutputSizePixel();
84 	m_aGridArea.setWidth( aSize.Width() - aBtnSize.Width() - 80 );
85 	m_aGridArea.setHeight( aSize.Height() - 40 );
86 
87 	if( m_pOrigYValues && m_nValues )
88 	{
89 		m_pNewYValues = new double[ m_nValues ];
90 		memcpy( m_pNewYValues, m_pOrigYValues, sizeof( double ) * m_nValues );
91 	}
92 
93 	setBoundings( 0, 0, 1023, 1023 );
94 	computeExtremes();
95 
96 	// create left and right marker as first and last entry
97 	m_BmOffX = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Width() >> 1);
98 	m_BmOffY = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Height() >> 1);
99 	m_aHandles.push_back(impHandle(transform(findMinX(), findMinY()), m_BmOffX, m_BmOffY));
100 	m_aHandles.push_back(impHandle(transform(findMaxX(), findMaxY()), m_BmOffX, m_BmOffY));
101 
102 	FreeResource();
103 }
104 
105 // ---------------------------------------------------------------------
106 
~GridWindow()107 GridWindow::~GridWindow()
108 {
109 	if( m_pNewYValues )
110 		delete [] m_pNewYValues;
111 }
112 
113 // ---------------------------------------------------------------------
114 
findMinX()115 double GridWindow::findMinX()
116 {
117 	if( ! m_pXValues )
118 		return 0.0;
119 	double fMin = m_pXValues[0];
120 	for( int i = 1; i < m_nValues; i++ )
121 		if( m_pXValues[ i ] < fMin )
122 			fMin = m_pXValues[ i ];
123 	return fMin;
124 }
125 
126 // ---------------------------------------------------------------------
127 
findMinY()128 double GridWindow::findMinY()
129 {
130 	if( ! m_pNewYValues )
131 		return 0.0;
132 	double fMin = m_pNewYValues[0];
133 	for( int i = 1; i < m_nValues; i++ )
134 		if( m_pNewYValues[ i ] < fMin )
135 			fMin = m_pNewYValues[ i ];
136 	return fMin;
137 }
138 
139 // ---------------------------------------------------------------------
140 
findMaxX()141 double GridWindow::findMaxX()
142 {
143 	if( ! m_pXValues )
144 		return 0.0;
145 	double fMax = m_pXValues[0];
146 	for( int i = 1; i < m_nValues; i++ )
147 		if( m_pXValues[ i ] > fMax )
148 			fMax = m_pXValues[ i ];
149 	return fMax;
150 }
151 
152 // ---------------------------------------------------------------------
153 
findMaxY()154 double GridWindow::findMaxY()
155 {
156 	if( ! m_pNewYValues )
157 		return 0.0;
158 	double fMax = m_pNewYValues[0];
159 	for( int i = 1; i < m_nValues; i++ )
160 		if( m_pNewYValues[ i ] > fMax )
161 			fMax = m_pNewYValues[ i ];
162 	return fMax;
163 }
164 
165 // ---------------------------------------------------------------------
166 
computeExtremes()167 void GridWindow::computeExtremes()
168 {
169 	if( m_nValues && m_pXValues && m_pOrigYValues )
170 	{
171 		m_fMaxX = m_fMinX = m_pXValues[0];
172 		m_fMaxY = m_fMinY = m_pOrigYValues[0];
173 		for( int i = 1; i < m_nValues; i++ )
174 		{
175 			if( m_pXValues[ i ] > m_fMaxX )
176 				m_fMaxX = m_pXValues[ i ];
177 			else if( m_pXValues[ i ] < m_fMinX )
178 				m_fMinX = m_pXValues[ i ];
179 			if( m_pOrigYValues[ i ] > m_fMaxY )
180 				m_fMaxY = m_pOrigYValues[ i ];
181 			else if( m_pOrigYValues[ i ] < m_fMinY )
182 				m_fMinY = m_pOrigYValues[ i ];
183 		}
184 		setBoundings( m_fMinX, m_fMinY, m_fMaxX, m_fMaxY );
185 	}
186 }
187 
188 // ---------------------------------------------------------------------
189 
transform(double x,double y)190 Point GridWindow::transform( double x, double y )
191 {
192 	Point aRet;
193 
194 	aRet.X() = (long)( ( x - m_fMinX ) *
195 		(double)m_aGridArea.GetWidth() / ( m_fMaxX - m_fMinX )
196 		+ m_aGridArea.Left() );
197 	aRet.Y() = (long)(
198 		m_aGridArea.Bottom() -
199 		( y - m_fMinY ) *
200 		(double)m_aGridArea.GetHeight() / ( m_fMaxY - m_fMinY ) );
201 	return aRet;
202 }
203 
204 // ---------------------------------------------------------------------
205 
transform(const Point & rOriginal,double & x,double & y)206 void GridWindow::transform( const Point& rOriginal, double& x, double& y )
207 {
208 	x = ( rOriginal.X() - m_aGridArea.Left() ) * (m_fMaxX - m_fMinX) / (double)m_aGridArea.GetWidth() + m_fMinX;
209 	y = ( m_aGridArea.Bottom() - rOriginal.Y() ) * (m_fMaxY - m_fMinY) / (double)m_aGridArea.GetHeight() + m_fMinY;
210 }
211 
212 // ---------------------------------------------------------------------
213 
drawLine(double x1,double y1,double x2,double y2)214 void GridWindow::drawLine( double x1, double y1, double x2, double y2 )
215 {
216 	DrawLine( transform( x1, y1 ), transform( x2, y2 ) );
217 }
218 
219 // ---------------------------------------------------------------------
220 
computeChunk(double fMin,double fMax,double & fChunkOut,double & fMinChunkOut)221 void GridWindow::computeChunk( double fMin, double fMax, double& fChunkOut, double& fMinChunkOut )
222 {
223 	// get a nice chunk size like 10, 100, 25 or such
224 	fChunkOut = ( fMax - fMin ) / 6.0;
225 	int logchunk = (int)std::log10( fChunkOut );
226 	int nChunk = (int)( fChunkOut / std::exp( (double)(logchunk-1) * M_LN10 ) );
227 	if( nChunk >= 75 )
228 		nChunk = 100;
229 	else if( nChunk >= 35 )
230 		nChunk = 50;
231 	else if ( nChunk > 20 )
232 		nChunk = 25;
233 	else if ( nChunk >= 13 )
234 		nChunk = 20;
235 	else if( nChunk > 5 )
236 		nChunk = 10;
237 	else
238 		nChunk = 5;
239 	fChunkOut = (double) nChunk * exp( (double)(logchunk-1) * M_LN10 );
240 	// compute whole chunks fitting into fMin
241 	nChunk = (int)( fMin / fChunkOut );
242 	fMinChunkOut = (double)nChunk * fChunkOut;
243 	while( fMinChunkOut < fMin )
244 		fMinChunkOut += fChunkOut;
245 }
246 
247 // ---------------------------------------------------------------------
248 
computeNew()249 void GridWindow::computeNew()
250 {
251 	if(2L == m_aHandles.size())
252 	{
253 		// special case: only left and right markers
254 		double xleft, yleft;
255 		double xright, yright;
256 		transform(m_aHandles[0L].maPos, xleft, yleft);
257 		transform(m_aHandles[1L].maPos, xright, yright );
258 		double factor = (yright-yleft)/(xright-xleft);
259 		for( int i = 0; i < m_nValues; i++ )
260 		{
261 			m_pNewYValues[ i ] = yleft + ( m_pXValues[ i ] - xleft )*factor;
262 		}
263 	}
264 	else
265 	{
266 		// sort markers
267 		std::sort(m_aHandles.begin(), m_aHandles.end());
268 		const int nSorted = m_aHandles.size();
269 		int i;
270 
271 		// get node arrays
272 		double* nodex = new double[ nSorted ];
273 		double* nodey = new double[ nSorted ];
274 
275 		for( i = 0L; i < nSorted; i++ )
276 			transform( m_aHandles[i].maPos, nodex[ i ], nodey[ i ] );
277 
278 		for( i = 0; i < m_nValues; i++ )
279 		{
280 			double x = m_pXValues[ i ];
281 			m_pNewYValues[ i ] = interpolate( x, nodex, nodey, nSorted );
282 			if( m_bCutValues )
283 			{
284 				if( m_pNewYValues[ i ] > m_fMaxY )
285 					m_pNewYValues[ i ] = m_fMaxY;
286 				else if( m_pNewYValues[ i ] < m_fMinY )
287 					m_pNewYValues[ i ] = m_fMinY;
288 			}
289 		}
290 
291 		delete [] nodex;
292 		delete [] nodey;
293 	}
294 }
295 
296 // ---------------------------------------------------------------------
297 
interpolate(double x,double * pNodeX,double * pNodeY,int nNodes)298 double GridWindow::interpolate(
299 	double x,
300 	double* pNodeX,
301 	double* pNodeY,
302 	int nNodes )
303 {
304 	// compute Lagrange interpolation
305 	double ret = 0;
306 	for( int i = 0; i < nNodes; i++ )
307 	{
308 		double sum = pNodeY[ i ];
309 		for( int n = 0; n < nNodes; n++ )
310 		{
311 			if( n != i )
312 			{
313 				sum *= x - pNodeX[ n ];
314 				sum /= pNodeX[ i ] - pNodeX[ n ];
315 			}
316 		}
317 		ret += sum;
318 	}
319 	return ret;
320 }
321 
322 // ---------------------------------------------------------------------
323 
setBoundings(double fMinX,double fMinY,double fMaxX,double fMaxY)324 void GridWindow::setBoundings( double fMinX, double fMinY, double fMaxX, double fMaxY )
325 {
326 	m_fMinX = fMinX;
327 	m_fMinY = fMinY;
328 	m_fMaxX = fMaxX;
329 	m_fMaxY = fMaxY;
330 
331 	computeChunk( m_fMinX, m_fMaxX, m_fChunkX, m_fMinChunkX );
332 	computeChunk( m_fMinY, m_fMaxY, m_fChunkY, m_fMinChunkY );
333 }
334 
335 // ---------------------------------------------------------------------
336 
drawGrid()337 void GridWindow::drawGrid()
338 {
339 	char pBuf[256];
340 	SetLineColor( Color( COL_BLACK ) );
341 	// draw vertical lines
342 	for( double fX = m_fMinChunkX; fX < m_fMaxX; fX += m_fChunkX )
343 	{
344 		drawLine( fX, m_fMinY, fX, m_fMaxY );
345 		// draw tickmarks
346 		Point aPt = transform( fX, m_fMinY );
347         std::sprintf( pBuf, "%g", fX );
348 		String aMark( pBuf, gsl_getSystemTextEncoding() );
349 		Size aTextSize( GetTextWidth( aMark ), GetTextHeight() );
350 		aPt.X() -= aTextSize.Width()/2;
351 		aPt.Y() += aTextSize.Height()/2;
352 		DrawText( aPt, aMark );
353 	}
354 	// draw horizontal lines
355 	for( double fY = m_fMinChunkY; fY < m_fMaxY; fY += m_fChunkY )
356 	{
357 		drawLine( m_fMinX, fY, m_fMaxX, fY );
358 		// draw tickmarks
359 		Point aPt = transform( m_fMinX, fY );
360         std::sprintf( pBuf, "%g", fY );
361 		String aMark( pBuf, gsl_getSystemTextEncoding() );
362 		Size aTextSize( GetTextWidth( aMark ), GetTextHeight() );
363 		aPt.X() -= aTextSize.Width() + 2;
364 		aPt.Y() -= aTextSize.Height()/2;
365 		DrawText( aPt, aMark );
366 	}
367 
368 	// draw boundings
369 	drawLine( m_fMinX, m_fMinY, m_fMaxX, m_fMinY );
370 	drawLine( m_fMinX, m_fMaxY, m_fMaxX, m_fMaxY );
371 	drawLine( m_fMinX, m_fMinY, m_fMinX, m_fMaxY );
372 	drawLine( m_fMaxX, m_fMinY, m_fMaxX, m_fMaxY );
373 }
374 
375 // ---------------------------------------------------------------------
376 
drawOriginal()377 void GridWindow::drawOriginal()
378 {
379 	if( m_nValues && m_pXValues && m_pOrigYValues )
380 	{
381 		SetLineColor( Color( COL_RED ) );
382 		for( int i = 0; i < m_nValues-1; i++ )
383 		{
384 			drawLine( m_pXValues[ i   ], m_pOrigYValues[ i   ],
385 					  m_pXValues[ i+1 ], m_pOrigYValues[ i+1 ] );
386 		}
387 	}
388 }
389 
390 // ---------------------------------------------------------------------
391 
drawNew()392 void GridWindow::drawNew()
393 {
394 	if( m_nValues && m_pXValues && m_pNewYValues )
395 	{
396 		SetClipRegion( m_aGridArea );
397 		SetLineColor( Color( COL_YELLOW ) );
398 		for( int i = 0; i < m_nValues-1; i++ )
399 		{
400 			drawLine( m_pXValues[ i   ], m_pNewYValues[ i   ],
401 					  m_pXValues[ i+1 ], m_pNewYValues[ i+1 ] );
402 		}
403 		SetClipRegion();
404 	}
405 }
406 
407 // ---------------------------------------------------------------------
408 
drawHandles()409 void GridWindow::drawHandles()
410 {
411 	for(sal_uInt32 i(0L); i < m_aHandles.size(); i++)
412 	{
413 		m_aHandles[i].draw(*this, m_aMarkerBitmap);
414 	}
415 }
416 
417 // ---------------------------------------------------------------------
418 
Paint(const Rectangle & rRect)419 void GridWindow::Paint( const Rectangle& rRect )
420 {
421 	ModalDialog::Paint( rRect );
422 	drawGrid();
423 	drawOriginal();
424 	drawNew();
425 	drawHandles();
426 }
427 
428 // ---------------------------------------------------------------------
429 
MouseMove(const MouseEvent & rEvt)430 void GridWindow::MouseMove( const MouseEvent& rEvt )
431 {
432 	if( rEvt.GetButtons() == MOUSE_LEFT && m_nDragIndex != 0xffffffff )
433 	{
434 		Point aPoint( rEvt.GetPosPixel() );
435 
436 		if( m_nDragIndex == 0L || m_nDragIndex == m_aHandles.size() - 1L)
437 		{
438 			aPoint.X() = m_aHandles[m_nDragIndex].maPos.X();
439 		}
440 		else
441 		{
442 			if(aPoint.X() < m_aGridArea.Left())
443 				aPoint.X() = m_aGridArea.Left();
444 			else if(aPoint.X() > m_aGridArea.Right())
445 				aPoint.X() = m_aGridArea.Right();
446 		}
447 
448 		if( aPoint.Y() < m_aGridArea.Top() )
449 			aPoint.Y() = m_aGridArea.Top();
450 		else if( aPoint.Y() > m_aGridArea.Bottom() )
451 			aPoint.Y() = m_aGridArea.Bottom();
452 
453 		if( aPoint != m_aHandles[m_nDragIndex].maPos )
454 		{
455 			m_aHandles[m_nDragIndex].maPos = aPoint;
456 			Invalidate( m_aGridArea );
457 		}
458 	}
459 
460 	ModalDialog::MouseMove( rEvt );
461 }
462 
463 // ---------------------------------------------------------------------
464 
MouseButtonUp(const MouseEvent & rEvt)465 void GridWindow::MouseButtonUp( const MouseEvent& rEvt )
466 {
467 	if( rEvt.GetButtons() == MOUSE_LEFT )
468 	{
469 		if( m_nDragIndex != 0xffffffff )
470 		{
471 			m_nDragIndex = 0xffffffff;
472 			computeNew();
473 			Invalidate( m_aGridArea );
474 			Paint( m_aGridArea );
475 		}
476 	}
477 
478 	ModalDialog::MouseButtonUp( rEvt );
479 }
480 
481 // ---------------------------------------------------------------------
482 
MouseButtonDown(const MouseEvent & rEvt)483 void GridWindow::MouseButtonDown( const MouseEvent& rEvt )
484 {
485 	Point aPoint( rEvt.GetPosPixel() );
486 	sal_uInt32 nMarkerIndex = 0xffffffff;
487 
488 	for(sal_uInt32 a(0L); nMarkerIndex == 0xffffffff && a < m_aHandles.size(); a++)
489 	{
490 		if(m_aHandles[a].isHit(*this, aPoint))
491 		{
492 			nMarkerIndex = a;
493 		}
494 	}
495 
496 	if( rEvt.GetButtons() == MOUSE_LEFT )
497 	{
498 		// user wants to drag a button
499 		if( nMarkerIndex != 0xffffffff )
500 		{
501 			m_nDragIndex = nMarkerIndex;
502 		}
503 	}
504 	else if( rEvt.GetButtons() == MOUSE_RIGHT )
505 	{
506 		// user wants to add/delete a button
507 		if( nMarkerIndex != 0xffffffff )
508 		{
509 			if( nMarkerIndex != 0L && nMarkerIndex != m_aHandles.size() - 1L)
510 			{
511 				// delete marker under mouse
512 				if( m_nDragIndex == nMarkerIndex )
513 					m_nDragIndex = 0xffffffff;
514 
515 				m_aHandles.erase(m_aHandles.begin() + nMarkerIndex);
516 			}
517 		}
518 		else
519 		{
520 			m_BmOffX = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Width() >> 1);
521 			m_BmOffY = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Height() >> 1);
522 			m_aHandles.push_back(impHandle(aPoint, m_BmOffX, m_BmOffY));
523 		}
524 
525 		computeNew();
526 		Invalidate( m_aGridArea );
527 		Paint( m_aGridArea );
528 	}
529 
530 	ModalDialog::MouseButtonDown( rEvt );
531 }
532 
533 // ---------------------------------------------------------------------
534 
IMPL_LINK(GridWindow,ClickButtonHdl,Button *,pButton)535 IMPL_LINK( GridWindow, ClickButtonHdl, Button*, pButton )
536 {
537 	if( pButton == &m_aResetButton )
538 	{
539 		int nType = (int)(sal_IntPtr)m_aResetTypeBox.GetEntryData( m_aResetTypeBox.GetSelectEntryPos() );
540 		switch( nType )
541 		{
542 			case RESET_TYPE_LINEAR_ASCENDING:
543 			{
544 				for( int i = 0; i < m_nValues; i++ )
545 				{
546 					m_pNewYValues[ i ] = m_fMinY + (m_fMaxY-m_fMinY)/(m_fMaxX-m_fMinX)*(m_pXValues[i]-m_fMinX);
547 				}
548 			}
549 			break;
550 			case RESET_TYPE_LINEAR_DESCENDING:
551 			{
552 				for( int i = 0; i < m_nValues; i++ )
553 				{
554 					m_pNewYValues[ i ] = m_fMaxY - (m_fMaxY-m_fMinY)/(m_fMaxX-m_fMinX)*(m_pXValues[i]-m_fMinX);
555 				}
556 			}
557 			break;
558 			case RESET_TYPE_RESET:
559 			{
560 				if( m_pOrigYValues && m_pNewYValues && m_nValues )
561 					memcpy( m_pNewYValues, m_pOrigYValues, m_nValues*sizeof(double) );
562 			}
563 			break;
564 			case RESET_TYPE_EXPONENTIAL:
565 			{
566 				for( int i = 0; i < m_nValues; i++ )
567 				{
568 					m_pNewYValues[ i ] = m_fMinY + (m_fMaxY-m_fMinY)*(std::exp((m_pXValues[i]-m_fMinX)/(m_fMaxX-m_fMinX))-1.0)/(M_E-1.0);
569 				}
570 			}
571 			break;
572 
573 			default:
574 				break;
575 		}
576 
577 		for(sal_uInt32 i(0L); i < m_aHandles.size(); i++)
578 		{
579 			// find nearest xvalue
580 			double x, y;
581 			transform( m_aHandles[i].maPos, x, y );
582 			int nIndex = 0;
583 			double delta = std::fabs( x-m_pXValues[0] );
584 			for( int n = 1; n < m_nValues; n++ )
585 			{
586 				if( delta > std::fabs( x - m_pXValues[ n ] ) )
587 				{
588 					delta = std::fabs( x - m_pXValues[ n ] );
589 					nIndex = n;
590 				}
591 			}
592 			if( 0 == i )
593 				m_aHandles[i].maPos = transform( m_fMinX, m_pNewYValues[ nIndex ] );
594 			else if( m_aHandles.size() - 1L == i )
595 				m_aHandles[i].maPos = transform( m_fMaxX, m_pNewYValues[ nIndex ] );
596 			else
597 				m_aHandles[i].maPos = transform( m_pXValues[ nIndex ], m_pNewYValues[ nIndex ] );
598 		}
599 
600 		Invalidate( m_aGridArea );
601 		Paint(Rectangle());
602 	}
603 	return 0;
604 }
605