|
A rounding bug in symbol placement in scatter plot |
Posted by Danila on Mar-11-2014 16:45 |
|
Hello.
I'm placing a scatter plot of crosses above a line graph, using the same values of X and Y. X values are equally spaced with an interval of 300 seconds between them. I use
chart.xAxis().setLabelFormat('{={value}|hh:nn}')
on the X axis.
I see that the centers of crosses shift horizontally relative to the point position on the line. Please see the zoom-in view into the generated pdf for a reference. In the Example02.png below, the cross on the bottom of the image is shifted to the right too much. In Example03.png, the top cross is shifted to the left too much.
In the Example01.png, I show the same portion of the graph twice (I overlayed them in mspaint). As one can see, there is aperiodicity of the distances between the markers. Example04.png confirms that this aperiodicity is observed even if I use CircleShape instead of the cross.
Looking at the .svg file that is generated while preparing the .pdf file, I see the following:
...
<symbol id='g12535' viewBox='0 0 11 11' preserveAspectRatio='none'>
<g font-family='Arial' font-size='11px' fill='none' fill-rule='evenodd' stroke-linecap='square'>
<path id='b12537' stroke-linecap='butt' d='M5.5,5.5 L5.5,10.5 L5.5,5.5 L10.5,5.5 L5.5,5.5 L5.5,0.5 L5.5,5.5 L0.5,5.5 L5.5,5.5 Z'/>
<use xlink:href='#b12537' fill='#abcdef' stroke='#000000'/>
</g>
</symbol>
</defs>
...
<use x='57' y='273' width='11' height='11' xlink:href='#g12535'/>
<use x='71' y='456' width='11' height='11' xlink:href='#g12535'/>
<use x='86' y='498' width='11' height='11' xlink:href='#g12535'/>
<use x='100' y='462' width='11' height='11' xlink:href='#g12535'/>
<use x='114' y='511' width='11' height='11' xlink:href='#g12535'/>
<use x='128' y='505' width='11' height='11' xlink:href='#g12535'/>
<use x='143' y='533' width='11' height='11' xlink:href='#g12535'/>
<use x='157' y='524' width='11' height='11' xlink:href='#g12535'/>
<use x='171' y='553' width='11' height='11' xlink:href='#g12535'/>
<use x='185' y='560' width='11' height='11' xlink:href='#g12535'/>
<use x='199' y='548' width='11' height='11' xlink:href='#g12535'/>
<use x='214' y='579' width='11' height='11' xlink:href='#g12535'/>
<use x='228' y='556' width='11' height='11' xlink:href='#g12535'/>
<use x='242' y='520' width='11' height='11' xlink:href='#g12535'/>
...
As you can see, most of the time the distance between ?x? elements is 4, but sometimes it is 5. This is definitely a rounding (or unnecessary translation to int) working in a place where it should not be. I also can see the elements jumping even in the graph displayed inside a window (not exported to svg), so probably this loss of precision happens before .svg export.
The svg format supports floating point ? I see, for example, ?<polyline id='b12564' points='62.615 278.446 76.846 461.71 91.077 503.592 105.308 467.618 119.538 516.124 133.769 510.846 148 538.814 162.231 529.921 176.462 558.973?? in the same file.
Can you please let me know if there is an option in ChartDirector that will switch off this rounding of scatter element X position? Or maybe this is a bug in the handling of x axis with time values in it?
I use ChartDirector 5.1.
My X data is like this: X = [34350.0, 34650.0, 34950.0, 35250.0, 35550.0, 35850.0, 36150.0, 36450.0, 36750.0, 37050.0, 37350.0, 37650.0, 37950.0, 38250.0,
38550.0, 38850.0, 39150.0, 39450.0, 39750.0, 40050.0, 40350.0, 40650.0, 40950.0, 41250.0, 41550.0, 41850.0, 42150.0, 42450.0, 42750.0, 43050.0,
43350.0, 43650.0, 43950.0, 44250.0, 44550.0, 44850.0, 45150.0, 45450.0, 45750.0, 46050.0, 46350.0, 46650.0, 46950.0, 47250.0,
47550.0, 47850.0, 48150.0, 48450.0, 48750.0, 49050.0, 49350.0, 49650.0, 49950.0, 50250.0, 50550.0, 50850.0, 51150.0, 51450.0,
51750.0, 52050.0, 52350.0, 52650.0, 52950.0, 53250.0, 53550.0, 53850.0, 54150.0, 54450.0, 54750.0, 55050.0,
55350.0, 55650.0, 55950.0, 56250.0, 56550.0, 56850.0, 57150.0, 57450.0]
This is the part of the code that places symbols on top of the line:
plotWindowFilteredXs = [ x for x in xs if x >= minX and x<= maxX ]
layer0 = c.addLineLayer2()
dataset0=layer0.addDataSet(filteredValues, curve.color, curve.name)
layer0.setXData(plotWindowFilteredXs)
layer0.setLineWidth(curve.lineWidth)
dataset0.setDataSymbol(curve.symbol, curve.symbolSize, \\
curve.fillColor, curve.edgeColor, curve.lineWidth)
With best regards,
Danila
|
Re: A rounding bug in symbol placement in scatter plot |
Posted by Peter Kwan on Mar-11-2014 22:57 |
|
Hi Danila,
You are correct that ChartDirector rounds certain coordinates to integers. For raster
outputs (bitmap images, such as PNG, JPG, BMP, GIF), if the coordinates are not rounded
(called grid-fitting), each symbol may look different. For example, symbols that happened
to be at integer coordinates will look shape and clear, while symbols at non-integer
coordinates will look blurred due to anti-alias, and every symbol can be blurred
differently.
By using integer coordinates, the symbols will always be shape and the same. It also
allows ChartDirector to output bitmap images much faster, as it only needs to rasterize
the symbol once and "copy them". (It can copy them because each symbol looks the
same.) That is one reason why ChartDirector can render hundreds of thousands or
millions of points quickly.
Rounding causes +/- 0.5 pixel of error, which is generally not noticible in raster outputs.
For SVG, if the chart is viewed at 100% size, it is similar to a raster image, and rounding
can make all symbols looking the same. As SVG can be magnified indefinitely by the
viewer, if the chart is magnified large enough, the +/- 0.5 pixel error will become
noticible.
Unluckily, currently there is no option to avoid the rounding for SVG.
In some cases, for equally spaced points, it may be possible to adjust the plot area width
so that the distance between the points is naturally an integer, and so no rounding is
needed.
An example is like:
... create chart as usual .....
#Automatically adjust the plot area so that the plot area and the axis stays within a
#bounding box. Also implies auto-scaling the axis.
c.packPlotArea(20, 30, c.getWidth() - 30, c.getHeight() - 20)
#Determine the spacing for 300 seconds
spacing = 300.0 / (c.xAxis().getMaxValue() - c.xAxis().getMinValue()) *
(c.getXCoor(c.xAxis().getMaxValue()) - c.getXCoor(c.xAxis().getMinValue()))
#Determine the plot area width so that the spacing would become the integer
math.ceil(spacing)
desiredWidth = math.ceil(spacing) / 300.0 * (c.xAxis().getMaxValue() -
c.xAxis().getMinValue())
#Add (desiredWidth - c.getPlotArea().getWidth()) to the bounding box width
c.packPlotArea(20, 30, c.getWidth() - 30 + desiredWidth - c.getPlotArea().getWidth(),
c.getHeight() - 20)
Regards
Peter Kwan |
|