استخدام مكتبة Morris.js لإنشاء مخططات بيانية في Laravel


محمد أحمد العيل

يكثُر استخدام المخططات البيانيّة في مواقع الويب لتقريب المعلومة للزّائر. سنرى في هذا الدرس كيفية إنشاء مخطّطات بيانية في تطبيق Laravel باستخدام مكتبة Morris.js.

morris-charts-laravel.png

سنفترض أن لديك مشروع Laravel معدًّا وجاهزًا للعمل، وقاعدة بيانات مضبوطة. تتلخّص خطوات هذا الدرس في ما يلي:

  • إنشاء نموذج Model للتطبيق إضافة للتهجيرات Migrations المرتبطة به.
  • إنشاء متحكّمات Controllers وعروض Views لإظهار البيانات للزائر.
  • إنشاء معمل نماذج Model Factory لإضافة بيانات إلى قاعدة البيانات من أجل عرضها على هيئة مخططات بيانية.
  • استخدام Mirror.js لتنسيق البيانات وعرضها في مخطّطات.

النموذج

نبدأ بإنشاء نموذج نُسمّيه Widget؛ نستخدم الخيار m- لإنشاء تهجير في نفس الوقت:

php artisan make:model Widget -m

نفتح ملف التهجير ونعدّله ليصبح على النحو التالي:

class Widget extends Model
{
    //
    protected $fillable = ['widget_name'];
    public $timestamps = false;
}

أعطينا القيمة false للمتغيّر timestamps$ حتى لا يُملأ الحقلان created_at وupdated_at تلقائيا في جدول البيانات. سنتستخدم معمل النماذج في ما بعد لملْء هذين الحقلين.

ننتقل لملف التهجير ونضيف حقلا جديدا باسم widget_name إلى الجدول في قاعدة البيانات؛ تصبح الدالة up على النحو التالي:

public function up()
    {
        Schema::create('widgets', function (Blueprint $table) {
            $table->increments('id');
            $table->string('widget_name')->unique();
            $table->timestamps();
        });
    }

انتهينا الآن من العمل على النموذج والتهجير. يمكنك مراجعة مقال كيف تنشئ نموذجا Model في Laravel للمزيد.

نفذ التهجير بالأمر التالي في مجلد المشروع:

php artisan migrate

تهيئة التطبيق

سنحتاج لمسارات للوصول إلى صفحات الموقع، عروض لإظهار البيانات، ومتحكمات للربط بين المسارات، النماذج والعروض. سنستخدم الأمر التالي الذي يُنشئ نموذجا للمستخدمين مع مسارات وعروض جاهزة لتسجيل المستخدمين في قاعدة البيانات وللولوج إلى التطبيق:

php artisan make:auth

نفتح ملفّ العرض app.blade.php الموجود في المجلّد resources/views/layouts لتحريره. هذا هو المخطّط الرئيس لعروض Blade الذي أنشأه الأمر السابق.

ابحث عن التعليق التالي:

<!-- Left Side Of Navbar -->

والذي توجد أسفله العناصر اليسرى من القائمة العلوية. سنضيف رابطين جديدين إلى القائمة، الأول للائحة بجميع التسجيلات الموجودة في الجدول widgets (كما فعلنا في درس استخدام الاستثناءات Exceptions المخصَّصة في Laravel) والثاني لصفحة المخطَّط البياني:

<li><a href="{{ url('/widgets') }}">Widgets</a></li>
<li><a href="{{ url('/charts') }}">Chart</a></li>

المسارات

أضفنا أعلاه رابطين؛ إلا أن المسارات التي تتولى الإجابة عنهما غير موجودة حتى الآن. نفتح ملف routes.php الموجود على المسار app\Http لإضافتها:

Route::group(['middleware' => 'web'], function () {
    Route::auth();

    Route::get('/charts', 'HomeController@charts');
});

Route::group(['middleware' => 'web'], function () {
    Route::auth();

    Route::get('/widgets', 'HomeController@widgets_list');
});

Route::group(['middleware' => 'web'], function () {
    Route::auth();

    Route::get('/widget/{id}', 'HomeController@widget_detail');
});

جعلنا المسارات محمية لكي يصل إليها المستخدمون المسجّلون فقط. الجديد هنا (مقارنة مع درس الاستثناءات المخصّصة المُشار إليه أعلاه) هو إضافة المسار charts/ الذي تتولى دالة charts في المتحكّم HomeController الإجابة عنه. بالنسبة للمسارين الآخرين فأحدهما (widgets/) يعرض أسماء جميع التسجيلات في الجدول widgets والآخر ({widget/{id/) يعرض اسم التسجيلة ذات المعرّف id.

المتحكم

يوجد لدينا متحكّم واحد ناتج عن تطبيق أمر artisan الأخير. هذا المتحكّم هو HomeController. نفتح ملف المتحكّم HomeController.php الموجود على المسار app/Http/Controllers لتحريره. نضيف الدوال المذكورة في ملف المسارات السابق:

public function widgets_list()
    {
        $widgets = Widget::Paginate(10);
        return view('widgets.widgets',array('widgets' => $widgets));
    }
    public function widget_detail($id)
    {
        $widget = Widget::find($id);
        return view('widgets.widget_detail', array('widget_name' => $widget->widget_name));
    }
    public function charts()
    {
    $yearCounts = Widget::select(DB::raw('year(created_at) as year'),
            DB::raw('count(widget_name) as `count`'))
            ->groupBy('year')->get();

    $chartData = (sizeof($yearCounts) > ) ? $yearCounts : null;

    if ($chartData) {
        return view('widgets.widgets_chart',array('chartData' => $chartData));
    }
    else {
        return view('widgets.nowidgets');
    }
    }

تبحث الدالة widgets_list عن محتويات الجدول widgets باستخدام دوال النموذج Widget. لاحظ أننا نريد تقسيم النتائج إلى صفحات بحيث تحوي كل صفحة عشرة نتائج، لذا استدعينا الدالة Paginate في الصّنف (النموذج) Widget. نُرسل ما عثرنا عليه إلى العرض widgets الموجود في مجلّد widgets ضمن مجلد العروض resources/views. في الدالة widget_detail نبحث عن التسجيلة ذات المعرّف id ونظهرها في العرض widget_detail ضمن مجلد widgets السابق.

نأتي الآن للدالة charts التي تهيّئ البيانات لعرضها على هيئة مخطط بياني. نريد أن نعثُر على عدد التسجيلات المدرجة في جدول قاعدة البيانات مجموعة حسب السنة؛ على النحو التالي مثلا:

year-|--count
2016 |  16
2015 |  12
2014 |  15
2013 |  11

يمكّننا استعلام SQL التالي من الحصول على ما نريد:

SELECT COUNT(widget_name) AS count, YEAR(created_at) AS year FROM widgets GROUP BY year;

يمكن أن ننفّذ هذا الاستعلام باستخدام الواجهة DB كالتالي:

DB::select('SELECT COUNT(widget_name) AS count, YEAR(created_at) AS year FROM widgets GROUP BY year;');

أو على النحو التالي:

Widget::select(DB::raw('year(created_at) as year'), DB::raw('count(widget_name) as `count`')) ->groupBy('year')->get();

نحصُل في كلتا الحالتين على مصفوفة Array من عمودين بناتج تنفيذ الاستعلام. راجع مقال استخدام Eloquent ORM للتعامل مع قاعدة البيانات في Laravel 5 حول الموضوع.

لا تنس استيراد النموذج Widget والواجهة DB قبل استخدامهما في المتحكّم:

use App\Widget;
use DB;

نفحص المصفوفة المتحصَّل عليها؛ في حال وجود نتائج نرسلها إلى العرض widgets_chart ضمن المجلد resources/views/widgets؛ وإلا نظهر العرض nowidgets الموجود في نفس المجلّد. سنؤخّر الحديث عن العروض إلى أن ننشئ معمل نماذج لملْء جدول قاعدة البيانات.

معمل النماذج وبذر قاعدة البيانات

تحدثنا في درس عن استخدام معمل النماذج Model factory لتوليد بيانات الاختبار، سنعمل بنفس المبدأ هنا. ننشئ ملفا باسم WidgetFactory.php على المسار database/factories ونضيف إليه المحتوى التالي:

<?php
$factory->define(App\Widget::class, function (Faker\Generator $faker){
    return [
        'widget_name' => $faker->unique()->word,
        'created_at' => $faker->dateTimeBetween($startDate = '-4 years', $endDate = 'now')
    ];
});

تنشئ الشفرة السابقة تسجيلا في جدول البيانات widgets بتوليد محتويات الحقلين widget_name وcreated_at. لاحظ أننا نستخدم مكتبة Faker. حدّدنا تاريخ created_at بالمجال من قبل أربع سنوات إلى الآن.

يأتي الآن دور بذر البيانات. ننشئ صنفا لبذر الجدول widgets:

php artisan make:seeder WidgetTableSeeder

نفتح الملف database/seeds/WidgetTableSeeder.php ونحرّر الدالة run ليصبح كالتالي:

public function run()
    {
        Widget::truncate();
        factory(Widget::class, 50)->create();
    }

نحذف محتوى الجدول لكي لا يحصُل تعارض مع البيانات الجديدة التي سنولّدها؛ ثم نستخدم معمل النماذج لإدراج 50 تسجيلة. لا تنس استيراد النموذج Widget

use App\Widget;

يمكننا الآن تنفيذ الأمر التالي لبذر البيانات في الجدول: 

php artisan db:seed --class=WidgetTableSeeder

توجد طريقة أخرى هي التي سنستخدمها. نحرّر الملف DataBaseSeeder.php الموجود على نفس مسار WidgetTableSeeder.php ونضيف ما يلي إلى الدالة run:

Widget::unguard();
$this->call(WidgetTableSeeder::class);
Widget::reguard();

نطلُب نزع الحماية عن الجدول قبل بذره ثم نعيدها إليه. يؤدي النداء call مهمّة البذر.

يتيح الملف DataBaseSeeder إمكانية تنفيذ أصناف بذر عدّة بنفس الأمر التالي:

php artisan db:seed

بقيت الخطوة الأخيرة وهي إنشاء العروض.

العروض

نأتي الآن للعروض التي ورد ذكرها في المتحكّم دون أن تكون أنشئت. ستجد العروض في الملف المرفق. نشير إلى استخدام التعليمة التاليّة في العرض widgets:

{!! $widgets->render() !!}

تظهر هذه التعليمة روابط للتنقل بين التسجيلات إذا كان عددها يفوق العشرة. هذا ناتج عن استخدامنا للدالة Paginate في المتحكم أعلاه.

نأتي الآن لشرح العرض widgets_chart الذي يتضمن المخطّط البياني الذي نهدف لإنشائه في هذا الدرس.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-10 col-md-offset-1">
            <div class="panel panel-default">
                <div class="panel-heading">Chart</div>

                <div class="panel-body">
                <div id="chart" style="height: 250px;">
                </div>
                </div>
            </div>
        </div>
    </div>
</div>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
<script>
        var data = <?php echo json_encode($chartData) ?>;

        Morris.Line({
            element: 'chart',
            data: data,
            xkey: 'year',
            ykeys: ['count'],
            labels: ['widgets created']
        });
    </script>
@endsection

يمدّد العرضُ العرض الرئيس layouts.app ويعرّف محتوى المقطع content. يوجد في هذا المقطع العنصُر التالي ذي المعرّف chart:

<div id="chart" style="height: 250px;">
</div>

سيُعرَض المخطّط البياني في هذا العنصُر. تأتي بعد ذلك اعتماديات مكتبة Morris:

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>

وأخيرا السكربت الذي سيرسُم المخطّط:

<script>
    var data = <?php echo json_encode($chartData) ?>;

    Morris.Line({
        element: 'chart',
        data: data,
        xkey: 'year',
        ykeys: ['count'],
        labels: ['widgets created']
    });
</script>

يستقبل السكربت البيانات التي أرسلتها الدالة charts المعرّفة في المتحكم HomeController ويحوّلها إلى صيغة JSON التي تستخدمها مكتبة Morris:

var data = <?php echo json_encode($chartData) ?>;

ثم نستدعي الدالة Line في مكتبة Morris:

Morris.Line({
    element: 'chart',
    data: data,
    xkey: 'year',
    ykeys: ['count'],
    labels: ['widgets created']
});

نمرّر للدالة معرّف العنصُر الذي سنرسم في المخطّط (المعطى element)، البيانات التي نودّ رسمها (data)، عنصُر المصفوفة الذي سيكون في المحور الأفقي للمخطّط (xkey) والعنصُر-أ و العناصر- الذي سيكون على المحور العمودي (ykeys)؛ إضافة للصيقة Label تظهر في المخطّط.

نذهب الآن لرابط المشروع؛ بالنسبة لي الرابط هو:

http://laraveltips.dev

نسجّل مستخدما جديدا (register) ثم نضغط على الرابط Chart لنجد النتيجة التالية (قد يختلف المخطّط البياني عن الموجود في الصورة بسسب أن البيانات مولَّدة عشوائيا).

01_chart.png

الملف المرفق: laravelchart.zip

ترجمة -وبتصرّف- للمقال Charts in Laravel 5.1 with Morris.js لصاحبه Bill Keck.



1 شخص أعجب بهذا


تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن