This is a migrated thread and some comments may be shown as answers.

Export RadCartesianChart to JPG/PNG/PDF UWP windows 10

4 Answers 156 Views
Chart
This is a migrated thread and some comments may be shown as answers.
Bhushan
Top achievements
Rank 1
Bhushan asked on 01 Dec 2016, 11:43 AM

Do kendo support export chart functionality for RadCartesianChart control. If yes how can achieve it?

I am using RadCartesianChart with MVVM pattern.

4 Answers, 1 is accepted

Sort by
0
Accepted
Lance | Manager Technical Support
Telerik team
answered on 01 Dec 2016, 04:43 PM
Hello Bhushan,

I'm not sure which product you're asking about since you mention Kendo and RadCartesianChart. The product you submitted this post under (UI for UWP) doesn't use Kendo components.

I will answer in the context of UI for UWP:

The UI for UWP suite doesn't explicitly have an export chart function, however this is very easy to do in a UWP application. You can render any UIElement to a jpg by using RenderTargetBitmap. See that documentation's example for the full code, here is how to use it for the Chart.

Just pass the chart into the RenderAsync method like this:

var renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(MyChart);
RenderOutputImage.Source = renderTargetBitmap;

You can then encode the bitmap and share/save it as you see fit using the BitmapEncoder class. Note that this isn't directly related to Telerik UI for UWP components, so it falls outside the scope of Telerik Support.

That said, I built you a small demo that shows how to accomplish this (see screenshot and demo attached). The first button click renders the chart to a bitmap, the second button click saves it to a file.

Here is the code:

MainPage.xaml
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
 
        <TextBlock Text="Telerik RadChart"
                   HorizontalAlignment="Center"
                   Style="{StaticResource TitleTextBlockStyle}" />
 
        <TextBlock Text="Output Image"
                   Grid.Column="1"
                   HorizontalAlignment="Center"
                   Style="{StaticResource TitleTextBlockStyle}" />
 
        <chart:RadCartesianChart x:Name="MyChart"
                                 Grid.Row="1">
            <chart:RadCartesianChart.HorizontalAxis>
                <chart:CategoricalAxis />
            </chart:RadCartesianChart.HorizontalAxis>
            <chart:RadCartesianChart.VerticalAxis>
                <chart:LinearAxis />
            </chart:RadCartesianChart.VerticalAxis>
            <chart:BarSeries x:Name="MyBarSeries" />
        </chart:RadCartesianChart>
 
        <Image x:Name="RenderOutputImage"
               Grid.Column="1"
               Grid.Row="1" />
         
        <Button x:Name="RenderButton"
                Content="Render Chart to Bitmap"
                Margin="20,10,20,30"
                Grid.Row="2"
                Grid.ColumnSpan="2"
                HorizontalAlignment="Center"
                Click="RenderToJpgButton_OnClick" />
 
        <Button x:Name="SaveButton"
                Content="Render Chart to Bitmap"
                Margin="20,10,20,30"
                Grid.Row="2"
                Grid.ColumnSpan="2"
                HorizontalAlignment="Center"
                Visibility="Collapsed"
                Click="SaveButton_OnClick"/>
 
        <TextBlock x:Name="StatusTextBlock"
                   Text="Click button to render Chart into bitmap"
                   Grid.Row="2"
                   Grid.ColumnSpan="2"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Bottom"/>
    </Grid>


MainPage.xaml.cs
public sealed partial class MainPage : Page
    {
        private RenderTargetBitmap rtb;
 
        public MainPage()
        {
            this.InitializeComponent();
            MyBarSeries.ItemsSource = new ObservableCollection<double>  { 20, 30, 50, 10, 60, 40, 20, 80 };
        }
 
        private async void RenderToJpgButton_OnClick(object sender, RoutedEventArgs e)
        {
            await RenderChartToBitmapAsync();
        }
 
        private async void SaveButton_OnClick(object sender, RoutedEventArgs e)
        {
            await SaveBitmapAsync();
        }
 
        private async Task RenderChartToBitmapAsync()
        {
            StatusTextBlock.Text = $"starting render...";
 
            rtb = new RenderTargetBitmap();
            await rtb.RenderAsync(MyChart);
 
            StatusTextBlock.Text = $"render complete, setting Image source in UI...";
 
            // Show the image in the UI
            RenderOutputImage.Source = rtb;
 
            StatusTextBlock.Text = $"Rednering done! Click button to save to file";
 
            RenderButton.Visibility = Visibility.Collapsed;
            SaveButton.Visibility = Visibility.Visible;
        }
 
        private async Task SaveBitmapAsync()
        {
            if (rtb == null)
            {
                StatusTextBlock.Text = $"RenderTargetBitmap was null. Try again.";
                RenderButton.Visibility = Visibility.Visible;
                SaveButton.Visibility = Visibility.Collapsed;
                return;
            }
 
            var pixelBuffer = await rtb.GetPixelsAsync();
 
            var savePicker = new FileSavePicker();
            savePicker.DefaultFileExtension = ".png";
            savePicker.FileTypeChoices.Add(".png", new List<string> { ".png" });
            savePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
            savePicker.SuggestedFileName = "MyChart.png";
             
            var saveFile = await savePicker.PickSaveFileAsync();
             
            if (saveFile == null)
                return;
 
            // Encode the image to the selected file on disk (docs https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmapencoder.aspx )
            using (var fileStream = await saveFile.OpenAsync(FileAccessMode.ReadWrite))
            {
                var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, fileStream);
 
                encoder.SetPixelData(
                    BitmapPixelFormat.Bgra8,
                    BitmapAlphaMode.Ignore,
                    (uint)rtb.PixelWidth,
                    (uint)rtb.PixelHeight,
                    DisplayInformation.GetForCurrentView().LogicalDpi,
                    DisplayInformation.GetForCurrentView().LogicalDpi,
                    pixelBuffer.ToArray());
 
                await encoder.FlushAsync();
            }
 
            StatusTextBlock.Text = $"File Saved to {saveFile.Path}";
        }
    }

Please let us know if you have any further questions. If this answer works for you, please consider marking this thread as answered.

Thank you for contacting support and for choosing Telerik by Progress!

Regards,
Lance | Tech Support Engineer, Sr.
Telerik by Progress
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Bhushan
Top achievements
Rank 1
answered on 02 Dec 2016, 05:55 AM

Hello Lance,

Thank you for your help.

I am using "UI for Universal Windows Platform" product in my UWP application.

I have tried your given solution and it works fine. Thank you.

Can we do the same thing in ViewModel instead of Code Behind.

Thanks.

0
Lance | Manager Technical Support
Telerik team
answered on 02 Dec 2016, 03:38 PM
Hello Bhushan,

I don't see why not. All you need from the View is the UIElement for the RenderTargetBitmap to do it's job. 

Create a Command that passes a UIElement as it's parameter. A DelegateCommand is best for this type of thing. For your convenience, here is a DelegateCommand that I've written that works with and without a parameter:

public class DelegateCommand : DelegateCommand<object>
    {
        public DelegateCommand(Action execute, Func<bool> canExecute)
            : base(_ => execute(), _ => canExecute())
        {
 
            if (execute == null)
                throw new ArgumentNullException(nameof(execute));
 
            if (canExecute == null)
                throw new ArgumentNullException(nameof(canExecute));
        }
 
        public DelegateCommand(Action execute)
            : this(execute, () => true)
        {
        }
    }
 
    public class DelegateCommand<T> : ICommand
    {
 
        private Action<T> execute;
        private Func<T, bool> canExecute;
 
        public event EventHandler CanExecuteChanged;
 
        public DelegateCommand(Action<T> execute, Func<T, bool> canExecute)
        {
 
            if (execute == null)
                throw new ArgumentNullException(nameof(execute));
 
            if (canExecute == null)
                throw new ArgumentNullException(nameof(canExecute));
 
 
            this.execute = execute;
            this.canExecute = canExecute;
        }
 
        public DelegateCommand(Action<T> execute)
            : this(execute, _ => true)
        {
        }
 
        public bool CanExecute(object parameter)
        {
            if (!(parameter is T) && parameter != (object)default(T))
                return false;
 
            return canExecute((T)parameter);
        }
 
        public void Execute(object parameter)
        {
            execute((T)parameter);
        }
 
        public void NotifyCanExecuteChanged()
        {
            EventHandler eventHandler = CanExecuteChanged;
 
            eventHandler?.Invoke(this, EventArgs.Empty);
        }
    }

With this, in your ViewModel you can create an instance of the command and the UIElement is passed to the action.

private DelegateCommand<UIElement> renderChartCommand;
public DelegateCommand<UIElement> RenderChartCommand
{
    get { return renderChartCommand ?? (renderChartCommand = new DelegateCommand<UIElement>(async (e) => await RenderElement(e))); }
}
 
private async Task RenderElement(UIElement element)
{
    // Do your rendering work here
}


Then in the view, you can set up your Button to use the command and pass the UIElement:

<chart:RadCartesianChart x:Name="MyChart" ...>
 
<Button  Command="{Binding RenderChartCommand}"
     CommandParameter="{Binding ElementName=MyChart}"/>


I've updated my demo to use MVVM for everything and the rendering happens in MainPageViewModel.

Regards,
Lance | Tech Support Engineer, Sr.
Telerik by Progress
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Bhushan
Top achievements
Rank 1
answered on 05 Dec 2016, 05:42 AM

Hello lance,

Thank you for your help. :)

This is working as per expectations. 

 

Tags
Chart
Asked by
Bhushan
Top achievements
Rank 1
Answers by
Lance | Manager Technical Support
Telerik team
Bhushan
Top achievements
Rank 1
Share this question
or