This project is read-only.

Printing charts using XPS

Jul 17, 2009 at 10:43 PM

I’m having trouble getting charts to print using the XPS document related APIs.  Our app includes several UserControls in a Grid and some of the UserControls have a ChartPlotter.  We have a requirement for printing the grid so that the grid and all content (including the charts) are scaled to fill the page.  The content and range of values for all data and axes on the printed chart images should match the values seen on the screen, but the shape of the charts should be transformed to fit the appropriate area on the page dedicated to each chart. 

I get the output page dimension and set up new transforms for the Grid that contains the UserControls (charts and other).  Then I do a Measure/Arrange pass on the Grid, expecting that the charts would be appropriately transformed according to what was happening to the grid container.  When printed, the grid is properly scaled and positioned on the page, but the charts are not scaled as expected.  If the charts on the monitor are smaller than the physical page size, they don’t scale up to fill the space on the paper.  If the charts on the monitor are larger than the physical page size, the charts are clipped rather than scaled down to fit the proper space.  Other controls in the Grid do resize as expected.

I’d appreciate any guidance you could offer on how to get the charts to scale properly when printing.  The charts definitely scale properly as windows are resized on the screen, so it may just be something simple that I’m missing when printing.  I'm new to WPF, so that's definitely a possibility.

I’ve created a modified version of the D3 HelloWorldSample that demonstrates the problem.  It’s posted at http://cid-c53097b576e89f3d.skydrive.live.com/self.aspx/.Public/D3/HelloWorld%7C_PrintXPS.zip.  I added a Grid parent element on the ChartPlotter, similar to how our app has a grid with multiple charts in it.  Clicking the print button brings up a simple dialog for choosing to send to the printer or to an XPS file.  Sending to an XPS file works fine because we don’t do any transforms on the grid.  Printing is problematic as described above.  The PrintElement() function in the PrintManager class does most of the interesting work for setting up the transforms.

Thank you in advance for any assistance or guidance you can offer.

Best regards,

Gary Sinner

Jul 22, 2009 at 6:29 AM

Hi Gary,

seems like it is not a D3's problem with printing - please take a look at http://cid-eaf0a921258b5980.skydrive.live.com/self.aspx/.Public/D3/PrintingToXps.zip

I've a little broken youк sample, but not it works conceptually better)

 

As I remember my last printing exercises, I was detaching element being printed from its parent (or create a copy of element to print using, for example, XAML serialization) and changed its size. Also it is important, if I'm not wrong, to give an element a time to changes is size - as Measure or Arrange may not  happen as soon as their call ended, so in that sample I used Dispatcher.BeginInvoke() with low priority. But in this part (about non-instant measure and arrange) I can be wrong.

 

Best regards,

Mikhail.

Aug 13, 2009 at 9:49 PM

Hi Gary,

  I have also faced the same problem. The workaround I am using is I have exposed the screenshot of the chart as a CLR property. In the XPS Documents, I have hosted a Image block with source property bound to this CLR property.

// Here the chartArea is the d3:ChartPlotter element.

public BitmapSource Plot        {
            get
            {
                return chartArea.CreateScreenshot();
            }
        }

in the XPS flow document

<TableCell >
                    <BlockUIContainer>
                        <Image Height="325" Name="PlotControl" Source="{Binding Path=Plot}"/>
                    </BlockUIContainer>
</TableCell>

 

I made sure the PropertyChanged notifications do not wreck havoc trying to refresh the binding and will update the Report only when ABSOLUTELY neccessary.

 

Hope this helps,

Srikanth Kotagiri

 

 

Aug 21, 2009 at 12:25 PM

Hi,

 

Of course, it is good if you have created a workaround that helps you to print D3's chart into XPS, but I don't think printing plotter to XPS as a picture is a great idea, because by default, and in the sample of Gary, we are using vector images, that are forming plotter, and in your case we are using raster images doesn't provide good quality being scaled, as opposed to vector images. I have made some changes in Gary's sample that allows to print successfully DynamicDataDisplay's ChartPlotter to XPS, so I don't see any need in such workarounds :)

 

Regards,

Mikhail.

Aug 22, 2009 at 1:01 AM
thecentury wrote:

 and in your case we are using raster images doesn't provide good quality being scaled, as opposed to vector images.

 

Of course youa re right. The Problem I had was not scaling, but whenever I tried to add the chart to flow document, it required me to disconnect it from the window. Maybe i am doing some thing wrong. I will check out this solution once.

 

Thanks

Srikanth

 

Apr 26, 2010 at 4:54 AM

The link to the sample that Mikhail fixed no longer works.  Does anyone have a working example of ChartPlotter printing?

Apr 26, 2010 at 9:25 AM

Sorry, I have deleted it as I thought it is no longer in use.

Apr 26, 2010 at 10:55 AM

Mikhail,

You mentioned that:

" I was detaching element being printed from its parent (or create a copy of element to print using, for example, XAML serialization) and changed its size. Also it is important, if I'm not wrong, to give an element a time to changes is size - as Measure or Arrange may not  happen as soon as their call ended, so in that sample I used Dispatcher.BeginInvoke() with low priority"

Can you give any more detail on this?

When you say "detach an element from its parent" are you referring to removing it from the list of child controls  (e.g. setting window.Content = null)?

It's not clear to me what needs to be be called by Dispatcher.BeginInvoke.  I've tried wrapping all calls following the call to element.Arrange in a Dispatcher.BeginInvoke with SystemIdle priority but this hasn't made any difference to the chart scaling.

Grant.

Mar 31, 2011 at 1:45 PM

Mikhail,

A colleage pointed me at your post this morning. I was trying something similar - detaching the ChartPlotter from its parent and using Measure/Arrange to try to resize it for printing, reattaching it afterwards to the parent again.

It wasn't working and your post lead me to realise that the actual resize could be happening asyncronously. With that in mind, I attempted the attached approach. It doesn't work either, the handler for chartPlotter.SizeChanged never gets called. The printing is obviouslt omitted here, but would occur around the "// Size should now be changed..." comment. I wonder if you have any insight?

    public void PrintThreadFn(Object state)
    {
      ChartPlotter chartPlotter = ((PrintThreadInfo)state).chartPlotter;
      DockPanel parent = ((PrintThreadInfo)state).parent;
      System.Windows.Threading.Dispatcher dispatcher = ((PrintThreadInfo)state).dispatcher;
  
      double oldWidth = double.NaN;
      double oldHeight = double.NaN;
      
      dispatcher.Invoke((Action)delegate 
                        {
                          oldWidth = chartPlotter.Width;
                          oldHeight = chartPlotter.Height;
      
                          parent.Children.Remove(chartPlotter);
                          chartPlotter.SizeChanged += OnWaitingPlotterSizeChange;
                          _plotterSizeChangeMutex.WaitOne();
                          chartPlotter.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                          chartPlotter.Arrange(new Rect(0, 0, 400, 200));
                        });

      _plotterSizeChangeMutex.WaitOne();
      
      // Size should now be changed...
      Debug.Print("Here");

      dispatcher.Invoke((Action)delegate
                        {
                          chartPlotter.SizeChanged -= OnWaitingPlotterSizeChange;
                          chartPlotter.Width = oldWidth;
                          chartPlotter.Height = oldHeight;
                          parent.Children.Add(chartPlotter);
                        });

    }

    void OnWaitingPlotterSizeChange(object sender, SizeChangedEventArgs e)
    {
      _plotterSizeChangeMutex.ReleaseMutex();
      Debug.Print("Here2");
    }

 


Apr 1, 2011 at 10:45 AM
Edited Apr 1, 2011 at 10:47 AM

I figured this out. Since somone else might come looking here for an answer, let me say the approach above is fairly close. The final working version is different in that:

1. I create a Grid in code to host the ChartPlotter. I set the Width/Height of the Grid to the desired size (normally reflective of the printed page size, the 400/200 above is a nominal value for testing) and call it's Measure/Arrange. The ChartPlotter is already using Stretch alignment. I then make the ChartPlotter a child of this Grid and wait for the ChartPlotter to fire it's SizeChanged, as above.

2. That Mutex should be a Semaphore (min 0, max 1), since this is cross-thread, and should be Release'd immediately after taking above "// Size should now be changed...", if you want this code to run more than once :)

3. In case it's not clear enough, PrintThreadFn is running in a worker thread so it doesn't block the main (Dispatcher) thread. if you block the dispatcher, the SizeChanged can't fire.

Jul 30, 2012 at 6:25 AM

Call UpdateLayout before printing the doc. You're welcome.