2008년 4월 23일 수요일

OWC를 이용한 CHart 표현

 

From the July 2001 issue of MSDN Magazine.

Office Chart Control, Opening Windows in ASP, Getting a Referred URL, and More
Edited by Nancy Michell
Download the code for this article: WebQA0107.exe (39KB)
Browse the code for this article at Code Center: :Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl02',this);" href="http://msdn.microsoft.com/code/default.asp?URL=/code/sample.asp?url=/MSDN-FILES/026/002/386/msdncompositedoc.xml">Office Chart Control

Q Can you recommend any sample files or code for creating dynamic charts or graphs from XML data? I'd like them to look like the graphs in Microsoft® Office.

A The following is a good solution that uses Office Web Components. Look at the two charts in Figure 1. The top one uses Office Web Components that ship with Office XP, and the bottom one uses Office 2000 Web Components. The HTML for the page is shown in :Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl03',this);" href="http://msdn2.microsoft.com/ko-kr/magazine/bb985943(en-us).aspx">Figure 2. To include the chart control from Office XP, use the class ID listed first in :Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl04',this);" href="http://msdn2.microsoft.com/ko-kr/magazine/bb985943(en-us).aspx">Figure 2. To use the chart control from Windows 2000, use the second class ID. The code also alerts the user if Office XP components are not installed.


Figure 1 Using Office Web Components

      The data for the charts is taken from chartXP.xml and chartme.xml. The formatting is found in chartXP.xsl. All of these files are available for download from the link at the top of this article.
      More information on using XML as an API can be found on the MSDN Online Web Workshop at :Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl05',this);" href="http://msdn2.microsoft.com/ko-kr/magazine/ms950804(en-us).aspx">http://msdn.microsoft.com/xml/articles/xml10162000.asp.

Q I'd like to be able to pop up a small overlay window, allow someone to fill in some database information, and then have the window close. I'm not sure how to create and close the window or if there are any special tricks using ASP code.

A ASP is a server-side scripting environment. Any instance of an opened window would need to run on the client side in the user's browser utilizing client-side scripting (such as VBScript or JScript®). When running VBScript on the server, user interface elements such as msgBox are actually disabled. ASP comes into play here for inserting the form data into your database.
      First, create a small JScript function that will launch the popup window and create the form when the Enter Company data link is clicked (see Figure 3). An interesting point to note about the code for this (shown in :Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl06',this);" href="http://msdn2.microsoft.com/ko-kr/magazine/bb985943(en-us).aspx">Figure 4) is that it creates the form dynamically. It is not actually opening an existing page containing the form elements. The HTML and form elements are created within the body of the function. You could expand on this idea to create your form based on the link the user selected.


Figure 3 Popup Window

      The LaunchWin function is called by clicking on the link:
<a href="#" onClick="LaunchWin()">Enter Company data</a>
After the user has selected an option, the window is launched with a form that presents the user with two textboxes, ready to accept input that will end up in the database (in this case, the classic Northwind database that ships with SQL Server™). When the Submit button is selected, the data is sent to the enterFormData.asp page. The code in :Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl07',this);" href="http://msdn2.microsoft.com/ko-kr/magazine/bb985943(en-us).aspx">Figure 5 belongs at the top of the enterFormData.asp page to extract and enter the form data. In this code, you are simply creating a Recordset object and using ADO to insert the values of your requesting form fields into the database. Then you close the connection and release the object reference from memory.
      Next, use the following logic to close the top layer window when the onLoad event is triggered during page loading.
<BODY onLoad="window.close()">
At this point, the new window containing the form has been opened, the user has entered their information, that information has been inserted into the database, and the opened window has been closed. It is good practice to provide the user with some kind of feedback to inform them that their information has been entered into the database. Figure 3 shows a view of both windows.
      Note that this example doesn't contain validation and error detection; you'd need to include these in a production environment.

Q I'm usually able to grab the referring URL sent via a form POST with the following code:
  strReferrer =    
    Request.ServerVariables("HTTP_REFERER")
But that fails if I get transferred via something like window.location.href.
      Is there a single ASP object that will return the URL regardless of the way you were transferred, or is there a nice technique to pick up the various possible transfers and redirects? Can I always read the header?

A Well, not all cases are actually catchable—not all requests have a referrer header in them. So the real question is whether the client request even had a referrer header in it to begin with. If the request did have a referrer header, then you should be able to get it with that code; if it didn't, then you would never be able to get it.
       Some of the MSN® pages employ the method of passing the referrer via a URL such as this:
http://server/page.asp?from=prevpage.asp
      In the ASP page you can then just use request.querystring("from") to get the value.
      It sounds like the problem in your case is that Microsoft Internet Explorer (or WinInet) is deciding not to add the referrer header because it's not coming from an <A HREF> tag. There may be some different client-side code that would result in Internet Explorer sending the referrer header, but for simplicity you may just want to use the solution I just presented.

Q I am looking for a way to add "Export to Microsoft Excel" functionality to one of my Web projects. I would like to take the data from an ADO recordset or a client-side HTML table and export it to an Excel file on the client, so the user can view HTML data and simply click a button to open the data in Excel. The closest thing I have found is piping HTML data (HTML table on a Web page) to a spreadsheet object on a Web page (see Figure 6). The code for this example is in :Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl08',this);" href="http://msdn2.microsoft.com/ko-kr/magazine/bb985943(en-us).aspx">Figure 7.


Figure 6 Excel Spreadsheet

      I am looking to export data with one click to the user, so ideally I'd like to create an Excel file on the server, then have it download to the client automatically. But maybe I should have SQL export to a csv file, then e-mail it to the user.

A If you upgrade to version 10 of the spreadsheet control, you can query data directly into the control. The user then has Export to Microsoft Excel capabilities right there.
      If upgrading is not possible, then you could use the version 9 Pivot Control. It can connect to data and has detail or recordset viewing and an Export to Microsoft Excel option. However, with the Pivot Control Export to Microsoft Excel you get a PivotTable® in Excel.
      Instead of exporting to Excel, a good option is to have the user get the data from within Excel. A simple sheet containing a macro can get the data from a Web page in XML format and then inject it into the sheet. Since you probably want this functionality only on specific pages that report data, this is a clean option that respects the security settings at both ends (client and server).
      Create a csv file on the Web vroot, configure the Internet Information Services (IIS) vroot to process .csv extensions with the ASP engine, then in the local csv file put a little ASP code that generates a comma-delimited list of all the columns you want to export. Put the following at the bottom:
Response.ContentType = "application/msexcel" 
This tells Internet Explorer to download the file (which is a .csv file that can be opened by Excel) and the user can choose to download it or open it. So far it works quite nicely.
      Also check out Andrew Clinick's article at :Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl09',this);" href="http://msdn2.microsoft.com/ko-kr/magazine/ms974608(en-us).aspx">http://msdn.microsoft.com/workshop/languages/clinic/scripting10162000.asp, which describes how to get ASP pages to generate Office content.

Q I use window.open to load a Web page that takes in a comma-delimited list of IDs. This works great if I pass in a few dozen. But once I go over roughly 700 IDs on the URL (approximately 4000 bytes of data), the list just gets cut off.
      How many bytes can a single URL be under Internet Explorer 5.01? How many bytes can you POST to a form in another window? What's the maximum number of bytes you can pass in as a window argument with window.open? Are there any other ways to pass data from one window to another?

A You may be running into limits with IIS as well as the limits for POST. IIS 5.0 has a limit of 128KB for the HTTP request (before the entity body), which is much smaller than the 2MB limit of IIS 4.0. This can be set via the MaxClientRequestBuffer setting in the registry (see :Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl10',this);" href="http://support.microsoft.com/default.aspx?scid=kb;EN-US;q260694">http://support.microsoft.com/default.aspx?scid=kb;EN-US;q260694).
      You can run into a 4096-byte limit for a URL and window parameter. If you use the POST you shouldn't run into any limit.
       If you're opening the new window with window.open, (and if the information you're passing to the page does not have to go to the server), why pass it as GET or POST at all? Why not just do the following on the client in the page being opened?
var w = window.opener;
myData = w.SomeFunctionDefinedOnMainPage();

Q I am trying to add a dropshadow effect to some of the text in my Web pages, but when I try using the TEXT-SHADOW attribute in my stylesheet file, nothing happens. Could you please show me how this should look?

A You can use DropShadow filter to achieve the result you're looking for. Figure 8 shows the effect.


Figure 8 The DropShadow Effect

:Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl11',this);" href="http://msdn2.microsoft.com/ko-kr/magazine/bb985943(en-us).aspx">Figure 9 shows how to use the DropShadow filter. If you have any further questions, check out the dropshadow reference at http://msdn.microsoft.com/workshop/author/filter/reference/filters/dropShadow.asp.
Thanks to the following Microsoft developers for their technical expertise: Tim Aranki, Pete Baxter, Dean Bell, Smitha Bhat, Daniel Boerner, Stephane Bouillon, Mike Christensen, Andrew Clinick, Zeb DeMeerleer, Rick Engle, John Frum, Geoff Gray, Deepak Gulati, Michael Kaplan, Andy Macourek, Jeremy Phelps, Dan Ricker.

 

 

Extreme XML
XML as the API
 

Chris Lovett
Microsoft Corporation

October 16, 2000


Download :Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl01',this);" href="http://download.microsoft.com/download/xml/utility/1/w982kmexp/en-us/test.exe">Test.exe.

Contents

First Attempt
XSL/T to the Rescue
Conclusion

Have you ever used Office Web Components? I recently had a very interesting experience— and I want to make a big deal about it, because I think it perfectly illustrates the difference between XML and object-oriented APIs.

I was writing some Active Server Pages (ASP) code to chart our team's bug-fix progress. We have a SQL Server database that contains all the bugs, to whom they are assigned, when they were opened, resolved, or closed, and their current status.

Our SQL Server database doesn't retain enough history to generate this exact graph, so I also had to create an XML file that could act as a cache for storing the results of a daily query made against the database. From the XML file, I generated the graph. This XML file is also a :Track('ctl00_ContentPlaceHolder1_ctl00|ctl00_ContentPlaceHolder1_ctl02',this);" href="http://msdn2.microsoft.com/ko-kr/magazine/ms950773(en-us).aspx">ListEditor file so that it can be directly edited with other information that does not come from the database, such as the target bug fix goal numbers (the black line in the chart above).

The XML file contains items that look like this:

   <item>
      <id>32</id><date>8/31/2000</date>
      <target>146</target><active>167</active>
      <dev>69</dev><pm>19</pm><test>1</test><ue>58</ue>
      <resolved>484</resolved>
      <rdev>15</rdev><rpm>36</rpm><rtest>396</rtest><rue>0</rue>
      <in>33</in><out>36</out>
   </item>

First Attempt

I read up on the Office 2000 Web Chart component API documented in MSOWCVBA.CHM, and I discovered that you can build charts from multiple input sources, including arrays, strings, recordsets, and spreadsheets. I looked at the samples and decided that arrays would be the easiest.

I then wrote a bunch of ASP code that generated the client-side JScript code for building the chart from these arrays. The ASP code looked something like this:

<object id=ChartSpace1 classid=CLSID:0002E500-0000-0000-C000-000000000046 style="width:100%;height:350"></object>

<script language=vbs>
Sub Window_OnLoad()
' The colors that I will use for the different chart series.
Dim colors(10)
colors(0) = "black"
colors(1) = "blue"
colors(2) = "red"
colors(3) = "green"
colors(4) = "magenta"
colors(5) = "orange"
colors(6) = "brown"
colors(7) = "gray"
colors(8) = "purple"
colors(9) = "yellow"
colors(10) = "lightgreen"

ChartSpace1.Charts.Add
Set c = ChartSpace1.Constants

' Create array of strings that describes each series.
<% var xpath = "//Schema/ElementType/element[@view = '']"
   var nodes = doc.selectNodes(xpath);%>
Dim series(<%=nodes.length%>)
<%
var series = new Array()
var node = nodes.nextNode();
i = 0
while (node != null) {
    var name = node.getAttribute("type");
    if (name != "id" && name != "date") {
        series[i] = name;
%>
series(<%=i%>) = "<%=name%>"
<%    i++;
    }
    node = nodes.nextNode();
}%>

' Now get the names of each category.
Dim dates()
<% nodes = doc.selectNodes("//item/date");%>
Dim categories(<%=nodes.length%>)
<%
k = 0
var node = nodes.nextNode();
while (node != null) {
    d = node.text.substr(0,node.text.length-5)
%>  categories(<%=k%>) = "<%=d%>"
<%
    k = k + 1
    node = nodes.nextNode();
}%>

' Now get the values for each series.
<%
var values = new Array();
for (j = 0; j < i; j++)
{
    name = series[j];
    nodes = doc.selectNodes("//item[date]");%>
Dim values(<%=k%>)
<%
    x = 0
    first = true
    var node = nodes.nextNode();
    while (node != null) {
        var child = node.selectSingleNode(name);
        if (child && child.text != "") {
            values[x] = parseInt(child.text);%>
values(<%=x%>) = <%=child.text%>
<%
            first = false;
        } else if (first) {
            values[x] = 0;%>
values(<%=x%>) = 0
<%      }
        x = x + 1;
        node = nodes.nextNode();
    } %>
ChartSpace1.Charts(0).SeriesCollection.Add
set chartseries = ChartSpace1.Charts(0).SeriesCollection(<%=j%>)
chartseries.Caption = series(<%=j%>)
chartseries.Line.Color = colors(<%=j%>)
chartseries.SetData c.chDimCategories, c.chDataLiteral, categories
chartseries.SetData c.chDimValues, c.chDataLiteral, values
<% } %>

ChartSpace1.Charts(0).HasLegend = True
ChartSpace1.Charts(0).Type = c.chChartTypeLine
ChartSpace1.Charts(0).Axes(c.chAxisPositionLeft).MajorUnit = 25

End Sub
</script>

I didn't particularly like this solution, because the ASP code was a nightmare to maintain. I changed it so that it would generate an HTML page that downloaded the XML to the client and built the chart entirely on the client side. This also took some of the load off my Web server.

Everything was moving along just fine until I hit a brick wall in the charting API itself. If you've played with Excel charts much, you may have run into their cool combination charts—which can display two vertical axes with a different scale on the same chart. If you look closely at the above chart, this is exactly what I wanted to do. I set up the chart so that the lines use the left axis, which goes up to 700; the bars use the right axis, which goes up to 160. It turns out that you cannot build such a chart using the charting API in Office 2000 Web components. I was stumped. I sent an e-mail to the Office guys to see whether they had some sort of workaround.

The Office team showed me a nifty little XmlData property on the ChartSpace object, which returns a complete XML format for the chart I had built. All I had to do was to tweak that XML a bit to insert the different scale on the right axis, set the XmlData property with the fixed version of the XML, and voilà, the right axis was fixed!

The XML format looks like this:

<xml xmlns:x="urn:schemas-microsoft-com:office:excel">
    <x:WebChart>
        <x:OWCVersion>9.0.0.2710</x:OWCVersion>
        <x:Width>20664</x:Width>
        <x:Height>9260</x:Height>
        <x:Chart>
            <x:PlotArea>
                <x:Interior>
                    <x:Color>#F0F0F0</x:Color>
                </x:Interior>
                <x:Graph>
                    <x:Type>Line</x:Type>
                    <x:SubType>Standard</x:SubType>
                    <x:Series>
                        <x:Line>
                            <x:Color>............</x:Color>
                        </x:Line>
                        <x:Caption>
                            <x:DataSource>-1</x:DataSource>
                            <x:Data>............</x:Data>
                        </x:Caption>
                        <x:Index>2</x:Index>
                        <x:Category>
                            <x:DataSource>-1</x:DataSource>
                            <x:Data>............</x:Data>
                        </x:Category>
                        <x:Value>
                            <x:DataSource>-1</x:DataSource>
                            <x:Data>............</x:Data>
                        </x:Value>
                    </x:Series>
...
Note   As of this writing, the Office team does not commit to supporting this XML format, as it may change.

XSL/T to the Rescue

I put two and two together and decided I didn't need all that code after all. I needed only a simple XSL style sheet, built from the XML representation of the chart that I really wanted. The XSL style sheet could insert the numbers that I wanted from my little XML file.

The client-side code became quite clean:

<HTML>
<object id=ChartSpace1 classid=CLSID:0002E500-0000-0000-C000-000000000046
        style="width:100%;height:350"></object>
<xml id="XmlDoc" src="daily.xml"></xml>
<xml id="Xsl" src="chart.xsl"></xml>
<script for=window event=onload>
    ChartSpace1.XMLData = XmlDoc.transformNode(Xsl.XMLDocument);
</script>
</HTML>

The result is the following:

File
Description
Test.asp The HTML page that displays the chart. You can view the chart if you have Internet Explorer 5.x and Office 2000 Web Components installed.
Daily.xml The XML file that contains the bug history.
Chart.xsl The XSL file that builds the chart in XML format.
Tableview.xsl The XSL file that builds the raw data HTML table view.
Mycss.css A cascading style sheet that defines the look of the HTML page.

Conclusion

What just happened here? A tightly bound, object-oriented API for manipulating charts was bigger and richer than my application required.

The high-powered charting API hit a brick wall. The feature for implementing different scales on multiple axes was not exposed. Developing high-powered, tightly coupled APIs is expensive. It takes time and effort, and the APIs are hard to test. Imagine having to test all the call sequences over scores of classes and hundreds of methods. It's almost impossible, which is why important features like this get cut.

I didn't need to animate the chart. I just needed a simple chart built from some simple XML—that wouldn't change much over time, except for the data.

Fortunately, the application exposed the loosely coupled, low-tech XML format. This XML format rips all the API stuff out of the way and gives you raw access to every piece of data that makes up the real chart. It enables you to reach into the guts of the chart and tweak whatever you need to.

The tradeoff is that the granularity is now much coarser. You cannot make the line colors animate in different colors every millisecond. You cannot make the line width pulse to the rhythm of a heartbeat. It turns out, however, that this coarser granularity is just perfect for most Web-based applications of the Office Web Chart component.

This is a perfect example of how a loosely coupled, coarse-grained XML API can also result in a deeper integration between components (in this case, the Office Web Chart Component and my ASP application), because the object-oriented interfaces that could get in the way have been bypassed. This assumes that your component's XML format is well designed, and that all the capabilities are exposed in that XML format.

The XML interface to your component should also be a lot cheaper to build, because it doesn't have the call sequence test matrix explosion. It simply accepts the XML in one specific schema—and if the XML doesn't validate against that schema, it is rejected. You have to test lots of different XML inputs, but that is still less work than testing all the possible call sequences through a rich API.

Don't get me wrong. I love objects and I love rich APIs—because I'm a programmer, after all. But there is now a paradigm shift in which, for many cases, a rich API is overkill—and customers are demanding simplicity paired with deeper integration between components. XML is the answer to this problem.

Chris Lovett is a program manager for Microsoft's XML team