TCVis v1.1

This commit is contained in:
Damillora 2018-12-22 03:47:09 +00:00
parent a86eedff83
commit 794b91088a
11 changed files with 9294 additions and 81 deletions

View File

@ -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);
}

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

File diff suppressed because one or more lines are too long

24
resources/js/app.js vendored
View File

@ -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'
});

View File

@ -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>

View File

@ -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

View File

@ -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>

View 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

View File

@ -12,3 +12,4 @@
*/
Route::get('/', 'HomeController@home');
Route::get('/statistics', 'HomeController@statistics');