Still needs date picker to limit range.gjungwirth/fix_tests
parent
e831fdb71b
commit
dd39baee6e
@ -0,0 +1,106 @@
|
||||
package NGCP::Panel::Controller::Calls;
|
||||
use Sipwise::Base;
|
||||
|
||||
BEGIN { extends 'Catalyst::Controller'; }
|
||||
|
||||
use NGCP::Panel::Utils::Navigation;
|
||||
use Number::Phone;
|
||||
|
||||
sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
|
||||
my ($self, $c) = @_;
|
||||
$c->log->debug(__PACKAGE__ . '::auto');
|
||||
NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub root :PathPart('/') :CaptureArgs(0) {
|
||||
my ( $self, $c ) = @_;
|
||||
}
|
||||
|
||||
sub index :Chained('/') :PathPart('calls') :Args(0) {
|
||||
my ( $self, $c ) = @_;
|
||||
|
||||
$c->stash(template => 'calls/chord.tt');
|
||||
}
|
||||
|
||||
sub calls_matrix_ajax :Chained('/') :PathPart('calls/ajax') :Args(0) {
|
||||
my ( $self, $c ) = @_;
|
||||
|
||||
my $matrix = [];
|
||||
my $countries = [];
|
||||
|
||||
my $rs = $c->model('DB')->resultset('cdr')->search({}, {
|
||||
select => [qw/source_cli destination_user_in/],
|
||||
});
|
||||
|
||||
my $n = $rs->count;
|
||||
|
||||
my $id_counter = 0;
|
||||
my $id_table = {};
|
||||
|
||||
while(my $ref = $rs->next) {
|
||||
next unless($ref->source_cli && $ref->source_cli =~ /^\d{5,}$/ &&
|
||||
$ref->destination_user_in && $ref->destination_user_in =~ /^\d{5,}$/);
|
||||
my $s = Number::Phone->new($ref->source_cli);
|
||||
my $d = Number::Phone->new($ref->destination_user_in);
|
||||
next unless($s && $d);
|
||||
|
||||
# register new ids for those country codes
|
||||
unless(exists $id_table->{$s->country_code}) {
|
||||
$id_table->{$s->country_code} = $id_counter;
|
||||
$countries->[$id_counter] = $s->country;
|
||||
++$id_counter;
|
||||
}
|
||||
unless(exists $id_table->{$d->country_code}) {
|
||||
$id_table->{$d->country_code} = $id_counter;
|
||||
$countries->[$id_counter] = $d->country;
|
||||
++$id_counter;
|
||||
}
|
||||
|
||||
my $sid = $id_table->{$s->country_code};
|
||||
my $did = $id_table->{$d->country_code};
|
||||
|
||||
unless(defined $matrix->[$sid]) {
|
||||
$matrix->[$sid] = [];
|
||||
}
|
||||
unless(defined $matrix->[$sid]->[$did]) {
|
||||
$matrix->[$sid]->[$did] = 1;
|
||||
} else {
|
||||
$matrix->[$sid]->[$did]++;
|
||||
}
|
||||
}
|
||||
|
||||
my $count = @{ $countries };
|
||||
for(my $i = 0; $i < $count; ++$i) {
|
||||
unless(defined $matrix->[$i]) {
|
||||
$matrix->[$i] = [];
|
||||
$matrix->[$i]->[$count-1] = undef;
|
||||
} elsif(@{ $matrix->[$i] } != $count) {
|
||||
$matrix->[$i]->[$count-1] = undef;
|
||||
}
|
||||
}
|
||||
|
||||
my $data = {
|
||||
countries => $countries,
|
||||
calls => $matrix,
|
||||
};
|
||||
my $json = JSON->new->allow_nonref;
|
||||
$c->res->body($json->encode($data));
|
||||
}
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Andreas Granig,,,
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This library is free software. You can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->meta->make_immutable;
|
||||
|
||||
1;
|
||||
|
||||
# vim: set tabstop=4 expandtab:
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,115 @@
|
||||
console.log("foo bar");
|
||||
var chord;
|
||||
jQuery.ajax({
|
||||
url: "/calls/ajax",
|
||||
type: "post",
|
||||
async: true,
|
||||
datatype: "json",
|
||||
error: function(xhr, textStatus, errorThrown) {
|
||||
$("#ngcp-cdr-chord-loading").remove();
|
||||
$("#ngcp-cdr-chord").append('<div class="error">Failed to load call data</div>');
|
||||
},
|
||||
success: function(resjson, textStatus, XMLHttpRequest) {
|
||||
$("#ngcp-cdr-chord-loading").remove();
|
||||
var json = $.parseJSON(resjson);
|
||||
var countries = json.countries;
|
||||
var matrix = json.calls;
|
||||
var width = 720,
|
||||
height = 720,
|
||||
outerRadius = Math.min(width, height) / 2 - 10,
|
||||
innerRadius = outerRadius - 24;
|
||||
|
||||
console.log(json);
|
||||
|
||||
//var formatPercent = d3.format(".1%");
|
||||
var formatPercent = function(v) {
|
||||
return Math.floor(v);
|
||||
};
|
||||
|
||||
var arc = d3.svg.arc()
|
||||
.innerRadius(innerRadius)
|
||||
.outerRadius(outerRadius);
|
||||
|
||||
var layout = d3.layout.chord()
|
||||
.padding(.04)
|
||||
.sortSubgroups(d3.descending)
|
||||
.sortChords(d3.ascending);
|
||||
|
||||
var path = d3.svg.chord()
|
||||
.radius(innerRadius);
|
||||
|
||||
console.log("appending svg");
|
||||
var svg = d3.select("#ngcp-cdr-chord").append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.append("g")
|
||||
.attr("id", "circle")
|
||||
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
|
||||
|
||||
svg.append("circle")
|
||||
.attr("r", outerRadius);
|
||||
|
||||
|
||||
// Compute the chord layout.
|
||||
layout.matrix(matrix);
|
||||
|
||||
// Add a group per neighborhood.
|
||||
var group = svg.selectAll(".group")
|
||||
.data(layout.groups)
|
||||
.enter().append("g")
|
||||
.attr("class", "group")
|
||||
.on("mouseover", mouseover);
|
||||
|
||||
// Add a mouseover title.
|
||||
group.append("title").text(function(d, i) {
|
||||
return countries[i] + ": " + formatPercent(d.value) + " originating calls";
|
||||
});
|
||||
|
||||
// Add the group arc.
|
||||
var fill = d3.scale.category10();
|
||||
var groupPath = group.append("path")
|
||||
.attr("id", function(d, i) { return "group" + i; })
|
||||
.attr("d", arc)
|
||||
.style("fill", function(d, i) { return fill(i); });
|
||||
|
||||
// Add a text label.
|
||||
var groupText = group.append("text")
|
||||
.attr("x", 6)
|
||||
.attr("dy", 15);
|
||||
|
||||
groupText.append("textPath")
|
||||
.attr("xlink:href", function(d, i) { return "#group" + i; })
|
||||
.text(function(d, i) { return countries[i]; });
|
||||
|
||||
// Remove the labels that don't fit. :(
|
||||
groupText.filter(function(d, i) { return groupPath[0][i].getTotalLength() / 2 - 16 < this.getComputedTextLength(); })
|
||||
.remove();
|
||||
|
||||
// Add the chords.
|
||||
chord = svg.selectAll(".chord")
|
||||
.data(layout.chords)
|
||||
.enter().append("path")
|
||||
.attr("class", "chord")
|
||||
.style("fill", function(d) { return fill(d.source.index); })
|
||||
.attr("d", path);
|
||||
|
||||
// Add an elaborate mouseover title for each chord.
|
||||
chord.append("title").text(function(d) {
|
||||
return countries[d.source.index]
|
||||
+ " → " + countries[d.target.index]
|
||||
+ ": " + formatPercent(d.source.value)
|
||||
+ "\n" + countries[d.target.index]
|
||||
+ " → " + countries[d.source.index]
|
||||
+ ": " + formatPercent(d.target.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function mouseover(d, i) {
|
||||
chord.classed("fade", function(p) {
|
||||
return p.source.index != i
|
||||
&& p.target.index != i;
|
||||
});
|
||||
}
|
||||
|
||||
/* vim: set tabstop=4 syntax=javascript expandtab: */
|
||||
@ -0,0 +1,56 @@
|
||||
[% META title = 'Call Distribution' -%]
|
||||
|
||||
<style>
|
||||
#circle circle {
|
||||
fill: none;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.group path {
|
||||
fill-opacity: .5;
|
||||
}
|
||||
|
||||
path.chord {
|
||||
stroke: #000;
|
||||
stroke-width: .25px;
|
||||
}
|
||||
|
||||
#circle:hover path.fade {
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
font: 10px sans-serif;
|
||||
}
|
||||
|
||||
.axis path, .axis line {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script type="text/javascript" src="/js/libs/d3.v2.min-2.8.1.js"></script>
|
||||
<div class="row">
|
||||
<span>
|
||||
<a class="btn btn-primary btn-large" href="[% c.uri_for('/back') %]"><i class="icon-arrow-left"></i> Back</a>
|
||||
</span>
|
||||
</div>
|
||||
[% back_created = 1 -%]
|
||||
<div class="ngcp-separator"></div>
|
||||
|
||||
<p>Mouseover on a country name to focus on calls to or from a single country. The thickness of links between countries encodes the relative frequency of calls between two countries: thicker links represent more calls.</p>
|
||||
<p>Links are colored by the more frequent origin. Mouseover on a link to see the direction details.</p>
|
||||
|
||||
<div class="offset2 span6" id="ngcp-cdr-chord" style="margin-top:20px">
|
||||
<div id="ngcp-cdr-chord-loading" style="margin-top:30px">
|
||||
<img src="/img/loader.gif" alt="loading" style="margin-right: 10px;"/>
|
||||
<span><i>crunching data, please be patient - it might take a while...</i></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="/js/libs/ngcp-chord.js"></script>
|
||||
|
||||
|
||||
[% # vim: set tabstop=4 syntax=html expandtab: -%]
|
||||
Loading…
Reference in new issue