Vue.jsを使用してAPIを使用してインタラクティブな天気ダッシュボードを作成する

公開: 2022-03-10
簡単な要約↬APIデータを使用してダッシュボードを作成することは、多くの場合複雑な作業です。 技術スタックの選択、APIの統合、適切なグラフの選択、CSSスタイルによる美化は難しい場合があります。 このチュートリアルは、APIデータを使用してVue.jsで天気ダッシュボードを作成するのに役立つステップバイステップガイドです。

(これはスポンサー記事です。)このチュートリアルでは、簡単な天気ダッシュボードを最初から作成します。 これは、「Hello World」の例ではなく、そのサイズと複雑さにおいてあまりにも威圧的でもないクライアントエンドアプリケーションになります。

プロジェクト全体は、Node.js + npmエコシステムのツールを使用して開発されます。 特に、データにはDark Sky API、すべての手間のかかる作業にはVue.js、データの視覚化にはFusionChartsに大きく依存します。

前提条件

あなたが以下に精通していることを期待します:

  • HTML5とCSS3 (Bootstrapが提供する基本機能も使用します。
  • JavaScript (特に言語を使用するES6の方法);
  • Node.jsとnpm (環境とパッケージ管理の基本は問題ありません)。

上記のものとは別に、 Vue.jsまたは他の同様のJavaScriptフレームワークに精通していると便利です。 FusionChartsについて知っていることは期待していません。使い方はとても簡単なので、その場で学ぶことができます。

期待される学習

このプロジェクトからの主な学習内容は次のとおりです。

  1. 優れたダッシュボードの実装を計画する方法
  2. Vue.jsでアプリケーションを開発する方法
  3. データ駆動型アプリケーションを作成する方法
  4. FusionChartsを使用してデータを視覚化する方法

特に、各セクションでは、学習目標に一歩近づきます。

  1. 天気ダッシュボードの紹介
    この章では、事業のさまざまな側面の概要を説明します。
  2. プロジェクトを作成する
    このセクションでは、Vueコマンドラインツールを使用してプロジェクトを最初から作成する方法について説明します。
  3. デフォルトのプロジェクト構造をカスタマイズする
    前のセクションで取得したデフォルトのプロジェクトスキャフォールディングでは不十分です。 ここでは、構造的な観点からプロジェクトに必要な追加事項を学びます。
  4. データの取得と処理
    このセクションはプロジェクトの要です。 APIからデータを取得して処理するためのすべての重要なコードがここに示されています。 このセクションに最大限の時間を費やすことを期待してください。
  5. FusionChartsによるデータの視覚化
    プロジェクトのすべてのデータとその他の可動部分が安定したら、このセクションでは、FusionChartsと少しのCSSを使用してデータを視覚化することに専念します。

1.ダッシュボードワークフロー

実装に飛び込む前に、計画を明確にすることが重要です。 私たちは計画を4つの異なる側面に分けます。

要件

このプロジェクトの要件は何ですか? 言い換えれば、天気ダッシュボードで紹介したいものは何ですか? 私たちの対象読者はおそらく単純な趣味の単なる人間であることを念頭に置いて、次のことを示したいと思います。

  • 彼らが天気を見たい場所の詳細と、天気に関するいくつかの主要な情報。 厳しい要件はないので、退屈な詳細は後でわかります。 ただし、この段階では、視聴者が関心のある場所の入力を提供できるように、検索ボックスを視聴者に提供する必要があることに注意することが重要です。
  • 次のような、関心のある場所の天気に関するグラフィック情報。
    • 問い合わせ当日の気温変化
    • 今日の天気のハイライト:
      • 風速と風向
      • 可視性
      • UV指数

APIから取得したデータは、天気の他の多くの側面に関する情報を提供します。 コードを最小限に抑えるために、これらすべてを使用しないことを選択します。

構造

要件に基づいて、ダッシュボードを次のように構成できます。

ダッシュボードの構造
(大プレビュー)

データ

適切なデータがないときれいな視覚化ができないため、ダッシュボードは取得したデータと同じくらい優れています。 気象データを提供するパブリックAPIはたくさんあります。無料のものもあれば、そうでないものもあります。 このプロジェクトでは、Dark SkyAPIからデータを収集します。 ただし、クライアントエンドからAPIエンドポイントを直接ポーリングすることはできません。 心配しないでください。適切なタイミングで明らかになる回避策があります。 検索された場所のデータを取得したら、データ処理とフォーマットを行います。ご存知のとおり、請求書の支払いに役立つ技術の種類です。

視覚化

クリーンでフォーマットされたデータを取得したら、それをFusionChartsに接続します。 FusionChartsほど機能的なJavaScriptライブラリは世界にほとんどありません。 FusionChartsが提供する膨大な数の製品のうち、使用するのはごくわずかです。すべてJavaScriptで記述されていますが、FusionChartsのVueラッパーと統合するとシームレスに機能します。

全体像を把握して、手を汚しましょう。具体的にするときが来ました。 次のセクションでは、基本的なVueプロジェクトを作成し、その上にさらに構築します。

2.プロジェクトの作成

プロジェクトを作成するには、次の手順を実行します。

  1. Node.js + npmをインストールします
    コンピューターにNode.jsがインストールされている場合は、この手順をスキップしてください。
    Node.jsにはnpmがバンドルされているため、npmを個別にインストールする必要はありません。 オペレーティングシステムに応じて、ここに記載されている手順に従ってNode.jsをダウンロードしてインストールします。

    インストールしたら、ソフトウェアが正しく機能しているかどうか、およびそれらのバージョンを確認することをお勧めします。 これをテストするには、コマンドライン/ターミナルを開き、次のコマンドを実行します。
     node --version npm --version
  2. npmでパッケージをインストールする
    npmを起動して実行したら、次のコマンドを実行して、プロジェクトに必要な基本パッケージをインストールします。
     npm install -g vue@2 vue-cli@2
  3. vue-cliを使用してプロジェクトのスキャフォールディングを初期化します
    前の手順がすべて順調に進んだと仮定すると、次の手順は、vue.jsのコマンドラインツールであるvue-cliを使用してプロジェクトを初期化することです。 これを行うには、次を実行します。
    • webpack-simpleテンプレートを使用してスキャフォールディングを初期化します。
       vue init webpack-simple vue_weather_dashboard
      たくさんの質問があります—最後の質問を除いてすべてのデフォルトを受け入れることはこのプロジェクトには十分です。 最後の1つはNと答えてください。
      コマンドライン/ターミナルのスクリーンショット
      (大プレビュー)
      webpack-simpleは、ラピッドプロトタイピングや私たちのような軽いアプリケーションには優れていますが、本格的なアプリケーションや本番環境への展開には特に適していません。 他のテンプレートを使用する場合(初心者の場合は使用しないことをお勧めします)、またはプロジェクトに別の名前を付けたい場合、構文は次のとおりです。
       vue init [template-name] [project-name]
    • プロジェクトのvue-cliによって作成されたディレクトリに移動します。
       cd vue_weather_dashboard
    • webpack-simpleテンプレートのvue-cliツールによって作成されたpackage.jsonに記載されているすべてのパッケージをインストールします。
       npm install
    • 開発サーバーを起動し、デフォルトのVueプロジェクトがブラウザーで機能していることを確認してください。
       npm run dev

Vue.jsを初めて使用する場合は、少し時間を取って最新の成果を味わってください。小さなVueアプリケーションを作成し、localhost:8080で実行しています。

Vue.jsWebサイトのスクリーンショット
(大プレビュー)

デフォルトのプロジェクト構造の簡単な説明

ディレクトリvue_weather_dashboard内の構造を確認して、変更を開始する前に基本を理解してください。

構造は次のようになります。

 vue_weather_dashboard |--- README.md |--- node_modules/ | |--- ... | |--- ... | |--- [many npm packages we installed] | |--- ... | |--- ... |--- package.json |--- package-lock.json |--- webpack.config.js |--- index.html |--- src | |--- App.vue | |--- assets | | |--- logo.png | |--- main.js

デフォルトのファイルとディレクトリに慣れることをスキップしたくなるかもしれませんが、Vueを初めて使用する場合は、少なくともファイルの内容を確認することを強くお勧めします。 これは優れた教育セッションであり、自分で追求する必要のある質問、特に次のファイルをトリガーする可能性があります。

  • package.json 、そしてそのいとこpackage-lock.json
  • webpack.config.js
  • index.html
  • src/main.js
  • src/App.vue

ツリー図に示されている各ファイルとディレクトリの簡単な説明を以下に示します。

  • README.md
    推測する賞はありません—主に人間がプロジェクトの足場を作成するために必要な手順を読んで理解することです。
  • node_modules /
    これは、npmがプロジェクトのキックスタートに必要なパッケージをダウンロードするディレクトリです。 必要なパッケージに関する情報は、 package.jsonファイルにあります。
  • package.json
    このファイルは、 webpack-simpleテンプレートの要件に基づいてvue-cliツールによって作成され、インストールする必要のあるnpmパッケージに関する情報(バージョンやその他の詳細を含む)が含まれています。 このファイルの内容をよく見てください。ここにアクセスして、プロジェクトに必要なパッケージを追加/削除するために編集してから、npminstallを実行する必要があります。 package.jsonについて詳しくは、こちらをご覧ください。
  • package-lock.json
    このファイルはnpm自体によって作成され、主にnpmがダウンロードしてインストールしたもののログを保持することを目的としています。
  • webpack.config.js
    これは、webpackの構成を含むJavaScriptファイルです。これは、プロジェクトのさまざまな側面(コード、静的アセット、構成、環境、使用モードなど)をバンドルし、ユーザーに提供する前に縮小するツールです。 利点は、すべてのものが自動的に結び付けられ、アプリケーションのパフォーマンスが向上するため、ユーザーエクスペリエンスが大幅に向上することです(ページがすばやく表示され、ブラウザーでの読み込みが速くなります)。 後で遭遇するかもしれませんが、これは、ビルドシステム内の何かが意図したとおりに機能しない場合に検査する必要があるファイルです。 また、アプリケーションをデプロイする場合、これは編集が必要な重要なファイルの1つです(詳細はこちらをご覧ください)。
  • index.html
    このHTMLファイルは、データとコードが動的に埋め込まれ(Vueが主に行うことです)、ユーザーに提供されるマトリックス(またはテンプレート)として機能します。
  • src / main.js
    このJavaScriptファイルには、主に最上位/プロジェクトレベルの依存関係を管理し、最上位のVueコンポーネントを定義するコードが含まれています。 つまり、プロジェクト全体のJavaScriptを調整し、アプリケーションのエントリポイントとして機能します。 特定のノードモジュールに対するプロジェクト全体の依存関係を宣言する必要がある場合、またはプロジェクトの最上位のVueコンポーネントについて何かを変更する必要がある場合は、このファイルを編集します。
  • src / App.vue
    前のポイントで、「最上位のVueコンポーネント」について話していたときは、基本的にこのファイルについて話していました。 プロジェクト内の各.vueファイルはコンポーネントであり、コンポーネントは階層的に関連しています。 最初は、唯一のコンポーネントとして1つの.vueファイル( App.vue )しかありません。 ただし、まもなく、プロジェクトにコンポーネントを追加し(主にダッシュボードの構造に従って)、App.vueをすべての祖先として、目的の階層に従ってそれらをリンクします。 これらの.vueファイルには、Vueが記述したい形式のコードが含まれます。 心配しないでください。これらは、私たちを正気で整理された状態に保つことができる構造を維持して書かれたJavaScriptコードです。 警告されました—このプロジェクトの終わりまでに、Vueを初めて使用する場合は、 template — script — style template — script — style コードを整理するtemplate — script — style方法!

基盤を作成したので、次の手順を実行します。

  • テンプレートを変更し、構成ファイルを少し調整して、プロジェクトが希望どおりに動作するようにします。
  • 新しい.vueファイルを作成し、Vueコードを使用してダッシュボード構造を実装します。

次のセクションでそれらを学習しますが、これは少し長くなり、注意が必要です。 カフェインや水が必要な場合、または排出したい場合—今がその時です!

3.デフォルトのプロジェクト構造のカスタマイズ

足場プロジェクトが私たちに与えてくれた基盤をいじくり回す時が来ました。 開始する前に、 webpackが提供する開発サーバーが実行されていることを確認してください。 このサーバーを継続的に実行する利点は、ソースコードに加えた変更(サーバーを保存してWebページを更新したもの)がすぐにブラウザーに反映されることです。

開発サーバーを起動する場合は、ターミナルから次のコマンドを実行するだけです(現在のディレクトリがプロジェクトディレクトリであると想定しています)。

 npm run dev

次のセクションでは、既存のファイルのいくつかを変更し、いくつかの新しいファイルを追加します。 その後に、これらのファイルの内容について簡単に説明します。これにより、これらの変更が何を意味するのかがわかります。

既存のファイルを変更する

index.html

ブラウザに表示されるWebページは1つしかないため、このアプリケーションは文字通りシングルページアプリケーションです。 これについては後で説明しますが、最初に最初の変更を行います— <title>タグ内のテキストを変更します。

この小さなリビジョンでは、HTMLファイルは次のようになります。

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <!-- Modify the text of the title tag below --> <title>Vue Weather Dashboard</title> </head> <body> <div></div> <script src="/dist/build.js"></script> </body> </html>

少し時間を取ってlocalhost:8080のWebページを更新し、ブラウザのタブのタイトルバーに反映された変更を確認してください。「VueWeatherDashboard」と表示されているはずです。 ただし、これは、変更を加えて機能しているかどうかを確認するプロセスを示すためだけのものです。 やるべきことがもっとあります!

この単純なHTMLページには、プロジェクトに必要な多くのもの、特に次のものが欠けています。

  • いくつかのメタ情報
  • ブートストラップへのCDNリンク(CSSフレームワーク)
  • カスタムスタイルシートへのリンク(まだプロジェクトに追加されていません)
  • <script>タグからのGoogleMaps GeolocationAPIへのポインタ

これらを追加すると、最終的なindex.htmlには次のコンテンツが含まれます。

 <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="src/css/style.css"> <title>Weather Dashboard</title> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyC-lCjpg1xbw-nsCc11Si8Ldg2LKYizqI4&libraries=places"></script> </head> <body> <div></div> <script src="/dist/build.js"></script> </body> </html>

ファイルを保存し、Webページを更新します。 ページの読み込み中にわずかなバンプに気付いたかもしれません。これは主に、ページスタイルがBootstrapによって制御されており、フォントや間隔などのスタイル要素がデフォルトとは異なるためです。以前(不明な場合は、デフォルトにロールバックして違いを確認してください)。

localhost:8080でWebページを更新したときのスクリーンショット
(大プレビュー)

先に進む前に重要なことの1つは、Google Maps APIのURLに、FusionChartsのプロパティであるキーが含まれていることです。 今のところ、このキーを使用してプロジェクトをビルドできます。これらのタイプの詳細にとらわれてほしくないためです(これは、新しいときに気が散る可能性があります)。 ただし、ある程度の進歩があり、これらの細部に注意を払うことができたら、独自のGoogle MapsAPIキーを生成して使用することを強くお勧めします。

package.json

これを書いている時点で、私たちはプロジェクトに特定のバージョンのnpmパッケージを使用しましたが、それらが連携して機能することは確かです。 ただし、プロジェクトを実行するときには、npmがダウンロードするパッケージの最新の安定したバージョンが、使用したものと同じではない可能性が非常に高く、コードが破損する可能性があります(またはそれ以上のことを行う可能性があります)私たちのコントロール)。 したがって、このプロジェクトのビルドに使用されたものとまったく同じpackage.jsonファイルを用意することが非常に重要です。これにより、コード/説明と得られる結果の一貫性が保たれます。

package.jsonファイルの内容は次のとおりです。

 { "name": "vue_weather_dashboard", "description": "A Vue.js project", "version": "1.0.0", "author": "FusionCharts", "license": "MIT", "private": true, "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" }, "dependencies": { "axios": "^0.18.0", "babel": "^6.23.0", "babel-cli": "^6.26.0", "babel-polyfill": "^6.26.0", "fusioncharts": "^3.13.3", "moment": "^2.22.2", "moment-timezone": "^0.5.21", "vue": "^2.5.11", "vue-fusioncharts": "^2.0.4" }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ], "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-env": "^1.6.0", "babel-preset-stage-3": "^6.24.1", "cross-env": "^5.0.5", "css-loader": "^0.28.7", "file-loader": "^1.1.4", "vue-loader": "^13.0.5", "vue-template-compiler": "^2.4.4", "webpack": "^3.6.0", "webpack-dev-server": "^2.9.1" } }

新しいpackage.jsonを確認し、json内のさまざまなオブジェクトの機能を理解することをお勧めします。 「 author 」キーの値を自分の名前に変更することをお勧めします。 また、依存関係で言及されているパッケージは、コード内で適切なタイミングで表示されます。 当面は、次のことを知っておくだけで十分です。

  • babel関連のパッケージは、ブラウザでES6スタイルのコードを適切に処理するためのものです。
  • axiosはPromiseベースのHTTPリクエストを処理します。
  • momentとモーメント-タイムゾーンは日付/時刻の操作用です。
  • fusionchartsvue-fusionchartsは、チャートのレンダリングを担当します。
  • 明らかな理由で、 vue

webpack.config.js

package.jsonと同様に、プロジェクトのビルドに使用したファイルと一貫性のあるwebpack.config.jsファイルを維持することをお勧めします。 ただし、変更を加える前に、 webpack.config.jsのデフォルトのコードと以下に提供するコードを注意深く比較することをお勧めします。 あなたはかなりの数の違いに気付くでしょう—それらをグーグルで検索してそれらが何を意味するかについての基本的な考えを持っています。 Webpackの構成を詳細に説明することはこの記事の範囲外であるため、この点についてはご自身で行ってください。

カスタマイズされたwebpack.config.jsファイルは次のとおりです。

 var path = require('path') var webpack = require('webpack') module.exports = { entry: ['babel-polyfill', './src/main.js'], output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: 'build.js' }, module: { rules: [ { test: /\.css$/, use: [ 'vue-style-loader', 'css-loader' ], }, { test: /\.vue$/, loader: 'vue-loader', options: { loaders: { } // other vue-loader options go here } }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { name: '[name].[ext]?[hash]' } } ] }, resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' }, extensions: ['*', '.js', '.vue', '.json'] }, devServer: { historyApiFallback: true, noInfo: true, overlay: true, host: '0.0.0.0', port: 8080 }, performance: { hints: false }, devtool: '#eval-source-map' } if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' // https://vue-loader.vuejs.org/en/workflow/production.html module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), new webpack.LoaderOptionsPlugin({ minimize: true }) ]) }

プロジェクトのwebpack.config.jsに変更を加えたら、実行中の開発サーバーを停止し( Ctrl + C )、パッケージに記載されているすべてのパッケージをインストールした後、プロジェクトのディレクトリから次のコマンドを実行して再起動する必要がありpackage.jsonpackage.jsonファイル:

 npm install npm run dev

これで、構成を微調整し、適切なパッケージを確実に配置するという試練は終わります。 ただし、これはコードの変更と記述の旅でもあります。これは少し長いですが、非常にやりがいがあります。

src / main.js

このファイルは、プロジェクトのトップレベルのオーケストレーションの鍵です。ここで定義します。

  • トップレベルの依存関係は何ですか(必要な最も重要なnpmパッケージを入手する場所)。
  • 依存関係を解決する方法と、プラグイン/ラッパーの使用に関するVueへの指示(ある場合)。
  • プロジェクトの最上位コンポーネントを管理するVueインスタンス: src/App.vue (ノードの.vueファイル)。

src/main.jsファイルの目標に沿って、コードは次のようになります。

 // Import the dependencies and necessary modules import Vue from 'vue'; import App from './App.vue'; import FusionCharts from 'fusioncharts'; import Charts from 'fusioncharts/fusioncharts.charts'; import Widgets from 'fusioncharts/fusioncharts.widgets'; import PowerCharts from 'fusioncharts/fusioncharts.powercharts'; import FusionTheme from 'fusioncharts/themes/fusioncharts.theme.fusion'; import VueFusionCharts from 'vue-fusioncharts'; // Resolve the dependencies Charts(FusionCharts); PowerCharts(FusionCharts); Widgets(FusionCharts); FusionTheme(FusionCharts); // Globally register the components for project-wide use Vue.use(VueFusionCharts, FusionCharts); // Instantiate the Vue instance that controls the application new Vue({ el: '#app', render: h => h(App) })

src / App.vue

これは、プロジェクト全体で最も重要なファイルの1つであり、階層の最上位のコンポーネント、つまりアプリケーション全体を表します。 私たちのプロジェクトでは、このコンポーネントがすべての面倒な作業を行います。これについては後で説明します。 今のところ、デフォルトの定型文を取り除き、独自のものを配置したいと思います。

Vueのコード編成方法に慣れていない場合は、 .vueファイル内の一般的な構造を理解しておくことをお勧めします。 .vueファイルは次の3つのセクションで構成されています。

  • テンプレート
    ここで、ページのHTMLテンプレートが定義されます。 静的HTMLとは別に、このセクションには、二重中括弧{{ }}を使用して動的コンテンツを埋め込むVueの方法も含まれています。
  • 脚本
    JavaScriptはこのセクションを支配し、HTMLテンプレート内の適切な場所に行き来する動的コンテンツを生成する責任があります。 このセクションは、主にエクスポートされるオブジェクトであり、次のもので構成されます。
    • データ
      これは関数自体であり、通常、適切なデータ構造内にカプセル化された目的のデータを返します。
    • メソッド
      1つ以上の関数/メソッドで構成されるオブジェクト。各メソッドは通常、何らかの方法でデータを操作し、HTMLテンプレートの動的コンテンツも制御します。
    • 計算
      上記で説明したメソッドオブジェクトとよく似ていますが、重要な違いが1つあります。メソッドオブジェクト内のすべての関数は、いずれかが呼び出されるたびに実行されますが、計算されたオブジェクト内の関数は、はるかに賢明に動作し、実行された場合にのみ実行されます。と呼ばれる。
  • スタイル
    このセクションは、ページのHTML(テンプレート内に記述)に適用されるCSSスタイリング用です。古き良きCSSをここに配置して、ページを美しくしてください。

上記のパラダイムを念頭に置いて、 App.vueのコードを最小限にカスタマイズしましょう:

 <template> <div> <p>This component's code is in {{ filename }}</p> </div> </template> <script> export default { data() { return { filename: 'App.vue' } }, methods: { }, computed: { }, } </script> <style> </style>

上記のコードスニペットは、 App.vueが独自のコードで動作していることをテストするためのものであることを忘れないでください。 後で多くの変更が加えられますが、最初にファイルを保存して、ブラウザでページを更新します。

「このコンポーネントのコードはApp.vueにあります」というメッセージが表示されたブラウザのスクリーンショット
(大プレビュー)

この時点で、ツールの助けを借りることはおそらく良い考えです。 Chrome用のVuedevtoolsを確認してください。開発用のデフォルトのブラウザとしてGoogleChromeを使用しても問題がない場合は、ツールをインストールして少し試してみてください。 事態がさら​​に複雑になった場合、さらなる開発とデバッグに非常に役立ちます。

追加のディレクトリとファイル

次のステップは、プロジェクトの構造が完成するように、ファイルを追加することです。 次のディレクトリとファイルを追加します。

  • src/css/style.css
  • src/assets/calendar.svgvlocation.svgsearch.svgwinddirection.svgwindspeed.svg
  • src/components/Content.vueHighlights.vueTempVarChart.vueUVIndex.vueVisibility.vueWindStatus.vue

ハイパーリンクされた.svgファイルをプロジェクトに保存します。

上記のディレクトリとファイルを作成します。 最終的なプロジェクト構造は次のようになります(デフォルトの構造から不要になったフォルダーとファイルを削除することを忘れないでください)。

 vue_weather_dashboard/ |--- README.md |--- node_modules/ | |--- ... | |--- ... | |--- [many npm packages we installed] | |--- ... | |--- ... |--- package.json |--- package-lock.json |--- webpack.config.js |--- index.html |--- src/ | |--- App.vue | |--- css/ | | |--- style.css | |--- assets/ | | |--- calendar.svg | | |--- location.svg | | |--- location.svg | | |--- winddirection.svg | | |--- windspeed.svg | |--- main.js | |--- components/ | | |--- Content.vue | | |--- Highlights.vue | | |--- TempVarChart.vue | | |--- UVIndex.vue | | |--- Visibility.vue | | |--- WindStatus.vue

プロジェクトのルートフォルダには、.babelrc、.gitignore、.editorconfigなど.babelrc .gitignore .editorconfigファイルが含まれている可能性があります。 今のところ、それらを安全に無視してかまいません。

次のセクションでは、新しく追加されたファイルに最小限のコンテンツを追加し、それらが正しく機能しているかどうかをテストします。

src / css / style.css

すぐにはあまり役に立ちませんが、次のコードをファイルにコピーします。

 @import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500"); :root { font-size: 62.5%; } body { font-family: Roboto; font-weight: 400; width: 100%; margin: 0; font-size: 1.6rem; } #sidebar { position: relative; display: flex; flex-direction: column; background-image: linear-gradient(-180deg, #80b6db 0%, #7da7e2 100%); } #search { text-align: center; height: 20vh; position: relative; } #location-input { height: 42px; width: 100%; opacity: 1; border: 0; border-radius: 2px; background-color: rgba(255, 255, 255, 0.2); margin-top: 16px; padding-left: 16px; color: #ffffff; font-size: 1.8rem; line-height: 21px; } #location-input:focus { outline: none; } ::placeholder { color: #FFFFFF; opacity: 0.6; } #current-weather { color: #ffffff; font-size: 8rem; line-height: 106px; position: relative; } #current-weather>span { color: #ffffff; font-size: 3.6rem; line-height: 42px; vertical-align: super; opacity: 0.8; top: 15px; position: absolute; } #weather-desc { font-size: 2.0rem; color: #ffffff; font-weight: 500; line-height: 24px; } #possibility { color: #ffffff; font-size: 16px; font-weight: 500; line-height: 19px; } #max-detail, #min-detail { color: #ffffff; font-size: 2.0rem; font-weight: 500; line-height: 24px; } #max-detail>i, #min-detail>i { font-style: normal; height: 13.27px; width: 16.5px; opacity: 0.4; } #max-detail>span, #min-detail>span { color: #ffffff; font-family: Roboto; font-size: 1.2rem; line-height: 10px; vertical-align: super; } #max-summary, #min-summary { opacity: 0.9; color: #ffffff; font-size: 1.4rem; line-height: 16px; margin-top: 2px; opacity: 0.7; } #search-btn { position: absolute; right: 0; top: 16px; padding: 2px; z-index: 999; height: 42px; width: 45px; background-color: rgba(255, 255, 255, 0.2); border: none; } #dashboard-content { text-align: center; height: 100vh; } #date-desc, #location-desc { color: #ffffff; font-size: 1.6rem; font-weight: 500; line-height: 19px; margin-bottom: 15px; } #date-desc>img { top: -3px; position: relative; margin-right: 10px; } #location-desc>img { top: -3px; position: relative; margin-left: 5px; margin-right: 15px; } #location-detail { opacity: 0.7; color: #ffffff; font-size: 1.4rem; line-height: 20px; margin-left: 35px; } .centered { position: fixed; top: 45%; left: 50%; transform: translate(-50%, -50%); } .max-desc { width: 80px; float: left; margin-right: 28px; } .temp-max-min { margin-top: 40px } #dashboard-content { background-color: #F7F7F7; } .custom-card { background-color: #FFFFFF !important; border: 0 !important; margin-top: 16px !important; margin-bottom: 20px !important; } .custom-content-card { background-color: #FFFFFF !important; border: 0 !important; margin-top: 16px !important; margin-bottom: 0px !important; } .header-card { height: 50vh; } .content-card { height: 43vh; } .card-divider { margin-top: 0; } .content-header { color: #8786A4; font-size: 1.4rem; line-height: 16px; font-weight: 500; padding: 15px 10px 5px 15px; } .highlights-item { min-height: 37vh; max-height: 38vh; background-color: #FFFFFF; } .card-heading { color: rgb(33, 34, 68); font-size: 1.8rem; font-weight: 500; line-height: 21px; text-align: center; } .card-sub-heading { color: #73748C; font-size: 1.6rem; line-height: 19px; } .card-value { color: #000000; font-size: 1.8rem; line-height: 21px; } span text { font-weight: 500 !important; } hr { padding-top: 1.5px; padding-bottom: 1px; margin-bottom: 0; margin-top: 0; line-height: 0.5px; } @media only screen and (min-width: 768px) { #sidebar { height: 100vh; } #info { position: fixed; bottom: 50px; width: 100%; padding-left: 15px; } .wrapper-right { margin-top: 80px; } } @media only screen and (min-width:1440px) { #sidebar { width: 350px; max-width: 350px; flex: auto; } #dashboard-content { width: calc(100% — 350px); max-width: calc(100% — 350px); flex: auto; } }

src / Assets /

このディレクトリに、以下の.svgファイルをダウンロードして保存します。

  • calendar.svg
  • location.svg
  • search.svg
  • winddirection.svg
  • windspeed.svg

src / components / Content.vue

これは、階層を維持するためだけに存在し、基本的にその子コンポーネントにデータを渡す「ダムコンポーネント」(つまり、プレースホルダー)と呼ばれるものです。

App.vueファイルにすべてのコードを書き込むための技術的なバーはありませんが、次の2つの理由から、コンポーネントをネストすることでコードを分割するアプローチを採用しています。

  • 可読性と保守性を支援するクリーンなコードを作成する。
  • 画面に表示されるのと同じ構造、つまり階層を複製します。

Content.vueで定義されたコンポーネントをルートコンポーネントApp.vue内にネストする前に、 Content.vueのおもちゃ(ただし教育用)コードを記述しましょう。

 <template> <div> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> </div> </template> <script> export default { data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'] } }, methods: { }, computed: { }, } </script> <style> </style>

コードでは、次のことを注意深く観察して理解してください。

  • <script>タグ(明らかにJavaScriptコードを記述している場所)内で、デフォルトでエクスポートされる(他のファイルで使用できるようになる)オブジェクトを定義します。 このオブジェクトには、 childComponentsという配列オブジェクトを返す関数data()が含まれています。このオブジェクトの要素は、さらにネストする必要のあるコンポーネントファイルの名前です。
  • <template>タグ(HTMLテンプレートを作成する場所)内で重要なのは<ul>です。
    • 順序付けされていないリスト内では、各リストアイテムは、配列オブジェクトchildComponentsで定義されているように、目的の子コンポーネントの名前である必要があります。 さらに、リストは配列の最後の要素まで自動的に拡張されます。 forループを書くべきだと思いませんか? これを行うには、Vue.jsが提供するv-forディレクティブを使用します。 v-forディレクティブ:
      • <li>タグの属性として機能し、配列を反復処理し、反復子が{{ }}括弧内に示されている子コンポーネントの名前をレンダリングします(リスト項目のテキストを記述します)。

上記のコードと説明は、スクリプトとテンプレートがどのように相互に関連しているか、およびVue.jsによって提供されるディレクティブをどのように使用できるかについてのその後の理解の基礎を形成します。

私たちはかなり多くのことを学びましたが、これらすべての後でさえ、階層内のコンポーネントをシームレスに接続することについて学ぶべきことが1つ残っています。それは、親コンポーネントからその子にデータを渡すことです。 今のところ、このプロジェクトでネストしている残りのコンポーネントに同じ手法を使用できるように、src src/App.vueからsrc/components/Content.vue /Content.vueにデータを渡す方法を学ぶ必要があります。

親から子のコンポーネントにデータが流れ落ちるのは単純に聞こえるかもしれませんが、悪魔は細部に宿っています! 以下で簡単に説明するように、それを機能させるには複数のステップが含まれます。

  • 定義とデータ
    今のところ、静的データを試してみたいと思います。天気のさまざまな側面に関するハードコードされた値を含むオブジェクトで十分です。 App.vue weather_data data()関数から返します。 weather_dataオブジェクトは、以下のスニペットに示されています。
 weather_data: { location: "California", temperature: { current: "35 C", }, highlights: { uvindex: "3", windstatus: { speed: "20 km/h", direction: "NE", }, visibility: "12 km", }, },
  • 親からのデータの受け渡し
    データを渡すには、データを送信する宛先が必要です。 この場合、宛先はContent.vueコンポーネントであり、それを実装する方法は次のとおりです。
    • weather_dataオブジェクトを<Content>タグのカスタム属性に割り当てます
    • Vue.jsによって提供されるv-bind :ディレクティブを使用して、属性をデータにバインドします。これにより、属性値が動的になります(元のデータに加えられた変更に応答します)。
       <Content v-bind:weather_data=“weather_data”></Content>

データの定義と受け渡しは、ハンドシェイクのソース側(この場合はApp.vueファイル)で処理されます。

現在のステータスでのApp.vueファイルのコードを以下に示します。

 <template> <div> <p>This component's code is in {{ filename }}</p> <Content v-bind:weather_data="weather_data"></Content> </div> </template> <script> import Content from './components/Content.vue' export default { name: 'app', components: { 'Content': Content }, data () { return { filename: 'App.vue', weather_data: { location: "California", temperature: { current: "35 C", }, highlights: { uvindex: "3", windstatus: { speed: "20 km/h", direction: "NE", }, visibility: "12 km", }, }, } }, methods: { }, computed: { }, } </script> <style> </style> 
「このコンポーネントのコードはApp.vueにあります。 Content.vueのこの子コンポーネントは、TempVarChart.vue、Highlights.vueです。」
(大プレビュー)

ソース(親コンポーネント)から定義されて渡されたデータを使用して、次の2つの手順で説明するように、データを受信して​​適切にレンダリングするのは子の責任です。

  • 子供がデータを受け取る
    子コンポーネント(この場合はContent.vue )は、親コンポーネントApp.vueから送信されたweather_dataオブジェクトを受信する必要があります。 Vue.jsは、そうするためのメカニズムを提供します—必要なのは、 Content.vueによってエクスポートされたデフォルトオブジェクトで定義されたpropsと呼ばれる配列オブジェクトだけです。 配列propsの各要素は、親から受け取りたいデータオブジェクトの名前です。 今のところ、受信することになっている唯一のデータオブジェクトはApp.vueからのweather_dataです。 したがって、 props配列は次のようになります。
 <template> // HTML template code here </template> <script> export default { props: ["weather_data"], data () { return { // data here } }, } </script> <style> // component specific CSS here </style>
  • ページ内のデータのレンダリング
    データを確実に受信できるようになったので、完了する必要のある最後のタスクはデータをレンダリングすることです。 この例では、テクニックを説明するために、受信したデータをWebページに直接ダンプします。 ただし、実際のアプリケーション(これから作成するアプリケーションなど)では、通常、データは多くの処理を経て、目的に合った方法でデータの関連部分のみが表示されます。 たとえば、このプロジェクトでは、最終的に天気APIから生データを取得し、それをクリーンアップしてフォーマットし、グラフに必要なデータ構造にデータをフィードして、視覚化します。 とにかく、生データダンプを表示するには、以下のスニペットに示すように、Vueが理解する{{ }}括弧を使用します。
 <template> <div> // other template code here {{ weather_data }} </div> </template>

今こそ、すべての断片を吸収する時です。 Content.vueのコード(現在の状態)を以下に示します。

 <template> <div> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> {{ weather_data }} </div> </template> <script> export default { props: ["weather_data"], data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'] } }, methods: { }, computed: { }, } </script> <style> #pagecontent { border: 1px solid black; padding: 2px; } </style> 
提供されたコードの結果を含むブラウザのスクリーンショット
(大プレビュー)

上記の変更を行った後、ブラウザでWebページを更新し、どのように表示されるかを確認します。 Vueが処理する複雑さを理解してくださいApp.vueweather_dataオブジェクトを変更すると、 Content.vueにサイレントに伝達され、最終的にはWebページを表示するブラウザーに伝達されます。 キーの場所の値を変更してみてください。

静的データを使用した小道具とデータバインディングについて学習しましたが、アプリケーションでWeb APIを使用して収集された動的データを使用し、それに応じてコードを変更します

概要

残りの.vueファイルに移る前に、 App.vuecomponents/Content.vue /Content.vueのコードを書いている間に学んだことを要約しましょう。

  • App.vueファイルは、ルートコンポーネントと呼ばれるもので、コンポーネント階層の最上位にあります。 残りの.vueファイルは、直接の子、孫などのコンポーネントを表します。
  • Content.vueファイルはダミーコンポーネントです。その責任は、データを下のレベルに渡し、構造階層を維持することです。これにより、コードは「*私たちが見るものは私たちが実装するもの*」という哲学と一致し続けます。
  • コンポーネントの親子関係は、空中では発生しません。コンポーネントを登録し(コンポーネントの使用目的に応じて、グローバルまたはローカルで)、カスタムHTMLタグ(スペルは正確です)を使用してネストする必要があります。コンポーネントが登録されている名前と同じです)。
  • 登録してネストすると、データは親コンポーネントから子コンポーネントに渡され、フローが逆になることはありません(プロジェクトアーキテクチャで逆流が許可されている場合、問題が発生します)。 親コンポーネントはデータの相対的なソースであり、カスタムHTML要素の属性のv-bindディレクティブを使用して、関連するデータを子に渡します。 子供は小道具を使って自分向けのデータを受け取り、そのデータをどうするかを自分で決めます。

残りのコンポーネントについては、詳細な説明に甘んじることはありません。上記の要約からの学習に基づいてコードを記述するだけです。 コードは自明です。階層について混乱した場合は、次の図を参照してください。

コードの階層を説明する図
(大プレビュー)

この図は、 TempVarChart.vueHighlights.vueContent.vueの直接の子であることを示しています。 したがって、これらのコンポーネントにデータを送信するためにContent.vueを準備することをお勧めします。これは、以下のコードを使用して行います。

 <template> <div> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> {{ weather_data }} <temp-var-chart :tempVar="tempVar"></temp-var-chart> <today-highlights :highlights="highlights"></today-highlights> </div> </template> <script> import TempVarChart from './TempVarChart.vue' import Highlights from './Highlights.vue' export default { props: ["weather_data"], components: { 'temp-var-chart': TempVarChart, 'today-highlights': Highlights }, data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'], tempVar: this.weather_data.temperature, highlights: this.weather_data.highlights, } }, methods: { }, computed: { }, } </script> <style> </style>

このコードを保存すると、エラーが発生します。心配しないでください。 残りのコンポーネントファイルの準備ができたら、修正されます。 出力が表示されない場合は、カスタム要素タグ<temp-var-chart>および<today-highlights>を含む行をコメントアウトしてください。

このセクションでは、これがContent.vueの最終コードです。 このセクションの残りの部分では、学習のために作成した以前のコードではなく、このコードを参照します

src / components / TempVarChart.vue

次のコードに示すように、親コンポーネントContent.vueがデータを渡す場合、 TempVarChart.vueは、データを受信して​​レンダリングするように設定する必要があります。

 <template> <div> <p>Temperature Information:</p> {{ tempVar }} </div> </template> <script> export default { props: ["tempVar"], data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

src / components / Highlights.vue

このコンポーネントは、その親コン​​ポーネントであるApp.vueからもデータを受け取ります。 その後、子コンポーネントとリンクし、関連するデータを子コンポーネントに渡す必要があります。

まず、親からデータを受信するためのコードを見てみましょう。

 <template> <div> <p>Weather Highlights:</p> {{ highlights }} </div> </template> <script> export default { props: ["highlights"], data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

この時点で、Webページは次の画像のようになります。

ブラウザに表示されたコードの結果
(大プレビュー)

次に、 Highlights.vueのコードを変更して、その子コンポーネントを登録およびネストしてから、データを子に渡す必要があります。 そのためのコードは次のとおりです。

 <template> <div> <p>Weather Highlights:</p> {{ highlights }} <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </template> <script> import UVIndex from './UVIndex.vue'; import Visibility from './Visibility.vue'; import WindStatus from './WindStatus.vue'; export default { props: ["highlights"], components: { 'uv-index': UVIndex, 'visibility': Visibility, 'wind-status': WindStatus, }, data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

コードを保存してWebページを表示すると、ブラウザが提供する開発者コンソールツールにエラーが表示されることが予想されます。 これは、 Highlights.vueがデータを送信しているにもかかわらず、誰もデータを受信して​​いないために表示されます。 Highlights.vueのコードはまだ作成していません。

データ処理の多くを行っていないこと、つまり、ダッシュボードの[ハイライト]セクションにある気象データの個々の要素を抽出していないことに注意してください。 data()関数でそれを行うこともできますが、 Highlights.vueは、受け取ったデータダンプ全体を各子に渡すだけのダムコンポーネントを保持することをお勧めします。子は、子に必要なものを独自に抽出します。 。 ただし、 Highlights.vueでデータを抽出してみて、関連するデータを各子コンポーネントに送信することをお勧めします。それでも、これは良い練習です。

src / components / UVIndex.vue

このコンポーネントのコードは、 Highlights.vueからハイライトのデータダンプを受け取り、UVインデックスのデータを抽出して、ページにレンダリングします。

 <template> <div> <p>UV Index: {{ uvindex }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { uvindex: this.highlights.uvindex } }, methods: { }, computed: { }, } </script> <style> </style>

src / components / Visibility.vue

このコンポーネントのコードは、 Highlights.vueからハイライトのデータダンプを受け取り、Visibilityのデータを抽出して、ページにレンダリングします。

 <template> <div> <p>Visibility: {{ visibility }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { visibility: this.highlights.visibility, } }, methods: { }, computed: { }, } </script> <style> </style>

src / components / WindStatus.vue

このコンポーネントのコードは、 Highlights.vueからハイライトのデータダンプを受け取り、風の状態(速度と方向)のデータを抽出して、ページにレンダリングします。

 <template> <div> <p>Wind Status:</p> <p>Speed — {{ speed }}; Direction — {{ direction }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { speed: this.highlights.windstatus.speed, direction: this.highlights.windstatus.direction } }, methods: { }, computed: { }, } </script> <style> </style>

すべてのコンポーネントのコードを追加したら、ブラウザのWebページを確認してください。

ブラウザに表示されたコードの結果
(大プレビュー)

がっかりすることはありませんが、これらの苦労はすべて、階層内のコンポーネントをリンクし、それらの間でデータフローが発生しているかどうかをテストすることでした。 次のセクションでは、これまでに作成したコードのほとんどを破棄し、実際のプロジェクトに関連するコードをさらに追加します。 ただし、コンポーネントの構造とネストは確実に保持されます。 このセクションから学んだことにより、Vue.jsを使用して適切なダッシュボードを構築できます。

4.データの取得と処理

App.vueweather_dataオブジェクトを覚えていますか? すべてのコンポーネントが正しく機能しているかどうかをテストするために使用したハードコードされたデータがいくつかあり、実際のデータの詳細にとらわれることなくVueアプリケーションのいくつかの基本的な側面を学ぶのに役立ちました。 ただし、今度はシェルを削除して、APIからのデータがコードの大部分を占める現実の世界に足を踏み入れます。

実際のデータを受信して​​処理するための子コンポーネントの準備

このセクションでは、 App.vueを除くすべてのコンポーネントのコードダンプを取得します。 このコードは、 App.vueからの実際のデータの受信を処理します(ダミーデータを受信して​​レンダリングするために前のセクションで記述したコードとは異なります)。

各コンポーネントのコードを注意深く読んで、それらの各コンポーネントがどのデータを期待しているかを把握し、最終的には視覚化に使用することを強くお勧めします。

一部のコードと全体的な構造は、前の構造で見たものと似ているため、大幅に異なるものに直面することはありません。 しかし、悪魔は細部に宿っています! したがって、コードを注意深く調べ、それらをかなりよく理解したら、プロジェクト内のそれぞれのコンポーネントファイルにコードをコピーします。

このセクションのすべてのコンポーネントは、 src/components/ディレクトリにあります。 そのため、毎回、パスは言及されません。コンポーネントを識別するために、 .vueファイル名のみが言及されます。

Content.vue

 <template> <div> <temp-var-chart :tempVar="tempVar"></temp-var-chart> <today-highlights :highlights="highlights"></today-highlights> </div> </template> <script> import TempVarChart from './TempVarChart.vue'; import Highlights from './Highlights.vue'; export default { props: ['highlights', 'tempVar'], components: { 'temp-var-chart': TempVarChart, 'today-highlights': Highlights }, } </script>

以前のコードから次の変更が加えられました。

  • <template>では、 {{ }}内のテキストとデータが削除されました。これは、データを受信して​​子に渡すだけであり、このコンポーネントに固有のレンダリングは行われていないためです。
  • export default {}
    • propsは、親によって送信されるデータオブジェクトに一致するように変更されました: App.vue 。 小道具を変更する理由は、 App.vue自体が、ユーザーの検索クエリに基づいて、天気APIやその他のオンラインリソースから取得したデータの一部を表示し、残りのデータを渡すためです。 以前に作成したダミーコードでは、 App.vueはダミーデータダンプ全体を区別なく渡しており、それに応じてContent.vueの小道具が設定されていました。
    • このコンポーネントではデータ操作を行っていないため、data()関数は何も返さないようになりました。

TempVarChart.vue

このコンポーネントは、当日の残りの詳細な気温予測を受け取り、最終的にFusionChartsを使用してそれらを表示することになっています。 ただし、当面は、ウェブページにテキストとしてのみ表示します。

 <template> <div> {{ tempVar.tempToday }} </div> </template> <script> export default { props: ["tempVar"], components: {}, data() { return { }; }, methods: { }, }; </script> <style> </style>

Highlights.vue

 <template> <div> <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </template> <script> import UVIndex from './UVIndex.vue'; import Visibility from './Visibility.vue'; import WindStatus from './WindStatus.vue'; export default { props: ["highlights"], components: { 'uv-index': UVIndex, 'visibility': Visibility, 'wind-status': WindStatus, }, data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

前のコードから行われた変更は次のとおりです。

  • <template>では、 {{ }}内のテキストとデータが削除されています。これは、 Content.vueと同様に、構造階層を維持しながらデータを子に渡すことが唯一の仕事であるダムコンポーネントであるためです。 ダッシュボードの視覚的構造と私たちが作成するコードの間の同等性を維持するために、 Highlights.vueContent.vueなどのダムコンポーネントが存在することを忘れないでください。

UVIndex.vue

前のコードに加えられた変更は次のとおりです。

  • <template>および<style>では、 div idがより読みやすいuvIndexに変更されました。
  • export default {}では、 data()関数が文字列オブジェクトuvIndexを返すようになりました。このオブジェクトの値は、 propsを使用してコンポーネントが受け取ったハイライトオブジェクトから抽出されます。 このuvIndexは、値を<template>内のテキストとして表示するために一時的に使用されるようになりました。 後で、この値をグラフのレンダリングに適したデータ構造にプラグインします。

Visibility.vue

 <template> <div> <p>Visibility: {{ visibility }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { visibility: this.highlights.visibility.toString() } }, methods: { }, computed: { }, } </script> <style> </style>

このファイルで行われた唯一の変更(以前のコードに関して)は、親から受け取った値が浮動小数点になるため、 data()関数によって返されるvisibilityオブジェクトの定義の最後にtoString()が含まれるようになったことです。文字列に変換する必要があるポイント番号。

WindStatus.vue

 <template> <div> <p>Wind Speed — {{ windSpeed }}</p> <p>Wind Direction — {{ derivedWindDirection }}, or {{ windDirection }} degree clockwise with respect to true N as 0 degree.</p> </div> </template> <script> export default { props: ["highlights"], data () { return { windSpeed: this.highlights.windStatus.windSpeed, derivedWindDirection: this.highlights.windStatus.derivedWindDirection, windDirection: this.highlights.windStatus.windDirection } }, methods: { }, computed: { }, } </script> <style> </style>

前のコードに加えられた変更は次のとおりです。

  • ファイル全体で、 windstatusの名前がwindStatusに変更されました。これは、読みやすさを促進し、 App.vueが実際のデータで提供するハイライトオブジェクトと同期するためです。
  • 速度と方向についても同様の名前の変更が行われました—新しいものはwindSpeedwindDirectionです。
  • 新しいオブジェクトderivedWindDirectionが登場しました(これもハイライトバンドルのApp.vueによって提供されます)。

今のところ、受信したデータはテキストとしてレンダリングされます。 後で、視覚化に必要なデータ構造にプラグインされます。

ダミーデータを使用したテスト

ダミーデータに繰り返し頼るのは少しイライラするかもしれませんが、その背後にはいくつかの正当な理由があります。

  • 各コンポーネントのコードに多くの変更を加えました。これらの変更がコードを破壊していないかどうかをテストすることをお勧めします。 つまり、プロジェクトのより複雑な部分に移動しようとしているので、データフローが損なわれていないかどうかを確認する必要があります。
  • オンライン天気APIからの実際のデータには多くのマッサージが必要であり、データの取得と処理のためのコードと、コンポーネントをスムーズにデータフローするためのコードをやりとりするのは大変な作業になる可能性があります。 複雑さの量を制御し、直面する可能性のあるエラーをよりよく理解できるようにするという考え方です。

このセクションでは、基本的にApp.vueの一部のjsonデータをハードコーディングします。これは、近い将来、明らかにライブデータに置き換えられます。 ダミーのjson構造と、実際のデータに使用するjson構造には多くの類似点があります。 したがって、実際のデータに遭遇したときに、実際のデータから何を期待できるかについての大まかなアイデアも提供します。

ただし、これは、このようなプロジェクトを最初から構築するときに採用する可能性のある理想的なアプローチとはほど遠いことを認めます。 現実の世界では、実際のデータソースから始めて、それを飼いならすために何ができ、何をすべきかを少し遊んでから、適切なjsonデータ構造を考えて関連情報を取得することがよくあります。 Vue.jsとFusionChartsを使用してダッシュボードを構築する方法を学ぶことで、目的から遠く離れてしまうため、これらすべての汚い作業から意図的に保護しました。

App.vueの新しいコードに飛び込みましょう。

 <template> <div> <dashboard-content :highlights="highlights" :tempVar="tempVar"></dashboard-content> </div> </template> <script> import Content from './components/Content.vue' export default { name: 'app', components: { 'dashboard-content': Content }, data () { return { tempVar: { tempToday: [ {hour: '11.00 AM', temp: '35'}, {hour: '12.00 PM', temp: '36'}, {hour: '1.00 PM', temp: '37'}, {hour: '2.00 PM', temp: '38'}, {hour: '3.00 PM', temp: '36'}, {hour: '4.00 PM', temp: '35'}, ], }, highlights: { uvIndex: 4, visibility: 10, windStatus: { windSpeed: '30 km/h', windDirection: '30', derivedWindDirection: 'NNE', }, }, } }, methods: { }, computed: { }, } </script> <style> </style>

以前のバージョンに関してコードに加えられた変更は次のとおりです。

  • 子コンポーネントの名前がdashboard-contentに変更されたため、 <template>のカスタムHTML要素が改訂されました。 以前にカスタム要素で使用した単一の属性の代わりに、2つの属性( highlightstempVar )があることに注意してください。 したがって、これらの属性に関連付けられているデータも変更されています。 ここで興味深いのは、カスタムHTML要素の複数の属性でv-bind:ディレクティブまたはその省略形:ここで行ったように)を使用できることです。
  • data()関数は、(古いweather_dataの代わりに)2つの新しいオブジェクトとともにfilenameオブジェクト(以前に存在していた)を返すようになりました: tempVarhighlights 。 jsonの構造は、子コンポーネントに記述したコードに適しているため、ダンプから必要なデータを抽出できます。 構造は非常に自明であり、ライブデータを扱う場合は非常に類似していることが期待できます。 ただし、発生する重要な変更は、ハードコーディングがないことです(明らかに、そうではありません)。デフォルトの状態として値を空白のままにし、から受け取る値に基づいて動的に更新するコードを記述します。天気API。

このセクションでは、実際の出力を見ずに多くのコードを記述しました。 先に進む前に、ブラウザーを確認し(必要に応じて、 npm run devでサーバ​​ーを再起動します)、成果の栄光を満喫してください。 この時点で表示されるWebページは、次の画像のようになります。

ブラウザに表示されたコードの結果
(大プレビュー)

データの取得と処理のためのコード

このセクションはプロジェクトの要であり、 App.vueに記述されるすべてのコードは次のとおりです。

  • ユーザーからの場所の入力—入力ボックスと召喚状ボタンで十分です。
  • さまざまなタスクのためのユーティリティ関数。 これらの関数は、後でコンポーネントコードのさまざまな部分で呼び出されます。
  • JavaScript用のGoogleMapsAPIから詳細なジオロケーションデータを取得します。
  • Dark SkyAPIから詳細な気象データを取得します。
  • 子コンポーネントに渡されるジオロケーションおよび気象データのフォーマットと処理。

次のサブセクションでは、上記のポイントでレイアウトされたタスクを実装する方法を示します。 いくつかの例外を除いて、それらのほとんどはシーケンスに従います。

ユーザーからの入力

ユーザーが気象データを表示する必要のある場所の名前を入力すると、アクションが開始されることは明らかです。 これを実現するには、以下を実装する必要があります。

  • 場所を入力するための入力ボックス。
  • ユーザーが場所を入力し、残りの作業を行う時間であることをアプリケーションに通知する送信ボタン。 Enterキーを押して処理を開始するときの動作も実装します。

以下に示すコードは、 App.vueのHTMLテンプレート部分に制限されます。 クリックイベントに関連付けられているメソッドの名前について説明し、後でApp.vueの<script>のメソッドオブジェクトで定義します。

 <div> <input type="text" ref="input" placeholder="Location?" @keyup.enter="organizeAllDetails" > <button @click="organizeAllDetails"> <img src="./assets/Search.svg" width="24" height="24"> </button> </div>

上記のスニペットを適切な場所に配置するのは簡単です—それはあなたに任せます。 ただし、スニペットの興味深い部分は次のとおりです。

  • @keyup.enter="organizeAllDetails"
  • @click="organizeAllDetails"

前のセクションでご存知のように、 @はVueのディレクティブv-on :の省略形であり、何らかのイベントに関連付けられています。 新しいのは「 organizeAllDetails 」です。これは、イベント( Enterキーを押すかボタンをクリックする)が発生すると起動するメソッドに他なりません。 メソッドはまだ定義されていません。パズルはこのセクションの終わりまでに完成します。

App.vueによって制御されるテキスト情報の表示

ユーザー入力がアクションをトリガーし、APIから大量のデータが取得されると、「これらすべてのデータをどうするか」という避けられない質問に遭遇します。 明らかに、ある程度のデータマッサージが必要ですが、それでは私たちの質問に完全に答えることはできません。 データの最終用途を決定する必要があります。より直接的には、取得および処理されたデータのさまざまなチャンクを受け取るエンティティはどれですか。

App.vueの子コンポーネントは、その階層と目的に基づいて、データの大部分の最前線の候補です。 ただし、これらの子コンポーネントのいずれにも属さないデータもありますが、非常に有益であり、ダッシュボードを完全なものにします。 App.vueによって直接制御されるテキスト情報として表示し、残りのデータを子に渡して最終的にきれいなグラフとして表示すれば、それらを有効に活用できます。

このコンテキストを念頭に置いて、テキストデータを使用する段階を設定するためのコードに焦点を当てましょう。 この時点では、これは単純なHTMLテンプレートであり、データは最終的にその上に配置されます。

 <div> <div class="wrapper-left"> <div> {{ currentWeather.temp }} <span>°C</span> </div> <div>{{ currentWeather.summary }}</div> <div class="temp-max-min"> <div class="max-desc"> <div> <i>▲</i> {{ currentWeather.todayHighLow.todayTempHigh }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempHighTime }}</div> </div> <div class="min-desc"> <div> <i>▼</i> {{ currentWeather.todayHighLow.todayTempLow }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempLowTime }}</div> </div> </div> </div> <div class="wrapper-right"> <div class="date-time-info"> <div> <img src="./assets/calendar.svg" width="20" height="20"> {{ currentWeather.time }} </div> </div> <div class="location-info"> <div> <img src="./assets/location.svg" width="10.83" height="15.83" > {{ currentWeather.full_location }} <div class="mt-1"> Lat: {{ currentWeather.formatted_lat }} <br> Long: {{ currentWeather.formatted_long }} </div> </div> </div> </div> </div>

上記のスニペットでは、次のことを理解する必要があります。

  • {{ }}内のもの—これらは、ブラウザでレンダリングする前に、HTMLテンプレートに動的データを挿入するVueの方法です。 あなたは以前にそれらに遭遇したことがあり、新しいことや驚くべきことは何もありません。 これらのデータオブジェクトは、 App.vueexport default()オブジェクトのdata()メソッドに由来することに注意してください。 これらには、要件に応じて設定するデフォルト値があり、オブジェクトに実際のAPIデータを入力するための特定のメソッドを記述します。

ブラウザに変更が表示されないことを心配する必要はありません。データはまだ定義されていません。Vueが認識していないものをレンダリングしないのは当然です。 ただし、データが設定されると(そして今のところ、データをハードコーディングすることで確認することもできます)、テキストデータはApp.vueによって制御されます。

data()メソッド

data()メソッドは、 .vueファイルの特別な構造であり、アプリケーションにとって非常に重要なデータオブジェクトを含み、返します。 .vueファイルの<script>部分の一般的な構造を思い出してください—大まかに次のものが含まれています。

 <script> // import statements here export default { // name, components, props, etc. data() { return { // the data that is so crucial for the application is defined here. // the data objects will have certain default values chosen by us. // The methods that we define below will manipulate the data. // Since the data is bounded to various attributes and directives, they // will update as and when the values of the data objects change. } }, methods: { // methods (objects whose values are functions) here. // bulk of dynamic stuff (the black magic part) is controlled from here. }, computed: { // computed properties here }, // other objects, as necessary } </script>

これまでに、いくつかのデータオブジェクトの名前に遭遇しましたが、それだけではありません。 それらのほとんどは子コンポーネントに関連しており、各コンポーネントは気象情報ダンプのさまざまな側面を処理します。 以下に、このプロジェクトに必要なdata()メソッド全体を示します。オブジェクトの命名法に基づいて、APIに期待するデータと、データをどのように配布するかについて、公正なアイデアが得られます。

 data() { return { weatherDetails: false, location: '', // raw location from input lat: '', // raw latitude from google maps api response long: '', // raw longitude from google maps api response completeWeatherApi: '', // weather api string with lat and long rawWeatherData: '', // raw response from weather api currentWeather: { full_location: '', // for full address formatted_lat: '', // for N/S formatted_long: '', // for E/W time: '', temp: '', todayHighLow: { todayTempHigh: '', todayTempHighTime: '', todayTempLow: '', todayTempLowTime: '' }, summary: '', possibility: '' }, tempVar: { tempToday: [ // gets added dynamically by this.getSetHourlyTempInfoToday() ], }, highlights: { uvIndex: '', visibility: '', windStatus: { windSpeed: '', windDirection: '', derivedWindDirection: '' }, } }; },

ご覧のとおり、この時点ではデフォルト値で十分なので、ほとんどの場合、デフォルト値は空です。 データを操作して適切な値で埋めてから、レンダリングまたは子コンポーネントに渡すためのメソッドが作成されます。

App.vueのメソッド

.vueファイルの場合、メソッドは通常、 methods { }オブジェクトにネストされたキーの値として記述されます。 それらの主な役割は、コンポーネントのデータオブジェクトを操作することです。 同じ哲学を念頭に置いて、 App.vueでメソッドを記述します。 ただし、その目的に基づいて、 App.vueのメソッドを次のように分類できます。

  • ユーティリティメソッド
  • アクション/イベント指向のメソッド
  • データ取得方法
  • データ処理方法
  • 高レベルの接着方法

これを理解することが重要です。APIがどのように機能し、どのデータを提供し、プロジェクトでデータをどのように使用するかをすでに理解しているため、プラッターでメソッドを提示しています。 メソッドを空から引き出し、データを処理するための難解なコードを書いたわけではありません。 学習の目的のために、メソッドとデータのコードを熱心に読んで理解することは良い練習です。 ただし、最初から構築する必要のある新しいプロジェクトに直面した場合は、すべての汚い作業を自分で行う必要があります。つまり、APIをデータとシームレスに結合する前に、API(プログラムによるアクセスとデータ構造)で多くの実験を行う必要があります。プロジェクトが要求する構造。 手をつなぐことはなく、イライラする瞬間もありますが、それはすべて開発者としての成熟の一部です。

次のサブセクションでは、各メソッドタイプについて説明し、そのカテゴリに属する​​メソッドの実装についても説明します。 メソッド名は、その目的について非常に自明であり、実装も同様です。これは、簡単に理解できると思います。 ただし、その前に、 .vueファイルにメソッドを書き込む一般的なスキームを思い出してください。

 <script> // import statements here export default { // name, components, props, etc. data() { return { // the data that is so crucial for the application is defined here. } }, methods: { // methods (objects whose values are functions) here. // bulk of dynamic stuff (the black magic part) is controlled from here. method_1: function(arg_1) { }, method_2: function(arg_1, arg_2) { }, method_3: function(arg_1) { }, ……. }, computed: { // computed properties here }, // other objects, as necessary } </script>

ユーティリティメソッド

ユーティリティメソッドは、その名前が示すように、主にフリンジタスクに使用される反復コードをモジュール化する目的で記述されたメソッドです。 それらは、必要に応じて他のメソッドによって呼び出されます。 App.vueのユーティリティメソッドを以下に示します。

 convertToTitleCase: function(str) { str = str.toLowerCase().split(' '); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); },
 // To format the “possibility” (of weather) string obtained from the weather API formatPossibility: function(str) { str = str.toLowerCase().split('-'); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); },
 // To convert Unix timestamps according to our convenience unixToHuman: function(timezone, timestamp) { /* READ THIS BEFORE JUDGING & DEBUGGING For any location beyond the arctic circle and the antarctic circle, the goddamn weather api does not return certain keys/values in each of this.rawWeatherData.daily.data[some_array_index]. Due to this, console throws up an error. The code is correct, the problem is with the API. May be later on I will add some padding to tackle missing values. */ var moment = require('moment-timezone'); // for handling date & time var decipher = new Date(timestamp * 1000); var human = moment(decipher) .tz(timezone) .format('llll'); var timeArray = human.split(' '); var timeNumeral = timeArray[4]; var timeSuffix = timeArray[5]; var justTime = timeNumeral + ' ' + timeSuffix; var monthDateArray = human.split(','); var monthDate = monthDateArray[1].trim(); return { fullTime: human, onlyTime: justTime, onlyMonthDate: monthDate }; },
 // To convert temperature from fahrenheit to celcius fahToCel: function(tempInFahrenheit) { var tempInCelcius = Math.round((5 / 9) * (tempInFahrenheit — 32)); return tempInCelcius; },
 // To convert the air pressure reading from millibar to kilopascal milibarToKiloPascal: function(pressureInMilibar) { var pressureInKPA = pressureInMilibar * 0.1; return Math.round(pressureInKPA); },
 // To convert distance readings from miles to kilometers mileToKilometer: function(miles) { var kilometer = miles * 1.60934; return Math.round(kilometer); },
 // To format the wind direction based on the angle deriveWindDir: function(windDir) { var wind_directions_array = [ { minVal: 0, maxVal: 30, direction: 'N' }, { minVal: 31, maxVal: 45, direction: 'NNE' }, { minVal: 46, maxVal: 75, direction: 'NE' }, { minVal: 76, maxVal: 90, direction: 'ENE' }, { minVal: 91, maxVal: 120, direction: 'E' }, { minVal: 121, maxVal: 135, direction: 'ESE' }, { minVal: 136, maxVal: 165, direction: 'SE' }, { minVal: 166, maxVal: 180, direction: 'SSE' }, { minVal: 181, maxVal: 210, direction: 'S' }, { minVal: 211, maxVal: 225, direction: 'SSW' }, { minVal: 226, maxVal: 255, direction: 'SW' }, { minVal: 256, maxVal: 270, direction: 'WSW' }, { minVal: 271, maxVal: 300, direction: 'W' }, { minVal: 301, maxVal: 315, direction: 'WNW' }, { minVal: 316, maxVal: 345, direction: 'NW' }, { minVal: 346, maxVal: 360, direction: 'NNW' } ]; var wind_direction = ''; for (var i = 0; i < wind_directions_array.length; i++) { if ( windDir >= wind_directions_array[i].minVal && windDir <= wind_directions_array[i].maxVal ) { wind_direction = wind_directions_array[i].direction; } } return wind_direction; },

まだ実装していませんが、 .vueファイルからユーティリティメソッドを取り出して、別のJavaScriptファイルに入れることができます。 スクリプト部分の先頭にある.jsファイルを.vueファイルにインポートするだけで、準備は完了です。 このようなアプローチは非常にうまく機能し、コードをクリーンに保ちます。特に、目的に基づいてより適切にグループ化された多くのメソッドを使用する可能性がある大規模なアプリケーションではそうです。 このアプローチをこの記事にリストされているすべてのメソッドグループに適用して、効果自体を確認できます。 ただし、ここで紹介するコースを受講したら、この演習を行うことをお勧めします。これにより、すべての部分が完全に同期して動作していることを全体的に理解し、一度参照できるソフトウェアの動作部分を把握できます。実験中に壊れます。

アクション/イベント指向のメソッド

これらのメソッドは通常、イベントに対応するアクションを実行する必要がある場合に実行されます。 場合によっては、イベントはユーザーの操作から、またはプログラムによってトリガーされる場合があります。 App.vueファイルでは、これらのメソッドはユーティリティメソッドの下にあります。

 makeInputEmpty: function() { this.$refs.input.value = ''; },
 makeTempVarTodayEmpty: function() { this.tempVar.tempToday = []; },
 detectEnterKeyPress: function() { var input = this.$refs.input; input.addEventListener('keyup', function(event) { event.preventDefault(); var enterKeyCode = 13; if (event.keyCode === enterKeyCode) { this.setHitEnterKeyTrue(); } }); },
 locationEntered: function() { var input = this.$refs.input; if (input.value === '') { this.location = "New York"; } else { this.location = this.convertToTitleCase(input.value); } this.makeInputEmpty(); this.makeTempVarTodayEmpty(); },

上記のコードスニペットのいくつかで興味深いことの1つは、 $refの使用です。 簡単に言うと、これは、それを含むコードステートメントを影響を与えると思われるHTML構造に関連付けるVueの方法です(詳細については、公式ガイドを参照してください)。 たとえば、メソッドmakeInputEmpty()およびdetectEnterKeyPress()は、入力ボックスに影響を与えます。これは、入力ボックスのHTMLで、属性refの値をinputとして指定しているためです。

データ取得方法

プロジェクトでは、次の2つのAPIを使用しています。

  • Google Maps Geocoder API
    このAPIは、ユーザーが検索する場所の座標を取得するためのものです。 自分用のAPIキーが必要になります。これは、所定のリンクのドキュメントに従って入手できます。 今のところ、FusionChartsで使用されているAPIキーを使用できますが、悪用しないようにして、独自のキーを取得してください。 このプロジェクトのindex.htmlからJavaScriptAPIを参照し、 App.vueファイルのコードにJavaScriptAPIによって提供されるコンストラクターを使用します。
  • Dark Sky Weather API
    このAPIは、座標に対応する気象データを取得するためのものです。 ただし、直接使用することはありません。 FusionChartsのサーバーの1つを介してリダイレクトするURL内にラップします。 その理由は、私たちのような完全にクライアントエンドのアプリケーションからAPIにGETリクエストを送信すると、イライラするCORSエラーが発生するためです(詳細はこちらとこちら)。

重要な注意GoogleマップとDark Sky APIを使用しているため、これらのAPIには両方とも独自のAPIキーがあり、この記事で共有しています。 これは、バックエンド実装の頭痛の種ではなく、クライアント側の開発に集中するのに役立ちます。 ただし、APIキーには制限があり、これらの制限を超えるとアプリケーションを自分で試すことができないため、独自のキーを作成することをお勧めします。

Googleマップの場合は、この記事にアクセスしてAPIキーを取得してください。 Dark Sky APIの場合は、https://darksky.net/devにアクセスして、APIキーとそれぞれのエンドポイントを作成します。

コンテキストを念頭に置いて、プロジェクトのデータ取得方法の実装を見てみましょう。

 getCoordinates: function() { this.locationEntered(); var loc = this.location; var coords; var geocoder = new google.maps.Geocoder(); return new Promise(function(resolve, reject) { geocoder.geocode({ address: loc }, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { this.lat = results[0].geometry.location.lat(); this.long = results[0].geometry.location.lng(); this.full_location = results[0].formatted_address; coords = { lat: this.lat, long: this.long, full_location: this.full_location }; resolve(coords); } else { alert("Oops! Couldn't get data for the location"); } }); }); },
 /* The coordinates that Google Maps Geocoder API returns are way too accurate for our requirements. We need to bring it into shape before passing the coordinates on to the weather API. Although this is a data processing method in its own right, we can't help mentioning it right now, because the data acquisition method for the weather API has dependency on the output of this method. */ setFormatCoordinates: async function() { var coordinates = await this.getCoordinates(); this.lat = coordinates.lat; this.long = coordinates.long; this.currentWeather.full_location = coordinates.full_location; // Remember to beautify lat for N/S if (coordinates.lat > 0) { this.currentWeather.formatted_lat = (Math.round(coordinates.lat * 10000) / 10000).toString() + '°N'; } else if (coordinates.lat < 0) { this.currentWeather.formatted_lat = (-1 * (Math.round(coordinates.lat * 10000) / 10000)).toString() + '°S'; } else { this.currentWeather.formatted_lat = ( Math.round(coordinates.lat * 10000) / 10000 ).toString(); } // Remember to beautify long for N/S if (coordinates.long > 0) { this.currentWeather.formatted_long = (Math.round(coordinates.long * 10000) / 10000).toString() + '°E'; } else if (coordinates.long < 0) { this.currentWeather.formatted_long = (-1 * (Math.round(coordinates.long * 10000) / 10000)).toString() + '°W'; } else { this.currentWeather.formatted_long = ( Math.round(coordinates.long * 10000) / 10000 ).toString(); } },
 /* This method dynamically creates the the correct weather API query URL, based on the formatted latitude and longitude. The complete URL is then fed to the method querying for weather data. Notice that the base URL used in this method (without the coordinates) points towards a FusionCharts server — we must redirect our GET request to the weather API through a server to avoid the CORS error. */ fixWeatherApi: async function() { await this.setFormatCoordinates(); var weatherApi = 'https://csm.fusioncharts.com/files/assets/wb/wb-data.php?src=darksky&lat=' + this.lat + '&long=' + this.long; this.completeWeatherApi = weatherApi; },
 fetchWeatherData: async function() { await this.fixWeatherApi(); var axios = require('axios'); // for handling weather api promise var weatherApiResponse = await axios.get(this.completeWeatherApi); if (weatherApiResponse.status === 200) { this.rawWeatherData = weatherApiResponse.data; } else { alert('Hmm... Seems like our weather experts are busy!'); } },

Through these methods, we have introduced the concept of async-await in our code. If you have been a JavaScript developer for some time now, you must be familiar with the callback hell, which is a direct consequence of the asynchronous way JavaScript is written. ES6 allows us to bypass the cumbersome nested callbacks, and our code becomes much cleaner if we write JavaScript in a synchronous way, using the async-await technique. ただし、欠点があります。 It takes away the speed that asynchronous code gives us, especially for the portions of the code that deals with data being exchanged over the internet. Since this is not a mission-critical application with low latency requirements, and our primary aim is to learn stuff, the clean code is much more preferable over the slightly fast code.

Data Processing Methods

Now that we have the methods that will bring the data to us, we need to prepare the ground for properly receiving and processing the data. Safety nets must be cast, and there should be no spills — data is the new gold (OK, that might be an exaggeration in our context)! Enough with the fuss, let's get to the point.

Technically, the methods we implement in this section are aimed at getting the data out of the acquisition methods and the data objects in App.vue , and sometimes setting the data objects to certain values that suits the purpose.

getTimezone: function() { return this.rawWeatherData.timezone; },
 getSetCurrentTime: function() { var currentTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); this.currentWeather.time = this.unixToHuman( timezone, currentTime ).fullTime; },
 getSetSummary: function() { var currentSummary = this.convertToTitleCase( this.rawWeatherData.currently.summary ); if (currentSummary.includes(' And')) { currentSummary = currentSummary.replace(' And', ','); } this.currentWeather.summary = currentSummary; },
 getSetPossibility: function() { var possible = this.formatPossibility(this.rawWeatherData.daily.icon); if (possible.includes(' And')) { possible = possible.replace(' And', ','); } this.currentWeather.possibility = possible; },
 getSetCurrentTemp: function() { var currentTemp = this.rawWeatherData.currently.temperature; this.currentWeather.temp = this.fahToCel(currentTemp); },
 getTodayDetails: function() { return this.rawWeatherData.daily.data[0]; },
 getSetTodayTempHighLowWithTime: function() { var timezone = this.getTimezone(); var todayDetails = this.getTodayDetails(); this.currentWeather.todayHighLow.todayTempHigh = this.fahToCel( todayDetails.temperatureMax ); this.currentWeather.todayHighLow.todayTempHighTime = this.unixToHuman( timezone, todayDetails.temperatureMaxTime ).onlyTime; this.currentWeather.todayHighLow.todayTempLow = this.fahToCel( todayDetails.temperatureMin ); this.currentWeather.todayHighLow.todayTempLowTime = this.unixToHuman( timezone, todayDetails.temperatureMinTime ).onlyTime; },
 getHourlyInfoToday: function() { return this.rawWeatherData.hourly.data; },
 getSetHourlyTempInfoToday: function() { var unixTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); var todayMonthDate = this.unixToHuman(timezone, unixTime).onlyMonthDate; var hourlyData = this.getHourlyInfoToday(); for (var i = 0; i < hourlyData.length; i++) { var hourlyTimeAllTypes = this.unixToHuman(timezone, hourlyData[i].time); var hourlyOnlyTime = hourlyTimeAllTypes.onlyTime; var hourlyMonthDate = hourlyTimeAllTypes.onlyMonthDate; if (todayMonthDate === hourlyMonthDate) { var hourlyObject = { hour: '', temp: '' }; hourlyObject.hour = hourlyOnlyTime; hourlyObject.temp = this.fahToCel(hourlyData[i].temperature).toString(); this.tempVar.tempToday.push(hourlyObject); /* Since we are using array.push(), we are just adding elements at the end of the array. Thus, the array is not getting emptied first when a new location is entered. to solve this problem, a method this.makeTempVarTodayEmpty() has been created, and called from this.locationEntered(). */ } } /* To cover the edge case where the local time is between 10 — 12 PM, and therefore there are only two elements in the array this.tempVar.tempToday. We need to add the points for minimum temperature and maximum temperature so that the chart gets generated with atleast four points. */ if (this.tempVar.tempToday.length <= 2) { var minTempObject = { hour: this.currentWeather.todayHighLow.todayTempHighTime, temp: this.currentWeather.todayHighLow.todayTempHigh }; var maxTempObject = { hour: this.currentWeather.todayHighLow.todayTempLowTime, temp: this.currentWeather.todayHighLow.todayTempLow }; /* Typically, lowest temp are at dawn, highest temp is around mid day. Thus we can safely arrange like min, max, temp after 10 PM. */ // array.unshift() adds stuff at the beginning of the array. // the order will be: min, max, 10 PM, 11 PM. this.tempVar.tempToday.unshift(maxTempObject, minTempObject); } },
 getSetUVIndex: function() { var uvIndex = this.rawWeatherData.currently.uvIndex; this.highlights.uvIndex = uvIndex; },
 getSetVisibility: function() { var visibilityInMiles = this.rawWeatherData.currently.visibility; this.highlights.visibility = this.mileToKilometer(visibilityInMiles); },
 getSetWindStatus: function() { var windSpeedInMiles = this.rawWeatherData.currently.windSpeed; this.highlights.windStatus.windSpeed = this.mileToKilometer( windSpeedInMiles ); var absoluteWindDir = this.rawWeatherData.currently.windBearing; this.highlights.windStatus.windDirection = absoluteWindDir; this.highlights.windStatus.derivedWindDirection = this.deriveWindDir( absoluteWindDir ); },

高レベルの接着剤法

ユーティリティ、取得、および処理の方法が邪魔にならないので、私たちは今、すべてを調整するタスクを残されています。 これを行うには、操作全体がシームレスに実行されるように、基本的に上記のメソッドを特定の順序で呼び出す高レベルの接着メソッドを作成します。

 // Top level for info section // Data in this.currentWeather organizeCurrentWeatherInfo: function() { // data in this.currentWeather /* Coordinates and location is covered (get & set) in: — this.getCoordinates() — this.setFormatCoordinates() There are lots of async-await involved there. So it's better to keep them there. */ this.getSetCurrentTime(); this.getSetCurrentTemp(); this.getSetTodayTempHighLowWithTime(); this.getSetSummary(); this.getSetPossibility(); },
 // Top level for highlights organizeTodayHighlights: function() { // top level for highlights this.getSetUVIndex(); this.getSetVisibility(); this.getSetWindStatus(); },
 // Top level organization and rendering organizeAllDetails: async function() { // top level organization await this.fetchWeatherData(); this.organizeCurrentWeatherInfo(); this.organizeTodayHighlights(); this.getSetHourlyTempInfoToday(); },

マウント

Vueは、インスタンスのライフサイクルフックを提供します。これは本質的にメソッドであり、インスタンスのライフサイクルがその段階に達するとトリガーされます。 たとえば、created、mountaed、beforeUpdateなどはすべて、プログラマーが他の方法で可能であったよりもはるかにきめ細かいレベルでインスタンスを制御できるようにする非常に便利なライフサイクルフックです。

Vueコンポーネントのコードでは、これらのライフサイクルフックは、他のpropの場合と同じように実装されます。 例えば:

 <template> </template> <script> // import statements export default { data() { return { // data objects here } }, methods: { // methods here }, mounted: function(){ // function body here }, } </script> <style> </style>

この新しい理解を武器に、 App.vuemountedたプロップについて以下のコードを見てください:

 mounted: async function() { this.location = "New York"; await this.organizeAllDetails(); }

App.vueの完全なコード

このセクションでは多くのことを説明しましたが、最後のいくつかのセクションでは少しずつ説明しました。 ただし、 App.vueの完全なアセンブルされたコードを用意しておくことが重要です(以降のセクションでさらに変更される可能性があります)。 ここに行きます:

 <template> <div> <div class="container-fluid"> <div class="row"> <div class="col-md-3 col-sm-4 col-xs-12 sidebar"> <div> <input type="text" ref="input" placeholder="Location?" @keyup.enter="organizeAllDetails" > <button @click="organizeAllDetails"> <img src="./assets/Search.svg" width="24" height="24"> </button> </div> <div> <div class="wrapper-left"> <div> {{ currentWeather.temp }} <span>°C</span> </div> <div>{{ currentWeather.summary }}</div> <div class="temp-max-min"> <div class="max-desc"> <div> <i>▲</i> {{ currentWeather.todayHighLow.todayTempHigh }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempHighTime }}</div> </div> <div class="min-desc"> <div> <i>▼</i> {{ currentWeather.todayHighLow.todayTempLow }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempLowTime }}</div> </div> </div> </div> <div class="wrapper-right"> <div class="date-time-info"> <div> <img src="./assets/calendar.svg" width="20" height="20"> {{ currentWeather.time }} </div> </div> <div class="location-info"> <div> <img src="./assets/location.svg" width="10.83" height="15.83" > {{ currentWeather.full_location }} <div class="mt-1"> Lat: {{ currentWeather.formatted_lat }} <br> Long: {{ currentWeather.formatted_long }} </div> </div> </div> </div> </div> </div> <dashboard-content class="col-md-9 col-sm-8 col-xs-12 content" :highlights="highlights" :tempVar="tempVar" ></dashboard-content> </div> </div> </div> </template> <script> import Content from './components/Content.vue'; export default { name: 'app', props: [], components: { 'dashboard-content': Content }, data() { return { weatherDetails: false, location: '', // raw location from input lat: '', // raw latitude from google maps api response long: '', // raw longitude from google maps api response completeWeatherApi: '', // weather api string with lat and long rawWeatherData: '', // raw response from weather api currentWeather: { full_location: '', // for full address formatted_lat: '', // for N/S formatted_long: '', // for E/W time: '', temp: '', todayHighLow: { todayTempHigh: '', todayTempHighTime: '', todayTempLow: '', todayTempLowTime: '' }, summary: '', possibility: '' }, tempVar: { tempToday: [ // gets added dynamically by this.getSetHourlyTempInfoToday() ], }, highlights: { uvIndex: '', visibility: '', windStatus: { windSpeed: '', windDirection: '', derivedWindDirection: '' }, } }; }, methods: { // Some utility functions convertToTitleCase: function(str) { str = str.toLowerCase().split(' '); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); }, formatPossibility: function(str) { str = str.toLowerCase().split('-'); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); }, unixToHuman: function(timezone, timestamp) { /* READ THIS BEFORE JUDGING & DEBUGGING For any location beyond the arctic circle and the antarctic circle, the goddamn weather api does not return certain keys/values in each of this.rawWeatherData.daily.data[some_array_index]. Due to this, console throws up an error. The code is correct, the problem is with the API. May be later on I will add some padding to tackle missing values. */ var moment = require('moment-timezone'); // for handling date & time var decipher = new Date(timestamp * 1000); var human = moment(decipher) .tz(timezone) .format('llll'); var timeArray = human.split(' '); var timeNumeral = timeArray[4]; var timeSuffix = timeArray[5]; var justTime = timeNumeral + ' ' + timeSuffix; var monthDateArray = human.split(','); var monthDate = monthDateArray[1].trim(); return { fullTime: human, onlyTime: justTime, onlyMonthDate: monthDate }; }, fahToCel: function(tempInFahrenheit) { var tempInCelcius = Math.round((5 / 9) * (tempInFahrenheit — 32)); return tempInCelcius; }, milibarToKiloPascal: function(pressureInMilibar) { var pressureInKPA = pressureInMilibar * 0.1; return Math.round(pressureInKPA); }, mileToKilometer: function(miles) { var kilometer = miles * 1.60934; return Math.round(kilometer); }, deriveWindDir: function(windDir) { var wind_directions_array = [ { minVal: 0, maxVal: 30, direction: 'N' }, { minVal: 31, maxVal: 45, direction: 'NNE' }, { minVal: 46, maxVal: 75, direction: 'NE' }, { minVal: 76, maxVal: 90, direction: 'ENE' }, { minVal: 91, maxVal: 120, direction: 'E' }, { minVal: 121, maxVal: 135, direction: 'ESE' }, { minVal: 136, maxVal: 165, direction: 'SE' }, { minVal: 166, maxVal: 180, direction: 'SSE' }, { minVal: 181, maxVal: 210, direction: 'S' }, { minVal: 211, maxVal: 225, direction: 'SSW' }, { minVal: 226, maxVal: 255, direction: 'SW' }, { minVal: 256, maxVal: 270, direction: 'WSW' }, { minVal: 271, maxVal: 300, direction: 'W' }, { minVal: 301, maxVal: 315, direction: 'WNW' }, { minVal: 316, maxVal: 345, direction: 'NW' }, { minVal: 346, maxVal: 360, direction: 'NNW' } ]; var wind_direction = ''; for (var i = 0; i < wind_directions_array.length; i++) { if ( windDir >= wind_directions_array[i].minVal && windDir <= wind_directions_array[i].maxVal ) { wind_direction = wind_directions_array[i].direction; } } return wind_direction; }, // Some basic action oriented functions makeInputEmpty: function() { this.$refs.input.value = ''; }, makeTempVarTodayEmpty: function() { this.tempVar.tempToday = []; }, detectEnterKeyPress: function() { var input = this.$refs.input; input.addEventListener('keyup', function(event) { event.preventDefault(); var enterKeyCode = 13; if (event.keyCode === enterKeyCode) { this.setHitEnterKeyTrue(); } }); }, locationEntered: function() { var input = this.$refs.input; if (input.value === '') { this.location = "New York"; } else { this.location = this.convertToTitleCase(input.value); } this.makeInputEmpty(); this.makeTempVarTodayEmpty(); }, getCoordinates: function() { this.locationEntered(); var loc = this.location; var coords; var geocoder = new google.maps.Geocoder(); return new Promise(function(resolve, reject) { geocoder.geocode({ address: loc }, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { this.lat = results[0].geometry.location.lat(); this.long = results[0].geometry.location.lng(); this.full_location = results[0].formatted_address; coords = { lat: this.lat, long: this.long, full_location: this.full_location }; resolve(coords); } else { alert("Oops! Couldn't get data for the location"); } }); }); }, // Some basic asynchronous functions setFormatCoordinates: async function() { var coordinates = await this.getCoordinates(); this.lat = coordinates.lat; this.long = coordinates.long; this.currentWeather.full_location = coordinates.full_location; // Remember to beautify lat for N/S if (coordinates.lat > 0) { this.currentWeather.formatted_lat = (Math.round(coordinates.lat * 10000) / 10000).toString() + '°N'; } else if (coordinates.lat < 0) { this.currentWeather.formatted_lat = (-1 * (Math.round(coordinates.lat * 10000) / 10000)).toString() + '°S'; } else { this.currentWeather.formatted_lat = ( Math.round(coordinates.lat * 10000) / 10000 ).toString(); } // Remember to beautify long for N/S if (coordinates.long > 0) { this.currentWeather.formatted_long = (Math.round(coordinates.long * 10000) / 10000).toString() + '°E'; } else if (coordinates.long < 0) { this.currentWeather.formatted_long = (-1 * (Math.round(coordinates.long * 10000) / 10000)).toString() + '°W'; } else { this.currentWeather.formatted_long = ( Math.round(coordinates.long * 10000) / 10000 ).toString(); } }, fixWeatherApi: async function() { await this.setFormatCoordinates(); var weatherApi = 'https://csm.fusioncharts.com/files/assets/wb/wb-data.php?src=darksky&lat=' + this.lat + '&long=' + this.long; this.completeWeatherApi = weatherApi; }, fetchWeatherData: async function() { await this.fixWeatherApi(); var axios = require('axios'); // for handling weather api promise var weatherApiResponse = await axios.get(this.completeWeatherApi); if (weatherApiResponse.status === 200) { this.rawWeatherData = weatherApiResponse.data; } else { alert('Hmm... Seems like our weather experts are busy!'); } }, // Get and set functions; often combined, because they are short // For basic info — left panel/sidebar getTimezone: function() { return this.rawWeatherData.timezone; }, getSetCurrentTime: function() { var currentTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); this.currentWeather.time = this.unixToHuman( timezone, currentTime ).fullTime; }, getSetSummary: function() { var currentSummary = this.convertToTitleCase( this.rawWeatherData.currently.summary ); if (currentSummary.includes(' And')) { currentSummary = currentSummary.replace(' And', ','); } this.currentWeather.summary = currentSummary; }, getSetPossibility: function() { var possible = this.formatPossibility(this.rawWeatherData.daily.icon); if (possible.includes(' And')) { possible = possible.replace(' And', ','); } this.currentWeather.possibility = possible; }, getSetCurrentTemp: function() { var currentTemp = this.rawWeatherData.currently.temperature; this.currentWeather.temp = this.fahToCel(currentTemp); }, getTodayDetails: function() { return this.rawWeatherData.daily.data[0]; }, getSetTodayTempHighLowWithTime: function() { var timezone = this.getTimezone(); var todayDetails = this.getTodayDetails(); this.currentWeather.todayHighLow.todayTempHigh = this.fahToCel( todayDetails.temperatureMax ); this.currentWeather.todayHighLow.todayTempHighTime = this.unixToHuman( timezone, todayDetails.temperatureMaxTime ).onlyTime; this.currentWeather.todayHighLow.todayTempLow = this.fahToCel( todayDetails.temperatureMin ); this.currentWeather.todayHighLow.todayTempLowTime = this.unixToHuman( timezone, todayDetails.temperatureMinTime ).onlyTime; }, getHourlyInfoToday: function() { return this.rawWeatherData.hourly.data; }, getSetHourlyTempInfoToday: function() { var unixTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); var todayMonthDate = this.unixToHuman(timezone, unixTime).onlyMonthDate; var hourlyData = this.getHourlyInfoToday(); for (var i = 0; i < hourlyData.length; i++) { var hourlyTimeAllTypes = this.unixToHuman(timezone, hourlyData[i].time); var hourlyOnlyTime = hourlyTimeAllTypes.onlyTime; var hourlyMonthDate = hourlyTimeAllTypes.onlyMonthDate; if (todayMonthDate === hourlyMonthDate) { var hourlyObject = { hour: '', temp: '' }; hourlyObject.hour = hourlyOnlyTime; hourlyObject.temp = this.fahToCel(hourlyData[i].temperature).toString(); this.tempVar.tempToday.push(hourlyObject); /* Since we are using array.push(), we are just adding elements at the end of the array. Thus, the array is not getting emptied first when a new location is entered. to solve this problem, a method this.makeTempVarTodayEmpty() has been created, and called from this.locationEntered(). */ } } /* To cover the edge case where the local time is between 10 — 12 PM, and therefore there are only two elements in the array this.tempVar.tempToday. We need to add the points for minimum temperature and maximum temperature so that the chart gets generated with atleast four points. */ if (this.tempVar.tempToday.length <= 2) { var minTempObject = { hour: this.currentWeather.todayHighLow.todayTempHighTime, temp: this.currentWeather.todayHighLow.todayTempHigh }; var maxTempObject = { hour: this.currentWeather.todayHighLow.todayTempLowTime, temp: this.currentWeather.todayHighLow.todayTempLow }; /* Typically, lowest temp are at dawn, highest temp is around mid day. Thus we can safely arrange like min, max, temp after 10 PM. */ // array.unshift() adds stuff at the beginning of the array. // the order will be: min, max, 10 PM, 11 PM. this.tempVar.tempToday.unshift(maxTempObject, minTempObject); } }, // For Today Highlights getSetUVIndex: function() { var uvIndex = this.rawWeatherData.currently.uvIndex; this.highlights.uvIndex = uvIndex; }, getSetVisibility: function() { var visibilityInMiles = this.rawWeatherData.currently.visibility; this.highlights.visibility = this.mileToKilometer(visibilityInMiles); }, getSetWindStatus: function() { var windSpeedInMiles = this.rawWeatherData.currently.windSpeed; this.highlights.windStatus.windSpeed = this.mileToKilometer( windSpeedInMiles ); var absoluteWindDir = this.rawWeatherData.currently.windBearing; this.highlights.windStatus.windDirection = absoluteWindDir; this.highlights.windStatus.derivedWindDirection = this.deriveWindDir( absoluteWindDir ); }, // top level for info section organizeCurrentWeatherInfo: function() { // data in this.currentWeather /* Coordinates and location is covered (get & set) in: — this.getCoordinates() — this.setFormatCoordinates() There are lots of async-await involved there. So it's better to keep them there. */ this.getSetCurrentTime(); this.getSetCurrentTemp(); this.getSetTodayTempHighLowWithTime(); this.getSetSummary(); this.getSetPossibility(); }, organizeTodayHighlights: function() { // top level for highlights this.getSetUVIndex(); this.getSetVisibility(); this.getSetWindStatus(); }, // topmost level orchestration organizeAllDetails: async function() { // top level organization await this.fetchWeatherData(); this.organizeCurrentWeatherInfo(); this.organizeTodayHighlights(); this.getSetHourlyTempInfoToday(); }, }, mounted: async function() { this.location = "New York"; await this.organizeAllDetails(); } }; </script>

そして最後に、多くの忍耐と努力の末、その生の力でデータフローを見ることができます! ブラウザでアプリケーションにアクセスし、ページを更新して、アプリケーションの検索ボックスで場所を検索し、 Enterキーを押します。

ブラウザに表示されるアプリケーション
(大プレビュー)

面倒な作業がすべて終わったので、休憩します。 以降のセクションでは、データを使用して美しく有益なグラフを作成することに焦点を当て、続いて、見苦しいアプリケーションにCSSを使用した非常に価値のあるグルーミングセッションを提供します。

5.FusionChartsによるデータの視覚化

チャートの基本的な考慮事項

エンドユーザーにとって、ダッシュボードの本質は本質的にこれです。特定のトピックに関するフィルタリングされ、慎重にキュレーションされた情報のコレクションであり、視覚的/グラフィック機器を介して迅速に取り込むことができます。 彼らは、データパイプラインエンジニアリングの微妙さや、コードの美しさを気にしません。必要なのは、3秒で高レベルのビューを表示することだけです。 したがって、テキストデータを表示する大まかなアプリケーションは、彼らにとって何の意味もありません。データをグラフでラップするメカニズムを実装する時期が来ています。

ただし、チャートの実装について深く掘り下げる前に、いくつかの関連する質問と考えられる答えを私たちの観点から考えてみましょう。

  • 扱っているデータの種類に適したグラフの種類は何ですか?
    答えには、コンテキストと目的の2つの側面があります。 コンテキストとは、データのタイプを意味し、プロジェクトの範囲と対象者によって制限される、より大きなもののスキームに全体的に適合します。 そして、目的によって、私たちは本質的に「私たちが強調したいこと」を意味します。 たとえば、縦棒グラフ(同じ幅の縦の柱、高さは柱が表す値に比例する)を使用して、1日のさまざまな時間における今日の気温を表すことができます。 ただし、個々の値に関心があることはめったになく、データ全体の全体的な変動と傾向に関心があります。 目的に合わせて、折れ線グラフを使用することが最善の策であり、まもなく使用します。
  • チャートライブラリを選択する前に、何に留意する必要がありますか?
    私たちは主にJavaScriptベースのテクノロジーを使用してプロジェクトを行っているため、プロジェクト用に選択するグラフ作成ライブラリはJavaScriptの世界にネイティブである必要があります。 その基本的な前提を念頭に置いて、特定のライブラリをゼロにする前に、次のことを考慮する必要があります。
    • 選択したフレームワーク(この場合はVue.js)のサポート。 プロジェクトは、ReactやAngularなどの他の一般的なJavaScriptフレームワークで開発できます。お気に入りのフレームワークについては、グラフ作成ライブラリのサポートを確認してください。 また、Python、Java、C ++ 、. Net(ASおよびVB)などの他の一般的なプログラミング言語のサポートも、特にプロジェクトに深刻なバックエンドが含まれる場合は、検討する必要があります。
    • プロジェクトのデータの最終的な形状と目的を事前に知ることはほとんど不可能であるため、チャートのタイプと機能の可用性(特に要件が専門的な設定でクライアントによって規制されている場合)。 この場合、ネット全体をキャストし、チャートのコレクションが最も広いチャートライブラリを選択する必要があります。 さらに重要なことに、プロジェクトを他のプロジェクトと区別するには、ライブラリに構成可能なグラフ属性の形式で十分な機能が必要です。これにより、グラフのほとんどの側面と適切なレベルの粒度を微調整およびカスタマイズできます。 また、デフォルトのチャート構成は賢明である必要があり、プロの開発者には明らかな理由から、ライブラリのドキュメントは一流でなければなりません。
    • 特にデータの視覚化に慣れていない場合は、学習曲線、サポートコミュニティ、およびバランスも考慮する必要があります。 スペクトルの一端には、爆弾の費用がかかり、学習曲線がスムーズであるが、カスタマイズ性、統合、および展開に関して非常に多くの制限があるTableauやQlickviewなどの独自のツールがあります。 もう一方の端にはd3.jsがあります—広大で、無料(オープンソース)で、コアに合わせてカスタマイズできますが、ライブラリで生産的なことを何でもできるようにするには、非常に急な学習曲線の代償を払う必要があります。

必要なのはスイートスポットです。生産性、カバレッジ、カスタマイズ性、学習曲線、そしてコース外のコストの適切なバランスです。 このプロジェクトでグラフを作成するために使用する、Webおよびモバイル向けの世界で最も包括的でエンタープライズ対応のJavaScriptグラフ作成ライブラリであるFusionChartsをご覧ください。

FusionChartsの紹介

FusionChartsは、世界中の何百もの国に広がる何百万もの開発者によって、頼りになるJavaScriptチャートライブラリとして世界中で使用されています。 技術的には、可能な限りロードおよび構成可能であり、Webベースのプロジェクトで使用されるほとんどすべての一般的な技術スタックとの統合をサポートしています。 FusionChartsを商業的に使用するにはライセンスが必要であり、ユースケースに応じてライセンスの料金を支払う必要があります(興味がある場合は営業担当者にお問い合わせください)。 ただし、このプロジェクトでは、いくつかのことを試すためにFusionChartsを使用しているため、ライセンスのないバージョン(チャートに小さな透かしがあり、その他のいくつかの制限があります)。 チャートを試したり、非営利または個人のプロジェクトで使用したりする場合は、ライセンスのないバージョンを使用してもまったく問題ありません。 アプリケーションを商用展開する予定がある場合は、FusionChartsからライセンスを取得していることを確認してください。

これはVue.jsを含むプロジェクトであるため、以前に実行しなかった場合は、インストールする必要のある2つのモジュールが必要になります。

  • チャートの作成に必要なものがすべて含まれているため、 fusionchartsモジュール
  • vue-fusionchartsモジュール。これは基本的にfusionchartsのラッパーであるため、Vue.jsプロジェクトで使用できます。

以前にインストールしていない場合(3番目のセクションの説明に従って)、プロジェクトのルートディレクトリから次のコマンドを実行してインストールします。

 npm install fusioncharts vue-fusioncharts --save

次に、プロジェクトのsrc/main.jsファイルに次のコードが含まれていることを確認します(セクション3でも説明されています)。

 import Vue from 'vue'; import App from './App.vue'; import FusionCharts from 'fusioncharts'; import Charts from 'fusioncharts/fusioncharts.charts'; import Widgets from 'fusioncharts/fusioncharts.widgets'; import PowerCharts from 'fusioncharts/fusioncharts.powercharts'; import FusionTheme from 'fusioncharts/themes/fusioncharts.theme.fusion'; import VueFusionCharts from 'vue-fusioncharts'; Charts(FusionCharts); PowerCharts(FusionCharts); Widgets(FusionCharts); FusionTheme(FusionCharts); Vue.use(VueFusionCharts, FusionCharts); new Vue({ el: '#app', render: h => h(App) })

おそらく、上記のスニペットで最も重要な行は次のとおりです。

 Vue.use(VueFusionCharts, FusionCharts)

これは、Vueにvue-fusionchartsモジュールを使用して、プロジェクト内で明示的に定義されていないが、モジュール自体で定義されている多くのことを理解するように指示します。 また、このタイプのステートメントはグローバル宣言を意味します。つまり、Vueがプロジェクトのコードで奇妙なもの(FusionChartsの使用について明示的に定義していないもの)に遭遇すると、少なくとも1回はvue-fusionchartsを検索します。エラーをスローする前に、それらの定義についてfusionchartsノードモジュール。 プロジェクトの分離された部分でFusionChartsを使用した場合(ほとんどすべてのコンポーネントファイルで使用しなかった場合)、ローカル宣言の方が理にかなっている可能性があります。

これで、プロジェクトでFusionChartsを使用する準備が整いました。 かなりの数のさまざまなグラフを使用します。選択は、視覚化する気象データの側面によって異なります。 また、データバインディング、カスタムコンポーネント、ウォッチャーの動作を確認できます。

.vueファイルでFusionchartsを使用するための一般的なスキーム

このセクションでは、FusionChartsを使用して.vueファイルにさまざまなグラフを作成する一般的な考え方について説明します。 しかし、最初に、コアアイデアを概略的に示す擬似コードを見てみましょう。

 <template> <div> <fusioncharts :attribute_1="data_object_1" :attribute_2="data_object_2" … … ... > </fusioncharts> </div> </template> <script> export default { props: ["data_prop_received_by_the_component"], components: {}, data() { return { data_object_1: "value_1", data_object_2: "value_2", … … }; }, methods: {}, computed: {}, watch: { data_prop_received_by_the_component: { handler: function() { // some code/logic, mainly data manipulation based }, deep: true } } }; </script> <style> // component specific special CSS code here </style>

上記の擬似コードのさまざまな部分を理解しましょう。

  • <template>のトップレベルの<div> (これはすべてのコンポーネントのテンプレートHTMLコードにほぼ必須です)内に、カスタムコンポーネント<fusioncharts>があります。 このプロジェクト用にインストールしたvue-fusionchartsノードモジュールに含まれるコンポーネントの定義があります。 内部的には、 vue-fusionchartsは、同じくインストールされているfusionchartsモジュールに依存しています。 必要なモジュールをインポートして依存関係を解決し、 src/main.jsファイルでラッパーをグローバルに(プロジェクト全体で)使用するようにVueに指示したため、使用したカスタム<fusioncharts>コンポーネントの定義が不足していません。ここ。 また、カスタムコンポーネントにはカスタム属性があり、各カスタム属性は、省略形がコロン( :記号であるv-bindディレクティブによってデータオブジェクト(およびその値)にバインドされます。 このプロジェクトで使用される特定のグラフのいくつかについて説明するときに、属性とそれに関連するデータオブジェクトについて詳しく学習します。
  • <script>で、最初にコンポーネントが受け取ることになっている小道具を宣言し、次に<fusioncharts>の属性にバインドされているデータオブジェクトを定義します。 データオブジェクトに割り当てられた値は、 <fusioncharts>の属性がプルインする値であり、チャートはプルインされた値に基づいて作成されます。 これらとは別に、コードの最も興味深い部分はwatch { }オブジェクトです。 これは、Vueのスキームにおける非常に特殊なオブジェクトです。基本的に、特定のデータに発生する変更を監視し、そのデータのhandler関数がどのように定義されているかに基づいてアクションを実行するようにVueに指示します。 たとえば、Vueが受信したprop 、つまり擬似コードのdata_prop_received_by_the_componentを監視するようにします。 propwatch { }オブジェクトのキーになり、キーの値は別のオブジェクトになります。これは、 propが変更されるたびに実行する必要があることを記述するハンドラーメソッドです。 変更を処理するためのこのようなエレガントなメカニズムにより、アプリはその反応性を維持します。 deep: trueは、ウォッチャーに関連付けることができるブールフラグを表します。これにより、監視対象のオブジェクトがかなり深く監視されます。つまり、オブジェクトのネストされたレベルで行われた変更も追跡されます。
    ウォッチャーの詳細については、公式ドキュメントを参照してください)。

物事の一般的なスキームの理解ができたので、 .vueコンポーネントファイルのチャートの特定の実装に飛び込みましょう。 コードはかなり自明であり、詳細が上記の一般的なスキームにどのように適合するかを理解するようにしてください。

.vueファイルでのチャートの実装

実装の詳細はチャートごとに異なりますが、次の説明はそれらすべてに当てはまります。

  • <template>
    前に説明したように、 <fusioncharts>カスタムコンポーネントにはいくつかの属性があり、それぞれがv-bind :ディレクティブを使用してdata()関数で定義された対応するデータオブジェクトにバインドされます。 属性名は、それらが何を意味するかを非常に自明であり、対応するデータオブジェクトを理解することも簡単です。
  • <script>
    data()関数では、 <fusioncharts>の属性で使用されるv-bind:ディレクティブによってバインドが行われるため、データオブジェクトとその値がグラフを機能させます。 個々のデータオブジェクトを深く掘り下げる前に、いくつかの一般的な特性について説明する価値があります。
    • 値が0または1のデータオブジェクトは本質的にブール値であり、 0は使用できない/オフになっているものを表し、 1は使用可能/オンになっている状態を表します。 ただし、ブール値以外のデータオブジェクトも、他の可能な値に加えて、値として0または1を持つ可能性があることに注意してください。これは、コンテキストによって異なります。 たとえば、デフォルト値が0containerbackgroundopacityはブール値ですが、デフォルト値が0lowerLimitは、単に数値ゼロがそのリテラル値であることを意味します。
    • 一部のデータオブジェクトは、マージン、パディング、フォントサイズなどのCSSプロパティを処理します。値には、「px」またはピクセルの暗黙の単位があります。 同様に、他のデータオブジェクトは、それらの値に関連付けられた暗黙の単位を持つことができます。 詳細については、FusionCharts DevCenterのそれぞれのチャート属性ページを参照してください。
  • data()関数では、おそらく最も興味深く、自明ではないオブジェクトはdataSourceです。 このオブジェクトには、3つの主要なオブジェクトがネストされています。
    • チャート:このオブジェクトは、チャートの構成と外観に関連する多くのチャート属性をカプセル化します。 これは、このプロジェクト用に作成するすべてのチャートに見られるほぼ必須の構成です。
    • colorrange :このオブジェクトは、検討中のチャートにいくらか固有であり、チャートで使用されるスケールのさまざまなサブ範囲を区別するために複数の色/色合いを処理するチャートに主に存在します。
    • value:このオブジェクトも、スケールの範囲で強調表示する必要がある特定の値を持つチャートに存在します。
  • watch { }オブジェクトは、おそらくこのチャートを作成する最も重要なものであり、このプロジェクトで使用される他のチャートは、生き生きとしています。 チャートの反応性、つまり、新しいユーザークエリの結果として生じる新しい値に基づいてチャートが更新されることは、このオブジェクトで定義されたウォッチャーによって制御されます。 たとえば、コンポーネントが受け取るプロップhighlightsのウォッチャーを定義し、プロジェクト全体で監視されているオブジェクトについて何かが変更されたときに実行する必要のあるアクションについてVueに指示するハンドラー関数を定義しました。 これは、 App.vuehighlights内のオブジェクトのいずれかに新しい値を生成するたびに、情報がこのコンポーネントに至るまで細流化され、このコンポーネントのデータオブジェクトで新しい値が更新されることを意味します。 値にバインドされているチャートも、このメカニズムの結果として更新されます。

上記の説明は、全体像を直感的に理解するのに役立つ非常に幅広いストロークです。 概念を直感的に理解すると、コード自体から何かが明確でない場合は、いつでもVue.jsとFusionChartsのドキュメントを参照できます。 演習はあなたに任せます。次のサブセクション以降、このサブセクションで取り上げた内容については説明しません。

src / components / TempVarChart.vue

毎時温度を示す図
(大プレビュー)
 <template> <div class="custom-card header-card card"> <div class="card-body pt-0"> <fusioncharts type="spline" width="100%" height="100%" dataformat="json" dataEmptyMessage="i-https://i.postimg.cc/R0QCk9vV/Rolling-0-9s-99px.gif" dataEmptyMessageImageScale=39 :datasource="tempChartData" > </fusioncharts> </div> </div> </template> <script> export default { props: ["tempVar"], components: {}, data() { return { tempChartData: { chart: { caption: "Hourly Temperature", captionFontBold: "0", captionFontColor: "#000000", captionPadding: "30", baseFont: "Roboto", chartTopMargin: "30", showHoverEffect: "1", theme: "fusion", showaxislines: "1", numberSuffix: "°C", anchorBgColor: "#6297d9", paletteColors: "#6297d9", drawCrossLine: "1", plotToolText: "$label<br><hr><b>$dataValue</b>", showAxisLines: "0", showYAxisValues: "0", anchorRadius: "4", divLineAlpha: "0", labelFontSize: "13", labelAlpha: "65", labelFontBold: "0", rotateLabels: "1", slantLabels: "1", canvasPadding: "20" }, data: [], }, }; }, methods: { setChartData: function() { var data = []; for (var i = 0; i < this.tempVar.tempToday.length; i++) { var dataObject = { label: this.tempVar.tempToday[i].hour, value: this.tempVar.tempToday[i].temp }; data.push(dataObject); } this.tempChartData.data = data; }, }, mounted: function() { this.setChartData(); }, watch: { tempVar: { handler: function() { this.setChartData(); }, deep: true }, }, }; </script> <style> </style>

src / components / UVIndex.vue

このコンポーネントには、非常に便利なチャート(Angular Gauge)が含まれています。

UV指数
(大プレビュー)

コンポーネントのコードを以下に示します。 Angular Gaugeのチャート属性の詳細については、AngularGaugeのFusionChartsDevCenterページを参照してください。

 <template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-top"> <div> <fusioncharts :type="type" :width="width" :height="height" :containerbackgroundopacity="containerbackgroundopacity" :dataformat="dataformat" :datasource="datasource" ></fusioncharts> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, data() { return { type: "angulargauge", width: "100%", height: "100%", containerbackgroundopacity: 0, dataformat: "json", datasource: { chart: { caption: "UV Index", captionFontBold: "0", captionFontColor: "#000000", captionPadding: "30", lowerLimit: "0", upperLimit: "15", lowerLimitDisplay: "1", upperLimitDisplay: "1", showValue: "0", theme: "fusion", baseFont: "Roboto", bgAlpha: "0", canvasbgAlpha: "0", gaugeInnerRadius: "75", gaugeOuterRadius: "110", pivotRadius: "0", pivotFillAlpha: "0", valueFontSize: "20", valueFontColor: "#000000", valueFontBold: "1", tickValueDistance: "3", autoAlignTickValues: "1", majorTMAlpha: "20", chartTopMargin: "30", chartBottomMargin: "40" }, colorrange: { color: [ { minvalue: "0", maxvalue: this.highlights.uvIndex.toString(), code: "#7DA9E0" }, { minvalue: this.highlights.uvIndex.toString(), maxvalue: "15", code: "#D8EDFF" } ] }, annotations: { groups: [ { items: [ { id: "val-label", type: "text", text: this.highlights.uvIndex.toString(), fontSize: "20", font: "Source Sans Pro", fontBold: "1", fillcolor: "#212529", x: "$gaugeCenterX", y: "$gaugeCenterY" } ] } ] }, dials: { dial: [ { value: this.highlights.uvIndex.toString(), baseWidth: "0", radius: "0", borderThickness: "0", baseRadius: "0" } ] } } }; }, methods: {}, computed: {}, watch: { highlights: { handler: function() { this.datasource.colorrange.color[0].maxvalue = this.highlights.uvIndex.toString(); this.datasource.colorrange.color[1].minvalue = this.highlights.uvIndex.toString(); this.datasource.annotations.groups[0].items[0].text = this.highlights.uvIndex.toString(); }, deep: true } } }; </script>

src / components / Visibility.vue

このコンポーネントでは、次の画像に示すように、水平線形ゲージを使用して可視性を表します。

視程(16km)を表す水平線形ゲージのスクリーンショット
(大プレビュー)

コンポーネントのコードを以下に示します。 このチャートタイプのさまざまな属性の詳細については、水平線形ゲージのFusionCharts DevCenterページを参照してください。

 <template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-left border-right border-top"> <div> <fusioncharts :type="type" :width="width" :height="height" :containerbackgroundopacity="containerbackgroundopacity" :dataformat="dataformat" :datasource="datasource" > </fusioncharts> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, methods: {}, computed: {}, data() { return { type: "hlineargauge", width: "100%", height: "100%", containerbackgroundopacity: 0, dataformat: "json", creditLabel: false, datasource: { chart: { caption: "Air Visibility", captionFontBold: "0", captionFontColor: "#000000", baseFont: "Roboto", numberSuffix: " km", lowerLimit: "0", upperLimit: "40", showPointerShadow: "1", animation: "1", transposeAnimation: "1", theme: "fusion", bgAlpha: "0", canvasBgAlpha: "0", valueFontSize: "20", valueFontColor: "#000000", valueFontBold: "1", pointerBorderAlpha: "0", chartBottomMargin: "40", captionPadding: "30", chartTopMargin: "30" }, colorRange: { color: [ { minValue: "0", maxValue: "4", label: "Fog", code: "#6297d9" }, { minValue: "4", maxValue: "10", label: "Haze", code: "#7DA9E0" }, { minValue: "10", maxValue: "40", label: "Clear", code: "#D8EDFF" } ] }, pointers: { pointer: [ { value: this.highlights.visibility.toString() } ] } } }; }, watch: { highlights: { handler: function() { this.datasource.pointers.pointer[0].value = this.highlights.visibility.toString(); }, deep: true } } }; </script>

src / components / WindStatus.vue

このコンポーネントは、風速と風向(物理学に精通している場合は風速)を表示し、グラフを使用してベクトルを表すことは非常に困難です。 そのような場合は、いくつかの素敵な画像とテキスト値を使用してそれらを表現することをお勧めします。 私たちが考えた表現は完全にCSSに依存しているので、CSSを扱う次のセクションでそれを実装します。 ただし、私たちが作成しようとしているものを見てください。

風の状態:風向(左)と風速(右)
(大プレビュー)
 <template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-top"> <div> <div class="card-heading pt-5">Wind Status</div> <div class="row pt-4 mt-4"> <div class="col-sm-6 col-md-6 mt-2 text-center align-middle"> <p class="card-sub-heading mt-3">Wind Direction</p> <p class="mt-4"><img src="../assets/winddirection.svg" height="40" width="40"></p> <p class="card-value mt-4">{{ highlights.windStatus.derivedWindDirection }}</p> </div> <div class="col-sm-6 col-md-6 mt-2"> <p class="card-sub-heading mt-3">Wind Speed</p> <p class="mt-4"><img src="../assets/windspeed.svg" height="40" width="40"></p> <p class="card-value mt-4">{{ highlights.windStatus.windSpeed }} km/h</p> </div> </div> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, data() { return {}; }, methods: {}, computed: {} }; </script>

Highlights.vueでまとめ

Content.vueHighlights.vueを除くすべてのコンポーネントにCSSを使用したコードをすでに実装していることを思い出してください。 Content.vueはデータを中継するだけのダムコンポーネントであるため、必要な最小限のスタイル設定はすでにカバーされています。 また、サイドバーとチャートを含むカードのスタイルを設定するための適切なコードをすでに作成しています。 したがって、あとは、 Highlights.vueにいくつかのスタイルビットを追加するだけです。これには、主にCSSクラスの使用が含まれます。

 <template> <div class="custom-content-card content-card card"> <div class="card-body pb-0"> <div class="content-header h4 text-center pt-2 pb-3">Highlights</div> <div class="row"> <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </div> </div> </template> <script> import UVIndex from "./UVIndex.vue"; import Visibility from "./Visibility.vue"; import WindStatus from "./WindStatus.vue"; export default { props: ["highlights"], components: { "uv-index": UVIndex, "visibility": Visibility, "wind-status": WindStatus, }, }; </script>

展開とソースコード

チャートとスタイルを順番に並べて、完了です。 あなたの創造物の美しさを鑑賞するために少し時間を取ってください。

結果
(大プレビュー)

今度は、アプリケーションをデプロイして、ピアと共有するときです。 展開についてあまり理解しておらず、私たちがあなたを助けることを期待している場合は、ここで展開のアイデアに関する私たちの見解を見てください。 リンクされた記事には、すべてのチャートの左下にあるFusionChartsウォーターマークを削除する方法に関する提案も含まれています。

どこかで混乱して参照ポイントが必要な場合は、Githubでソースコードを入手できます。