MT#5103 Add basic call distribution graph.

Still needs date picker to limit range.
gjungwirth/fix_tests
Andreas Granig 13 years ago
parent e831fdb71b
commit dd39baee6e

2
debian/control vendored

@ -32,6 +32,7 @@ Build-Depends: debhelper (>= 8),
libmoosex-singleton-perl,
libnamespace-sweep-perl,
libnet-telnet-perl,
libnumber-phone-perl,
libperl5i-perl,
libregexp-parser-perl,
libsipwise-base-perl,
@ -87,6 +88,7 @@ Depends: libcatalyst-actionrole-acl-perl,
libnamespace-sweep-perl,
libnet-http-perl,
libnet-telnet-perl,
libnumber-phone-perl,
libregexp-parser-perl,
libsys-sig-perl,
libtemplate-perl,

@ -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: -%]

@ -1,3 +1,17 @@
<li class="dropdown">
<a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-signal"></i>
<span>Monitoring &amp; Statistics</span>
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a href="[% c.uri_for('/statistics') %]">System Statistics</a></li>
<li><a href="[% c.uri_for('/calls') %]">Call Distribution</a></li>
[% IF c.config.features.callflow -%]
<li><a href="[% c.uri_for('/callflow') %]">SIP Call Flows</a></li>
[% END -%]
</ul>
</li>
<li class="dropdown">
<a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-th"></i>
@ -18,9 +32,6 @@
<li><a href="[% c.uri_for('/ncos') %]">NCOS Levels</a></li>
<li><a href="[% c.uri_for('/sound') %]">Sound Sets</a></li>
<li><a href="[% c.uri_for('/security') %]">Security Bans</a></li>
[% IF c.config.features.callflow -%]
<li><a href="[% c.uri_for('/callflow') %]">SIP Call Flows</a></li>
[% END -%]
[% IF c.config.features.cloudpbx -%]
<li><a href="[% c.uri_for('/device') %]">Device Management</a></li>
[% END -%]

Loading…
Cancel
Save