初心者のためのExtJS入門

ExtJSを使うので、ついでにまとめていきます

(ExtJS6.5)グリッドの新機能[modern] (3)

今回もグリッドについてです。細かいところばかりなので、前回書いとけば良かったです。

Virtual Stores

Ext.data.virtual.Storeというクラスが追加されたようです。

BufferedStoreと同じようなクラスであるとの記述があります。

管理するデータセットのうち必要なレコードおよびその前後のレコードをロード・保持できるようです。

たしかにBufferedStoreと同じような気がしますが、残念ながら私自身は具体的な利用箇所をイメージできていません。

いくつか違いがあるようですが、そのうち「複数のビューで共有可能」という点があるので、VirtualStoreのほうが応用効くのかな。(そもそもBufferedStoreが共有できないことを知らなかったですが)

もう少し情報を仕入れてから判断したいところです。

選択用クラス名が変わってる

名前変わっただけじゃなくて、いくつか機能も追加されているようです。

Ext.grid.plugin.MultiSelection -> Ext.grid.plugin.RowOperations

グルーピング

グリッドのgroupHeaderコンフィグでテンプレートなどを指定するようになりました。

前回しれっとグルーピングしているので、具体的なコードはそちらで確認できます。

http://sham-memo.hatenablog.com/entry/2017/06/02/200247

グリッドのサマリー

Ext.grid.SummaryRowを使いますが、groupFooterかpinnedFooterコンフィグに指定するようになりました。

年齢を追加して、平均年齢を表示させてみました。

Ext.define('Sample.view.main.Main', {
    extend: 'Ext.Panel',
    xtype: 'app-main',

    requires: [
        'Ext.layout.Fit'
    ],

    layout: 'fit',

    title: 'ユーザ一覧',

    items: {
        xtype: 'grid',

        store: {
            proxy: 'memory',

            fields: [
                'id',
                'state',
                'name',
                'tel',
                {
                    name: 'age',
                    summary: 'average'
                }
            ],

            data: [
                { id: 1, state: '鹿児島県', name: 'yamada1', tel: '09011111111', age: 35 },
                { id: 2, state: '鹿児島県', name: 'yamada2', tel: '09022222222', age: 28 },
                { id: 3, state: '鹿児島県', name: 'yamada3', tel: '09033333333', age: 24 },
                { id: 4, state: '鹿児島県', name: 'yamada4', tel: '09044444444', age: 20 },
                { id: 5, state: '熊本県', name: 'yamada5', tel: '09055555555', age: 62 },
                { id: 6, state: '熊本県', name: 'yamada6', tel: '09066666666', age: 61 },
                { id: 7, state: '熊本県', name: 'yamada7', tel: '09077777777', age: 40 },
                { id: 8, state: '熊本県', name: 'yamada8', tel: '09088888888', age: 38 },
                { id: 9, state: '熊本県', name: 'yamada9', tel: '09099999999', age: 32 }
            ],

            grouper: {
                property: 'state'
            }
        },

        grouped: true,

        groupHeader: {
            tpl: 'Group: {name} ({count})'
        },

        groupFooter: {
            xtype: 'gridsummaryrow',
            cls: 'user-summaryrow'
        },

        columns: [
            {
                dataIndex: 'id',
                text: 'ID',
                align: 'right'
            },
            {
                dataIndex: 'name',
                text: '氏名',
                minWidth: 150,
                flex: 1
            },
            {
                dataIndex: 'tel',
                text: '電話番号',
                width: 200
            },
            {
                dataIndex: 'age',
                text: '年齢',
                width: 150,
                align: 'right',
                summaryCell: 'numbercell'
            }
        ]
    }
});

f:id:sham-memo:20170603154156p:plain

pinnedFooterコンフィグを使う場合は、pinFooters: trueも併せて指定します。pinnedFooterを使うと、フッター部分をピン留めするようにグリッドのスクロール時にサマリー行が下部に留まってくれるようです。

あと、試しにサマリー行の背景色を変えようとして気づいたことがあります。

scssファイルの配置場所がjsファイルと同じところになってるようです。Main.jsと同じディレクトリにMain.scssを作成したら確かに反映されました。ExtJS7でもこれでいくのかな。

レーティング

Ext.ux.rating.Pickerで5段階のレーティングを表現できます。標準装備に加わりました。

 

 

他にもあるようですが、グリッドは以上で。次はリストを見てみるつもりです。

(ExtJS6.5)グリッドの新機能[modern] (2)

今回もExtJS6.5のmodernグリッド改善点です。

グリッドの各所にアイコンボタンを設置できるようになった!

スマフォやタブレットでお馴染みのアイコンのボタンを、グリッドの色々な箇所に設置できるようになりました。

Ext.define('Sample.view.main.Main', {
    extend: 'Ext.Panel',
    xtype: 'app-main',

    requires: [
        'Ext.layout.Fit'
    ],

    controller: 'main',
    viewModel: 'main',

    layout: 'fit',

    items: {
        xtype: 'grid',

        store: {
            proxy: 'memory',
            data: [
                { id: 1, state: '鹿児島県', name: 'yamada1', tel: '09011111111' },
                { id: 2, state: '鹿児島県', name: 'yamada2', tel: '09022222222' },
                { id: 3, state: '鹿児島県', name: 'yamada3', tel: '09033333333' },
                { id: 4, state: '鹿児島県', name: 'yamada4', tel: '09044444444' },
                { id: 5, state: '鹿児島県', name: 'yamada5', tel: '09055555555' },
                { id: 6, state: '鹿児島県', name: 'yamada6', tel: '09066666666' },
                { id: 7, state: '鹿児島県', name: 'yamada7', tel: '09077777777' },
                { id: 8, state: '鹿児島県', name: 'yamada8', tel: '09088888888' },
                { id: 9, state: '鹿児島県', name: 'yamada9', tel: '09099999999' }
            ],

            grouper: {
                property: 'state'
            }
        },

        platformConfig: {
            desktop: {
                plugins: {
                    gridcellediting: true
                }
            },

            '!desktop': {
                plugins: {
                    grideditable: true
                }
            }
        },

        grouped: true,

        groupHeader: {
            tpl: 'Group: {name}',

            tools: {
                print: {
                    tooltip: 'Print group',
                    zone: 'tail'
                },

                save: {

                },

                refresh: {

                }
            }
        },

        columns: [
            {
                dataIndex: 'id',
                text: 'ID',
                align: 'right'
            },
            {
                dataIndex: 'name',
                text: '氏名',
                minWidth: 150,
                flex: 1,
                editable: true,

                tools: {
                    gear: {
                        zone: 'end',
                        handler: function (column, tool, e) {
                            // columnは列への参照。
                            // toolはアイコンボタンへの参照(Ext.Toolクラスのインスタンス)
                            // eはイベントオブジェクト。
                        }
                    }
                },

                cell: {
                    tools: {
                        gear: {
                            zone: 'end',
                            handler: function (grid, config) {
                                // gridはグリッドへの参照。
                                // configには、cell・column・event・grid・record・toolのプロパティが存在
                            }
                        }
                    }
                }
            },
            {
                dataIndex: 'tel',
                text: '電話番号',
                width: 200,
                editor: 'textfield'
            }
        ]
    }
});

上記のように、toolsプロパティを指定すると、これまで設置が大変だったカラムやグループの部分にもボタンを設置できます。

f:id:sham-memo:20170602194128p:plain

gearやprintのプロパティ名がアイコンの種類になるようです。Developer Toolで直接参照したところ、該当アイコンのclass属性は「x-icon-el x-font-icon x-tool-type-print」となっていたので(printの場合)、おそらくMaterial Iconsに存在するアイコンが指定できるのだと思われます。ext/modern/theme-neptune/sass/src/Tool.scssにアイコンの定義があったので、そこに存在する分は指定できそうです。無ければ同じように追加できる気がします。

あと、ツリー形式のグリッドでも対応してるみたいです(本投稿では省略します)。

カラムメニューが使える!

classicのようなカラムのメニューが実装されました。

ソート順やカラムの表示・非表示を切り替える、classicでは定番のUIですね。

さらに、メニューの項目も簡単に追加カスタマイズできるようです。

columns: [
    {
        dataIndex: 'id',
        text: 'ID',
        align: 'right',

        menu: {
            custom: {
                text: 'Custom Item',
                separator: true,
                handler: 'onCustom'
            }
        }
    },
・・・        

f:id:sham-memo:20170602161350p:plain

(ExtJS6.5)グリッドの新機能[modern] (1)

ExtJS6.5でmodernのグリッドにいくつか変更箇所があります。

今回は選択と編集を試してみました。

選択

Ext.grid.Gridのselectableコンフィグを使って、どういう選択を可能にするかを設定できるようになりました。

items: [{
    xtype: 'grid',

    selectable: {
        checkbox: true,
        cells: true
    }
}]

プラグインで指定していた以前より、可読性が良くなりました。

ドキュメントを見る限り8つの項目を指定できるようです。

- mode `'single'`, `'multi'` Allow selection of only a single or multiple *records*. This is only valid when selecting rows.
- deselectable Configure as false to disallow deselecting down to zero selected *records*. This is only valid when selecting rows.
- drag `true` or `false` to allow drag gestures to swipt a rage of cells or rows.
- columns `true` to enable column selection by clicking on headers. Defaults to `false`
- cells `true` to enable cell selection by clicking or dragging on cells. Defaults to `false`
- rows Set to `false` to disable selecting rows. Defaults to `true`
- checkbox `true` to add a checkbox column to display selected state. `'only'` to indicate that only clicks on the checkbox affect row selected state.
- extensible `true` to enable the selection to be extended either in the `X` or `Y` axis or `'x'` or `'y'` to configure

グリッドの場合は、Ext.grid.selection.Model(http://docs.sencha.com/extjs/6.5.0/modern/Ext.grid.selection.Model.html)というセレクションモデルのクラスを使っているようで、このクラスのコンフィグとして渡してくれるみたいです。

編集

Ext.grid.plugin.CellEditingプラグインクラスを使って、セル単位で編集できるようになりました。classicでは当たり前にできていましたが、modernでは新規です。

これまでの編集は、行を選択すると、編集フォームが画面外からスライドイン表示され、そこで編集作業を行うという流れでした。

しかし、PCで扱う場合は直接編集できたほうが操作感が良いため、PC向けに作成されたようです。

このことから、最終的にはPC・モバイルともにmodernにまとめたいという意思を感じます。

PC向けの場合は新しく追加されたプラグインを、モバイル向けの場合はこれまでのUIを使いたい場合は、platformConfigで設定するコンフィグを切り替えできるようです。

items: [{
    xtype: 'grid',

    platformConfig: {

        desktop: {
            plugins: {
                gridcellediting: true
            }
        },

        '!desktop': {
            plugins: {
                grideditable: true
            }
        }
    },
    ...]
}]

試してみると、PCのユーザエージェントだと↓のようにセル単位でのインライン編集となり

f:id:sham-memo:20170530223417p:plain

タブレットのユーザエージェントだと↓のように編集フォームでの編集操作となりました。

f:id:sham-memo:20170530223430p:plain

インライン編集は、編集中にグリッドをスクロールしても編集の行はその位置に固定された状態になるようです。なるほど、スーパーパワー!(意味が分からない人はドキュメントをお読みください)

f:id:sham-memo:20170530224750p:plain

アプリケーションで使用するExtJSのバージョンを上げる

ExtJS6.5がリリースされましたね。

https://www.sencha.com/blog/announcing-ext-js-6-5-and-sencha-cmd-6-5-ga/

いろいろ変更ありますが、modern向け機能の強化がうれしいです。

早速SDKとSenchaコマンドをダウンロードしたので、アプリケーションで使用するExtJSのバージョンを6.5に上げてみます。

> cd (アプリケーションのパス)
> sencha app upgrade (ExtJS6.5 SDKのパス)

Sencha Cmd v6.5.0.180
[INF] Upgrading framework ext
[INF] Copying framework to (アプリケーションのパス)/ext
[INF] Framework 'ext' upgraded

workspace.jsonを見ると↓のように6.5になりました。

"frameworks": {
    "ext": {
        "path":"ext",
        "version":"6.5.0.775"
    }
}

更新後は.senchaディレクトリのファイルがごそっと削除されています。cfgやxmlファイルをカスタマイズしてた場合は影響を受けそうですね。6.5以降は、.senchaディレクトリを使わなくなるのかな?

app.jsonのrequiresで、modernに存在しないext-localeを定義していたらsencha app buildでエラーとなるようになっていました。

requiresはclassicとmodernで分けておいたほうが良さそうです。

 

 

あとはES6サポートを試してみました。class Panel {} みたいに書けるのかと思っていましたが、まだそこまでの対応ではないようです。

PromiseのようなES6の機能を使って実装した場合、ビルドしたときにサポートしていないブラウザでも動作するように出力してくれるだけみたいです。

おそらくExtJS7で構文が変わるんでしょうね。

今後、他の機能をちょこちょこ試していこうと思います。

あ、以降の投稿ではExtJS6.5を使うつもりです。

ダッシュボード[classic]

Ext.dashboard.Dashboardを使うと、業務系の画面でよくあるダッシュボードのUIを実現できます。

partsコンフィグに、適当なキー名でExt.dashboard.Partクラスのコンフィグを指定します。

さらにExt.dashboard.PartのviewTemplateコンフィグのitemsに表示したいビューを指定することになります。

まだpartsコンフィグだけでは表示されないです。

defaultContentコンフィグには、最低限typeとcolumnIndexを指定したオブジェクトリテラルの配列を指定します。

ここでtypeはpartsコンフィグで指定したキー名、columnIndexは列のインデックス番号(0から)です。

/**
 * ダッシュボードパネルクラス。
 *
 * @class Sample.view.main.dashboard.Panel
 * @extend Ext.dashboard.Dashboard
 */
Ext.define('Sample.view.main.dashboard.Panel', {
    extend: 'Ext.dashboard.Dashboard',
    xtype: 'main_dashboard_panel',

    parts: {
        testKey: {
            viewTemplate: {
                items: {
                    xtype: 'panel',
                    html: 'パネル'
                }
            }
        }
    },

    defaultContent: [
        {
            type: 'testKey',
            columnIndex: 0
        },
        {
            type: 'testKey',
            columnIndex: 1
        },
        {
            type: 'testKey',
            columnIndex: 2
        },
        {
            type: 'testKey',
            columnIndex: 1
        }
    ]
});

f:id:sham-memo:20170507232637p:plain

上記だと、testKeyのビューを4つダッシュボードに表示しています。columnIndex=1を2つ指定しているので、2列目のビューは2つになっています。

f:id:sham-memo:20170508004633p:plain

ドラッグ&ドロップ操作で場所を移動させることができます。

動的にビューを追加する

ボタンクリック時にビューを追加してみます。

/**
 * ダッシュボードパネルクラス。
 *
 * @class Sample.view.main.dashboard.Panel
 * @extend Ext.dashboard.Dashboard
 */
Ext.define('Sample.view.main.dashboard.Panel', {
    extend: 'Ext.dashboard.Dashboard',
    xtype: 'main_dashboard_panel',

    parts: {
        testKey: {
            viewTemplate: {
                items: {
                    xtype: 'panel',
                    html: 'パネル'
                }
            }
        }
    }
});

/**
 * メインパネル。
 *
 * @class Sample.view.main.Panel
 * @extend Ext.panel.Panel
 */
Ext.define('Sample.view.main.Panel', {
    extend: 'Ext.panel.Panel',
    xtype: 'main_panel',

    requires: [
        'Sample.view.main.dashboard.Panel',
        'Sample.view.main.ViewController'
    ],

    controller: 'main',

    cls: 'main-panel',

    layout: 'fit',

    dockedItems: {
        xtype: 'toolbar',
        items: [
            '->',
            {
                text: '追加',
                handler: 'onClickAddButton'
            }
        ]
    },

    items: {
        reference: 'dashboard_panel',
        xtype: 'main_dashboard_panel'
    }
});

/**
 * ビューコントローラクラス。
 *
 * @class Sample.view.main.ViewController
 * @extend Ext.app.ViewController
 */
Ext.define('Sample.view.main.ViewController', {
    extend: 'Ext.app.ViewController',
    alias: 'controller.main',

    onClickAddButton: function () {
        var me = this,
            dashboardPanel = me.lookupReference('dashboard_panel');

        dashboardPanel.addNew('testKey', 0);
    }
});

Ext.dashboard.DashboardのaddNewメソッドで追加できます。第1引数はpartsコンフィグに定義したキー名、第2引数は列のインデックス番号です。

でも、これだとpartsコンフィグに定義されていない呼び出せないです。なので、partsコンフィグにも動的に追加してみます。

/**
 * ダッシュボードパネルクラス。
 *
 * @class Sample.view.main.dashboard.Panel
 * @extend Ext.dashboard.Dashboard
 */
Ext.define('Sample.view.main.dashboard.Panel', {
    extend: 'Ext.dashboard.Dashboard',
    xtype: 'main_dashboard_panel',

    columnWidths: [
        0.5,
        0.5
    ]
});

/**
 * メインパネル。
 *
 * @class Sample.view.main.Panel
 * @extend Ext.panel.Panel
 */
Ext.define('Sample.view.main.Panel', {
    extend: 'Ext.panel.Panel',
    xtype: 'main_panel',

    requires: [
        'Sample.view.main.dashboard.Panel',
        'Sample.view.main.ViewController'
    ],

    controller: 'main',

    cls: 'main-panel',

    layout: 'fit',

    dockedItems: {
        xtype: 'toolbar',
        items: [
            '->',
            {
                text: '列1に追加',
                handler: 'onClickAddColumn1Button'
            },
            {
                text: '列2に追加',
                handler: 'onClickAddColumn2Button'
            }
        ]
    },

    items: {
        reference: 'dashboard_panel',
        xtype: 'main_dashboard_panel'
    }
});

/**
 * ビューコントローラクラス。
 *
 * @class Sample.view.main.ViewController
 * @extend Ext.app.ViewController
 */
Ext.define('Sample.view.main.ViewController', {
    extend: 'Ext.app.ViewController',
    alias: 'controller.main',

    /**
     * 列1に追加ボタンクリック時の処理。
     */
    onClickAddColumn1Button: function () {
        var me = this;
        
        // 第1,2引数は、応用を考えて仮の値を固定値で渡している。
        // 実際には、他の入力フィールドの値やサーバからのレスポンスデータを渡すことが考えられる。
        me.addComponent(me.getContentId(), 'panel', 0);
    },

    /**
     * 列2に追加ボタンクリック時の処理。
     */
    onClickAddColumn2Button: function () {
        var me = this;
        me.addComponent(me.getContentId(), 'panel', 1);
    },

    /**
     * コンポーネントを追加する。
     * 
     * @param {Number} id ID
     * @param {String} type ビューの種類
     * @param {Number} columnIndex 列インデックス番号
     */
    addComponent: function (id, type, columnIndex) {
        var me = this,
            dashboardPanel = me.lookupReference('dashboard_panel'),
            partKey = me.getPartKey(id, type),
            parts = dashboardPanel.getParts();

        dashboardPanel.setParts(me.getPartConfig(id, type), parts);

        dashboardPanel.addNew(partKey, columnIndex);
    },

    /**
     * Ext.dashboard.Partのコンフィグを返す。
     * 
     * @param {Number} id ID
     * @param {String} type ビューの種類
     * @returns {Object} コンフィグ
     */
    getPartConfig: function (id, type) {
        var me = this,
            partKey = me.getPartKey(id, type),
            config = {};

        config[partKey] = {
            viewTemplate: {
                items: me.getPartItemConfig(id, type)
            }
        };

        return config;
    },

    /**
     * Ext.dashboard.Partに設定するアイテムコンポーネントのコンフィグを返す。
     * 
     * @param {Number} id ID
     * @param {String} type ビューの種類
     * @returns {Object} コンフィグ
     */
    getPartItemConfig: function (id, type) {
        // MEMO: 実案件などに使う場合は、おそらくtypeに応じて返すコンフィグを切り替えたりすることになる
        return {
            xtype: type,
            html: 'content ' + id
        };
    },

    /**
     * partsコンフィグのキーを返す。
     * 
     * @param {Number} id ID
     * @param {String} type ビューの種類
     * @returns {String} キー
     */
    getPartKey: function (id, type) {
        return type + id;
    },

    /**
     * IDを生成する。
     * @returns {number} ID
     */
    getContentId: function () {
        return Math.floor(Math.random() * 100);
    }
});

少し応用などを考えた実装になっていますが、やりたかったことの実装部分はaddComponentメソッドです。

注意としては、Ext.dashboard.DashboardクラスのcolumnWidthsコンフィグを指定しておくことです。

columnWidthsコンフィグは列の幅の割合を、1を分け合う値で指定するのですが、これを指定していないとおかしな挙動になります。

 

 

 

ビューの削除やドロップのイベントについても取り上げようと思いましたが、Ext.dashboardパッケージのクラスのソースを見ればわかりそうなので今回は割愛です。

本番用のapp.jsでrequireの設定不足によるJSエラーが発生する場合の対処

ExtJSで良くあるのが、requireの記述漏れです。

本番用にsencha app buildで出力したファイルで動かしたら、↓のようなJSエラーが発生。この場合は、まずrequiresにクラス指定が漏れています。

Uncaught TypeError: c is not a constructor
    at eval (eval at getInstantiator (app.js:1), <anonymous>:3:8)
    at Object.create (app.js:1)
    at Ext.Inventory.instantiateByAlias (app.js:1)
    at ai.create (app.js:1)
    at ai.constructPlugin (app.js:1)
    at ai.constructPlugins (app.js:1)
    at ai.initComponent (app.js:1)
    at ai.initComponent (app.js:1)
    at ai.initComponent (app.js:1)
    at ai.initComponent (app.js:1)

ブラウザコンソール上に警告が出ていないか確認する

sencha app watchで動かしている場合、下記のような警告を出してくれることがあるので、この場合は速やかにrequiresに追記しましょう。

[W] [Ext.Loader] Synchronously loading 'Ext.chart.series.Line'; consider adding Ext.require('Ext.chart.series.Line') above Ext.onReady

Ext.ClassManagerのinstantiateByAliasメソッドで確認する

しかし、時々、警告が出ないパターンがあります。

その場合は、Ext.ClassManagerのinstantiateByAliasメソッド上にどうにかコンソールログなどを加えて直接見るしかありません。

画面表示時にいきなりエラーが出ないのであれば、エラー表示前に↓をブラウザコンソールで実行して、コンソールログ出力を加えます。

var originFunc = Ext.ClassManager.instantiateByAlias;

Ext.ClassManager.instantiateByAlias = function () {
  console.log(arguments);
  return originFunc.apply(this, arguments);
}

この状態でrequire不足のJSエラーを発生させれば、どのクラスが足りていないのかログ出力されます。

ブラウザでの初期画面表示時にエラーが出る場合は、ブラウザコンソールで実行しても間に合わないので、Application.jsのlaunchメソッドなどに上記コードを追加してしまうか直接ExtJSの該当コードにコンソールログを埋め込むなどの工夫が必要でしょう。Ext.ClassManager自体がシングルトンなので、overridesではオーバーライドはできません。

チャート[classic] (3)

スタック形式の棒グラフ

Ext.chart.series.Barのstackedコンフィグをtrueにすると、積み上げた形状の棒グラフを表現できます。

これを使うとデータを比較しやすくなります。

モデル、ストア

車の販売台数のデータでモデル、ストアを作成しました。(データ元: http://www.jada.or.jp/contents/data/hanbai/maker.html)

/**
 * 車販売台数モデルクラス。
 *
 * @class Sample.model.CarSales
 * @extend Ext.data.Model
 */
Ext.define('Sample.model.CarSales', {
    extend: 'Ext.data.Model',

    fields: [
        {
            name: 'ym',
            type: 'string'
        },
        {
            name: 'data1',
            type: 'int'
        },
        {
            name: 'data2',
            type: 'int'
        },
        {
            name: 'data3',
            type: 'int'
        },
        {
            name: 'data4',
            type: 'int'
        },
        {
            name: 'data5',
            type: 'int'
        },
        {
            name: 'data6',
            type: 'int'
        },
        {
            name: 'data7',
            type: 'int'
        },
        {
            name: 'data8',
            type: 'int'
        },
        {
            name: 'data9',
            type: 'int'
        },
        {
            name: 'data10',
            type: 'int'
        },
        {
            name: 'data11',
            type: 'int'
        },
        {
            name: 'data12',
            type: 'int'
        },
        {
            name: 'data13',
            type: 'int'
        }
    ]
});

/**
 * 車販売台数ストアクラス。
 *
 * @class Sample.store.CarSales
 * @extend Ext.data.Store
 */
Ext.define('Sample.store.CarSales', {
    extend: 'Ext.data.Store',

    model: [
        'Sample.model.CarSales'
    ],

    model: 'Sample.model.CarSales',

    proxy: 'memory',

    data: [
        { ym: '2016/04', data1: 721, data2: 7885, data3: 3681, data4: 23993, data5: 4184, data6: 9441, data7: 1879, data8: 2620, data9: 19177, data8: 2620, data9: 19177, data10: 7871, data11: 111693, data12: 670, data13: 18898 },
        { ym: '2016/05', data1: 430, data2: 8243, data3: 4146, data4: 27949, data5: 4967, data6: 10484, data7: 1510, data8: 3082, data9: 24690, data8: 3082, data9: 24690, data10: 7353, data11: 105528, data12: 647, data13: 24724 },
        { ym: '2016/06', data1: 491, data2: 8870, data3: 5754, data4: 33944, data5: 6549, data6: 10478, data7: 1876, data8: 4897, data9: 30561, data8: 4897, data9: 30561, data10: 9239, data11: 138878, data12: 1034, data13: 35199 },
        { ym: '2016/07', data1: 676, data2: 8906, data3: 4622, data4: 31698, data5: 6716, data6: 13431, data7: 2279, data8: 3313, data9: 27747, data8: 3313, data9: 27747, data10: 8647, data11: 146536, data12: 844, data13: 26338 },
        { ym: '2016/08', data1: 854, data2: 8367, data3: 4623, data4: 22381, data5: 6481, data6: 12195, data7: 1295, data8: 3316, data9: 22466, data8: 3316, data9: 22466, data10: 6438, data11: 110527, data12: 799, data13: 23531 },
        { ym: '2016/09', data1: 657, data2: 11145, data3: 7168, data4: 38064, data5: 10833, data6: 18112, data7: 1505, data8: 4936, data9: 31573, data8: 4936, data9: 31573, data10: 8781, data11: 144012, data12: 1071, data13: 39191 },
        { ym: '2016/10', data1: 538, data2: 9249, data3: 4527, data4: 32629, data5: 5382, data6: 10227, data7: 2279, data8: 2913, data9: 25695, data8: 2913, data9: 25695, data10: 6457, data11: 118712, data12: 791, data13: 23470 },
        { ym: '2016/11', data1: 994, data2: 11832, data3: 5500, data4: 31681, data5: 6902, data6: 13537, data7: 2242, data8: 3426, data9: 34997, data8: 3426, data9: 34997, data10: 6892, data11: 126837, data12: 917, data13: 27285 },
        { ym: '2016/12', data1: 1106, data2: 10269, data3: 5688, data4: 28676, data5: 7501, data6: 8564, data7: 1954, data8: 4625, data9: 31751, data8: 4625, data9: 31751, data10: 5919, data11: 124348, data12: 1045, data13: 33492 },
        { ym: '2017/01', data1: 1123, data2: 12722, data3: 4380, data4: 30338, data5: 5103, data6: 13946, data7: 2301, data8: 2352, data9: 40324, data8: 2352, data9: 40324, data10: 8828, data11: 114916, data12: 577, data13: 21175 },
        { ym: '2017/02', data1: 975, data2: 13218, data3: 5284, data4: 34267, data5: 6824, data6: 16027, data7: 2596, data8: 3278, data9: 44952, data8: 3278, data9: 44952, data10: 9183, data11: 147714, data12: 699, data13: 27018 },
        { ym: '2017/03', data1: 1819, data2: 18496, data3: 11443, data4: 49847, data5: 13160, data6: 28751, data7: 5494, data8: 5878, data9: 64395, data8: 5878, data9: 64395, data10: 12834, data11: 200587, data12: 1366, data13: 46584 }
    ]

});

チャート

あとはExt.chart.series.Barを使って棒グラフを表示してみます。

積み上げる順番にyFieldに複数のフィールド名を指定します。

あとはstacked: trueを指定します。

/**
 * チャートクラス。
 *
 * @class Sample.views.main.chart.Panel
 * @extend Ext.chart.CartesianChart
 */
Ext.define('Sample.views.main.chart.Panel', {
    extend: 'Ext.chart.CartesianChart',
    xtype: 'chart_panel',

    requires: [
        'Ext.chart.axis.Numeric',
        'Ext.chart.axis.Category',
        'Ext.chart.series.Bar'
    ],

    store: 'CarSales',

    legend: {
        type: 'sprite',
        docked: 'bottom'
    },

    axes: [
        {
            type: 'numeric',
            position: 'left',
            minimum: 0,
            title: {
                text: '販売台数'
            }
        },
        {
            type: 'category',
            position: 'bottom',
            label: {
                rotate: {
                    degrees: -90
                }
            },
            title: {
                text: '年月'
            }
        }
    ],

    series: [
        {
            type: 'bar',
            xField: 'ym',
            yField: ['data1','data2','data3','data4','data5','data6','data7','data8','data9','data10','data11','data12','data13'],
            title: ['ダイハツ','富士重工','日野','ホンダ','いすゞ','マツダ','三菱','三菱ふそう','日産','スズキ','トヨタ','UDトラックス','輸入車'],
            stacked: true,
            style: {
                minGapWidth: 20
            }
        }
    ]
});

f:id:sham-memo:20170427203121p:plain

legendコンフィグで、Excelの凡例みたいなのを表示できます。複数のデータがあると、どの色が何を表しているのか分からないので、この形式だと必須ですね。

スタック形式の割合で表示

さらに自動的に各データの全体での割合のチャートにすることができます。

fullStack: trueを指定するだけです。

/**
 * チャートクラス。
 *
 * @class Sample.views.main.chart.Panel
 * @extend Ext.chart.CartesianChart
 */
Ext.define('Sample.views.main.chart.Panel', {
    extend: 'Ext.chart.CartesianChart',
    xtype: 'chart_panel',

    requires: [
        'Ext.chart.axis.Numeric',
        'Ext.chart.axis.Category',
        'Ext.chart.series.Bar'
    ],

    store: 'CarSales',

    legend: {
        type: 'sprite',
        docked: 'bottom'
    },

    axes: [
        {
            type: 'numeric',
            position: 'left',
            minimum: 0,
            title: {
                text: '販売台数の割合(%)'
            }
        },
        {
            type: 'category',
            position: 'bottom',
            label: {
                rotate: {
                    degrees: -90
                }
            },
            title: {
                text: '年月'
            }
        }
    ],

    series: [
        {
            type: 'bar',
            xField: 'ym',
            yField: ['data1','data2','data3','data4','data5','data6','data7','data8','data9','data10','data11','data12','data13'],
            title: ['ダイハツ','富士重工','日野','ホンダ','いすゞ','マツダ','三菱','三菱ふそう','日産','スズキ','トヨタ','UDトラックス','輸入車'],
            stacked: true,
            fullStack: true,
            style: {
                minGapWidth: 20
            }
        }
    ]
});

f:id:sham-memo:20170427203336p:plain

各年月で、全体を100%としたときの割合を表示しています。

データを横に並べる

データを横並びにして比べるなら、stack: falseを指定します(stackコンフィグの初期値はtrueなんですね。知らなかった)。

/**
 * チャートクラス。
 *
 * @class Sample.views.main.chart.Panel
 * @extend Ext.chart.CartesianChart
 */
Ext.define('Sample.views.main.chart.Panel', {
    extend: 'Ext.chart.CartesianChart',
    xtype: 'chart_panel',

    requires: [
        'Ext.chart.axis.Numeric',
        'Ext.chart.axis.Category',
        'Ext.chart.series.Bar'
    ],

    store: 'CarSales',

    legend: {
        type: 'sprite',
        docked: 'bottom'
    },

    axes: [
        {
            type: 'numeric',
            position: 'left',
            minimum: 0,
            title: {
                text: '販売台数'
            },
            grid: {
                odd: {
                    fillStyle: 'rgba(255, 255, 255, 0.06)'
                },
                even: {
                    fillStyle: 'rgba(0, 0, 0, 0.03)'
                }
            }
        },
        {
            type: 'category',
            position: 'bottom',
            label: {
                rotate: {
                    degrees: -90
                }
            },
            title: {
                text: '年月'
            }
        }
    ],

    series: [
        {
            type: 'bar',
            xField: 'ym',
            yField: ['data11', 'data4', 'data1'],
            stacked: false,
            style: {
                inGroupGapWidth: -7
            },
            title: ['トヨタ', 'ホンダ', 'ダイハツ']
        }
    ]
});

f:id:sham-memo:20170429151744p:plain

立体的な棒グラフ

軸、データ系列のクラス名が「~3D」となっているものを使用します。その他の設定は、これまでとほぼ同じです。

/**
 * チャートクラス。
 *
 * @class Sample.views.main.chart.Panel
 * @extend Ext.chart.CartesianChart
 */
Ext.define('Sample.views.main.chart.Panel', {
    extend: 'Ext.chart.CartesianChart',
    xtype: 'chart_panel',

    requires: [
        'Ext.chart.axis.Numeric3D',
        'Ext.chart.axis.Category3D',
        'Ext.chart.series.Bar3D',
        'Ext.chart.grid.HorizontalGrid3D'
    ],

    store: 'CarSales',

    legend: {
        type: 'sprite',
        docked: 'bottom'
    },

    axes: [
        {
            type: 'numeric3d',
            position: 'left',
            minimum: 0,
            title: {
                text: '販売台数'
            },
            grid: {
                odd: {
                    fillStyle: 'rgba(255, 255, 255, 0.06)'
                },
                even: {
                    fillStyle: 'rgba(0, 0, 0, 0.03)'
                }
            }
        },
        {
            type: 'category3d',
            position: 'bottom',
            label: {
                rotate: {
                    degrees: -90
                }
            },
            title: {
                text: '年月'
            }
        }
    ],

    series: [
        {
            type: 'bar3d',
            xField: 'ym',
            yField: ['data11', 'data4', 'data1'],
            stacked: false,
            style: {
                inGroupGapWidth: -7
            },
            title: ['トヨタ', 'ホンダ', 'ダイハツ']
        }
    ]
});

f:id:sham-memo:20170429152018p:plain