Visifire 4.0 introduces Silverlight & WPF Gauges

Chirag
Hi,

We are pleased to announce the release of Visifire 4.0 beta. As the title says Visifire adds Gauges to its quiver already filled with wide range of charts. Visifire supports Circular & Linear Gauges. It also provides many ready-to-use templates to achieve the looks you need – Speedometer Chart, Dial Chart, Angular Gauge, Thermometer to name a few. Play around with the Gauge Designer here to try Gauges hands-on. Showcased below are some Silverlight Gauge samples.

Get Microsoft Silverlight

Checkout wide range of gauges in the Gallery below:

Visit this post to create your First Silverlight Gauge in minutes.

Great news! All Enterprise Licensees will get Visifire Gauges at no charge.

Download Visifire 4.0 Trial here. We are very excited about this release & hope you like it. Let us know your feedback. We are all ears. Follow @visifire for tips on Silverlight, WPF, WP7 Charts & Gauges.

Cheers,
Team Visifire


Visifire Silverlight and WPF Charts 3.0.4 beta Released!

somnath

Hi,

Today we are releasing the much awaited Zooming feature. In this version of Zooming you will be able to zoom/scale the PlotArea of the chart using ZoomBar. You can have a look at the example below. Please follow Visifire documentation here for more info.

Get Microsoft Silverlight

Also this release contains fix for the following bug:

  • Pie chart was not behaving as expected if Exploded property was set at realtime.

You can download Visifire v3.0.4 beta here.

Cheers,
Team Visifire


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.


Visifire 2.2.3 beta 5 Released

somnath

Hi,

This release contains fix for the following bugs:

  • In Line Chart, if LightingEnabled property was set to true in DataSeries then lighting was not applied over the symbol of line shown in Legend.
  • In CandleStick multiseries Chart, color of the symbol displayed in legend was not as same as PriceUpColor defined in DataSeries.
  • StreamLineFunnel and SectionFunnel Chart threw exception if YValue property was set to true for all DataPoints.

Current release also contains the following enhancements:

  • Shadow can be applied to CandleStick and Stock Chart by setting the ShadowEnabled Property to true in DataSeries/DataPoint.
  • In CandleStick Chart, if PriceUpColor is not defined then default color for PriceUpColor will be applied from the default ColorSet named CandleLight.

Download Visifire v2.2.3 beta 5 here.

Cheers,
Team Visifire


Visifire 2.2.3 beta 4 Released

somnath

Hi,

This release contains fix for the following bugs:

  • In a Line chart if MovingMarkerEnabled property was set to true for a DataSeries, moving-marker was visible over the first DataPoint. Instead it should be visible only if mouse enters inside the PlotArea.
  • Axis was misaligned in a chart with very less height if Title property was set for it.

Download Visifire v2.2.3 beta 4 here.

Cheers,
Team Visifire


Drilldown Silverlight Charts using Visifire

Chirag

Creating Drilldown chart is a common requirement in any RIA dashboard. Drilldown charts give rich user experience. In this blog let’s see how we can create drilldown charts using Visifire.

In this sample application, chart can be drilled down till 2 levels. The default chart would show yearly sales of a fictitious company for the period 2006-2008. On click of column representing the sales for an year, monthly sales for that year is shown up. Further it can be drilled down to find product wise sales for the selected month.

This sample uses Javascript API to generate charts. The complete application can be downloaded from here. The code is completely commented & explained wherever necessary. Refer the rest of the blog if you need more help.

Click to see the sample in action

Data can be pulled in from any data source. To keep the example simple let’s store the sales data in an array.

var sales;
// Create array for the Sales data
sales = [{year: 2006, month: "Jan", ProductX: 20000, ProductY: 25000, ProductZ: 15000},
         {year: 2006, month: "Feb", ProductX: 10000, ProductY: 23000, ProductZ: 25000},
         {year: 2006, month: "Mar", ProductX: 22000, ProductY: 38000, ProductZ: 83000},
         {year: 2006, month: "Apr", ProductX: 23456, ProductY: 32435, ProductZ: 24223},
         {year: 2006, month: "May", ProductX: 25142, ProductY: 32415, ProductZ: 10923},
         {year: 2006, month: "Jun", ProductX: 18254, ProductY: 15342, ProductZ: 10934},
         {year: 2006, month: "Jul", ProductX: 18562, ProductY: 19832, ProductZ: 10293},
         {year: 2006, month: "Aug", ProductX: 28376, ProductY: ..............

Construct the XML string from the data stored in the array.

function getYearlySales()
{
    isChart1Added = true;
    var myXAML;
    // Constructing Data XAML
    myXAML = '<vc:Chart Theme="Theme2" Width="650" Height="450" ColorSet="Visifire1" Background="Black" xmlns:vc="clr-namespace:Visifire.Charts;assembly=SLVisifire.Charts" >' + "\n" + "\n";
    myXAML = myXAML + '<vc:Chart.Titles>' + "\n";
    myXAML = myXAML + '<vc:Title Text= "Yearly Sales" />' + "\n";
    myXAML = myXAML + '</vc:Chart.Titles>' + "\n";
    myXAML = myXAML + '<vc:Chart.PlotArea>' + "\n";
    myXAML = myXAML + '<vc:PlotArea Background= "#0A0A0A" />' + "\n";
    myXAML = myXAML + '</vc:Chart.PlotArea>' + "\n";
    myXAML = myXAML + '<vc:Chart.AxesX>' + "\n";
    myXAML = myXAML + '<vc:Axis Title="Year" />' + "\n";
    myXAML = myXAML + '</vc:Chart.AxesX>' + "\n";
    myXAML = myXAML + '<vc:Chart.AxesY>' + "\n";
    myXAML = myXAML + '<vc:Axis Title="Amount" Prefix="$" />' + "\n" + "\n";
    myXAML = myXAML + '</vc:Chart.AxesY>' + "\n";
    myXAML = myXAML + '<vc:Chart.Series>' + "\n";
    myXAML = myXAML + '<vc:DataSeries RenderAs="Column">' + "\n";
    myXAML = myXAML + '<vc:DataSeries.DataPoints>' + "\n";
    sales.sort(sortByYear);
    var totalSales = 0;
    currYear = sales[0].year;
    for (var arrayIndex = 0; arrayIndex < sales.length; arrayIndex++)
    {
        if(sales[arrayIndex].year == currYear)
        {
            currYear = sales[arrayIndex].year;
            totalSales += sales[arrayIndex].ProductX + sales[arrayIndex].ProductY + sales[arrayIndex].ProductZ;
        }
        else
        {
            myXAML = myXAML + '<vc:DataPoint AxisXLabel="' + currYear + '" YValue="' + totalSales + '"/>' + "\n";
            totalSales = 0;
            currYear++;
        }
    }
    myXAML = myXAML + '<vc:DataPoint AxisXLabel="' + currYear + '" YValue="' + totalSales + '"/>' + "\n";
    myXAML = myXAML + '</vc:DataSeries.DataPoints>' + "\n";
    myXAML = myXAML + "\n" + '</vc:DataSeries>' + "\n";
    myXAML = myXAML + '</vc:Chart.Series>' + "\n";
    myXAML = myXAML + '</vc:Chart>';
    return myXAML;
}

First, create the chart for yearly sales. Next, attach an event handler for “MouseLeftButtonUp” event to the DataSeries. On fire of this event, this chart is replaced with a chart with monthly sales for the year represented by the selected DataPoint in that DataSeries. Event handler can be attached as shown in the code snippet below.

// Attaching event handler for MouseLeftButtonUp event
vChart1.preLoad = function(args) {
    chart1 = args[0];
    chart1.Series[0].MouseLeftButtonUp = function(e) {
        onMouseUpMonthlySales(e.AxisXLabel); //Calling the function to replace the chart
    };
};

Use the same logic to replace this chart with a chart with product wise sales for the selected month. The crux of a drilldown chart is to attach an event handler to update the chart on fire of some event. I think we have accomplished the same swiftly in this sample. A link/button to return back to the previous state of the chart would give a holistic user experience.

You can see the sample in action here. The complete solution can be downloaded from here.

Cheers,

Team Visifire.


Visifire 2.0 beta Released!

Chirag

Hi,

The wait is over. Visifire 2.0 beta is here! Visifire 2.0 is a complete rewrite of Visifire 1.x, it is a result of complete architectural overhaul. We had to make changes in the XAML structure. The XAML used by Visifire 1.x is incompatible with Visifire 2.0. A notable change in XAML is that similar elements are grouped under an element collection. Visifire 2.0 follows the best practices laid down by Microsoft for component development. In Visifire 2.0 all properties are dependency properties. Dependency properties provide a way to compute the value of a property based on the value of other inputs.

The major features in this release are:-

Visifire works in WPF too
Visifire 2.0 boasts of multi-targeting feature. A single API to draw charts in Silverlight or WPF or WPF Browser Application. Yes, its true, you can start using Visifire in your desktop applications also.

Realtime Update
Any property of Visifire charts can be updated in realtime. No need to redraw the chart. Visifire1.x supported realtime update through Javascript, but not from managed code. We learnt that Visifire is being extensively used in managed code & updating charts in realtime is one of the most coveted feature. Hence, we implemented it. Real time update is showcased both in Chart Designer & Gallery.

Scrollable Charts
The name says it all. You can scroll the datapoints inside the charts. All chart types in the Gallery have an example showcasing this feature.

Styling
Visifire 2.0 charts can be styled. If you want to make the charts look the way you want & still harness the flexibility of Visifire, you can do so by styling the charts globally.

Editable in Blend
Designing requires a good Design Tool. It is true even for Silverlight Apps & that’s why Microsoft has Expression Blend. Visifire charts can now be designed in Microsoft Expression Blend.

Elegant Animation
Currently Visifire2.0 contains only one animation per chart type. The same animation doesn’t look good for all chart types. Hence, for every chart type a specific animation has been handpicked & polished to perfection.

Download Visifire 2.0 beta here.

This blog will be followed by a series of blogs on new features of Visifire2.0 & their implementation, so stay tuned.

PS: Forget not to check out Chart Designer. It updates the chart instantly & no longer requires redraw!

Cheers,

Team Visifire


Visifire v1.5.5 released

Chirag

Hi,

The following bugs have been fixed in this release

  • Chart type StackedArea100 crashed on setting YValue = 0 for any DataPoint.
  • In DataPoint, the property MarkerEnabled was being taken true for both boolean values.
  • ToolTip and Events were not working with 3D Chart if AnimationType = Type4 and no. of DataPoint > 900.
  • Pie and Doughnut Charts threw exception on MouseOver event with Href being set.
  • ShadowEnabled threw exception when chart is drawn from managed code.

Enhancements

  • Improved Axis Label Placement.
  • LineStyle will be reflected on LegendEntry in Legend.

Download the latest release here.

Cheers,
Team Visifire


Open Source Silverlight Charts Visifire now supports Silverlight 2!

Chirag

Hi,

Visifire now works with Silverlight 2. In fact no changes were required to port Visifire from
Silverlight 2 RC0 (Visifire v1.5.0 was released recently to developers with Silverlight 2 RC0 compatibility).

All live examples in the website are successfully tested against Silverlight 2.
Download your copy of Visifire here.

Thanks for listening,

Team Visifire


Sparkline Charts using Visifire

admin

In this blog I’ll show how to create Sparkline Charts using Line charts from Visifire.

Let me start by showing a sample of the sparkline chart: image

The following XML is a bare bone XML required to create sparkline charts:

<vc:Chart xmlns:vc="clr-namespace:Visifire.Charts;assembly=Visifire.Charts" 
AnimationEnabled="False" Padding="1" BorderThickness="0" Watermark="False"> 

  <vc:AxisX Enabled="False"/> 
  <vc:AxisY Enabled="False" StartFromZero="False"/> 

  <vc:DataSeries RenderAs="Line" LabelEnabled="False" MarkerEnabled="False" > 
    . 
    . 
    . 
  </vc:DataSeries> 

</vc:Chart>

Highlighting of the various points has to be done by selecting the color for the marker.

To highlight any point you have to enable the marker for the point and set the color. If color is not selected then default color will be applied. Also set the marker size as per requirement. Have a look at the example below, it shows how to highlight the start and end points.

<vc:Chart xmlns:vc="clr-namespace:Visifire.Charts;assembly=Visifire.Charts" Theme="Theme1" 
          AnimationEnabled="False" Padding="1" BorderThickness="0" Watermark="False"> 

  <vc:AxisX Enabled="False"/> 
  <vc:AxisY Enabled="False" StartFromZero="False"/> 

  <vc:DataSeries RenderAs="Line" LabelEnabled="False" MarkerEnabled="False" 
                 MarkerSize="5" LineThickness="0.5" Color="Black"> 
    <vc:DataPoint YValue="44.01" MarkerEnabled="True" Color="Blue" /> 
    . 
    . 
    . 
    <vc:DataPoint YValue="44.5" MarkerEnabled="True" Color="Blue" /> 
  </vc:DataSeries> 

</vc:Chart>

Any point can be highlighted to show its importance

The screen shot below shows a HTML table containing Visifire sparkline charts, click on the image below to see silverlight example:

 image

The code for this example can be downloaded from the here.

Visifire sparkline charts support all events that are supported by line charts and hence can be easily used for creating drill down charts.


Next Page »