Step by step breakdown of Visifire rendering logic

admin

Introduction

Since the release of Visifire, we have got a lot of positive response from users on the simplicity, looks, customizability of Visifire over other Charting Controls in the market. What made it possible is, Visifire’s underlying set of algorithms that determine the best Visualization for a given set of parameters. So even when you provide bare minimum data required for rendering, Visifire renders a nice looking Chart which has tooltips, default interactivity like explode in Pie Chart, auto font-color for labels depending on the Chart Background color, auto indexing of DataPoints when XValue is not provided, etc.

Though Visifire makes effort to find the best visualization for any given case, its not always enough for all the applications. So we do have relevant properties for making it possible to customize according to individual needs. In this blog am going to explain the default rendering behavior of Visifire and how one can override them according to his needs.

1. Pictorial representation of Various elements/regions in a Chart

Silverlight Chart Control

2. A Minimal Chart

Below is a Minimal Chart and the XAML required to create it.

Img2

<vc:Chart xmlns:vc="clr-namespace:Visifire.Charts;assembly=SLVisifire.Charts"
          Width="500" Height="300">            
    <vc:Chart.Series>
 
        <vc:DataSeries>
            <vc:DataSeries.DataPoints>
                <vc:DataPoint YValue="207349" />
                <vc:DataPoint YValue="274316" />
                <vc:DataPoint YValue="318845" />
                <vc:DataPoint YValue="345254" />
                <vc:DataPoint YValue="351139" />
            </vc:DataSeries.DataPoints>
        </vc:DataSeries>
 
    </vc:Chart.Series>
</vc:Chart>

 

In the above XML we have defined YValue for all the DataPoints. When we look at the rendered chart, we can observe that the DataPoints have been numbered from 1 to 5. DataPoints are numbered automatically in the order of their appearance. If you want to override this default behavior, you can either set XValue for numbers or AxisXLabel in case you want to display string. Note that setting XValue will place DataPoints according to the position of XValue on X-Axis. Chart also shows ToolTip by default.

So by default the Chart adds

  1. XValue – index values
  2. ToolTip
  3. Comma for every thousandth digit on YAxis

3. Customizing Visifire

Chart in the previous section is Minimal in the sense we have not set any property required to customize the Chart. Below are few of the common stuff that you might want to add to the Chart.

  1. Title
  2. XValue/AxisXLabel for each DataPoint
  3. Prefix a symbol to the labels on YAxis – $ for example to make Label to look like $50,000
  4. Customize Values shown in the Tooltip

Below is how the Chart looks after customization

Img3

<vc:Chart xmlns:vc="clr-namespace:Visifire.Charts;assembly=SLVisifire.Charts"
           Width="500" Height="300">
     
     <vc:Chart.Titles>
         <vc:Title Text="Product Sales by Month"></vc:Title>    
     </vc:Chart.Titles>
     
     <vc:Chart.AxesY>
         <vc:Axis Prefix="$"></vc:Axis>
     </vc:Chart.AxesY>
     
     <vc:Chart.Series>
         <vc:DataSeries ToolTipText="#AxisXLabel, Y = #YValue">
             <vc:DataSeries.DataPoints>
                 <vc:DataPoint AxisXLabel="Jan" YValue="207349" />
                 <vc:DataPoint AxisXLabel="Feb" YValue="274316" />
                 <vc:DataPoint AxisXLabel="Mar" YValue="318845" />
                 <vc:DataPoint AxisXLabel="Apr" YValue="345254" />
                 <vc:DataPoint AxisXLabel="May" YValue="351139" />
             </vc:DataSeries.DataPoints>
         </vc:DataSeries>
     </vc:Chart.Series>
 </vc:Chart>

In the above example I’ve used AxisXLabel, as the Labels on the XAxis are strings. If the values are numeric, then you can use XValue. In case you set both the properties, then the DataPoints are placed according to XValue and XValues are replaced by AxisXLabels wherever available. Below is an example.

Img4

<vc:Chart xmlns:vc="clr-namespace:Visifire.Charts;assembly=SLVisifire.Charts"
          Width="500" Height="300">
    
    <vc:Chart.Series>
        <vc:DataSeries ToolTipText="#AxisXLabel, Y = #YValue">
            <vc:DataSeries.DataPoints>
                <vc:DataPoint XValue="1" YValue="207349" />
                <vc:DataPoint XValue="3" YValue="274316" />
                <vc:DataPoint XValue="4" YValue="318845" />
                <vc:DataPoint XValue="5" AxisXLabel="Five" YValue="345254" />
                <vc:DataPoint XValue="6" YValue="351139" />
            </vc:DataSeries.DataPoints>
        </vc:DataSeries>                
    </vc:Chart.Series>
 
</vc:Chart>

4. How the PlotArea width is calculated and how to control it

While allocating region for various elements in the Chart, PlotArea viewport width is determined at the end after allocating region for Title, Legend, AxisX, AxisY. So for example, if we place Legend to the right, then the viewport size reduces depending on the Legend Size. Below is an illustration.

Img5

You can see that the PlotArea "Viewport" width has reduced because of the Legend. But at the same time, the Actual PlotArea Size can extend further resulting in a ScrollBar. This can happen whenever ScrollingEnabled is set to true.

Through some trials, we have determined the relation between the DataPoint Width and Chart Size which makes the Chart to look good/readable and accordingly we are setting the PlotArea Width. Below is the algorithm.

If, ( Max Difference of XValues / Min Difference of XValues ) > MagicNumber, that means PlotArea width needs to be more than that of the viewport width – which results in ScrollBar. We found the equation for MagicNumber through trials with varying chart sizes and varying number of DataPoints. Below is how we calculate it.

MagicNumber = Current PlotArea width * 35 / 550

Now, PlotArea width can be calculated as below.

PlotArea width = ( Max Difference of XValues / Min Difference of XValues ) * 550 / 34

For multi series Column and Bar Charts, we need to multiply 550 with appropriate number of series that get rendered side by side.

5. How Datapoint width is calculated and how to control it

In case of Column and Bar Charts, DataPointWidth is determined based on the two closest DataPoints. Say, the XValues of 4 DataPoints are {1, 4, 5, 7, 9}, then the closest two DataPoints are 2nd and 3rd with difference in XValue of 1. So, in order to be able to draw both the DataPoints without overlap, we need to divide the available space between the two. As a result of which, Maximum Width of DataPoints will be equal to the minimum difference between XValues.

Now, we also need to give some spacing between the two DataPoints which is determined as .1 * ( Minimum Difference of XValues ). So, the DataPoint width finally comes down to .9 * ( Minimum Difference of XValues ) . In case the Chart is MultiSeries, then we need to place multiple columns within the same region. So, the column width calculated as above is divided by the number of series needed to be drawn.

Img6

Img7

In certain cases, the calculated width can go below 1px which will make Columns invisible. So, Visifire sets a lower limit of 2px to DataPoint width. You can also control the width of DataPoint by setting DataPointWidth property as a percentage of PlotArea ViewPort width. For example, setting the DataPointWidth to 5 will result in a Chart as below.

Img8

Please note that increasing/decreasing the Width of DataPoints will not change the relative position of DataPoints. So, if you increase the DataPointWidth over a certain range, columns will actually overlap. In the next section I’ll explain how you can overcome this limitation.

6. How Axis, Interval and AxisLabels work

Whenever you create a chart, by default Visifire tries to calculate the best possible Range and interval for Axis so that it is easier for the Humans to grasp/read. Human readable in the sense, it tries to make the interval a multiple of 1,2,5 or 10 and tries to keep the number of intervals around 8. So, whenever the interval is not 1, it might skip some of the AxisXLabel if you have defined.

Img10

<vc:DataSeries.DataPoints>
     <vc:DataPoint XValue="1" YValue="207349" />
     <vc:DataPoint XValue="3" YValue="274316" />
     <vc:DataPoint XValue="4" AxisXLabel="Four" YValue="318845" />
     <vc:DataPoint XValue="5" YValue="345254" />
     <vc:DataPoint XValue="6" YValue="351139" />
     <vc:DataPoint XValue="7" YValue="217349" />
     <vc:DataPoint XValue="9" YValue="224316" />
     <vc:DataPoint XValue="10" YValue="298845" />
     <vc:DataPoint XValue="11" YValue="335254" />
     <vc:DataPoint XValue="13" YValue="371139" />
 </vc:DataSeries.DataPoints>

Note that I’ve skipped all other elements apart from DataPoints collection just to keep the xaml compact. In the above example you can see that the interval is 2. So, if in case you set AxisXLabel for 3rd DataPoint, then it’ll not be shown. You can override this default behavior by setting the interval property of Axis to 1 as shown below.

Img11

<vc:Chart.AxesX>
    <vc:Axis Interval="1"></vc:Axis>
</vc:Chart.AxesX>
 
<vc:Chart.Series>
    <vc:DataSeries ToolTipText="#AxisXLabel, Y = #YValue">
        <vc:DataSeries.DataPoints>
            <vc:DataPoint XValue="1" YValue="207349" />
            <vc:DataPoint XValue="3" YValue="274316" />
            <vc:DataPoint XValue="4" AxisXLabel="Four" YValue="318845" />
            <vc:DataPoint XValue="5" YValue="345254" />
            <vc:DataPoint XValue="6" YValue="351139" />
            <vc:DataPoint XValue="7" YValue="217349" />
            <vc:DataPoint XValue="9" YValue="224316" />
            <vc:DataPoint XValue="10" YValue="298845" />
            <vc:DataPoint XValue="11" YValue="335254" />
            <vc:DataPoint XValue="13" YValue="371139" />
        </vc:DataSeries.DataPoints>
    </vc:DataSeries>          
</vc:Chart.Series>

While rendering the Axis, Visifire tries to automatically place AxisLabels in such a way that they don’t overlap. Say, if we change the AxisXLabel from "Four" to "XValue is Four", then it is obvious that the Label is long and it’ll overlap the neighboring AxisLabels. So, Visifire starts showing labels in 2 rows as shown below.

Img12

Now if we further increase the AxisXLabel to "DataPoint XValue is Four", then it becomes difficult to show Labels in two rows. So the next alternative will be to rotate the AxisLabel so that it doesn’t overlap the neighboring AxisXLabels.

Img13

You can customize Axis by setting Interval, MinimumValue, MaximumValue, etc as mentioned in the following document.

7. How Labels are placed

Below is an image showing Labels in Visifire. In order to show Label, one needs to enable them by setting LabelEnabled to True in DataSeries.

Img14

<vc:DataSeries LabelEnabled="True" ToolTipText="#AxisXLabel, Y = #YValue">
    <vc:DataSeries.DataPoints>
        <vc:DataPoint YValue="207349" />
        <vc:DataPoint YValue="274316" />
        <vc:DataPoint YValue="318845" />
        <vc:DataPoint LabelText="Label" YValue="345254" />
        <vc:DataPoint YValue="351139" />
        <vc:DataPoint YValue="217349" />
        <vc:DataPoint YValue="224316" />
        <vc:DataPoint YValue="298845" />
        <vc:DataPoint YValue="335254" />
        <vc:DataPoint YValue="371139" />                           
    </vc:DataSeries.DataPoints>
</vc:DataSeries>

See that Labels and AxisLabels are different. AxisLabel is one which is shown below the Axis and Labels are shows on/above the DataPoints.

You can observe that few of the Labels are outside the DataPoint and few are inside. Few have Black font color and few have White. Though the placement seems random at first place, it is not. Below I’ll explain the flow.

While placing Label for a DataPoint, Visifire first tries to place it horizonatally outside DataPoint. If the DataPoint width is more than the Label’s width, then Label is placed horizontally as in case of 4th DataPoint. If the DataPoint width is less than that of Label’s, then the Label is placed vertically. Now, whether the Label is placed on top of columns or inside the columns is determined by the availability of space outside the Column. If there is enough space outside the column, then the Labels are placed outside of DataPoint. If not, then the Labels are placed inside DataPoint.


Comments

  1. October 20th, 2009 | 1:29 am

    Great, how about package all gallery’s chart script into documentation?

  2. vivek
    November 2nd, 2009 | 8:43 am

    Hi jeason,

    Do you mean to say all gallery charts XML should be added inside documentation?

  3. CI
    December 19th, 2009 | 9:37 am

    Can & How to use Visifire Chart with XCP Hostting Native App?

Leave a reply