サーバーの視覚化:NodeJS + D3.js + PhantomJS

ノード+ファントム

私たちのプロジェクトには気まぐれがありました-サーバー側のチャートに描画しますが、単純ではありませんが、クライアント側の既存のチャートにできるだけ似ています

はい、そうです、クライアントはすでにd3.jsにあらゆる種類の可愛さを実装しています。

可能性を研究するために、複雑なグーグル駆動の調査分析方法が適用され、最初の反復では、選択はノード + ファントムで行われました



詳細については、投稿の深さでお願いします。





退屈な紹介



状況を説明するためにプロジェクトについて簡単に説明します。 私たちの会社はBigDataのスタートアップを見つけ、チームが入札に勝ちました。そして今、私たち4人はクラウドで大量のデータセットの分析を見ています。

私たちの動物園は、AWS自動クラスター、 ScalaSparkShark 、Mesos、NodeJS、およびその他の恐ろしい技術で構成されています(このプロジェクトによって、私と同僚が私の知的欲求を満たし、いくつかの記事を書くことができます)。



免責事項
私たちのチームは、2人のベテランのジャビストと2人の「多言語」(java / scala + javascript)で構成されています。 Javaを重視していますが、私たちは優れたエンジニアであり、ツールとして言語を使用しています。 したがって、アプローチと実践の観点から資料が「非正統的」と思われる場合は、コメントで、腐った卵を個人的かつ建設的な批判で投げてください。



毎週の反復と、週の終わりに回顧+デモがあります。 これは、研究とベストプラクティスの検索に多くの制限を課します。

ソリューションの実装時には、ノードのロックアンドレストサービスに「デジタルチューインマシン」が既にありました。



エッセンス



必要条件







なぜノードとファントムなのか?



問題の大まかな調査中に、3つのオプションが発見されました。

  1. ハウスツリーのjs-implementationとImage Magicを使用して、SVGをPNGに変換します(例が見つかりました )。

  2. 岩石(または岩石の対応物 )のチャートにJavaライブラリを使用し、d3で可能な限りスタイルを設定します。

  3. ロック/ノードと組み合わせてファントムを使用する



オプション番号1は、CSSスタイルと一般的な便宜の問題を残しました(プロセッサに計算をロードするのはNodの仕事ではありません)。

オプション番号2は妥当と思われましたが、坐骨神経の長期にわたる痛みを保証します。

オプション番号3を使用することが決定されました。



その後の研究と実験では、次のことが示されました。



そのような橋とは何ですか?
おもしろいことがわかりました。 古くなったモジュールは元々ファントムを操作するために選択されたという事実のため、私はモジュールのデバッグに真っ向から飛び込み、自己記述モジュールのサポートのためにgithubでコミュニティをトローリングしなければなりませんでした。



ファントムには外部APIがまったくないことが判明しました。 ノードでも。 ただし、内部APIはsocket.ioを介してエミュレートされ、ファントムで開かれたページのアラートハンドラーをオーバーライドします。



著者の独創性を尊重してください!



アルゴリズムは次のようなものです。

  1. ファントム内でsocket.ioメッセージを受信するスクリプトが作成されます

  2. スクリプトが添付されたスタブページが作成されます。

  3. socket.ioメッセージに対するページの「応答」を含むアラートメッセージのリスナーが再定義されます

  4. エクスプレスサーバーがノードで発生し、ページを提供してsocket.ioリクエストを処理します。

  5. ファントムプロセスが開始され、ダミーページが送られます。

  6. モジュールは、「ミラーリングされた」APIファントムをエクスポートします(ただし、すべてのメソッドは非同期になります。ファントムでは、それらはほぼすべて同期です)







「ファントム+ノード」オプションを詳しく調べたところ、クライアントの既存のJavaScriptコードを使用して、サーバー側でグラフを作成できることがわかりました。

Phantomは、ハウスツリー、スタイル、javascriptを完全に実装したWebキットです。 また、レンダリングされたページの写真を撮ることができます。 このソリューションにより、 グラフィック構築コードを複製する必要がなくなります!



落とし穴
ファントムが機能するには、システムにインストールする必要があります:)

sudo apt-get install phantomjs





または

brew install phantomjs





これらの魔法の言葉の後、ブリッジはWebページモジュールを使用できるようになります。



実装中、ノードを通してファントムを使用して汗をかかなければなりませんでした。 最初のモジュールはかなり貧弱で曲がっていたことが判明し(前のスポイラーを参照)、そのため選択肢はnode-phantomに落ちました。

APIドキュメントが不足しているという世界的な問題がありました。



科学的な突く方法を使用して、それを見つけることができました:

  • ファイルシステムのフルパスに沿ったページのみのファントムインジェクションpage.indectJs



    )スクリプト。

  • ファントムインクルードpage.includeJs



    )スクリプトは完全なURLのページにありますが、モジュールでは、実装の特性により内部API page.includeJsのコントラクトが破損しています。

  • 空の星の位置により、ファントム .





    追加 .





    ことで動的に接続されたスタイルを解析しません .





    .







    ファントムページ内の処理に渡されるパラメーターは、文字列にシリアル化する必要があります







待望の決定



vow vowモジュールを使用してコードの「パスタ」を減らします。 良いか悪い使用-コメントを書いてください!



 //       (    package.json) var phantom = require("node-phantom") //  , vow = require("vow") //   - , cfg = require("../config") //       , fs = require('fs') //      , pi; //          exports.init = function () { if (pi) { pi.exit(); } phantom.create(function (err, instance) { pi = instance; }); } //        -   exports.render = function (dataset, opts) { var promise = vow.promise(); //       pi.createPage(function (err, page) { //       ,   page.set("viewportSize", opts.viewport); //    d3    (.  " ") var d3Path = __dirname + "/../client/scripts/vendor/d3.v3.js"; //     ,    d3 // type -    (line, bar, pie) //   chart.xxx.js      var chartJs = __dirname + "/../client/scripts/chart." + opts.type + ".js"; //        var chartCss = __dirname + "/../client/styles/charts.css"; var innerStyle = ""; //   //    ? ?    injectLib_(page, d3Path)() .then(injectLib_(page, chartJs)) .then(readCssStyles_(chartCss)) .then(drawChart_(page, {dataset: dataset, innerCss: innerStyle}, opts)) .then(function (res) { //   ,       promise.fulfill({filename: res.filename}); }) .fail(function (err) { promise.reject(err) } ) }); return promise; } //       () //   -    " " function readCssStyles_(chartCss) { return function(){ var prom = vow.promise(); fs.readFile(chartCss, 'utf8', function (err,innerCss) { if (err) { console.log(chartCss + ": read failed, err: " + err); prom.reject(chartCss + ": read failed, err: " + err); } else { console.log(chartCss + " read"); prom.fulfill(innerCss); } }); return prom; } } function injectLib_(page, path) { return function () { var prom = vow.promise(); //      ,       page.evaluate page.injectJs(path, function (err) { if (err) { console.log(path + " injection failed") prom.reject(path + " injection failed"); } else { console.log(path + " injected") prom.fulfill(); } }); return prom; } } function drawChart_(page, data, opts) { return function (innerCss) { data.innerCss = innerCss; var prom = vow.promise(); //          //   -  "".        //  ,      ,    page.evaluate(function (data) { //        //    data = JSON.parse(data); //       //     charts.xxx.js charts.line("body",data.dataset); //    ,          var style = document.createElement("style"); style.innerHTML = data.innerCss; document.getElementsByTagName("head")[0].appendChild(style); } , function (err, result) { if (err) { prom.reject("phantomjs evaluation failed : " + err) } //             //   png, pdf, gif  jpeg var filename = cfg.server.chartsPath + '/' + opts.type + "_" + Date.now() + ".png"; var savingPath = "client" + filename; //        page.render(savingPath, function (err, res) { console.log("Saving image: " + filename); page.close(); prom.fulfill({filename: filename}); }); }, JSON.stringify(data)); return prom; } }
      
      







PS
質問、希望、建設的およびトローリング-コメントで。

「偉大で力強い? -PM。

コードの品質、記事の品質、プレゼンテーションのスタイルなど、あらゆる側面に関するフィードバックをお待ちしております。




All Articles