インフラジスティックス・ジャパン株式会社Blog

インフラジスティックス・ジャパン株式会社のチームメンバーが技術トレンド、製品Tips、サポート情報からライセンス、日々の業務から感じることなど、さまざまなトピックについてお伝えするBlogです。

+[ASP.NET MVC] Chart + Grid の Excel出力

以前、[ASP.NET]チャート+グリッドのExcel出力 という記事を書きましたが、今回はそれの jQuery 版を書きます。弊社では Ignite UI という jQuery コントロールが提供しておりますが、今回はそれにMVCヘルパーを使ってチャートとグリッドを1ページ内に表示し、サーバ処理でそのチャート画像とグリッドの内容を Excel 出力する方法をご紹介します。

概要

本ポストでは、Ignite UIの基本的な使用方法やチャートとグリッドの表示方法についての詳しい説明は割愛させていただき、以下の点にフォーカスしたいと思います。

・チャート(Canvasタグ)の画像化とサーバへの送信
・グリッドの Excel 出力と画像埋め込み

基本的なコントロールの表示方法は以下のサンプルページや、Infragsiticsスタータープロジェクトをご確認下さい。
※末尾の”こちら”リンクからダウンロードできるサンプルプロジェクトもご参考に。

[オンラインサンプル]
・グリッド MVC ヘルパー:http://jp.igniteui.com/grid/aspnet-mvc-helper
・チャート MVC ヘルパー:http://jp.igniteui.com/data-chart/aspnet-mvc-helper

 

チャートとグリッドの表示

今回のサンプルでは、Ignite UI のスターターテンプレートプロジェクトを作成し、そこから各クラスをサンプルように少しカスタマイズしました。スターターテンプレートには、グリッドやチャートなど基本的なコントロールが既に View、Model、Controller の連携も含めて作成されているため、非常に簡単にMVCモデルのサンプルアプリを作成することができます。

image
スターターテンプレートプロジェクト

スターターテンプレートの View 部分(index.cshtml)を以下のように書きました。※スクリプトの読み込み部分などは割愛しています。

    <h3>Sample Application</h3>
    @(Html.Infragistics().Loader()
        .ScriptPath(Url.Content("~/Infragistics/js/"))
        .CssPath(Url.Content("~/Infragistics/css/"))
        .Render()
    )

    @(Html.Infragistics().DataChart(Model.CountSummaries.AsQueryable())
        .ID("igDataChart1")
        .Height("300px")
        .Width("500px")
        .PlotAreaBackground("White")
        
        .Legend(legend => legend.ID("Legend"))
        .Axes(a =>
        {
            a.CategoryX("NameAxis").Label(cl => cl.CountryName);

            a.NumericY("CountAxis");
                //.MinimumValue(0)
                //.MaximumValue(Model.Customers.Count());
        })
        .Series(s =>
        {
            s.Line("Line1")
                .Title("1995年")
                .XAxis("NameAxis")
                .YAxis("CountAxis")
                .ShowTooltip(true)
                .IsHighlightingEnabled(true)
                .IsTransitionInEnabled(true)
                .ValueMemberPath("Pop1995");
            s.Line("Line2")
                .Title("2005年")
                .XAxis("NameAxis")
                .YAxis("CountAxis")
                .ShowTooltip(true)
                .IsHighlightingEnabled(true)
                .IsTransitionInEnabled(true)
                .ValueMemberPath("Pop2005");
            s.Line("Line3")
                .Title("2015年")
                .XAxis("NameAxis")
                .YAxis("CountAxis")
                .ShowTooltip(true)
                .IsHighlightingEnabled(true)
                .IsTransitionInEnabled(true)
                .ValueMemberPath("Pop2015");
            s.Line("Line4")
                .Title("2025年")
                .XAxis("NameAxis")
                .YAxis("CountAxis")
                .ShowTooltip(true)
                .IsHighlightingEnabled(true)
                .IsTransitionInEnabled(true)
                .ValueMemberPath("Pop2025");
                
            //積層エリアチャートで出す場合
            //s.StackedArea("Chart1")
            //    .Title("1995")
            //    .XAxis("NameAxis")
            //    .YAxis("CountAxis")
            //    .ShowTooltip(true)
            //            .Series(ss =>
            //            {
            //                ss.Fragment("Pop1995")
            //                    .Title("1995")
            //                    .ValueMemberPath("Pop1995");
            //                ss.Fragment("Pop2005")
            //                    .Title("2005")
            //                    .ValueMemberPath("Pop2005");
            //                ss.Fragment("Pop2015")
            //                    .Title("2015")
            //                    .ValueMemberPath("Pop2015");
            //                ss.Fragment("Pop2025")
            //                    .Title("2025")
            //                    .ValueMemberPath("Pop2025");
            //            });

        })
        .DataBind()
        .Render()
    )
    <table>
        <tr>
            <td>
            @(Html.Infragistics().Grid(Model.CountSummaries.AsQueryable())
                .Width("550px")
                .DefaultColumnWidth("120px")
                .DataBind()
                .Columns(column =>
                    {
                        column.For(x => x.CountryName).HeaderText("Area").Width("150px");
                        column.For(x => x.Pop1995).HeaderText("1995年").Width("100px");
                        column.For(x => x.Pop2005).HeaderText("2005年").Width("100px");
                        column.For(x => x.Pop2015).HeaderText("2015年").Width("100px");
                        column.For(x => x.Pop2025).HeaderText("2025年").Width("100px");
                    })
                .Render()
            )
            </td>   
            <td id="Legend" style="float: left"/>
        </tr>
    </table>
    
    <input id="Button1" type="button" value="Excel出力" onclick="excelExport();" />
    <div style="display: none;" class="dl-parent"></div>

上から、チャート、グリッド、Excel 出力ボタンが並んでいます。Model や Controller 部分の記述は割愛しますが、以下の画像のグリッドに表示されているデータを生成し、それぞれにバインドしています。(※コードが必要な場合は本ポスト末尾の添付サンプルご参照ください。)
ちなみに、igDataChart の定義内の Series の定義をコメント化されている部分と差し替えると積層チャートエリアチャートとして表示させることができます。

image

 

チャートの画像化とサーバへの送信

チャートとグリッドが表示されたので、ここからエクセル出力処理に入ります。エクセル処理にはInfragistics.Excelエンジン を利用し、サーバ側でエクセルを生成しクライアント側へFileResponseとして返すことを想定します。

まず、表示されているチャートを、サーバ側で生成するエクセルに埋め込むために画像としてサーバへ送る必要があります。方法としては、

・チャートを画像化し、画像のバイナリデータをサーバへPOSTする。
・チャートを画像化し、サーバのディレクトリへ画像をアップロードしたあと、エクセル出力するよう POST する。

が考えられます。画像のアップロードは、弊社の igUpload コントロールや jQuery の Upload プラグインなどありますが、扱いが面倒そうなので前者の方法で実装します。Excel 出力ボタン押下時の JavaScript は以下の通り。

<script type="text/javascript">
    function excelExport() {
        //Canvasのイメージをエクスポート
        var pngImage = $("#igDataChart1").igDataChart("exportImage");
        var pngImageSrc = pngImage.src; //画像のバイナリデータがBase64でエンコードされたものを取得
        pngImageSrc = pngImageSrc.replace('data:image/png;base64,', ''); //サーバ側でBase64でデコードするように識別子を取り除く

        // POST処理
        var form = document.getElementById("form1");
        var imageText = document.createElement("input");
        // 画像のバイナリデータをimageTextへ設定
        imageText.setAttribute("name", "imageText");
        imageText.setAttribute("type", "hidden");
        imageText.setAttribute("value", pngImageSrc);
        form.appendChild(imageText);

        form.action = "Home/ExcelExport";
        form.method = "post";
        form.submit();
        return false;

    }
</script>

コメントの表記通りですが、チャートを画像化し、その Base64 エンコードのデータをポストデータとして Controller の ExcelExport へ POST しています。

チャートの画像化は exportImage() というメソッド(これは Infragistics 独自のもの)を使っていますが、canvas.toDataURL() といった具合で JavaScript で canvas の内容を画像化することができます。

また、POST処理に関しては、ダミーのフォーム form1 を用意しており、そこに imageText という名前のエレメントを追加し POST しています。

 

グリッドの Excel 出力と画像埋め込み

続いて、サーバ側の処理です。

        [HttpPost]
        public FileResult ExcelExport(string imageText)
        {
            // ******************************************  
            // エクセルファイルへチャート画像を埋め込む
            // ******************************************

            // クライアントより取得したチャートのイメージを取得
            byte[] bs = System.Convert.FromBase64String(imageText);
            ImageConverter imgconv = new ImageConverter();
            Image img = (Image)imgconv.ConvertFrom(bs);

            // Excel用の画像シェイプに埋め込む
            Infragistics.Documents.Excel.WorksheetImage imageShape =
                new Infragistics.Documents.Excel.WorksheetImage(img);

            //Excel出力用のワークブックとワークシートを準備  
            Infragistics.Documents.Excel.Workbook workbook = new Infragistics.Documents.Excel.Workbook(WorkbookFormat.Excel2007);
            Infragistics.Documents.Excel.Worksheet worksheet1 = workbook.Worksheets.Add("Sheet1");

            // チャート画像出力先の開始位置の指定  
            Infragistics.Documents.Excel.WorksheetCell cellB2 = worksheet1.Rows[1].Cells[1]; //チャート画像の出力先の開始位置  
            imageShape.TopLeftCornerCell = cellB2;
            imageShape.TopLeftCornerPosition = new PointF(0.0F, 0.0F);

            // チャート画像出力先の終了位置の指定  
            Infragistics.Documents.Excel.WorksheetCell cellH17 = worksheet1.Rows[16].Cells[7]; //チャート画像の出力先の終了位置  
            imageShape.BottomRightCornerCell = cellH17;
            imageShape.BottomRightCornerPosition = new PointF(100.0F, 100.0F);

            // シートに画像を追加  
            worksheet1.Shapes.Add(imageShape);


            // ******************************************  
            // エクセルファイルへグリッドの内容を書き込む
            // ******************************************

            //ヘッダ部分の書き込み
            foreach (var cell in worksheet1.GetRegion("B20:F20"))
            {
                cell.CellFormat.Fill = CellFill.CreateSolidFill(Color.Gray);
                cell.CellFormat.Font.ColorInfo = new WorkbookColorInfo(Color.White);
            }
            worksheet1.Rows[19].Cells[1].Value = "CountryName";
            worksheet1.Rows[19].Cells[2].Value = "Pop1995";
            worksheet1.Rows[19].Cells[3].Value = "Pop2005";
            worksheet1.Rows[19].Cells[4].Value = "Pop2015";
            worksheet1.Rows[19].Cells[5].Value = "Pop2025";

            //各列の幅定義
            worksheet1.Columns[0].Width = 1000;
            worksheet1.Columns[1].Width = 5000;
            worksheet1.Columns[2].Width = 3000;
            worksheet1.Columns[3].Width = 3000;
            worksheet1.Columns[4].Width = 3000;
            worksheet1.Columns[5].Width = 3000;

            //データ部分の書き込み
            int i = 20;
            foreach (CountSummary list in Lists)
            {
                worksheet1.RowsIdea.Cells[1].Value = list.CountryName;
                worksheet1.RowsIdea.Cells[2].Value = list.Pop1995;
                worksheet1.RowsIdea.Cells[3].Value = list.Pop2005;
                worksheet1.RowsIdea.Cells[4].Value = list.Pop2015;
                worksheet1.RowsIdea.Cells[5].Value = list.Pop2025;
                i++;
            }

            // ******************************************  
            // エクセル出力  
            // ******************************************  
            // メモリストリーム  
            System.IO.MemoryStream theStream = new System.IO.MemoryStream();

            // ストリームに作成したワークブックを保存  
            workbook.Save(theStream);

            // レスポンスの作成 
            byte[] byteArr = (byte[])Array.CreateInstance(typeof(byte), theStream.Length);
            theStream.Position = 0;
            theStream.Read(byteArr, 0, (int)theStream.Length);
            theStream.Close();
            
            string fileName = "ExportedFile.xlsx";
            return File(byteArr, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
            
        }

上記コードは Excel 出力ボタンが押された時の Controller 側の記述です。

まず初めに POST された画像のバイナリデータよ読み込み Base64 でデコードし画像に戻しています。(※パラメタの名前(imageText)は、ポストデータを作成したときに追加した Input エレメント の名前と一致する必要があります。)

続いて、Excelエンジンを利用して新規でワークブック/シートを作成し、作成したシートに対して位置を指定したうえでクライアントから渡されたチャート画像を貼り付けています。

オンラインサンプル Excel ワークシートの作成:
http://jp.igniteui.com/infragistics-excel/create-excel-worksheet

グリッドの内容をエクセルへ出力する部分については、全て手組みで全セルへ値設定を行っています。値の取得元については、サンプルコードでは上記コード外で定義している IEnumerable<CountSummary> 型の Lists をぐるぐる回していますが、バインドしているデータが取得できればOKです。(※クライアント側で値の変更がある場合は要注意です。)
上記のサンプルコードでは、セルの背景色の設定や列幅の変更を行っておりますが、Excel エンジンでできる事はここでもできます。

オンラインヘルプ Excel エンジン:

Infragistics Excel Engine - Infragistics ASP.NET™ ヘルプ

最後に、生成した Excel ファイルをストリームに保存して、FileResult として返却すれば Excel 出力処理の完成です。

image
出力された Excel ファイル

Excel ファイルが出力されました。

が・・・よく見ると凡例が出力されていませんね。これは、凡例部分はチャートの Canvas 部分とは別の Div タグで構成されており画像化できなかったためです。無念です。。。
現状考えられる策としては、予め凡例を含んだExcelのテンプレートファイルをサーバ側で用意しておき、Excelを新規作成ではなくファイル読み込みにすることです。
もし Div タグ内をクライアント上で画像化できれば同じプロセスでExcelファイルへ含めることができるので、アイデアある方がいらしたら是非コメント下さい!

Excel エンジンはかなり優秀で、テンプレートとなるファイルを読み込んで利用したりオートシェイプを挿入したりセルのフォーマットを変更したりとかなり柔軟にExcel操作を行うます。皆さんも是非お試しあれ!

 

[サンプルプログラム]
※今回作成したサンプルは こちら からダウンロードいただけます。

NetAdvantage トライアル版ダウンロード
NetAdvantage は無料トライアルを用意しています。是非一度お試し下さい。

jp.infragistics.com