Working on d3.js vizualizer

This commit is contained in:
Hillel Coren 2014-10-03 02:51:21 +03:00
parent b3a0b6f83d
commit 020158a0ae
24 changed files with 1765 additions and 1363 deletions

View File

@ -0,0 +1,88 @@
<?php
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class CreateRandomData extends Command {
protected $name = 'ninja:create-data';
protected $description = 'Create random data';
public function fire()
{
$this->info(date('Y-m-d') . ' Running CreateRandomData...');
$user = User::first();
if (!$user) {
$this->error("Error: please create user account by logging in");
return;
}
$productNames = ['Arkansas', 'New York', 'Arizona', 'California', 'Colorado', 'Alabama', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'Alaska', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'];
$clientNames = ['IBM', 'Nestle', 'Mitsubishi UFJ Financial', 'Vodafone', 'Eni', 'Procter & Gamble', 'Johnson & Johnson', 'American International Group', 'Banco Santander', 'BHP Billiton', 'Pfizer', 'Itaú Unibanco Holding', 'Ford Motor', 'BMW Group', 'Commonwealth Bank', 'EDF', 'Statoil', 'Google', 'Siemens', 'Novartis', 'Royal Bank of Canada', 'Sumitomo Mitsui Financial', 'Comcast', 'Sberbank', 'Goldman Sachs Group', 'Westpac Banking Group', 'Nippon Telegraph & Tel', 'Ping An Insurance Group', 'Banco Bradesco', 'Anheuser-Busch InBev', 'Bank of Communications', 'China Life Insurance', 'General Motors', 'Telefónica', 'MetLife', 'Honda Motor', 'Enel', 'BASF', 'Softbank', 'National Australia Bank', 'ANZ', 'ConocoPhillips', 'TD Bank Group', 'Intel', 'UBS', 'Hewlett-Packard', 'Coca-Cola', 'Cisco Systems', 'UnitedHealth Group', 'Boeing', 'Zurich Insurance Group', 'Hyundai Motor', 'Sanofi', 'Credit Agricole', 'United Technologies', 'Roche Holding', 'Munich Re', 'PepsiCo', 'Oracle', 'Bank of Nova Scotia'];
for ($i=1; $i<=40; $i++) {
$product = Product::createNew($user);
$product->id = $i;
$product->product_key = $productNames[$i-1];
$product->save();
}
for ($i=0; $i<60; $i++) {
$client = Client::createNew($user);
$client->name = $clientNames[$i];
$client->save();
$contact = Contact::createNew($user);
$contact->email = "client@aol.com";
$contact->is_primary = 1;
$client->contacts()->save($contact);
$numInvoices = rand(1, 25);
if ($numInvoices == 4 || $numInvoices == 10 || $numInvoices == 25) {
// leave these
} else if ($numInvoices % 3 == 0) {
$numInvoices = 1;
} else if ($numInvoices > 10) {
$numInvoices = $numInvoices % 2;
}
$paidUp = rand(0, 1) == 1;
for ($j=1; $j<=$numInvoices; $j++) {
$price = rand(10, 1000);
if ($price < 900) {
$price = rand(10, 150);
}
$invoice = Invoice::createNew($user);
$invoice->invoice_number = $user->account->getNextInvoiceNumber();
$invoice->amount = $invoice->balance = $price;
$invoice->created_at = date('Y-m-d', strtotime(date("Y-m-d") . ' - ' . rand(1, 100) . ' days'));
$client->invoices()->save($invoice);
$productId = rand(0, 40);
if ($productId > 20) {
$productId = ($productId % 2) + rand(0, 2);
}
$invoiceItem = InvoiceItem::createNew($user);
$invoiceItem->product_id = $productId+1;
$invoiceItem->product_key = $productNames[$invoiceItem->product_id];
$invoiceItem->cost = $invoice->amount;
$invoiceItem->qty = 1;
$invoice->invoice_items()->save($invoiceItem);
if ($paidUp || rand(0,2) > 1) {
$payment = Payment::createNew($user);
$payment->invoice_id = $invoice->id;
$payment->amount = $invoice->amount;
$client->payments()->save($payment);
}
}
}
}
}

View File

@ -0,0 +1,24 @@
<?php
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class ResetData extends Command {
protected $name = 'ninja:reset-data';
protected $description = 'Reset data';
public function fire()
{
$this->info(date('Y-m-d') . ' Running ResetData...');
if (!Utils::isNinjaDev()) {
return;
}
Artisan::call('migrate:reset');
Artisan::call('migrate');
Artisan::call('db:seed');
}
}

View File

@ -46,9 +46,9 @@ class AccountController extends \BaseController {
return Redirect::to('/');
}
/*
public function reset()
{
/*
if (Utils::isNinjaDev()) {
Confide::logout();
try {
@ -59,9 +59,10 @@ class AccountController extends \BaseController {
Response::make($e->getMessage(), 500);
}
}
*/
return Redirect::to('/');
}
*/
public function getStarted()
{

View File

@ -2,6 +2,23 @@
class ReportController extends \BaseController {
public function d3()
{
$account = Auth::user()->account;
$account = $account->with(['clients.invoices.invoice_items', 'clients.contacts'])->first();
$account = $account->hideFieldsForViz();
$clients = $account->clients;
//dd($clients->toJson());
$data = [
'feature' => ACCOUNT_DATA_VISUALIZER,
'clients' => $clients
];
return View::make('reports.d3', $data);
}
public function report()
{
if (Input::all())

View File

@ -333,6 +333,13 @@ return array(
'created_product' => 'Produkt erfolgreich erstellt',
'archived_product' => 'Produkt erfolgreich archiviert',
'product_library' => 'Produktbibliothek',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Diagrammersteller',
'ninja_email_footer' => 'Nutze :site um Kunden eine Rechnung zu stellen und online bezahlt zu werden, kostenlos!',
@ -407,10 +414,12 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?',
'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
'data_visualizer' => 'Data Visualizer',
);

View File

@ -422,10 +422,12 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?',
'confirm_recurring_email_invoice' => 'Recurring is enabled,re you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
'data_visualizer' => 'Data Visualizer',
);

View File

@ -332,6 +332,13 @@ return array(
'updated_product' => 'Successfully updated product',
'created_product' => 'Successfully created product',
'archived_product' => 'Successfully archived product',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Chart Builder',
'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!',
@ -405,10 +412,12 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?',
'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
'data_visualizer' => 'Data Visualizer',
);

View File

@ -333,6 +333,13 @@ return array(
'updated_product' => 'Successfully updated product',
'created_product' => 'Successfully created product',
'archived_product' => 'Successfully archived product',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Chart Builder',
'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!',
@ -407,10 +414,12 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?',
'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
'data_visualizer' => 'Data Visualizer',
);

View File

@ -333,6 +333,13 @@ return array(
'updated_product' => 'Prodotto aggiornato con successo',
'created_product' => 'Prodotto creato con successo',
'archived_product' => 'Prodotto archiviato con successo',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Creatore grafico',
'ninja_email_footer' => 'Usa :site per fatturare ai tuoi clienti e venire pagato online gratis!',
@ -407,10 +414,11 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?',
'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
'data_visualizer' => 'Data Visualizer',
);

View File

@ -422,11 +422,13 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?',
'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
'data_visualizer' => 'Data Visualizer',
);

View File

@ -422,10 +422,12 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?',
'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
'data_visualizer' => 'Data Visualizer',
);

View File

@ -334,6 +334,13 @@ return array(
'updated_product' => 'Successfully updated product',
'created_product' => 'Successfully created product',
'archived_product' => 'Successfully archived product',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Chart Builder',
'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!',
@ -408,10 +415,12 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?',
'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
'data_visualizer' => 'Data Visualizer',
);

View File

@ -322,6 +322,13 @@ return array(
'updated_product' => 'Successfully updated product',
'created_product' => 'Successfully created product',
'archived_product' => 'Successfully archived product',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Chart Builder',
'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!',
@ -396,10 +403,12 @@ return array(
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?',
'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
'data_visualizer' => 'Data Visualizer',
);

View File

@ -252,4 +252,47 @@ class Account extends Eloquent
{
return Subscription::where('account_id', '=', $this->id)->where('event_id', '=', $eventId)->first();
}
public function hideFieldsForViz()
{
foreach ($this->clients as $client)
{
$client->setVisible([
'name',
'balance',
'paid_to_date',
'invoices',
'contacts',
]);
foreach ($client->invoices as $invoice)
{
$invoice->setVisible([
'invoice_number',
'amount',
'balance',
'invoice_status_id',
'invoice_items',
'created_at',
]);
foreach ($invoice->invoice_items as $invoiceItem)
{
$invoiceItem->setVisible([
'product_key',
'cost',
'qty',
]);
}
}
foreach ($client->contacts as $contact)
{
$contact->setVisible(['public_id']);
}
}
return $this;
}
}

View File

@ -51,7 +51,7 @@ class Activity extends Eloquent
if ($entity)
{
$activity->user_id = $entity->user_id;
$activity->user_id = $entity instanceof User ? $entity->id : $entity->user_id;
$activity->account_id = $entity->account_id;
}
else if (Auth::check())
@ -272,7 +272,7 @@ class Activity extends Eloquent
}
else
{
$activity = Activity::getBlank();
$activity = Activity::getBlank($client);
$message = $payment->payment_type_id == PAYMENT_TYPE_CREDIT ? 'applied credit for ' : 'entered ' . $payment->getName() . ' for ';
$activity->message = Utils::encodeActivity(Auth::user(), $message, $payment->invoice);
}

View File

@ -204,6 +204,7 @@ class Client extends EntityModel
return $this->created_at->format('m/d/y h:i a');
}
}
}
/*

View File

@ -5,7 +5,7 @@ class EntityModel extends Eloquent
protected $softDelete = true;
public $timestamps = true;
protected $hidden = ['id', 'created_at', 'deleted_at', 'updated_at'];
protected $hidden = ['id'];
public static function createNew($parent = false)
{
@ -14,7 +14,7 @@ class EntityModel extends Eloquent
if ($parent)
{
$entity->user_id = $parent->user_id;
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id;
}
else if (Auth::check())

View File

@ -80,6 +80,7 @@ Route::group(array('before' => 'auth'), function()
Route::resource('products', 'ProductController');
Route::get('products/{product_id}/archive', 'ProductController@archive');
Route::get('company/advanced_settings/data_visualizer', 'ReportController@d3');
Route::get('company/advanced_settings/chart_builder', 'ReportController@report');
Route::post('company/advanced_settings/chart_builder', 'ReportController@report');
@ -170,7 +171,7 @@ define('ACCOUNT_CUSTOM_FIELDS', 'custom_fields');
define('ACCOUNT_INVOICE_DESIGN', 'invoice_design');
define('ACCOUNT_CHART_BUILDER', 'chart_builder');
define('ACCOUNT_USER_MANAGEMENT', 'user_management');
define('ACCOUNT_DATA_VISUALIZER', 'data_visualizer');
define('DEFAULT_INVOICE_NUMBER', '0001');
define('RECENTLY_VIEWED_LIMIT', 8);

View File

@ -12,3 +12,5 @@
*/
Artisan::resolve('SendRecurringInvoices');
Artisan::resolve('CreateRandomData');
Artisan::resolve('ResetData');

View File

@ -1,6 +1,7 @@
<ul class="nav nav-tabs nav nav-justified">
{{ HTML::nav_link('company/advanced_settings/custom_fields', 'custom_fields') }}
{{ HTML::nav_link('company/advanced_settings/invoice_design', 'invoice_design') }}
{{ HTML::nav_link('company/advanced_settings/data_visualizer', 'data_visualizer') }}
{{ HTML::nav_link('company/advanced_settings/chart_builder', 'chart_builder') }}
{{ HTML::nav_link('company/advanced_settings/user_management', 'user_management') }}
</ul>

View File

@ -0,0 +1,300 @@
@extends('accounts.nav')
@section('head')
@parent
<script src="{{ asset('vendor/d3/d3.min.js') }}" type="text/javascript"></script>
<style type="text/css">
#tooltip {
position: absolute;
width: 200px;
height: auto;
padding: 10px 10px 2px 10px;
background-color: #F6F6F6;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
}
.no-pointer-events {
pointer-events: none;
}
</style>
@stop
@section('content')
@parent
@include('accounts.nav_advanced')
<div id="tooltip" class="hidden">
<p>
<strong><span id="tooltipTitle"></span></strong>
<a class="pull-right" href="#" target="_blank">View</a>
</p>
<p>Total <span id="tooltipTotal" class="pull-right"></span></p>
<p>Balance <span id="tooltipBalance" class="pull-right"></span></p>
<p>Age <span id="tooltipAge" class="pull-right"></span></p>
</div>
<form class="form-inline" role="form">
Group By &nbsp;&nbsp;
<select id="groupBySelect" class="form-control" onchange="update()">
<option>Clients</option>
<option>Invoices</option>
<option>Products</option>
</select>
</form>
<p>&nbsp;</p>
<div class="svg-div"/>
<script type="text/javascript">
// store data as JSON
var data = {{ $clients->toJson() }};
_.each(data, function(client) {
_.each(client.invoices, function(invoice) {
_.each(invoice.invoice_items, function(invoice_item) {
invoice_item.invoice = invoice;
});
});
});
// pre-process the possible groupings (clients, invoices and products)
var clients = data.concat();
var invoices = _.flatten(_.pluck(clients, 'invoices'));
/*
// remove quotes and recurring invoices
invoices = _.filter(invoices, function(invoice) {
return !parseInt(invoice.is_quote) && !parseInt(invoice.is_recurring);
});
*/
var products = _.flatten(_.pluck(invoices, 'invoice_items'));
products = d3.nest()
.key(function(d) { return d.product_key; })
.sortKeys(d3.ascending)
.rollup(function(d) { return {
amount: d3.sum(d, function(g) {
return g.qty * g.cost;
}),
paid: d3.sum(d, function(g) {
return g.invoice && g.invoice.invoice_status_id == 5 ? (g.qty * g.cost) : 0;
}),
age: d3.median(d, function(g) {
return calculateInvoiceAge(g.invoice) || null;
}),
}})
.entries(products);
// create standardized display properties
_.each(clients, function(client) {
client.displayName = getClientDisplayName(client);
client.displayTotal = +client.paid_to_date + +client.balance;
client.displayBalance = +client.balance;
client.displayPercent = (+client.paid_to_date / (+client.paid_to_date + +client.balance)).toFixed(2);
var oldestInvoice = _.max(client.invoices, function(invoice) { return calculateInvoiceAge(invoice) });
client.displayAge = oldestInvoice ? calculateInvoiceAge(oldestInvoice) : 0;
})
_.each(invoices, function(invoice) {
invoice.displayName = invoice.invoice_number;
invoice.displayTotal = +invoice.amount;
invoice.displayBalance = +invoice.balance;
invoice.displayPercent = parseInt((+invoice.amount - +invoice.balance) / +invoice.amount);
invoice.displayAge = calculateInvoiceAge(invoice);
})
_.each(products, function(product) {
product.displayName = product.key;
product.displayTotal = product.values.amount;
product.displayBalance = product.values.amount - product.values.paid;
product.displayPercent = (product.values.paid / product.values.amount).toFixed(2);
product.displayAge = product.values.age;
})
/*
_.each(clients, function(client) {
_.each(client.invoices, function(invoice) {
_.each(invoice.invoice_items, function(invoice_item) {
delete invoice_item.invoice;
});
});
});
*/
//console.log(JSON.stringify(clients));
//console.log(JSON.stringify(invoices));
//console.log(JSON.stringify(products));
var arc = d3.svg.arc()
.innerRadius(function(d) { return d.r - 2 })
.outerRadius(function(d) { return d.r - 8 })
.startAngle(0);
var fullArc = d3.svg.arc()
.innerRadius(function(d) { return d.r - 3 })
.outerRadius(function(d) { return d.r - 7 })
.startAngle(0)
.endAngle(2 * Math.PI);
var diameter = 1050,
format = d3.format(",d");
//color = d3.scale.category10();
var color = d3.scale.linear()
.domain([0, 100])
.range(["yellow", "red"]);
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.value(function(d) { return Math.max(30, d.displayTotal) })
.padding(12);
var svg = d3.select(".svg-div").append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("class", "bubble");
svg.append("rect")
.attr("stroke-width", "1")
.attr("stroke", "rgb(150,150,150)")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "white");
function update() {
var data = {};
var groupBy = $('#groupBySelect').val().toLowerCase();
data.children = window[groupBy];
data = bubble.nodes(data).filter(function(d) {
return !d.children && d.displayTotal && d.displayName;
});
var selection = svg.selectAll(".node")
.data(data, function(d) { return d.displayName; });
//.data(data);
var node = selection.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + (d.x+20) + "," + (d.y+20) + ")"; });
var visibleTooltip = false;
node.on("mousemove", function(d) {
if (!visibleTooltip || visibleTooltip != d.displayName) {
d3.select("#tooltip")
.classed("hidden", false)
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY + "px");
visibleTooltip = d.displayName;
}
d3.select("#tooltipTitle").text(d.displayName);
d3.select("#tooltipTotal").text(formatMoney(d.displayTotal));
d3.select("#tooltipBalance").text(formatMoney(d.displayBalance));
d3.select("#tooltipAge").text(pluralize('? day', parseInt(d.displayAge)));
if (groupBy == "products") {
d3.select("#tooltip a").classed("hidden", true);
} else {
d3.select("#tooltip a").classed("hidden", false);
d3.select("#tooltip a").attr("href", "/" + groupBy + "/" + d.public_id);
}
});
svg.on("click", function() {
visibleTooltip = false;
d3.select("#tooltip")
.classed("hidden", true);
});
node.append("circle")
.attr("fill", "#ffffff")
.attr("r", function(d) { return d.r });
node.append("path")
.each(function(d) { d.endAngle = 0; })
.attr("class", "no-pointer-events")
.attr("class", "animate-fade")
.attr("d", fullArc)
.style("fill", function(d, i) { return 'white'; });
node.append("text")
.attr("dy", ".3em")
.attr("class", "no-pointer-events")
.style("text-anchor", "middle")
.text(function(d) { return d.displayName; });
node.append("path")
.each(function(d) { d.endAngle = 0; })
.attr("class", "no-pointer-events")
.attr("class", "animate-grow")
.attr("d", arc)
.style("fill", function(d, i) { return 'grey'; });
d3.selectAll("path.animate-grow")
.transition()
.delay(function(d, i) { return (Math.random() * 500) })
.duration(1000)
.call(arcTween, 5);
d3.selectAll("path.animate-fade")
.transition()
.duration(1000)
.style("fill", function(d, i) {
return d.displayAge ? color(d.displayAge) : 'grey';
//return 'red';
});
selection.exit().remove();
}
update();
// http://bl.ocks.org/mbostock/5100636
function arcTween(transition, newAngle) {
transition.attrTween("d", function(d) {
var interpolate = d3.interpolate( 0, 360 * d.displayPercent * Math.PI/180 );
return function(t) {
d.endAngle = interpolate(t);
return arc(d);
};
});
}
function calculateInvoiceAge(invoice) {
if (!invoice || invoice.invoice_status_id == 5) {
return 0;
}
return parseInt((new Date().getTime() - Date.parse(invoice.created_at)) / (1000*60*60*24));
}
function pluralize(string, count) {
string = string.replace('?', count);
if (count !== 1) {
string += 's';
}
return string;
};
</script>
@stop

View File

@ -17,10 +17,10 @@
"typeahead.js": "~0.9.3",
"accounting": "~0.*",
"pdfjs": "*",
"spectrum": "~1.3.4"
"spectrum": "~1.3.4",
"d3": "~3.4.11"
},
"resolutions": {
"datatables": "~1.*",
"jquery": "~1.11"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff