As you see in picture: https://imgur.com/kDBu95D column that has 0 data is higher than column with value of 1 and 2. And y axis starting reverse, 0 is on the top instead on bottom. This happened when I use laravel eloquent.
Here is my code:
#extends('layouts.app')
#section('content')
<div class="container">
<script type="text/javascript">
google.charts.load('current', {'packages':['bar']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['User', 'Added'],
#if ($users)
#foreach ($users as $user)
['{{$user->name}}', '{{$user->members->count()}}'],
#endforeach
#endif
]);
var options = {
chart: {
title: 'Users',
},
vAxis: {
format: 'decimal',
},
};
var chart = new google.charts.Bar(document.getElementById('columnchart_material'));
chart.draw(data, google.charts.Bar.convertOptions(options));
}
</script>
<div id="columnchart_material" style="width: 'auto'; height: 500px;"></div>
</div>
#endsection
I tried to add this:
vAxis: { minValue: 0 },
and this:
vAxis: { direction: -1, },
but nothing helps...
looks like the values for the y-axis are strings instead of numbers...
remove the single quotes from the second array value...
change --> ['{{$user->name}}', '{{$user->members->count()}}'],
to --> ['{{$user->name}}', {{$user->members->count()}}],
Related
I recently ran into an issue when building a Laravel Livewire component where the javascript portion wouldn't update when a select input changed. The component is a chart from the Chartist.js library and it displays on load but when I change the select input the chart disappears. I came up with a solution but it feels dirty, anyone have a better solution to this.
line-chart.blade.php
<div class="mt-6">
<h2 class="text-xl text-gray-600 py-3 font-bold">Location Views</h2>
<div class="bg-white rounded-lg shadow overflow-hidden overflow-y-scroll">
<div class="flex justify-end px-10 py-6">
<select wire:model="days" class="block w-40 pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-yellow-500 focus:border-yellow-500 sm:text-sm rounded-md">
<option value="30">Last 30 Days</option>
<option value="365">Last 12 Months</option>
</select>
</div>
<div id="line-chart" class="relative ct-chart">
<div class="hidden absolute inline-block chartist-tooltip bg-white text-xs shadow text-center px-3 py-1 rounded-md w-36">
<span class="chartist-tooltip-key"></span><br>
<span class="chartist-tooltip-date"></span><br>
<span class="chartist-tooltip-value"></span>
</div>
</div>
</div>
</div>
#push('styles')
<link rel="stylesheet" href="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.css">
#endpush
#push('scripts')
<script src="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartist-plugin-tooltips#0.0.17/dist/chartist-plugin-tooltip.min.js"></script>
#endpush
#push('js')
<script>
document.addEventListener('livewire:load', function () {
setTimeout(() => {
Livewire.emit('updateJS')
})
Livewire.on('updateJS', function () {
var data = {
labels: #this.labels,
// Our series array that contains series objects or in this case series data arrays
series: #this.data,
};
var options = {
height: 300,
fullWidth: true,
chartPadding: 40,
axisX: {
offset: 12,
showGrid: true,
showLabel: true,
},
axisY: {
offset: 0,
showGrid: true,
showLabel: true,
onlyInteger: true,
},
}
new Chartist.Line('#line-chart', data, options).on("draw", function (data) {
if (data.type === "point") {
data.element._node.addEventListener('mouseover', e => {
const tooltip = document.getElementsByClassName('chartist-tooltip')
tooltip[0].style.top = data.y - 75 + 'px'
tooltip[0].style.left = data.x > 200 ? data.x - 150 + 'px' : data.x + 'px'
tooltip[0].classList.remove('hidden')
const key = document.getElementsByClassName('chartist-tooltip-key')
key[0].innerHTML = data.meta[1]
const meta = document.getElementsByClassName('chartist-tooltip-date')
meta[0].innerHTML = data.meta[0]
const value = document.getElementsByClassName('chartist-tooltip-value')
value[0].innerHTML = data.value.y === 1 ? data.value.y + ' view' : data.value.y + ' views'
})
data.element._node.addEventListener('mouseleave', e => {
const tooltip = document.getElementsByClassName('chartist-tooltip')
tooltip[0].classList.add('hidden')
})
}
})
})
})
</script>
#endpush
LineChart.php
<?php
namespace App\Http\Livewire\Components;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use Livewire\Component;
class LineChart extends Component
{
/**
* #var Collection
*/
public Collection $data;
/**
* #var array
*/
public array $labels;
/**
* #var int
*/
public int $days = 30;
/**
*
*/
public function mount()
{
$this->data();
$this->labels = $this->labels();
}
/**
* Trigger mount when days is updated.
*/
public function updatedDays()
{
$this->mount();
$this->emit('updateJS');
}
/**
* #return Application|Factory|View
*/
public function render()
{
return view('livewire.components.line-chart');
}
/**
* Generates the chart data.
*/
public function data()
{
$locations = request()->user()->locations;
if ($this->days === 30) {
$this->data = $locations->map(function ($location) {
return $this->getDatesForPeriod()->map(function ($date) use ($location) {
return [
'meta' => [
Carbon::parse($date)->format('M j'),
$location->name
],
'value' => $location->views->filter(function ($view) use ($date) {
return $view->viewed_on->toDateString() === $date;
})->count()
];
})->toArray();
});
}
if ($this->days === 365) {
$this->data = $locations->map(function ($location) {
return $this->getDatesForPeriod()->map(function ($date) use ($location) {
return [
'meta' => [
Carbon::parse($date)->format('M'),
$location->name
],
'value' => $location->views->filter(function ($view) use ($date) {
return $view->viewed_on->month === Carbon::parse($date)->month;
})->count()
];
})->toArray();
});
}
}
/**
* Creates the labels for the chart.
*
* #return array
*/
protected function labels()
{
return $this->getDatesForPeriod()->map(function ($date) {
if ($this->days === 30) {
return Carbon::parse($date)->format('M j');
} else {
return Carbon::parse($date)->format('M');
}
})->toArray();
}
/**
* Gets the dates for the specified period.
*
* #return Collection
*/
protected function getDatesForPeriod()
{
if ($this->days === 30) {
return collect(CarbonPeriod::create(now()->subDays($this->days)->toDateString(), now()->toDateString()))
->map(function ($date) {
return $date->toDateString();
});
}
if ($this->days === 365) {
return collect(now()->startOfMonth()->subMonths(11)->monthsUntil(now()))
->map(function ($date) {
return $date->toDateString();
});
}
}
}
If I change document.addEventListener('livewire:load', function () {} to livewire:update then the chart works as expected when I use the select input, but then the chart doesn't display on load. So to get around this I had to set a timeout and trigger an event that displays the chart on load but will also display the chart on update. I feel like there is a better way to do this, I'm just missing something.
To get this to work I removed the setTimeout() and the Livewire.On('updateJS') and added another event listener inside of livewire:load that listens for livewire:update and in here we're updating the chart with
chart.update({labels: #this.labels, series: #this.data})
Here is the entire code snippet. I feel like this is a much cleaner solution and doesn't feel dirty lol.
<script>
document.addEventListener('livewire:load', function () {
var data = {
labels: #this.labels,
// Our series array that contains series objects or in this case series data arrays
series: #this.data,
};
var options = {
height: 300,
fullWidth: true,
chartPadding: 40,
axisX: {
offset: 12,
showGrid: true,
showLabel: true,
},
axisY: {
offset: 0,
showGrid: true,
showLabel: true,
onlyInteger: true,
},
}
const chart = new Chartist.Line('#line-chart', data, options).on("draw", function (data) {
if (data.type === "point") {
data.element._node.addEventListener('mouseover', e => {
const tooltip = document.getElementsByClassName('chartist-tooltip')
tooltip[0].style.top = data.y - 75 + 'px'
tooltip[0].style.left = data.x > 200 ? data.x - 150 + 'px' : data.x + 'px'
tooltip[0].classList.remove('hidden')
const key = document.getElementsByClassName('chartist-tooltip-key')
key[0].innerHTML = data.meta[1]
const meta = document.getElementsByClassName('chartist-tooltip-date')
meta[0].innerHTML = data.meta[0]
const value = document.getElementsByClassName('chartist-tooltip-value')
value[0].innerHTML = data.value.y === 1 ? data.value.y + ' view' : data.value.y + ' views'
})
data.element._node.addEventListener('mouseleave', e => {
const tooltip = document.getElementsByClassName('chartist-tooltip')
tooltip[0].classList.add('hidden')
})
}
})
document.addEventListener('livewire:update', function () {
chart.update({labels: #this.labels, series: #this.data})
})
})
</script>
I never used the chartist, but I usually use this approach (let me know if the code make sense to you).
<div class="mt-6" x-data="{
labels: #entangle('labels'),
series: #entangle('data'),
chart: null
}"
x-init="() => {
const options = {}; // I'll omit part of the code here
chart = new Chartist.Line('#line-chart', data, options);
},
$watch('series', (dataChart) => {
// I usually put both, the series data and the labels in an associative array on the livewire component back-end
const { series, labels } = dataChart;
chart.update(dataChart);
"
>
<h2 class="text-xl text-gray-600 py-3 font-bold">Location Views</h2>
<div class="bg-white rounded-lg shadow overflow-hidden overflow-y-scroll">
<div class="flex justify-end px-10 py-6">
<select wire:model="days" class="block w-40 pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-yellow-500 focus:border-yellow-500 sm:text-sm rounded-md">
<option value="30">Last 30 Days</option>
<option value="365">Last 12 Months</option>
</select>
</div>
<div id="line-chart" class="relative ct-chart">
<div class="hidden absolute inline-block chartist-tooltip bg-white text-xs shadow text-center px-3 py-1 rounded-md w-36">
<span class="chartist-tooltip-key"></span><br>
<span class="chartist-tooltip-date"></span><br>
<span class="chartist-tooltip-value"></span>
</div>
</div>
</div>
</div>
I think that watching a value from the livewire component it's easier to debug. The use of events to make the communication between back and front-end can make debug challenge when the page gets bigger (and with a lot of events).
With this code your char will be loaded after livewire initial load (you don't need to check the 'livewire:load' event). The chart instance will not be mounted every time you dispatch the updateJS event, only the data will be update.
Ps: I don't know how Chartist works, so I didn't put the code on chart.update(...), but since it's a chart lib it should have an options to update the data. I just want to show you my approach, hope it helps.
I have a chart where I need to calculate the amount I'm earning as time goes by. In this chart, I have the amount (red line). My target is to calculate the total of every transaction that is going in. My current code is not working properly because when the time is 18:26:23 it is 1000 amount when it is 18:26:24, it is still 1000... It should be 2000. It should solve for the sum over time. I have provided my codes below and a screenshot of my current system and my target. Thank you in advance.
Views:
<div class="col-md-12">
<!-- LINE CHART -->
<div class="card card">
<div class="card-header">
<h3 class="card-title">Stats Per Day</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse" style="width:30%;">
<i class="fas fa-minus"></i>
</button>
</div>
</div>
<div class="card-body">
<div class="chart">
<div id="wholechart" style="min-height: 250px; height: 250px; max-height: 250px; max-width: 100%;"></div>
</div>
</div>
<!-- /.card-body -->
</div>
</div>
Ajax:
function sampleeesasw(){
$.ajax({
type: 'post',
url: '<?=site_url('report/datas')?>',
dataType:'json',
success: function(result) {
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(function(){drawChart(result);});
function drawChart(result) {
var data = new google.visualization.DataTable();
data.addColumn('string', 'req');
data.addColumn('number', 'total');
data.addColumn('number', 'amount');
var dataArray =[];
$.each(result,function(i,obj){
dataArray.push([obj.req,parseInt(obj.total),parseInt(obj.amount)]);
});
data.addRows(dataArray );
var options = {
seriesType: "line",
};
var chart = new google.visualization.ComboChart(document.getElementById('wholechart')).
// Line,Bar,Area,Clomun,pie
draw(data, {curveType: "function",
vAxes: {0: {logScale: false},
1: {logScale: false, maxValue: 2}},
series:{
0:{targetAxisIndex:0},
1:{targetAxisIndex:1},
2:{targetAxisIndex:1}}}
);
}
}
});
}
setInterval(function(){
sampleeesasw()
},1000);
Controller:
public function datas(){
$data= $this->reports->wholedatachart();
foreach($data as $row){
$data['req']=$row['req'];
$data['amount']=$row['amount'];
$data['total']=$row['total'];
}
echo json_encode($data);
}
Model:
function wholedatachart(){
$query=$this->db->query("SELECT timeProcess as 'req', transID as 'total', amount as 'amount' FROM tbl_transaction");
return $query->result_array();
}
You can do calculation part inside your drawChart function. Inside that function you can simply total value of amount then , save it in some variable and pass it to your dataArray.push(..).
Demo Code :
//suppose data look like this..
var result = [{
"req": "1",
"amount": 2000,
"total": 1000
},{
"req": "2",
"amount": 1000,
"total": 1000
},{
"req": "3",
"amount": 1000,
"total": 1000
}]
function sampleeesasw() {
/*$.ajax({
type: 'post',
url: '',
dataType: 'json',
success: function(result) {*/
google.charts.load('current', {
'packages': ['corechart']
});
google.charts.setOnLoadCallback(function() {
drawChart(result);
});
function drawChart(result) {
var data = new google.visualization.DataTable();
data.addColumn('string', 'req');
data.addColumn('number', 'total');
data.addColumn('number', 'amount');
var dataArray = [];
var total=0 //intialze..
$.each(result, function(i, obj) {
total +=parseInt(obj.amount)//add on each iteration
dataArray.push([obj.req, parseInt(obj.total), parseInt(total)]); //add value here ..
});
data.addRows(dataArray);
var options = {
seriesType: "line",
};
var chart = new google.visualization.ComboChart(document.getElementById('wholechart')).draw(data, {
curveType: "function",
vAxes: {
0: {
logScale: false
},
1: {
logScale: false,
maxValue: 2
}
},
series: {
0: {
targetAxisIndex: 0
},
1: {
targetAxisIndex: 1
},
2: {
targetAxisIndex: 1
}
}
});
}
/* }
});*/
}
setInterval(function() {
sampleeesasw()
}, 1000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<div class="col-md-12">
<!-- LINE CHART -->
<div class="card card">
<div class="card-header">
<h3 class="card-title">Stats Per Day</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse" style="width:30%;">
<i class="fas fa-minus"></i>
</button>
</div>
</div>
<div class="card-body">
<div class="chart">
<div id="wholechart" style="min-height: 250px; height: 250px; max-height: 250px; max-width: 100%;"></div>
</div>
</div>
<!-- /.card-body -->
</div>
</div>
I think you can try this one, you need to increment the amount of each data, you can use a temporary variable and increment all the amount.
I edited the answer sorry my bad.
change your controller to
public function datas(){
$data= $this->reports->wholedatachart();
$tempAmount = 0;
foreach($data as $row){
$tempAmount = $tempAmount + $row['amount'];
$output[]=array(
'req' => $timeline['time'],
'amount' => $tempAmount,
'amount' => $row['amount'],
);
}
echo json_encode($output);
}
if you want to add the total also you can do the same thing
I would like to know if it is possible to use datatables (Metronic Laravel Theme) to display a 6*4 grid of user-images instead of displaying the images row-by-row (the standard datatable behavior). Anyone able to help?
ive just started on this so i don't want to waste a lot of time if it's not possible at all, this is what i have so far:
let members_datatable,
members_element = $('...');
members_datatable = members_element.MyCustomDataTable({
columns: [
{
field: 'id',
title: members_element.data('column-id'),
width: 50,
template: function (row) {
return row.id;
}
},
{
field: 'first_name',
title: members_element.data('column-name'),
width: 150,
template: function (row) {
let user_id = ...;
let user_company = ...;
let company_branche = ...;
return `<div class="mt-card-item">
<div class="mt-card-avatar mt-overlay-4">
<img src="storage/public/userImages/${user_id}.jpg">
<div class="mt-overlay">
<h2>${user_company}</h2>
<div class="mt-info font-white">
<div class="mt-card-content">
${company_branche}
</div>
</div>
</div>
</div>
</div>`;
}
},
],
});
I have a scrollview which get json data from an api and render items inside scrollview. From the data i only need to render one item at a time.
Inorder to do that i try adding pageSize:1 to the data source but it does not render anything inside the scrollview. When pageSize change to 2, items render fine with 2 items inside scrollview.
How can i render only a one item inside scrollview?
<div id="example" style="margin:auto; width:60%">
<div id="scrollView" style="height: 600px; width:100%;"></div>
</div>
<script id="scrollview-template" type="text/x-kendo-template">
# for (var i = 0; i < data.length; i++) { #
<div data-role="page" style="width:100%;">
<img class="carousal-image" src="#=
getPreviewImageUrl(data[i].Type,data[i].PreviewImageUrl) #"/>
</div>
# } #
</script>
<script>
var dataSource = new kendo.data.DataSource({
transport: {
read: {
url: "sample api",
dataType: "json"
}
},
pageSize:1,
schema: {
data: "items"
},
});
$("#scrollView").kendoScrollView({
dataSource: dataSource,
template: $("#scrollview-template").html(),
contentHeight: "100%"
});
</script>
Sample josn data
{
"items": [
{
"PreviewImageUrl": "/images/ui/Default_News.png",
"Type": "NewsType"
},
{
"PreviewImageUrl": "/images/ui/Default_Blog.png",
"Type": "BlogType"
}
],
"total": 2
}
When the pageSize is greater than one, the data passed to the template is a javascript array, but when it equals one, data refers to the JSON object itself. Hence your template needs to be more like this:
<script id="scrollview-template" type="text/x-kendo-template">
# if (data != null) { #
<div data-role="page" style="width:100%;">
<img class="carousal-image"
src="#= getPreviewImageUrl(data.Type,data.PreviewImageUrl) #"/>
</div>
# } #
</script>
Working demo here. Hope this helps.
I want to change the chart-type based on the drop-down value. I had tried and followed the example but it still unable to work. I'm not sure which part that i had missed. I am using JavaScript Charts & Graphs from JSON Data Using AJAX. The code below:
<script src="../js/custom.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
<br/>
<script>
window.onload = function () {
var dataPoints = [];
$.getJSON( "<?php echo $url; ?>", function( json ) {
for (var i = 0; i < json.dates.length; i++) {
dataPoints.push({
x: new Date(json.dates[i]),
y: json.values[i]
});
}
var chart = new CanvasJS.Chart("chartContainer", {
animationEnabled: true,
exportEnabled: true,
theme: "dark2",
title: {
text: "REPORT"
},
axisY: {
title: "Qty"
},
data: [{
type: "column",
//indexLabel: "{y}", //Shows y value on all Data Points
yValueFormatString: "#,### Units",
indexLabelFontColor: "#5A5757",
indexLabelPlacement: "outside",
dataPoints: dataPoints
}]
});
chart.render();
})
.done(function(){
alert("Completed");
})
.fail(function(e){
console.log('error:');
console.error(e);
})
.always(function(){
alert("always runs");
});
}
var chartType = document.getElementById('chartType');
chartType.addEventListener( "change", function(){
for(var i = 0; i < chart.options.data.length; i++){
chart.options.data[i].type = chartType.options[chartType.selectedIndex].value;
}
chart.render();
});
</script>
Drop-down list:
<select id="chartType" class="form-control" name="chartType">
<option value="column">Column</option>
<option value="line">Line</option>
<option value="bar">Bar</option>
<option value="pie">Pie</option>
<option value="doughnut">Doughnut</option>
</select>
Thank you in advance.
It just seems like variable scope issue, you are creating chart inside AJAX but trying to access it outside during changing the chart type, it would have thrown error in Browser Console. I would suggest you to do it outside AJAX and update just dataPoints inside AJAX.
Here is the updated / working code (Sample JSON with just 1 dataPoint is stored in: https://api.myjson.com/bins/18hgaa):
var dataPoints = [];
var chart;
$.getJSON("https://api.myjson.com/bins/18hgaa", function(json){
for (var i = 0; i < json.dates.length; i++) {
dataPoints.push({
x: new Date(json.dates[i]),
y: json.values[i]
});
}
}).done(function(){
chart = new CanvasJS.Chart("chartContainer", {
animationEnabled: true,
exportEnabled: true,
theme: "dark2",
title: {
text: "REPORT"
},
axisY: {
title: "Qty"
},
data: [{
type: "column",
//indexLabel: "{y}", //Shows y value on all Data Points
yValueFormatString: "#,### Units",
indexLabelFontColor: "#5A5757",
indexLabelPlacement: "outside",
dataPoints: dataPoints
}]
});
chart.render();
});
var chartType = document.getElementById('chartType');
chartType.addEventListener( "change", function(){
for(var i = 0; i < chart.options.data.length; i++){
chart.options.data[i].type = chartType.options[chartType.selectedIndex].value;
}
chart.render();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
<div id="chartContainer" style="height: 360px; width: 100%;"></div>
<select id="chartType" class="form-control" name="chartType">
<option value="column">Column</option>
<option value="line">Line</option>
<option value="bar">Bar</option>
<option value="pie">Pie</option>
<option value="doughnut">Doughnut</option>
</select>