Using Snappy to render LavaChart on a PDF in Laravel - laravel

I am trying to use the snappy library with wkhtmltopdf to render a chart (LavaChart) on a generated PDF but I have not been able. The PDF generates fine but the chart does not show. If the view is not converted to PDF, the Chart is rendered as expected.
Below is my code for the LavaChart and Snappy.
The Chart Part
$chart = Lava::ColumnChart('Performance', $table, [
'title' => 'Performance Chart',
'png' => true,
'animation' => [
'startup' => true,
'easing' => 'inAndOut'
],
'titleTextStyle' => [
'fontName' => 'Arial',
'fontColor' => 'blue'
],
'legend' => [
'position' => 'top'
],
'vAxis' => [
'title' => 'Total Score'
],
'hAxis' => [
'title' => 'Class'
],
'events' => [
'ready' => 'getImageCallback'
],
'colors' => ['#3366CC','#DC2912', '#FF9900']
]);
The Snappy Part
$pdf = PDF::loadView('print.charts')->setPaper('portrait');
$pdf->setOption('enable-javascript', true);
$pdf->setOption('javascript-delay', 10000);
$pdf->setOption('no-stop-slow-scripts', true);
$pdf->setOption('page-size', 'A4');
$pdf->setOption('margin-left', 0);
$pdf->setOption('margin-right', 0);
$pdf->setOption('margin-top', 0);
$pdf->setOption('margin-bottom', 0);
$pdf->setOption('lowquality', false);
$pdf->setTimeout(1500);
$pdf->setOption('disable-smart-shrinking', true);
The View Part
<script type="text/javascript">
function getImageCallback (event, chart) {
console.log(chart.getImageURI());
}
</script>
<div id="chart" style="margin: 10px; height: 200px; width: 50%;"></div>
{!! Lava::render('ColumnChart', 'Performance', 'chart') !!}
Since the chart renders as expected when the view is not converted to pdf, I have reasons to believe the wkhtmltopdf does not execute the javascript has expected in the pdf version. I have the latest wkhtmltopdfinstalled but still no luck.
Library Version:
barryvdh/laravel-snappy: ^0.4.3
khill/lavacharts: 3.0.*
Any help will be appreciated, thanks.

I can show with a simple example, At first I have shown the chart on browser, The chart example is taken from Lavacharts docs(you can use yours). Keep a Note on
events with callback getImageCallback.
public function index(){
$lava = new Lavacharts;
$data = $lava->DataTable();
$data->addDateColumn('Day of Month')
->addNumberColumn('Projected')
->addNumberColumn('Official');
// Random Data For Example
for ($a = 1; $a < 20; $a++) {
$rowData = [
"2020-10-$a", rand(800,1000), rand(800,1000)
];
$data->addRow($rowData);
}
$lava->LineChart('Stocks', $data, [
'elementId' => 'stocks-div',
'title' => 'Stock Market Trends',
'animation' => [
'startup' => true,
'easing' => 'inAndOut'
],
'colors' => ['blue', '#F4C1D8'],
'events' => [
'ready' => 'getImageCallback'
]
]);
return view('charts-view', ['lava' => $lava]);
}
In view charts-view,
<div id="stocks-div">
<?= $lava->render('LineChart', 'Stocks', 'stocks-div'); ?>
</div>
<form action="{{ url('export-pdf') }}" method="post">
#csrf
<div class="form-group">
<input type="hidden" name="exportpdf" id="exportPDF">
<button class="btn btn-info" type="submit">Export as PDF</button>
</div>
</form>
<script type="text/javascript">
function getImageCallback (event, chart) {
console.log(chart.getImageURI());
document.getElementById("exportPDF").value = chart.getImageURI();
}
</script>
note the function name in script must be same as the value set for ready key in events in the controller. Upto this step you have done as well. I have passed the result obtained by as a hidden input field and posted the form to the controller.You can see in the diagram button export as PDF.
The url export-pdf calls the controller function exportPdf which willfinally generate the PDF. You need to pass the image (obtained as data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB .....) to the controller to pass it to the view as image.
In exportPdf,
public function exportPdf(Request $request){
$imageData = $request->get('exportpdf');
$pdf = SnappyPDF::loadView('export-pdf', ['imageData' => $imageData])->setPaper('a4')->setOrientation('portrait');
$pdf->setOption('lowquality', false);
$pdf->setTimeout(1500);
$pdf->setOption('disable-smart-shrinking', true);
return $pdf->download('stock-market.pdf');
}
The export-pdf blade view
<!DOCTYPE html>
<html lang="en">
<head>
<title>Stock Market</title>
</head>
<body>
<div class="container">
<div class="col">
<h2>Stock Market Detail</h2>
</div>
<div class="col">
<h4>Oct 2020</h4>
</div>
</div>
<img src="{{ $imageData }}" alt="image" width="720" height="230">
</body>
</html>
The final PDF obtained looks like,

Related

Change date format for Yajra Datatables as Service

I am using Yajra Datatables for Service for my Laravel project and wonder if i can use Carbon to change date format.
My datatables currently displaying date as bellow format
2020-11-11T16:03:13.000000Z
I want to display my date as
11-11-2020 03:13 PM
How can i do that. Please help.
My Datatables:
<?php
namespace App\DataTables;
// use App\App\OrderDataTable;
// use OrderDataTable;
use App\DataTables\OrderDataTable;
use Yajra\DataTables\Html\Button;
use Yajra\DataTables\Html\Column;
use Yajra\DataTables\Html\Editor\Editor;
use Yajra\DataTables\Html\Editor\Fields;
use Yajra\DataTables\Services\DataTable;
use App\Order;
use Illuminate\Support\Facades\DB;
class OrderDataTable extends DataTable
{
public function dataTable($query){
return datatables()
->eloquent($query);
}
public function query(OrderDataTable $model){
$from = date('2020-11-10 00:00:00');
$to = date('2020-11-11 23:59:59');
// $data = Order::where('created_at', '2020-11-11 22:03:13');
// $data = Order::select();
$data = Order::query()
// ->whereBetween('created_at', ['2020-11-10 00:00:00', '2020-11-11 23:59:59'])
->whereBetween('created_at', [$from, $to])
->select([
'orders.id',
'orders.ecomordid',
'orders.status_id',
'orders.awb',
'orders.created_at'
]);
return $this->applyScopes($data);
}
public function html(){
return $this->builder()
->setTableId('orderdatatable-table')
// ->columns($this->getColumns())
->columns([
'id' => [ 'title' => 'SHIPPING CODE' ],
'ecomordid' => [ 'title' => 'ECOM ORDER' ],
'status_id' => [ 'title' => 'STATUS' ],
'awb' => [ 'title' => 'AWB' ],
'created_at' => [ 'title' => 'DATE' ],
])
->minifiedAjax()
->dom('Bfrtip')
->orderBy(0)
->parameters([
'dom' => 'Bfrtip',
'buttons' => ['excel', 'print', 'reset', 'reload'],
'initComplete' => "function () {
this.api().columns([0,3]).every(function () {
var column = this;
var input = document.createElement(\"input\");
$(input).appendTo($(column.footer()).empty())
.on('change', function () {
column.search($(this).val(), false, false, true).draw();
});
});
}",
]);
}
protected function getColumns(){
return [
Column::make('id'),
Column::make('ecomordid'),
Column::make('status_id'),
Column::make('awb'),
Column::make('created_at'),
];
}
protected function filename(){
return 'Order_' . date('YmdHis');
}
}
View:
#extends('layouts.master')
#section('content')
<meta name="csrf-token" content="{{ csrf_token() }}" />
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-orange"><h3>{{ __('Update By AWB') }}</h3></div>
<div class="card-body">
<div class="table-responsive">
<div class="panel panel-default">
<div class="panel-heading">Sample Data</div>
<div class="panel-body">
{!! $dataTable->table([], true) !!}
{!! $dataTable->scripts() !!}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
#endsection
Controller
public function order(OrderDataTable $dataTable){
// dd($dataTable->request()->all());
return $dataTable->render('admin.search.order');
}
My current output
I am using:
Laravel: 7.28.4
laravel-datatables-buttons: 4.0
laravel-datatables-html: 4.0
laravel-datatables-oracle: 9.14
You can define an Accessor in Order model
public function getCreatedAtAttribute($value)
{
return Carbon::parse($value)->format('Y-m-d H:i:s');
}
Note that it will work globally wherever you access created_at field it will be fetched with this format.
Read more about Accessors Here

How to get proper data when using blade components and alpinejs?

I'm new in components and alpine in laravel. I have data from controller $positions.
$positions = [
['id' => 1, 'content' => 'king'],
['id' => 2, 'content' => 'lord']
];
When I pass it to laravel blade. here is my code
<div class="row">
<div class="col">
#foreach($list as $key => $position)
<x-list :position="$position"/>
#endforeach
</div>
</div>
I have component name it <x-list/> with a prop position, and here is the code of my x-list component
<div class="red w-60 mb-1">
<div x-data="positionData()" class="relative">
<button #click="submit()" class="p-2">Submit</button>
</div>
</div>
<script>;
var position = #json($position);
function positionData() {
return {
submit() {
console.log(position);
},
};
}
</script>
It is just very simple code but when I click the submit button, the data I get is the last position
from the list ['id' => 2, 'content' => 'lord'], even I click position 1 the data I get is still position 2 data. I don't know what happen now. I try to search it in google to fix it but I can't find the right answer on this.
I think the issue in this case is that the positionData function is being overwritten on each iteration of x-list (since each of the components is creating a new window.positionData function).
To solve it you could do:
<div class="red w-60 mb-1">
<div x-data="positionData(#json($position))" class="relative">
<button #click="submit()" class="p-2">Submit</button>
</div>
</div>
<script>;
function positionData(position) {
return {
submit() {
console.log(position);
},
};
}
</script>
Specifically, you should probably move the <script></script> out of the x-list component so that it doesn't get re-created for each component (it should only be added to the page once).

How to use plugins in chartjs and laravel chart consoletvs/chartsjs

Need use chartjs plugin datalabels in laravel 5.8 and consoletvs/chartsjs ver 6.*
This line generate a error in laravel.
$chart->plugins(['datalabels'=>['color'=>'#223388']]);
$chart = new Chart;
$chart->labels($arrHora);
$chart->dataset('Propostas Por Hora','bar', $arrQtdHora)
->backgroundColor('#64b5f6');
$chart->options([
'responsive' => true,
//'aspectRatio' => 1,
'tooltips' => ['enabled'=>false],
'legend' => ['display' => false],
'scales' => [
'yAxes'=> [[
'display'=>false,
'ticks'=> ['beginAtZero'=> true],
'gridLines'=> ['display'=> false],
]],
'xAxes'=> [[
'categoryPercentage'=> 0.55,
//'barThickness' => 100,
'barPercentage' => 0.5,
'ticks' => ['beginAtZero' => true],
'gridLines' => ['display' => false],
]],
],
]);
$chart->plugins(['datalabels'=>['color'=>'#223388']]);
//dd($chart);
//->backgroundColor('#64b5f6');
return view('dashboard', ['chart' => $chart]);
Two things I have to clarify, after playing around with the library, I came up with the following results:
First: The function $chart->plugins is used to create inline plugins only, under chartjs/script.blade.php the file begins with:
So for each plugin array that you define it will load a view from the pluginsView array that have the same name, but I think this is not full developed yet, and since this is not what the question is about lets move on.
Second: You can perfectly a nested option use options -> plugin like I have suggested before, but there's one thing that you will have to be careful whit, is that 'plugins' can't be an array like the others, and here is why:
The function expects a string to be printed raw, so with that you can use something like:
$chart->options([
//...
'plugins' => '{datalabels: {color: \'red\'}}',
//...
]);
Which will work as expected:
I share here the complete solution of my code and if you have any comments or suggestions, you will be welcome.
/* Controller */
<?php
namespace App\Http\Controllers\dashboard;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use ConsoleTVs\Charts\Classes\Chartjs\Chart;
use DB;
class DashBoardController extends Controller
{
public function index() {
$result = DB::select('select
convert(varchar(10),data_da_solicitacao,121) data,
convert(varchar(2),data_da_solicitacao,114) hora,
COUNT(id_controle_proposta_pac) qtdHora
from contact_std_parceiros_propostas (nolock) p
where convert(varchar(10),data_da_solicitacao,121) between convert(varchar(10),getdate(),121) and convert(varchar(10),getdate(),121)
and not exists (select * from contact_std_parceiros_propostas_fora (nolock) f where f.id_controle_proposta_pac = p.id_controle_proposta_pac)
Group by convert(varchar(10),data_da_solicitacao,121), convert(varchar(2),data_da_solicitacao,114)
Order by 1');
$arrHora = array();
$arrQtdHora = array();
$i = 0;
foreach ( $result as $r )
{
$arrHora[$i] = $r->hora;
$arrQtdHora[$i] = $r->qtdHora;
$i++;
}
$chart = new Chart;
$chart->labels($arrHora);
$chart->dataset('Propostas Por Hora no dia de hoje','bar', $arrQtdHora)->backgroundColor('#64b5f6');
$chart->options([
'responsive' => true,
'legend' => ['display' => true],
'tooltips' => ['enabled'=>true],
//'aspectRatio' => 1,
'scales' => [
'yAxes'=> [[
'display'=>false,
'ticks'=> ['beginAtZero'=> true],
'gridLines'=> ['display'=> false],
]],
'xAxes'=> [[
'categoryPercentage'=> 0.8,
//'barThickness' => 100,
'barPercentage' => 1,
'ticks' => ['beginAtZero' => true],
'gridLines' => ['display' => false],
]],
],
'plugins' => '{datalabels: {color: \'red\'}}',
]);
return view('dashboard',compact('chart'));
}
}
/* view */
#extends('layout.main')
#section('titulo','Pac - DashBoard')
#section('conteudo')
<div class="row">
<div class="col s6 col m12 l12">
<div class="x_panel">
<div class="x_title">
<div class="col s6 m6 l6"><h2>Propostas do dia</h2></div>
<ul class="nav navbar-right panel_toolbox">
<li><a class="collapse-link"><i class="fa fa-chevron-up"></i></a>
</li>
<li><a class="close-link"><i class="fa fa-close"></i></a></li>
</ul>
<div class="clearfix"></div>
</div>
<div class="x_content" style="display: block;">
<div class="row">
<div class="row top_tiles" id="card-0"></div>
<div class="row">
<div class="col s4 m7 l7">
<div id="graph-0" class="x_panel" style="height: 250px">
<!--<canvas id="bar-0" style="width: 70% !important;height: auto !important;">-->
{!! $chart->container() !!}
#foreach($chart->plugins as $plugins)
#include($chart->pluginsViews[$plugins])
#endforeach
<!--</canvas>-->
</div>
</div>
<div class="col s3 m5 l5">
<div id="graph-1" class="x_panel">
<canvas id="pie-1" style="width: 100% !important;height: auto !important;"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{!! $chart->script() !!}
#endsection

Displaying multiple charts on the same view

I want to display all my charts on the same view I used lavacharts I gave different no to my charts but in the view it shows me that the last chart in the controller.
Controller code:
function l()
{
$lava = new Lavacharts; // See note below for Laravel
$reasons = \Lava::DataTable();
$abs=Absencecollab::all();
$r=$abs->count();
$absm=Absencecollab::where('motif','maladie');
$tm=$absm->count();
$absc=Absencecollab::where('motif','conge');
$tc=$absc->count();
$absnj=Absencecollab::whereNull('motif');
$tnj=$absnj->count();
$pm=($tm100)/$r;
$pc=($tc100)/$r;
$pnj=($tnj100)/$r;
$reasons->addStringColumn('Reasons')
->addNumberColumn('Percent')
->addRow(['Maladie',$pm ])
->addRow(['Conge',$pc])
->addRow(['Absence non justifiée',$pnj]);
\Lava::PieChart('IMDB', $reasons, [
'title' => 'Abscences collaborateurs par motif',
'is3D' => true,
'slices' => [
['offset' => 0.2],
['offset' => 0.25],
['offset' => 0.3]
]
]);
$reasons1 = \Lava::DataTable();
$abs1=Absence::all();
$r1=$abs1->count();
$absm1=Absence::where('motif','maladie');
$tm1=$absm1->count();
$absc1=Absence::where('motif','conge');
$tc1=$absc1->count();
$absnj1=Absencecollab::whereNull('motif');
$tnj1=$absnj1->count();
$pm1=($tm1100)/$r;
$pc1=($tc1100)/$r;
$pnj1=($tnj1*100)/$r;
$reasons1->addStringColumn('Reasons')
->addNumberColumn('Percent')
->addRow(['Maladie',$pm1 ])
->addRow(['Congé parents',$pc1])
->addRow(['Absence non justifiée',$pnj1]);
\Lava::PieChart('abse', $reasons1, [
'title' => 'Abscences enfants par motif',
'is3D' => true,
'slices' => [
['offset' => 0.2],
['offset' => 0.25],
['offset' => 0.3]
]
]);
return view('statistiquesg');
view code
#piechart('IMDB', 'chart-div')
<canvas id="line" height="300" width="450"></canvas>
</div>
</section>
</div>
<!-- Bar -->
<div class="col-lg-6">
<section class="panel">
<header class="panel-heading">
Absence enfants
</header>
<div class="panel-body text-center" id="chart-div">
#piechart('abse', 'chart-div')
<canvas id="bar" height="300" width="500"></canvas>
</div>
</section>
</div>
The second parameter of #piechart is the ID of the div, both pie charts are using the chart-div id.

Laravel not able to pass more than 1 return - how can that be done using similar method?

I have a small problem with redirecting a user once page is created.
Problem is that all together I have 3 returns within 2 functions and obviously that can't be done and I am looking for a way to change this but I don't know what should it be.
Process is like this.
User creates a page: code is saved into javascript variable.
User chooses a name for a page: code is saved into another javascript variable.
Ajax is passing these values to Controller and then:
public function postDB(Request $request) {
$newName = $request->input('name_website');
$newLat = $request->input('newCode');
$websites = new Website();
$websites->name = $newName;
$websites->html = $newLat;
$websites->save();
return $newName;
return $this->website($newName);
}
public function website($newName)
{
dd($newName);
Website::where('name', $newName)->first()->html;
return view('layouts/website');
}
After saving into a database I am returning value of $newName which is the actual name for a website or I should say URL and also return (call another function) which then search for html code that matches a name, and return view that deals with displaying it:
#extends('layouts.master') #section('title', 'Website Builder') #section('content')
<meta name="csrf-token" content="{{ csrf_token() }}" />
{{html_entity_decode($name->html)}}
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
axios.get('/name').then(
html => document.querySelector('html').innerHTML = html
);
</script>
</html>
#endsection #show
So basically after user saved it to database I want to redirect them to a page which is in saved variable $newName and retrieve + display html that matches name that is in the variable.
Route::group(['middleware' => ['web']], function () {
Route::get('home', 'BuilderController#homepage');
Route::get('template', 'BuilderController#templates');
Route::post('template', 'BuilderController#postDB');
Route::get('logout', 'BuilderController#getLogout');
Route::get('/{name}', 'BuilderController#website');
});
template.blade.php
<button onClick=" updateDatabase(this);" type="button" class="form-control margin btn btn-success" id="getRequest changes">
Save Website
</button>
JS:
var web_name;
function updateDatabase(newCode, name_website)
{
code2 = document.getElementById("content-link2").innerHTML;
web_name = ($('#website_name').val());
// make an ajax request to a PHP file
// on our site that will update the database
// pass in our lat/lng as parameters
$.post('http://localhost/template', {
_token: $('meta[name=csrf-token]').attr('content'),
newCode: (code2),
name_website: (web_name),
})
.done(function() {
})
.fail(function() {
alert("error");
});
}
EDIT: I have update my code like I was told in the answer below but now I am getting:
Trying to get property of non-object (View: C:\xampp\htdocs\fyproject\resources\views\layouts\website.blade.php)
in 87bb9cbe60b5bd1fe6f78e1246f2227dbd0f328f.php line 5
at CompilerEngine->handleViewException(object(ErrorException), '1') in PhpEngine.php line 44
at PhpEngine->evaluatePath('C:\xampp\htdocs\fyproject\storage\framework\views/87bb9cbe60b5bd1fe6f78e1246f2227dbd0f328f.php', array('__env' => object(Factory), 'app' => object(Application), 'errors' => object(ViewErrorBag), 'name' => ' <title>Template 1</title> <link href="http://localhost/templates/css.css" rel="stylesheet" type="text/css"> <div class="logo"> <img class="images" id="image" src="#" alt="Your Logo"> </div> <div contenteditable="true" id="content" class="draggable ui-widget-content refresh ui-draggable ui-draggable-handle" style="position: relative; left: 209px; top: 139px;"><p>Change Text inside this box</p></div> <div id="editTxt" class="refresh" contenteditable="true"> <p>This text can be by the user.</p> </div> ')) in CompilerEngine.php line 59
at CompilerEngine->get('C:\xampp\htdocs\fyproject\resources\views/layouts/website.blade.php', array('__env' => object(Factory), 'app' => object(Application), 'errors' => object(ViewErrorBag), 'name' => ' <title>Template 1</title> <link href="http://localhost/templates/css.css" rel="stylesheet" type="text/css"> <div class="logo"> <img class="images" id="image" src="#" alt="Your Logo"> </div> <div contenteditable="true" id="content" class="draggable ui-widget-content refresh ui-draggable ui-draggable-handle" style="position: relative; left: 209px; top: 139px;"><p>Change Text inside this box</p></div> <div id="editTxt" class="refresh" contenteditable="true"> <p>This text can be by the user.</p> </div> ')) in View.php line 149
I would suggest something like this:
public function postDB(Request $request) {
$newName = $request->input('name_website');
$newLat = $request->input('newCode');
$websites = new Website();
$websites->name = $newName;
$websites->html = $newLat;
$websites->save();
// Now we go to our other function
$this->website($newName);
}
public function website($newName)
{
// Return our "website" object
$website = Website::where('name', $newName)->first();
// Pass the contents of the "html" property to the view
return view('layouts/website', [
'html' => $website->html
);
}
This will give you access to the content of your "html" attribute in your blade template as $html.
Edit
If you are using your controller for an API you might consider not returning a view but instead a json response which you can listen for:
return response()->json([
'url' => $website->name,
'html' => $website->html
]);
You can then handle the content of the page or redirect using javascript

Resources