TCVis v1.1
This commit is contained in:
parent
a86eedff83
commit
794b91088a
@ -26,10 +26,22 @@ class HomeController extends Controller
|
||||
|
||||
return $colors;
|
||||
}
|
||||
public function statistics(Request $request) {
|
||||
$dataPoints = Rank::count();
|
||||
return view('statistics',['data_points' => $dataPoints]);
|
||||
|
||||
}
|
||||
public function home(Request $request) {
|
||||
$minutes = $request->since ?? 60*24*90;
|
||||
$timepoints = Rank::orderBy('summary_time')->get()->unique('summary_time')->pluck('summary_time');
|
||||
if($minutes != "full") $timepoints = $timepoints->filter(function($item) use ($minutes) {
|
||||
$timepoints = Cache::get('timepoints');
|
||||
$requested_date = $request->date_since;
|
||||
if($requested_date) {
|
||||
$requested_date = Carbon::parse($requested_date);
|
||||
$timepoints = $timepoints->filter(function($item) use ($requested_date) {
|
||||
return $item->gte($requested_date);
|
||||
});
|
||||
}
|
||||
else if($minutes != "full") $timepoints = $timepoints->filter(function($item) use ($minutes) {
|
||||
return $item->gte(Carbon::now()->addHours(9)->subMinutes($minutes+5));
|
||||
});
|
||||
$take = $timepoints->count();
|
||||
@ -43,12 +55,13 @@ class HomeController extends Controller
|
||||
}
|
||||
$timelabels = collect($timelabels);
|
||||
$timelabels = $timelabels->splice(count($timelabels) - $take);
|
||||
if($request->take) $timelabels = $timelabels->take($request->take);
|
||||
foreach($ranks as $roleName => $roleRank) {
|
||||
$chartPoint = Cache::get('chart_point_'.$roleName);
|
||||
$chartPoint = Cache::get('data_'.$roleName);
|
||||
// dd($chartPoint);
|
||||
if(!isset($chartPoint)) {
|
||||
\App\MatsuriHime\Election::createSnapshot();
|
||||
$chartPoint = Cache::get('chart_point_'.$roleName);
|
||||
$chartPoint = Cache::get('data_'.$roleName);
|
||||
}
|
||||
$chart = new RankChart;
|
||||
$chart->options([
|
||||
@ -59,16 +72,15 @@ class HomeController extends Controller
|
||||
'lineOptions' => [
|
||||
'regionFill' => true
|
||||
],
|
||||
'title' => $roleName.' ('.$dramaNames[$roleName].')'
|
||||
]);
|
||||
$chart->isNavigable(true);
|
||||
$chart->labels($timelabels->toArray());
|
||||
$chart->hideDots(true);
|
||||
foreach($chartPoint as $idol => $dataPoints) {
|
||||
$values = [];
|
||||
foreach($timelabels as $timelabel) {
|
||||
$values[] = $dataPoints[$timelabel] ?? 0;
|
||||
}
|
||||
$values = collect($values);
|
||||
$values = collect($dataPoints);
|
||||
$values = $values->splice(count($values) - $take);
|
||||
if($request->take) $values = $values->take($request->take);
|
||||
$idol_color = $idolColors[$idol];
|
||||
$chart->dataSet($idol,'line',$values->toArray())->color($idol_color);
|
||||
}
|
||||
|
@ -36,22 +36,53 @@ class Election
|
||||
DB::beginTransaction();
|
||||
foreach($rankings as $ladder) {
|
||||
$existingSnapshot = Rank::where('role',$ladder->name)->where('summary_time',Carbon::parse($ladder->summaryTime))->count();
|
||||
$hasSnapshot = $hasSnapshot && ($existingSnapshot > 0);
|
||||
if($existingSnapshot == 0) {
|
||||
$idols = [];
|
||||
foreach($ladder->data[0] as $data) {
|
||||
$rank = new Rank();
|
||||
$rank->role = $ladder->name;
|
||||
$rank->character = $data->idol_name;
|
||||
$idols[] = $data->idol_name;
|
||||
$rank->score = $data->score;
|
||||
$rank->summary_time = Carbon::parse($ladder->summaryTime);
|
||||
$rank->save();
|
||||
}
|
||||
$remnant_idols = Rank::where('role',$ladder->name)->whereNotIn('character',$idols)->orderBy('summary_time','desc')->get()->groupBy('character');
|
||||
foreach($remnant_idols as $remnant_char => $remnant) {
|
||||
$rank = new Rank();
|
||||
$rank->role = $ladder->name;
|
||||
$rank->character = $remnant_char;
|
||||
$rank->score = $remnant->first()->score;
|
||||
$rank->summary_time = Carbon::parse($ladder->summaryTime);
|
||||
$rank->save();
|
||||
\Log::error($remnant_char.' '.$remnant->first()->score);
|
||||
}
|
||||
} else {
|
||||
\Log::info("snapshot has been taken for " . (string)$ladder->summaryTime);
|
||||
}
|
||||
}
|
||||
DB::commit();
|
||||
if($hasSnapshot == 1) return;
|
||||
Election::generateChartPointsNew();
|
||||
}
|
||||
public static function generateChartPointsNew() {
|
||||
$ranks = Rank::orderBy('summary_time')->get()->groupBy('role');
|
||||
$timepoints = Rank::distinct('summary_time')->orderBy('summary_time')->pluck('summary_time');
|
||||
Cache::forever('timepoints',$timepoints);
|
||||
foreach($ranks as $roleName => $roleRank) {
|
||||
$dataPoints = [];
|
||||
$ladder = $roleRank->sortBy('summary_time')->groupBy('character')->sortByDesc(function($rank,$key) {
|
||||
return $rank->last()->score;
|
||||
});
|
||||
foreach($ladder as $character => $rank) {
|
||||
$standing = $rank;
|
||||
|
||||
$standing = $standing->pad(-$timepoints->count(),(int)0);
|
||||
$dataPoints[$character] = $standing->pluck('score');
|
||||
}
|
||||
Cache::forever('data_'.$roleName,$dataPoints);
|
||||
}
|
||||
}
|
||||
public static function generateChartPoints() {
|
||||
$ranks = Rank::all()->groupBy('role');
|
||||
foreach($ranks as $roleName => $roleRank) {
|
||||
$chartPoint = [];
|
||||
@ -68,7 +99,5 @@ class Election
|
||||
Cache::forever('chart_point_'.$roleName,$chartPoint);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
9149
package-lock.json
generated
Normal file
9149
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,11 @@
|
||||
"resolve-url-loader": "^2.3.1",
|
||||
"sass": "^1.15.2",
|
||||
"sass-loader": "^7.1.0",
|
||||
"vue": "^2.5.17"
|
||||
"vue": "^2.5.17",
|
||||
"vue-template-compiler": "^2.5.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"eonasdan-bootstrap-datetimepicker": "^4.17.47",
|
||||
"install": "^0.12.2"
|
||||
}
|
||||
}
|
||||
|
14
public/pablo.min.js
vendored
Normal file
14
public/pablo.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
24
resources/js/app.js
vendored
24
resources/js/app.js
vendored
@ -7,27 +7,3 @@
|
||||
|
||||
require('./bootstrap');
|
||||
|
||||
window.Vue = require('vue');
|
||||
|
||||
/**
|
||||
* The following block of code may be used to automatically register your
|
||||
* Vue components. It will recursively scan this directory for the Vue
|
||||
* components and automatically register them with their "basename".
|
||||
*
|
||||
* Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
|
||||
*/
|
||||
|
||||
// const files = require.context('./', true, /\.vue$/i)
|
||||
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
|
||||
|
||||
Vue.component('example-component', require('./components/ExampleComponent.vue').default);
|
||||
|
||||
/**
|
||||
* Next, we will create a fresh Vue application instance and attach it to
|
||||
* the page. Then, you may begin adding components to this application
|
||||
* or customize the JavaScript scaffolding to fit your unique needs.
|
||||
*/
|
||||
|
||||
const app = new Vue({
|
||||
el: '#app'
|
||||
});
|
||||
|
@ -1,6 +1,22 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<script type="text/javascript">
|
||||
const EXPORTCSSTEXT = ".chart-container{background-color: #ffffff; width: 1920px; height: 1080px; position:relative;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .legend-dataset-text{fill:#6c7680;font-weight:600}.graph-svg-tip{position:absolute;z-index:99999;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ul{padding-left:0;display:flex}.graph-svg-tip ol{padding-left:0;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:' ';border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}";
|
||||
function exportPNG(filename, chart) {
|
||||
var clone = chart.svg.cloneNode(true);
|
||||
clone.classList.add('chart-container');
|
||||
clone.setAttribute('xmlns', "http://www.w3.org/2000/svg");
|
||||
clone.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink");
|
||||
var styleEl = document.createElement('style');
|
||||
styleEl.appendChild(document.createTextNode(EXPORTCSSTEXT));
|
||||
clone.insertBefore(styleEl, clone.firstChild);
|
||||
var width = 1920;
|
||||
var scale = width/clone.getAttribute('width');
|
||||
var svg = Pablo(clone);
|
||||
svg.viewbox([0,0,clone.getAttribute('width'),clone.getAttribute('height')]).attr('width',width).attr('height',clone.getAttribute('height')*scale).download('png',filename+'.png');
|
||||
}
|
||||
</script>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-12">
|
||||
@ -8,7 +24,8 @@
|
||||
<div class="card-header">Settings</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ url()->current() }}" method="GET">
|
||||
<p>Show last:
|
||||
<div class="form-group">
|
||||
<label for="show-last">Show last:</label>
|
||||
<select name="since" id="show-last" onchange="this.form.submit()">
|
||||
<option value="">Show last..</option>
|
||||
<option value="30" {{ $since == 30 ? "selected" : "" }} >30 minutes</option>
|
||||
@ -20,10 +37,27 @@
|
||||
<option value="10080" {{ $since == 10080 ? "selected" : "" }} >1 week</option>
|
||||
<option value="full" {{ $since == "full" ? "selected" : "" }} >Entire history</option>
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
<form action="{{ url()->current() }}" method="GET">
|
||||
<div class="form-group">
|
||||
<label for="datetimepicker1">Date since:</label>
|
||||
<div class="input-group date" id="datetimepicker1" data-target-input="nearest">
|
||||
<input name="date_since" type="text" class="form-control datetimepicker-input" data-target="#datetimepicker1" />
|
||||
<div class="input-group-append" data-target="#datetimepicker1" data-toggle="datetimepicker">
|
||||
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
$('#datetimepicker1').datetimepicker();
|
||||
$('#datetimepicker1').on('change.datetimepicker', function() {
|
||||
this.form.submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</form>
|
||||
<!--<button class="btn" type="submit">Show chart</button>-->
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@foreach($chart_groups as $drama_name => $charts)
|
||||
@ -36,10 +70,9 @@
|
||||
<div class="card-header">{{ $chart["name"] }}</div>
|
||||
<div class="card-body text-center">
|
||||
{!! $chart["chart"]->container() !!}
|
||||
<button class="btn btn-primary" onclick="exportPNG('{{ $chart["name"] }} ({{ $drama_name }})',window.{{ $chart["chart"]->id }})">Export as PNG</button>
|
||||
</div>
|
||||
{!! $chart["chart"]->script() !!}
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@ -47,9 +80,9 @@
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="card">
|
||||
<div class="card-header">Data Sources</div>
|
||||
<div class="card-header">Information</div>
|
||||
<div class="card-body text-center">
|
||||
<p>Data generated from <a href="https://api.matsurihi.me/docs/">Princess — Public REST API</a>
|
||||
<p>Data generated from <a href="https://api.matsurihi.me/docs/">Princess — Public REST API</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,34 +0,0 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-12">
|
||||
@foreach($chart_groups as $drama_name => $charts)
|
||||
<div class="card">
|
||||
<div c lass="card-header">{{ $drama_name }}</div>
|
||||
<div class="card-body text-center">
|
||||
<div>
|
||||
@foreach($charts as $chart)
|
||||
<div class="card">
|
||||
<div class="card-header">{{ $chart["name"] }}</div>
|
||||
<div class="card-body text-center">
|
||||
{!! $chart["chart"]->container() !!}
|
||||
</div>
|
||||
{!! $chart["chart"]->script() !!}
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="card">
|
||||
<div class="card-header">Data Sources</div>
|
||||
<div class="card-body text-center">
|
||||
<p>Data generated from <a href="https://api.matsurihi.me/docs/">Princess — Public REST API</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -13,9 +13,15 @@
|
||||
<script src="{{ asset('js/app.js') }}" defer></script>
|
||||
|
||||
<script src="//code.jquery.com/jquery-3.3.1.min.js"></script>
|
||||
<link rel="stylesheet" href="{{ url('jquery.fancybox.min.css') }}" />
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/frappe-charts@1.1.0/dist/frappe-charts.min.iife.js"></script>
|
||||
<script src="/pablo.min.js"></script>
|
||||
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.1/js/tempusdominus-bootstrap-4.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.1/css/tempusdominus-bootstrap-4.min.css" />
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="dns-prefetch" href="//fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">
|
||||
@ -24,6 +30,9 @@
|
||||
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url('magnific-popup.css') }}" type="text/css" media="screen" />
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<nav class="navbar navbar-expand-md navbar-light navbar-laravel">
|
||||
@ -43,6 +52,8 @@
|
||||
|
||||
<!-- Right Side Of Navbar -->
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li><a class="nav-link" href="{{ url('/') }}">{{ __('Home') }}</a></li>
|
||||
<li><a class="nav-link" href="{{ url('/statistics') }}">{{ __('Statistics') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
17
resources/views/statistics.blade.php
Normal file
17
resources/views/statistics.blade.php
Normal file
@ -0,0 +1,17 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card">
|
||||
<div class="card-header">Statistics</div>
|
||||
<div class="card-body text-center">
|
||||
<p>Data points: {{ $data_points }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -12,3 +12,4 @@
|
||||
*/
|
||||
|
||||
Route::get('/', 'HomeController@home');
|
||||
Route::get('/statistics', 'HomeController@statistics');
|
||||
|
Loading…
Reference in New Issue
Block a user