Chart.js

Official

https://www.chartjs.org/docs/latest/charts/mixed.html

HTML

<script type="text/javascript" charset="utf8" src="{{asset("plugins/Chart.min.js")}}"></script>

<canvas id="myChart" width="400" height="200"></canvas>

JS

var options = {
    responsive: true,
    legend: {
        position: 'bottom',
    },
    title: {
        display: true,
        text: '遵從率'
    },
    scales: {
        yAxes: [{
            ticks: {
                beginAtZero: true,
                callback: function(value, index, values) {
                    return value + '%';
                }
            }
        }]
    }
}

var ctx = null;
var chart = null;

$(document).ready(function(){
    $('.datetimepicker').datetimepicker({
        dateFormat: 'yy-mm-dd',
        timeFormat: 'HH:mm:ss',
        changeYear: true,
        changeMonth: true
    });

    $('.report-button').removeClass('active');
    $('#bt_bar').addClass('active');

    ctx = document.getElementById('myChart');

    chart = new Chart(ctx, {
        type: 'bar',
        data: {
            datasets: [
                {
                    label: '遵從率',
                    data: [],
                    backgroundColor: 'rgba(60, 141, 188, 0.8)'
                }, 
                {
                    label: '遵從率標準值',
                    data: [],
                    type: 'line',
                    backgroundColor: 'transparent',
                    borderColor: 'rgba(255, 174, 201, 1)'
                }
            ],
            labels: ['January', 'February', 'March', 'April']
        },
        options: options
    });

    $('#search').on('click', function(e) {
        report_data();
    });

    report_data();
});

function report_data(callback) {
    var params = {
        from: $('#from').val(),
        to: $('#to').val(),
    }
    $.get('/HAI/admin/dry_report/bar/data', params, function(res) {
        //console.log(data);
        //return;
        if (typeof res.status == 'undefined') {
            alert(res);
            return;
        } else if (res.status == 'fail') {
            alert(res.message);
            return;
        }

        if (typeof callback == 'function') {
            callback.call(null, res.data);
        }

        var data = res.data;
        chart.data.labels = data.labels;
        chart.data.datasets[0].data = data.data;
        chart.data.datasets[1].data = data.std;
        chart.update();
    });
}

function getRandomColor() {
    var letters = '0123456789ABCDEF'.split('');
    var color = '#';
    for (var i = 0; i < 6; i++ ) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}

PHP for Ajax

    public function bar_chart_data(Request $req){
        $from = $req->input('from');
        $to = $req->input('to');

        $options = [
            'from' => $from, 
            'to' => $to
        ];
        $data = DryReportM::bar_chart_data($options);
        $data = $this->format_bar_chart_data($data);
        //logg($data);

        return $this->success_response($data);
    }

    private function format_bar_chart_data($data){
        $values = array(
            'labels' => array(), 
            'data' => array(), 
            'std' => array(), 
        );

        $dry_wash_std = ConfigM::value('dry_wash_std');
        //logg($config);

        foreach ($data as $key => $item) {
            if(in_array($item->user, User::ADMINS) == true){
                continue;
            }

            array_push($values['labels'], $item->name);
            array_push($values['std'], $dry_wash_std);

            $blood = $item->blood_count;
            $violations = $item->violation_count;

            if($blood == 0){
                $p = 0;
            }else{
                $p = (1 - ($violations / $blood)) * 100;
                $p = round($p, 1);
            }
            array_push($values['data'], (int)$p);
        }

        return $values;
    }