mirror of https://github.com/abpframework/abp.git
committed by
GitHub
269 changed files with 397836 additions and 113 deletions
@ -1,21 +1,11 @@ |
|||||
using Microsoft.AspNetCore.Hosting; |
using AbpPerfTest.WithAbp; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
using Microsoft.Extensions.Hosting; |
using Microsoft.Extensions.Hosting; |
||||
|
|
||||
namespace AbpPerfTest.WithAbp |
var builder = WebApplication.CreateBuilder(args); |
||||
{ |
builder.Host.UseAutofac(); |
||||
public class Program |
await builder.AddApplicationAsync<AppModule>(); |
||||
{ |
var app = builder.Build(); |
||||
public static void Main(string[] args) |
await app.InitializeApplicationAsync(); |
||||
{ |
await app.RunAsync(); |
||||
CreateHostBuilder(args).Build().Run(); |
|
||||
} |
|
||||
|
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) => |
|
||||
Host.CreateDefaultBuilder(args) |
|
||||
.ConfigureWebHostDefaults(webBuilder => |
|
||||
{ |
|
||||
webBuilder.UseStartup<Startup>(); |
|
||||
}) |
|
||||
.UseAutofac(); |
|
||||
} |
|
||||
} |
|
||||
|
|||||
@ -1,18 +0,0 @@ |
|||||
using Microsoft.AspNetCore.Builder; |
|
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
|
|
||||
namespace AbpPerfTest.WithAbp |
|
||||
{ |
|
||||
public class Startup |
|
||||
{ |
|
||||
public void ConfigureServices(IServiceCollection services) |
|
||||
{ |
|
||||
services.AddApplication<AppModule>(); |
|
||||
} |
|
||||
|
|
||||
public void Configure(IApplicationBuilder app) |
|
||||
{ |
|
||||
app.InitializeApplication(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,17 +1,31 @@ |
|||||
using Microsoft.AspNetCore.Hosting; |
using AbpPerfTest.WithoutAbp.EntityFramework; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
using Microsoft.Extensions.Hosting; |
using Microsoft.Extensions.Hosting; |
||||
|
|
||||
namespace AbpPerfTest.WithoutAbp |
var builder = WebApplication.CreateBuilder(args); |
||||
|
|
||||
|
builder.Services.AddMvc(); |
||||
|
|
||||
|
builder.Services.AddDbContext<BookDbContext>(options => |
||||
|
{ |
||||
|
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")); |
||||
|
}); |
||||
|
|
||||
|
var app = builder.Build(); |
||||
|
|
||||
|
if (builder.Environment.IsDevelopment()) |
||||
{ |
{ |
||||
public class Program |
app.UseDeveloperExceptionPage(); |
||||
{ |
|
||||
public static void Main(string[] args) |
|
||||
{ |
|
||||
CreateHostBuilder(args).Build().Run(); |
|
||||
} |
|
||||
|
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) => |
|
||||
Host.CreateDefaultBuilder(args) |
|
||||
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
|
app.UseRouting(); |
||||
|
|
||||
|
app.UseEndpoints(endpoints => |
||||
|
{ |
||||
|
endpoints.MapControllers(); |
||||
|
}); |
||||
|
|
||||
|
app.Run(); |
||||
|
|||||
@ -1,45 +0,0 @@ |
|||||
using AbpPerfTest.WithoutAbp.EntityFramework; |
|
||||
using Microsoft.AspNetCore.Builder; |
|
||||
using Microsoft.AspNetCore.Hosting; |
|
||||
using Microsoft.EntityFrameworkCore; |
|
||||
using Microsoft.Extensions.Configuration; |
|
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
using Microsoft.Extensions.Hosting; |
|
||||
|
|
||||
namespace AbpPerfTest.WithoutAbp |
|
||||
{ |
|
||||
public class Startup |
|
||||
{ |
|
||||
private readonly IConfiguration _configuration; |
|
||||
|
|
||||
public Startup(IConfiguration configuration) |
|
||||
{ |
|
||||
_configuration = configuration; |
|
||||
} |
|
||||
|
|
||||
public void ConfigureServices(IServiceCollection services) |
|
||||
{ |
|
||||
services.AddMvc(); |
|
||||
|
|
||||
services.AddDbContext<BookDbContext>(options => |
|
||||
{ |
|
||||
options.UseSqlServer(_configuration.GetConnectionString("Default")); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) |
|
||||
{ |
|
||||
if (env.IsDevelopment()) |
|
||||
{ |
|
||||
app.UseDeveloperExceptionPage(); |
|
||||
} |
|
||||
|
|
||||
app.UseRouting(); |
|
||||
|
|
||||
app.UseEndpoints(endpoints => |
|
||||
{ |
|
||||
endpoints.MapControllers(); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
File diff suppressed because it is too large
@ -0,0 +1,48 @@ |
|||||
|
/* |
||||
|
Licensed to the Apache Software Foundation (ASF) under one or more |
||||
|
contributor license agreements. See the NOTICE file distributed with |
||||
|
this work for additional information regarding copyright ownership. |
||||
|
The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
|
(the "License"); you may not use this file except in compliance with |
||||
|
the License. You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
*/ |
||||
|
#stats td, th |
||||
|
{ |
||||
|
|
||||
|
width: 9%;//140px; |
||||
|
height: 30px; |
||||
|
} |
||||
|
#stats td |
||||
|
{ |
||||
|
text-align: center; |
||||
|
} |
||||
|
#stats th |
||||
|
{ |
||||
|
font-weight:bold; |
||||
|
} |
||||
|
.blue |
||||
|
{ |
||||
|
background-color: #dff0d8; |
||||
|
} |
||||
|
#errors td, th |
||||
|
{ |
||||
|
text-align: center; |
||||
|
width: 400px; |
||||
|
height: 30px; |
||||
|
} |
||||
|
#errors td |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
#errors th |
||||
|
{ |
||||
|
font-weight:bold; |
||||
|
} |
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,61 @@ |
|||||
|
/* |
||||
|
Licensed to the Apache Software Foundation (ASF) under one or more |
||||
|
contributor license agreements. See the NOTICE file distributed with |
||||
|
this work for additional information regarding copyright ownership. |
||||
|
The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
|
(the "License"); you may not use this file except in compliance with |
||||
|
the License. You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
*/ |
||||
|
.legend { |
||||
|
margin-left: -40px; |
||||
|
font-size: 12px; |
||||
|
display: inline; |
||||
|
font-family: 'Open Sans'; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.panel-footer |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.subLegend |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.legend li > div { |
||||
|
display: inline-block; |
||||
|
margin-right: 3px; |
||||
|
margin-left: 5px; |
||||
|
} |
||||
|
|
||||
|
.legend li label { |
||||
|
margin-left: 1px; |
||||
|
margin-right: 10px; |
||||
|
color: black; |
||||
|
font-family: Open sans; |
||||
|
/*width: 200px;*/ |
||||
|
} |
||||
|
li |
||||
|
{ |
||||
|
list-style-type:none; |
||||
|
display: inline-block; |
||||
|
} |
||||
|
|
||||
|
.nav-third-level |
||||
|
{ |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
|
||||
|
.legend-disabled { |
||||
|
border-color: #818181 !important; |
||||
|
} |
||||
@ -0,0 +1,233 @@ |
|||||
|
/************* |
||||
|
Blue Theme |
||||
|
*************/ |
||||
|
/* overall */ |
||||
|
.tablesorter-blue { |
||||
|
width: 100%; |
||||
|
background-color: #fff; |
||||
|
margin: 10px 0 15px; |
||||
|
text-align: left; |
||||
|
border-spacing: 0; |
||||
|
border: #cdcdcd 1px solid; |
||||
|
border-width: 1px 0 0 1px; |
||||
|
} |
||||
|
.tablesorter-blue th, |
||||
|
.tablesorter-blue td { |
||||
|
border: #cdcdcd 1px solid; |
||||
|
border-width: 0 1px 1px 0; |
||||
|
} |
||||
|
|
||||
|
/* header */ |
||||
|
.tablesorter-blue th, |
||||
|
.tablesorter-blue thead td { |
||||
|
font: 12px/18px Arial, Sans-serif; |
||||
|
font-weight: bold; |
||||
|
color: #000; |
||||
|
background-color: #99bfe6; |
||||
|
border-collapse: collapse; |
||||
|
padding: 4px; |
||||
|
text-shadow: 0 1px 0 rgba(204, 204, 204, 0.7); |
||||
|
} |
||||
|
.tablesorter-blue tbody td, |
||||
|
.tablesorter-blue tfoot th, |
||||
|
.tablesorter-blue tfoot td { |
||||
|
padding: 4px; |
||||
|
vertical-align: top; |
||||
|
} |
||||
|
.tablesorter-blue .header, |
||||
|
.tablesorter-blue .tablesorter-header { |
||||
|
/* black (unsorted) double arrow */ |
||||
|
background-image: url(data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==); |
||||
|
/* white (unsorted) double arrow */ |
||||
|
/* background-image: url(data:image/gif;base64,R0lGODlhFQAJAIAAAP///////yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==); */ |
||||
|
/* image */ |
||||
|
/* background-image: url(images/black-unsorted.gif); */ |
||||
|
background-repeat: no-repeat; |
||||
|
background-position: center right; |
||||
|
padding: 4px 18px 4px 4px; |
||||
|
white-space: normal; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
.tablesorter-blue .headerSortUp, |
||||
|
.tablesorter-blue .tablesorter-headerSortUp, |
||||
|
.tablesorter-blue .tablesorter-headerAsc { |
||||
|
background-color: #9fbfdf; |
||||
|
/* black asc arrow */ |
||||
|
background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); |
||||
|
/* white asc arrow */ |
||||
|
/* background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); */ |
||||
|
/* image */ |
||||
|
/* background-image: url(images/black-asc.gif); */ |
||||
|
} |
||||
|
.tablesorter-blue .headerSortDown, |
||||
|
.tablesorter-blue .tablesorter-headerSortDown, |
||||
|
.tablesorter-blue .tablesorter-headerDesc { |
||||
|
background-color: #8cb3d9; |
||||
|
/* black desc arrow */ |
||||
|
background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7); |
||||
|
/* white desc arrow */ |
||||
|
/* background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7); */ |
||||
|
/* image */ |
||||
|
/* background-image: url(images/black-desc.gif); */ |
||||
|
} |
||||
|
.tablesorter-blue thead .sorter-false { |
||||
|
background-image: none; |
||||
|
cursor: default; |
||||
|
padding: 4px; |
||||
|
} |
||||
|
|
||||
|
/* tfoot */ |
||||
|
.tablesorter-blue tfoot .tablesorter-headerSortUp, |
||||
|
.tablesorter-blue tfoot .tablesorter-headerSortDown, |
||||
|
.tablesorter-blue tfoot .tablesorter-headerAsc, |
||||
|
.tablesorter-blue tfoot .tablesorter-headerDesc { |
||||
|
/* remove sort arrows from footer */ |
||||
|
background-image: none; |
||||
|
} |
||||
|
|
||||
|
/* tbody */ |
||||
|
.tablesorter-blue td { |
||||
|
color: #3d3d3d; |
||||
|
background-color: #fff; |
||||
|
padding: 4px; |
||||
|
vertical-align: top; |
||||
|
} |
||||
|
|
||||
|
/* hovered row colors |
||||
|
you'll need to add additional lines for |
||||
|
rows with more than 2 child rows |
||||
|
*/ |
||||
|
.tablesorter-blue tbody > tr.hover > td, |
||||
|
.tablesorter-blue tbody > tr:hover > td, |
||||
|
.tablesorter-blue tbody > tr:hover + tr.tablesorter-childRow > td, |
||||
|
.tablesorter-blue tbody > tr:hover + tr.tablesorter-childRow + tr.tablesorter-childRow > td, |
||||
|
.tablesorter-blue tbody > tr.even.hover > td, |
||||
|
.tablesorter-blue tbody > tr.even:hover > td, |
||||
|
.tablesorter-blue tbody > tr.even:hover + tr.tablesorter-childRow > td, |
||||
|
.tablesorter-blue tbody > tr.even:hover + tr.tablesorter-childRow + tr.tablesorter-childRow > td { |
||||
|
background-color: #d9d9d9; |
||||
|
} |
||||
|
.tablesorter-blue tbody > tr.odd.hover > td, |
||||
|
.tablesorter-blue tbody > tr.odd:hover > td, |
||||
|
.tablesorter-blue tbody > tr.odd:hover + tr.tablesorter-childRow > td, |
||||
|
.tablesorter-blue tbody > tr.odd:hover + tr.tablesorter-childRow + tr.tablesorter-childRow > td { |
||||
|
background-color: #bfbfbf; |
||||
|
} |
||||
|
|
||||
|
/* table processing indicator */ |
||||
|
.tablesorter-blue .tablesorter-processing { |
||||
|
background-position: center center !important; |
||||
|
background-repeat: no-repeat !important; |
||||
|
/* background-image: url(images/loading.gif) !important; */ |
||||
|
background-image: url('data:image/gif;base64,R0lGODlhFAAUAKEAAO7u7lpaWgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgACACwAAAAAFAAUAAACQZRvoIDtu1wLQUAlqKTVxqwhXIiBnDg6Y4eyx4lKW5XK7wrLeK3vbq8J2W4T4e1nMhpWrZCTt3xKZ8kgsggdJmUFACH5BAEKAAIALAcAAAALAAcAAAIUVB6ii7jajgCAuUmtovxtXnmdUAAAIfkEAQoAAgAsDQACAAcACwAAAhRUIpmHy/3gUVQAQO9NetuugCFWAAAh+QQBCgACACwNAAcABwALAAACE5QVcZjKbVo6ck2AF95m5/6BSwEAIfkEAQoAAgAsBwANAAsABwAAAhOUH3kr6QaAcSrGWe1VQl+mMUIBACH5BAEKAAIALAIADQALAAcAAAIUlICmh7ncTAgqijkruDiv7n2YUAAAIfkEAQoAAgAsAAAHAAcACwAAAhQUIGmHyedehIoqFXLKfPOAaZdWAAAh+QQFCgACACwAAAIABwALAAACFJQFcJiXb15zLYRl7cla8OtlGGgUADs=') !important; |
||||
|
} |
||||
|
|
||||
|
/* Zebra Widget - row alternating colors */ |
||||
|
.tablesorter-blue tbody tr.odd > td { |
||||
|
background-color: #ebf2fa; |
||||
|
} |
||||
|
.tablesorter-blue tbody tr.even > td { |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
|
||||
|
/* Column Widget - column sort colors */ |
||||
|
.tablesorter-blue td.primary, |
||||
|
.tablesorter-blue tr.odd td.primary { |
||||
|
background-color: #99b3e6; |
||||
|
} |
||||
|
.tablesorter-blue tr.even td.primary { |
||||
|
background-color: #c2d1f0; |
||||
|
} |
||||
|
.tablesorter-blue td.secondary, |
||||
|
.tablesorter-blue tr.odd td.secondary { |
||||
|
background-color: #c2d1f0; |
||||
|
} |
||||
|
.tablesorter-blue tr.even td.secondary { |
||||
|
background-color: #d6e0f5; |
||||
|
} |
||||
|
.tablesorter-blue td.tertiary, |
||||
|
.tablesorter-blue tr.odd td.tertiary { |
||||
|
background-color: #d6e0f5; |
||||
|
} |
||||
|
.tablesorter-blue tr.even td.tertiary { |
||||
|
background-color: #ebf0fa; |
||||
|
} |
||||
|
|
||||
|
/* caption */ |
||||
|
caption { |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
|
||||
|
/* filter widget */ |
||||
|
.tablesorter-blue .tablesorter-filter-row { |
||||
|
background-color: #eee; |
||||
|
} |
||||
|
.tablesorter-blue .tablesorter-filter-row td { |
||||
|
background-color: #eee; |
||||
|
line-height: normal; |
||||
|
text-align: center; /* center the input */ |
||||
|
-webkit-transition: line-height 0.1s ease; |
||||
|
-moz-transition: line-height 0.1s ease; |
||||
|
-o-transition: line-height 0.1s ease; |
||||
|
transition: line-height 0.1s ease; |
||||
|
} |
||||
|
/* optional disabled input styling */ |
||||
|
.tablesorter-blue .tablesorter-filter-row .disabled { |
||||
|
opacity: 0.5; |
||||
|
filter: alpha(opacity=50); |
||||
|
cursor: not-allowed; |
||||
|
} |
||||
|
/* hidden filter row */ |
||||
|
.tablesorter-blue .tablesorter-filter-row.hideme td { |
||||
|
/*** *********************************************** ***/ |
||||
|
/*** change this padding to modify the thickness ***/ |
||||
|
/*** of the closed filter row (height = padding x 2) ***/ |
||||
|
padding: 2px; |
||||
|
/*** *********************************************** ***/ |
||||
|
margin: 0; |
||||
|
line-height: 0; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
.tablesorter-blue .tablesorter-filter-row.hideme * { |
||||
|
height: 1px; |
||||
|
min-height: 0; |
||||
|
border: 0; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
/* don't use visibility: hidden because it disables tabbing */ |
||||
|
opacity: 0; |
||||
|
filter: alpha(opacity=0); |
||||
|
} |
||||
|
/* filters */ |
||||
|
.tablesorter-blue input.tablesorter-filter, |
||||
|
.tablesorter-blue select.tablesorter-filter { |
||||
|
width: 98%; |
||||
|
height: auto; |
||||
|
margin: 0; |
||||
|
padding: 4px; |
||||
|
background-color: #fff; |
||||
|
border: 1px solid #bbb; |
||||
|
color: #333; |
||||
|
-webkit-box-sizing: border-box; |
||||
|
-moz-box-sizing: border-box; |
||||
|
box-sizing: border-box; |
||||
|
-webkit-transition: height 0.1s ease; |
||||
|
-moz-transition: height 0.1s ease; |
||||
|
-o-transition: height 0.1s ease; |
||||
|
transition: height 0.1s ease; |
||||
|
} |
||||
|
/* rows hidden by filtering (needed for child rows) */ |
||||
|
.tablesorter .filtered { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
/* ajax error row */ |
||||
|
.tablesorter .tablesorter-errorRow td { |
||||
|
text-align: center; |
||||
|
cursor: pointer; |
||||
|
background-color: #e6bf99; |
||||
|
} |
||||
|
|
||||
|
.tablesorter .tablesorter-no-sort td { |
||||
|
font-weight: bold; |
||||
|
} |
||||
@ -0,0 +1,483 @@ |
|||||
|
/* The MIT License |
||||
|
|
||||
|
Copyright (c) 2011 by Michael Zinsmaier and nergal.dev |
||||
|
Copyright (c) 2012 by Thomas Ritou |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
of this software and associated documentation files (the "Software"), to deal |
||||
|
in the Software without restriction, including without limitation the rights |
||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
copies of the Software, and to permit persons to whom the Software is |
||||
|
furnished to do so, subject to the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice shall be included in |
||||
|
all copies or substantial portions of the Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
|
THE SOFTWARE. |
||||
|
*/ |
||||
|
|
||||
|
/* |
||||
|
____________________________________________________ |
||||
|
|
||||
|
what it is: |
||||
|
____________________________________________________ |
||||
|
|
||||
|
curvedLines is a plugin for flot, that tries to display lines in a smoother way. |
||||
|
This is achieved through adding of more data points. The plugin is a data processor and can thus be used |
||||
|
in combination with standard line / point rendering options. |
||||
|
|
||||
|
=> 1) with large data sets you may get trouble |
||||
|
=> 2) if you want to display the points too, you have to plot them as 2nd data series over the lines |
||||
|
=> 3) consecutive x data points are not allowed to have the same value |
||||
|
|
||||
|
Feel free to further improve the code |
||||
|
|
||||
|
____________________________________________________ |
||||
|
|
||||
|
how to use it: |
||||
|
____________________________________________________ |
||||
|
|
||||
|
var d1 = [[5,5],[7,3],[9,12]]; |
||||
|
|
||||
|
var options = { series: { curvedLines: { active: true }}}; |
||||
|
|
||||
|
$.plot($("#placeholder"), [{data: d1, lines: { show: true}, curvedLines: {apply: true}}], options); |
||||
|
|
||||
|
_____________________________________________________ |
||||
|
|
||||
|
options: |
||||
|
_____________________________________________________ |
||||
|
|
||||
|
active: bool true => plugin can be used |
||||
|
apply: bool true => series will be drawn as curved line |
||||
|
monotonicFit: bool true => uses monotone cubic interpolation (preserve monotonicity) |
||||
|
tension: int defines the tension parameter of the hermite spline interpolation (no effect if monotonicFit is set) |
||||
|
nrSplinePoints: int defines the number of sample points (of the spline) in between two consecutive points |
||||
|
|
||||
|
deprecated options from flot prior to 1.0.0: |
||||
|
------------------------------------------------ |
||||
|
legacyOverride bool true => use old default |
||||
|
OR |
||||
|
legacyOverride optionArray |
||||
|
{ |
||||
|
fit: bool true => forces the max,mins of the curve to be on the datapoints |
||||
|
curvePointFactor int defines how many "virtual" points are used per "real" data point to |
||||
|
emulate the curvedLines (points total = real points * curvePointFactor) |
||||
|
fitPointDist: int defines the x axis distance of the additional two points that are used |
||||
|
} to enforce the min max condition. |
||||
|
*/ |
||||
|
|
||||
|
/* |
||||
|
* v0.1 initial commit |
||||
|
* v0.15 negative values should work now (outcommented a negative -> 0 hook hope it does no harm) |
||||
|
* v0.2 added fill option (thanks to monemihir) and multi axis support (thanks to soewono effendi) |
||||
|
* v0.3 improved saddle handling and added basic handling of Dates |
||||
|
* v0.4 rewritten fill option (thomas ritou) mostly from original flot code (now fill between points rather than to graph bottom), corrected fill Opacity bug |
||||
|
* v0.5 rewritten instead of implementing a own draw function CurvedLines is now based on the processDatapoints flot hook (credits go to thomas ritou). |
||||
|
* This change breakes existing code however CurvedLines are now just many tiny straight lines to flot and therefore all flot lines options (like gradient fill, |
||||
|
* shadow) are now supported out of the box |
||||
|
* v0.6 flot 0.8 compatibility and some bug fixes |
||||
|
* v0.6.x changed versioning schema |
||||
|
* |
||||
|
* v1.0.0 API Break marked existing implementation/options as deprecated |
||||
|
* v1.1.0 added the new curved line calculations based on hermite splines |
||||
|
* v1.1.1 added a rough parameter check to make sure the new options are used |
||||
|
*/ |
||||
|
|
||||
|
(function($) { |
||||
|
|
||||
|
var options = { |
||||
|
series : { |
||||
|
curvedLines : { |
||||
|
active : false, |
||||
|
apply : false, |
||||
|
monotonicFit : false, |
||||
|
tension : 0.5, |
||||
|
nrSplinePoints : 20, |
||||
|
legacyOverride : undefined |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
function init(plot) { |
||||
|
|
||||
|
plot.hooks.processOptions.push(processOptions); |
||||
|
|
||||
|
//if the plugin is active register processDatapoints method
|
||||
|
function processOptions(plot, options) { |
||||
|
if (options.series.curvedLines.active) { |
||||
|
plot.hooks.processDatapoints.unshift(processDatapoints); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//only if the plugin is active
|
||||
|
function processDatapoints(plot, series, datapoints) { |
||||
|
var nrPoints = datapoints.points.length / datapoints.pointsize; |
||||
|
var EPSILON = 0.005; |
||||
|
|
||||
|
//detects missplaced legacy parameters (prior v1.x.x) in the options object
|
||||
|
//this can happen if somebody upgrades to v1.x.x without adjusting the parameters or uses old examples
|
||||
|
var invalidLegacyOptions = hasInvalidParameters(series.curvedLines); |
||||
|
|
||||
|
if (!invalidLegacyOptions && series.curvedLines.apply == true && series.originSeries === undefined && nrPoints > (1 + EPSILON)) { |
||||
|
if (series.lines.fill) { |
||||
|
|
||||
|
var pointsTop = calculateCurvePoints(datapoints, series.curvedLines, 1); |
||||
|
var pointsBottom = calculateCurvePoints(datapoints, series.curvedLines, 2); |
||||
|
//flot makes sure for us that we've got a second y point if fill is true !
|
||||
|
|
||||
|
//Merge top and bottom curve
|
||||
|
datapoints.pointsize = 3; |
||||
|
datapoints.points = []; |
||||
|
var j = 0; |
||||
|
var k = 0; |
||||
|
var i = 0; |
||||
|
var ps = 2; |
||||
|
while (i < pointsTop.length || j < pointsBottom.length) { |
||||
|
if (pointsTop[i] == pointsBottom[j]) { |
||||
|
datapoints.points[k] = pointsTop[i]; |
||||
|
datapoints.points[k + 1] = pointsTop[i + 1]; |
||||
|
datapoints.points[k + 2] = pointsBottom[j + 1]; |
||||
|
j += ps; |
||||
|
i += ps; |
||||
|
|
||||
|
} else if (pointsTop[i] < pointsBottom[j]) { |
||||
|
datapoints.points[k] = pointsTop[i]; |
||||
|
datapoints.points[k + 1] = pointsTop[i + 1]; |
||||
|
datapoints.points[k + 2] = k > 0 ? datapoints.points[k - 1] : null; |
||||
|
i += ps; |
||||
|
} else { |
||||
|
datapoints.points[k] = pointsBottom[j]; |
||||
|
datapoints.points[k + 1] = k > 1 ? datapoints.points[k - 2] : null; |
||||
|
datapoints.points[k + 2] = pointsBottom[j + 1]; |
||||
|
j += ps; |
||||
|
} |
||||
|
k += 3; |
||||
|
} |
||||
|
} else if (series.lines.lineWidth > 0) { |
||||
|
datapoints.points = calculateCurvePoints(datapoints, series.curvedLines, 1); |
||||
|
datapoints.pointsize = 2; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function calculateCurvePoints(datapoints, curvedLinesOptions, yPos) { |
||||
|
if ( typeof curvedLinesOptions.legacyOverride != 'undefined' && curvedLinesOptions.legacyOverride != false) { |
||||
|
var defaultOptions = { |
||||
|
fit : false, |
||||
|
curvePointFactor : 20, |
||||
|
fitPointDist : undefined |
||||
|
}; |
||||
|
var legacyOptions = jQuery.extend(defaultOptions, curvedLinesOptions.legacyOverride); |
||||
|
return calculateLegacyCurvePoints(datapoints, legacyOptions, yPos); |
||||
|
} |
||||
|
|
||||
|
return calculateSplineCurvePoints(datapoints, curvedLinesOptions, yPos); |
||||
|
} |
||||
|
|
||||
|
function calculateSplineCurvePoints(datapoints, curvedLinesOptions, yPos) { |
||||
|
var points = datapoints.points; |
||||
|
var ps = datapoints.pointsize; |
||||
|
|
||||
|
//create interpolant fuction
|
||||
|
var splines = createHermiteSplines(datapoints, curvedLinesOptions, yPos); |
||||
|
var result = []; |
||||
|
|
||||
|
//sample the function
|
||||
|
// (the result is intependent from the input data =>
|
||||
|
// it is ok to alter the input after this method)
|
||||
|
var j = 0; |
||||
|
for (var i = 0; i < points.length - ps; i += ps) { |
||||
|
var curX = i; |
||||
|
var curY = i + yPos; |
||||
|
|
||||
|
var xStart = points[curX]; |
||||
|
var xEnd = points[curX + ps]; |
||||
|
var xStep = (xEnd - xStart) / Number(curvedLinesOptions.nrSplinePoints); |
||||
|
|
||||
|
//add point
|
||||
|
result.push(points[curX]); |
||||
|
result.push(points[curY]); |
||||
|
|
||||
|
//add curve point
|
||||
|
for (var x = (xStart += xStep); x < xEnd; x += xStep) { |
||||
|
result.push(x); |
||||
|
result.push(splines[j](x)); |
||||
|
} |
||||
|
|
||||
|
j++; |
||||
|
} |
||||
|
|
||||
|
//add last point
|
||||
|
result.push(points[points.length - ps]); |
||||
|
result.push(points[points.length - ps + yPos]); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
// Creates an array of splines, one for each segment of the original curve. Algorithm based on the wikipedia articles:
|
||||
|
//
|
||||
|
// http://de.wikipedia.org/w/index.php?title=Kubisch_Hermitescher_Spline&oldid=130168003 and
|
||||
|
// http://en.wikipedia.org/w/index.php?title=Monotone_cubic_interpolation&oldid=622341725 and the description of Fritsch-Carlson from
|
||||
|
// http://math.stackexchange.com/questions/45218/implementation-of-monotone-cubic-interpolation
|
||||
|
// for a detailed description see https://github.com/MichaelZinsmaier/CurvedLines/docu
|
||||
|
function createHermiteSplines(datapoints, curvedLinesOptions, yPos) { |
||||
|
var points = datapoints.points; |
||||
|
var ps = datapoints.pointsize; |
||||
|
|
||||
|
// preparation get length (x_{k+1} - x_k) and slope s=(p_{k+1} - p_k) / (x_{k+1} - x_k) of the segments
|
||||
|
var segmentLengths = []; |
||||
|
var segmentSlopes = []; |
||||
|
|
||||
|
for (var i = 0; i < points.length - ps; i += ps) { |
||||
|
var curX = i; |
||||
|
var curY = i + yPos; |
||||
|
var dx = points[curX + ps] - points[curX]; |
||||
|
var dy = points[curY + ps] - points[curY]; |
||||
|
|
||||
|
segmentLengths.push(dx); |
||||
|
segmentSlopes.push(dy / dx); |
||||
|
} |
||||
|
|
||||
|
//get the values for the desired gradients m_k for all points k
|
||||
|
//depending on the used method the formula is different
|
||||
|
var gradients = [segmentSlopes[0]]; |
||||
|
if (curvedLinesOptions.monotonicFit) { |
||||
|
// Fritsch Carlson
|
||||
|
for (var i = 1; i < segmentLengths.length; i++) { |
||||
|
var slope = segmentSlopes[i]; |
||||
|
var prev_slope = segmentSlopes[i - 1]; |
||||
|
if (slope * prev_slope <= 0) { // sign(prev_slope) != sign(slpe)
|
||||
|
gradients.push(0); |
||||
|
} else { |
||||
|
var length = segmentLengths[i]; |
||||
|
var prev_length = segmentLengths[i - 1]; |
||||
|
var common = length + prev_length; |
||||
|
//m = 3 (prev_length + length) / ((2 length + prev_length) / prev_slope + (length + 2 prev_length) / slope)
|
||||
|
gradients.push(3 * common / ((common + length) / prev_slope + (common + prev_length) / slope)); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
// Cardinal spline with t € [0,1]
|
||||
|
// Catmull-Rom for t = 0
|
||||
|
for (var i = ps; i < points.length - ps; i += ps) { |
||||
|
var curX = i; |
||||
|
var curY = i + yPos; |
||||
|
gradients.push(Number(curvedLinesOptions.tension) * (points[curY + ps] - points[curY - ps]) / (points[curX + ps] - points[curX - ps])); |
||||
|
} |
||||
|
} |
||||
|
gradients.push(segmentSlopes[segmentSlopes.length - 1]); |
||||
|
|
||||
|
//get the two major coefficients (c'_{oef1} and c'_{oef2}) for each segment spline
|
||||
|
var coefs1 = []; |
||||
|
var coefs2 = []; |
||||
|
for (i = 0; i < segmentLengths.length; i++) { |
||||
|
var m_k = gradients[i]; |
||||
|
var m_k_plus = gradients[i + 1]; |
||||
|
var slope = segmentSlopes[i]; |
||||
|
var invLength = 1 / segmentLengths[i]; |
||||
|
var common = m_k + m_k_plus - slope - slope; |
||||
|
|
||||
|
coefs1.push(common * invLength * invLength); |
||||
|
coefs2.push((slope - common - m_k) * invLength); |
||||
|
} |
||||
|
|
||||
|
//create functions with from the coefficients and capture the parameters
|
||||
|
var ret = []; |
||||
|
for (var i = 0; i < segmentLengths.length; i ++) { |
||||
|
var spline = function (x_k, coef1, coef2, coef3, coef4) { |
||||
|
// spline for a segment
|
||||
|
return function (x) { |
||||
|
var diff = x - x_k; |
||||
|
var diffSq = diff * diff; |
||||
|
return coef1 * diff * diffSq + coef2 * diffSq + coef3 * diff + coef4; |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
ret.push(spline(points[i * ps], coefs1[i], coefs2[i], gradients[i], points[i * ps + yPos])); |
||||
|
} |
||||
|
|
||||
|
return ret; |
||||
|
}; |
||||
|
|
||||
|
//no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226
|
||||
|
//if fit option is selected additional datapoints get inserted before the curve calculations in nergal.dev s code.
|
||||
|
function calculateLegacyCurvePoints(datapoints, curvedLinesOptions, yPos) { |
||||
|
|
||||
|
var points = datapoints.points; |
||||
|
var ps = datapoints.pointsize; |
||||
|
var num = Number(curvedLinesOptions.curvePointFactor) * (points.length / ps); |
||||
|
|
||||
|
var xdata = new Array; |
||||
|
var ydata = new Array; |
||||
|
|
||||
|
var curX = -1; |
||||
|
var curY = -1; |
||||
|
var j = 0; |
||||
|
|
||||
|
if (curvedLinesOptions.fit) { |
||||
|
//insert a point before and after the "real" data point to force the line
|
||||
|
//to have a max,min at the data point.
|
||||
|
|
||||
|
var fpDist; |
||||
|
if ( typeof curvedLinesOptions.fitPointDist == 'undefined') { |
||||
|
//estimate it
|
||||
|
var minX = points[0]; |
||||
|
var maxX = points[points.length - ps]; |
||||
|
fpDist = (maxX - minX) / (500 * 100); |
||||
|
//x range / (estimated pixel length of placeholder * factor)
|
||||
|
} else { |
||||
|
//use user defined value
|
||||
|
fpDist = Number(curvedLinesOptions.fitPointDist); |
||||
|
} |
||||
|
|
||||
|
for (var i = 0; i < points.length; i += ps) { |
||||
|
|
||||
|
var frontX; |
||||
|
var backX; |
||||
|
curX = i; |
||||
|
curY = i + yPos; |
||||
|
|
||||
|
//add point X s
|
||||
|
frontX = points[curX] - fpDist; |
||||
|
backX = points[curX] + fpDist; |
||||
|
|
||||
|
var factor = 2; |
||||
|
while (frontX == points[curX] || backX == points[curX]) { |
||||
|
//inside the ulp
|
||||
|
frontX = points[curX] - (fpDist * factor); |
||||
|
backX = points[curX] + (fpDist * factor); |
||||
|
factor++; |
||||
|
} |
||||
|
|
||||
|
//add curve points
|
||||
|
xdata[j] = frontX; |
||||
|
ydata[j] = points[curY]; |
||||
|
j++; |
||||
|
|
||||
|
xdata[j] = points[curX]; |
||||
|
ydata[j] = points[curY]; |
||||
|
j++; |
||||
|
|
||||
|
xdata[j] = backX; |
||||
|
ydata[j] = points[curY]; |
||||
|
j++; |
||||
|
} |
||||
|
} else { |
||||
|
//just use the datapoints
|
||||
|
for (var i = 0; i < points.length; i += ps) { |
||||
|
curX = i; |
||||
|
curY = i + yPos; |
||||
|
|
||||
|
xdata[j] = points[curX]; |
||||
|
ydata[j] = points[curY]; |
||||
|
j++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var n = xdata.length; |
||||
|
|
||||
|
var y2 = new Array(); |
||||
|
var delta = new Array(); |
||||
|
y2[0] = 0; |
||||
|
y2[n - 1] = 0; |
||||
|
delta[0] = 0; |
||||
|
|
||||
|
for (var i = 1; i < n - 1; ++i) { |
||||
|
var d = (xdata[i + 1] - xdata[i - 1]); |
||||
|
if (d == 0) { |
||||
|
//point before current point and after current point need some space in between
|
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
var s = (xdata[i] - xdata[i - 1]) / d; |
||||
|
var p = s * y2[i - 1] + 2; |
||||
|
y2[i] = (s - 1) / p; |
||||
|
delta[i] = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]) - (ydata[i] - ydata[i - 1]) / (xdata[i] - xdata[i - 1]); |
||||
|
delta[i] = (6 * delta[i] / (xdata[i + 1] - xdata[i - 1]) - s * delta[i - 1]) / p; |
||||
|
} |
||||
|
|
||||
|
for (var j = n - 2; j >= 0; --j) { |
||||
|
y2[j] = y2[j] * y2[j + 1] + delta[j]; |
||||
|
} |
||||
|
|
||||
|
// xmax - xmin / #points
|
||||
|
var step = (xdata[n - 1] - xdata[0]) / (num - 1); |
||||
|
|
||||
|
var xnew = new Array; |
||||
|
var ynew = new Array; |
||||
|
var result = new Array; |
||||
|
|
||||
|
xnew[0] = xdata[0]; |
||||
|
ynew[0] = ydata[0]; |
||||
|
|
||||
|
result.push(xnew[0]); |
||||
|
result.push(ynew[0]); |
||||
|
|
||||
|
for ( j = 1; j < num; ++j) { |
||||
|
//new x point (sampling point for the created curve)
|
||||
|
xnew[j] = xnew[0] + j * step; |
||||
|
|
||||
|
var max = n - 1; |
||||
|
var min = 0; |
||||
|
|
||||
|
while (max - min > 1) { |
||||
|
var k = Math.round((max + min) / 2); |
||||
|
if (xdata[k] > xnew[j]) { |
||||
|
max = k; |
||||
|
} else { |
||||
|
min = k; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//found point one to the left and one to the right of generated new point
|
||||
|
var h = (xdata[max] - xdata[min]); |
||||
|
|
||||
|
if (h == 0) { |
||||
|
//similar to above two points from original x data need some space between them
|
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
var a = (xdata[max] - xnew[j]) / h; |
||||
|
var b = (xnew[j] - xdata[min]) / h; |
||||
|
|
||||
|
ynew[j] = a * ydata[min] + b * ydata[max] + ((a * a * a - a) * y2[min] + (b * b * b - b) * y2[max]) * (h * h) / 6; |
||||
|
|
||||
|
result.push(xnew[j]); |
||||
|
result.push(ynew[j]); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
function hasInvalidParameters(curvedLinesOptions) { |
||||
|
if (typeof curvedLinesOptions.fit != 'undefined' || |
||||
|
typeof curvedLinesOptions.curvePointFactor != 'undefined' || |
||||
|
typeof curvedLinesOptions.fitPointDist != 'undefined') { |
||||
|
throw new Error("CurvedLines detected illegal parameters. The CurvedLines API changed with version 1.0.0 please check the options object."); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
}//end init
|
||||
|
|
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init : init, |
||||
|
options : options, |
||||
|
name : 'curvedLines', |
||||
|
version : '1.1.1' |
||||
|
}); |
||||
|
|
||||
|
})(jQuery); |
||||
|
|
||||
@ -0,0 +1,50 @@ |
|||||
|
/* |
||||
|
Licensed to the Apache Software Foundation (ASF) under one or more |
||||
|
contributor license agreements. See the NOTICE file distributed with |
||||
|
this work for additional information regarding copyright ownership. |
||||
|
The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
|
(the "License"); you may not use this file except in compliance with |
||||
|
the License. You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
|
||||
|
|
||||
|
$(document).ready(function() { |
||||
|
$(".click-title").mouseenter( function( e){ |
||||
|
e.preventDefault(); |
||||
|
this.style.cursor="pointer"; |
||||
|
}); |
||||
|
$(".click-title").mousedown( function(event){ |
||||
|
event.preventDefault(); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
$(".portlet-header").css("cursor", "auto"); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
function collapse(elem, collapsed){ |
||||
|
if(collapsed){ |
||||
|
$(elem).parent().find(".fa-chevron-up").removeClass("fa-chevron-up").addClass("fa-chevron-down"); |
||||
|
}else{ |
||||
|
$(elem).parent().find(".fa-chevron-down").removeClass("fa-chevron-down").addClass("fa-chevron-up"); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function toggleAll(id, checked){ |
||||
|
var placeholder = document.getElementById(id); |
||||
|
var cases = $(placeholder).find(':checkbox'); |
||||
|
cases.prop('checked', checked); |
||||
|
$(cases).parent().children().children().toggleClass("legend-disabled", !checked); |
||||
|
var choiceContainer; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,462 @@ |
|||||
|
/* |
||||
|
Licensed to the Apache Software Foundation (ASF) under one or more |
||||
|
contributor license agreements. See the NOTICE file distributed with |
||||
|
this work for additional information regarding copyright ownership. |
||||
|
The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
|
(the "License"); you may not use this file except in compliance with |
||||
|
the License. You may obtain a copy of the License at |
||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
*/ |
||||
|
var DAY_MS = 86400000; |
||||
|
var HOUR_MS = 3600000; |
||||
|
var MINUTE_MS = 60000; |
||||
|
|
||||
|
/** |
||||
|
* From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math/round
|
||||
|
* Licensed under https://creativecommons.org/licenses/by-sa/2.5/
|
||||
|
*/ |
||||
|
// Closure
|
||||
|
(function() { |
||||
|
/** |
||||
|
* Decimal adjustment of a number. |
||||
|
* |
||||
|
* @param {String} type The type of adjustment. |
||||
|
* @param {Number} value The number. |
||||
|
* @param {Integer} exp The exponent (the 10 logarithm of the adjustment base). |
||||
|
* @returns {Number} The adjusted value. |
||||
|
*/ |
||||
|
function decimalAdjust(type, value, exp) { |
||||
|
// If the exp is undefined or zero...
|
||||
|
if (typeof exp === 'undefined' || +exp === 0) { |
||||
|
return Math[type](value); |
||||
|
} |
||||
|
value = +value; |
||||
|
exp = +exp; |
||||
|
// If the value is not a number or the exp is not an integer...
|
||||
|
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) { |
||||
|
return NaN; |
||||
|
} |
||||
|
// Shift
|
||||
|
value = value.toString().split('e'); |
||||
|
value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp))); |
||||
|
// Shift back
|
||||
|
value = value.toString().split('e'); |
||||
|
return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); |
||||
|
} |
||||
|
|
||||
|
// Decimal round
|
||||
|
if (!Math.round10) { |
||||
|
Math.round10 = function(value, exp) { |
||||
|
return decimalAdjust('round', value, exp); |
||||
|
}; |
||||
|
} |
||||
|
// Decimal floor
|
||||
|
if (!Math.floor10) { |
||||
|
Math.floor10 = function(value, exp) { |
||||
|
return decimalAdjust('floor', value, exp); |
||||
|
}; |
||||
|
} |
||||
|
// Decimal ceil
|
||||
|
if (!Math.ceil10) { |
||||
|
Math.ceil10 = function(value, exp) { |
||||
|
return decimalAdjust('ceil', value, exp); |
||||
|
}; |
||||
|
} |
||||
|
})(); |
||||
|
|
||||
|
/* |
||||
|
* Suffixes the specified value with a unit |
||||
|
* The spaced argument defines whether a space character is introduced. |
||||
|
*/ |
||||
|
function formatUnit(value, unit, spaced){ |
||||
|
return spaced ? value + " " + unit : value + unit; |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
* Gets a string representing the specified duration in milliseconds. |
||||
|
* |
||||
|
* E.g : duration = 20000100, returns "45 min 20 sec 100 ms" |
||||
|
*/ |
||||
|
function formatDuration(duration, spaced) { |
||||
|
var type = $.type(duration); |
||||
|
if (type === "string") |
||||
|
return duration; |
||||
|
|
||||
|
// Calculate each part of the string
|
||||
|
var days = Math.floor(duration / 86400000); // 1000 * 60 * 60 * 24 = 1 day
|
||||
|
duration %= 8640000; |
||||
|
|
||||
|
var hours = Math.floor(duration / 3600000); // 1000 * 60 *60 = 1 hour
|
||||
|
duration %= 3600000; |
||||
|
|
||||
|
var minutes = Math.floor(duration / 60000); // 1000 * 60 = 1 minute
|
||||
|
duration %= 60000; |
||||
|
|
||||
|
var seconds = Math.floor(duration / 1000); // 1 second
|
||||
|
duration %= 1000; |
||||
|
|
||||
|
// Add non zero part.
|
||||
|
var formatArray = []; |
||||
|
if (days > 0) |
||||
|
formatArray.push(formatUnit(days, " day(s)", spaced)); |
||||
|
|
||||
|
if (hours > 0) |
||||
|
formatArray.push(formatUnit(hours, " hour(s)", spaced)); |
||||
|
|
||||
|
if (minutes > 0) |
||||
|
formatArray.push(formatUnit(minutes," min", spaced)); |
||||
|
|
||||
|
if (seconds > 0) |
||||
|
formatArray.push(formatUnit(seconds, " sec", spaced)); |
||||
|
|
||||
|
if (duration > 0) |
||||
|
formatArray.push(formatUnit(duration, " ms", spaced)); |
||||
|
|
||||
|
// Build the string
|
||||
|
return formatArray.join(" "); |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
* Gets axis label for the specified granularity |
||||
|
*/ |
||||
|
function getElapsedTimeLabel(granularity) { |
||||
|
return "Elapsed Time (granularity: " + formatDuration(granularity) + ")"; |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
* Gets time format based on granularity |
||||
|
*/ |
||||
|
function getTimeFormat(granularity) { |
||||
|
if (granularity >= DAY_MS) { |
||||
|
return "%y/%m/%d"; |
||||
|
} else if (granularity >= HOUR_MS) { |
||||
|
return "%m/%d %H"; |
||||
|
} else if (granularity >= MINUTE_MS) { |
||||
|
return "%d %H:%M"; |
||||
|
} else { |
||||
|
return "%H:%M:%S"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
* Gets axis label for the specified granularity |
||||
|
*/ |
||||
|
function getConnectTimeLabel(granularity) { |
||||
|
return "Connect Time (granularity: " + formatDuration(granularity) + ")"; |
||||
|
} |
||||
|
|
||||
|
//Get the property value of an object using the specified key
|
||||
|
//Returns the property value if all properties in the key exist; undefined
|
||||
|
//otherwise.
|
||||
|
function getProperty(key, obj) { |
||||
|
return key.split('.').reduce(function(prop, subprop){ |
||||
|
return prop && prop[subprop]; |
||||
|
}, obj); |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
* Removes quotes from the specified string |
||||
|
*/ |
||||
|
function unquote(str, quoteChar) { |
||||
|
quoteChar = quoteChar || '"'; |
||||
|
if (str.length > 0 && str[0] === quoteChar && str[str.length - 1] === quoteChar) |
||||
|
return str.slice(1, str.length - 1); |
||||
|
else |
||||
|
return str; |
||||
|
}; |
||||
|
|
||||
|
/* |
||||
|
* This comparison function evaluates abscissas to sort array of coordinates. |
||||
|
*/ |
||||
|
function compareByXCoordinate(coord1, coord2) { |
||||
|
return coord2[0] - coord1[0]; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* |
||||
|
* Followings functions and variables are used to generate statics graphs |
||||
|
* AND dynamics graphs (if you have the plugin) |
||||
|
*/ |
||||
|
|
||||
|
var showControllersOnly = false; |
||||
|
var seriesFilter = ""; |
||||
|
var filtersOnlySampleSeries = true; |
||||
|
|
||||
|
// Fixes time stamps
|
||||
|
function fixTimeStamps(series, offset){ |
||||
|
$.each(series, function(index, item) { |
||||
|
$.each(item.data, function(index, coord) { |
||||
|
coord[0] += offset; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Check if the specified jquery object is a graph
|
||||
|
function isGraph(object){ |
||||
|
return object.data('plot') !== undefined; |
||||
|
} |
||||
|
|
||||
|
// Collapse
|
||||
|
$(function() { |
||||
|
$('.collapse').on('shown.bs.collapse', function(){ |
||||
|
collapse(this, false); |
||||
|
}).on('hidden.bs.collapse', function(){ |
||||
|
collapse(this, true); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
$(function() { |
||||
|
$(".glyphicon").mousedown( function(event){ |
||||
|
var tmp = $('.in:not(ul)'); |
||||
|
tmp.parent().parent().parent().find(".fa-chevron-up").removeClass("fa-chevron-down").addClass("fa-chevron-down"); |
||||
|
tmp.removeClass("in"); |
||||
|
tmp.addClass("out"); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* Export graph to a PNG |
||||
|
*/ |
||||
|
function exportToPNG(graphName, target) { |
||||
|
var plot = $("#"+graphName).data('plot'); |
||||
|
var flotCanvas = plot.getCanvas(); |
||||
|
var image = flotCanvas.toDataURL(); |
||||
|
image = image.replace("image/png", "image/octet-stream"); |
||||
|
|
||||
|
var downloadAttrSupported = ("download" in document.createElement("a")); |
||||
|
if(downloadAttrSupported === true) { |
||||
|
target.download = graphName + ".png"; |
||||
|
target.href = image; |
||||
|
} |
||||
|
else { |
||||
|
document.location.href = image; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Override the specified graph options to fit the requirements of an overview
|
||||
|
function prepareOverviewOptions(graphOptions){ |
||||
|
var overviewOptions = { |
||||
|
series: { |
||||
|
shadowSize: 0, |
||||
|
lines: { |
||||
|
lineWidth: 1 |
||||
|
}, |
||||
|
points: { |
||||
|
// Show points on overview only when linked graph does not show
|
||||
|
// lines
|
||||
|
show: getProperty('series.lines.show', graphOptions) == false, |
||||
|
radius : 1 |
||||
|
} |
||||
|
}, |
||||
|
xaxis: { |
||||
|
ticks: 2, |
||||
|
axisLabel: null |
||||
|
}, |
||||
|
yaxis: { |
||||
|
ticks: 2, |
||||
|
axisLabel: null |
||||
|
}, |
||||
|
legend: { |
||||
|
show: false, |
||||
|
container: null |
||||
|
}, |
||||
|
grid: { |
||||
|
hoverable: false |
||||
|
}, |
||||
|
tooltip: false |
||||
|
}; |
||||
|
return $.extend(true, {}, graphOptions, overviewOptions); |
||||
|
} |
||||
|
|
||||
|
function prepareOptions(options, data) { |
||||
|
options.canvas = true; |
||||
|
var extraOptions = data.extraOptions; |
||||
|
if(extraOptions !== undefined){ |
||||
|
var xOffset = options.xaxis.mode === "time" ? 28800000 : 0; |
||||
|
var yOffset = options.yaxis.mode === "time" ? 28800000 : 0; |
||||
|
|
||||
|
if(!isNaN(extraOptions.minX)) |
||||
|
options.xaxis.min = parseFloat(extraOptions.minX) + xOffset; |
||||
|
|
||||
|
if(!isNaN(extraOptions.maxX)) |
||||
|
options.xaxis.max = parseFloat(extraOptions.maxX) + xOffset; |
||||
|
|
||||
|
if(!isNaN(extraOptions.minY)) |
||||
|
options.yaxis.min = parseFloat(extraOptions.minY) + yOffset; |
||||
|
|
||||
|
if(!isNaN(extraOptions.maxY)) |
||||
|
options.yaxis.max = parseFloat(extraOptions.maxY) + yOffset; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Filter, mark series and sort data
|
||||
|
/** |
||||
|
* @param data |
||||
|
* @param noMatchColor if defined and true, series.color are not matched with index |
||||
|
* @param ignoreFilterParam If true we don't apply seriesFilter |
||||
|
*/ |
||||
|
function prepareSeries(data, noMatchColor, ignoreFilterParam){ |
||||
|
var result = data.result; |
||||
|
var ignoreFilter = ignoreFilterParam === true; |
||||
|
// Keep only series when needed
|
||||
|
if(!ignoreFilter && seriesFilter && (!filtersOnlySampleSeries || result.supportsControllersDiscrimination)){ |
||||
|
// Insensitive case matching
|
||||
|
var regexp = new RegExp(seriesFilter, 'i'); |
||||
|
result.series = $.grep(result.series, function(series, index){ |
||||
|
return regexp.test(series.label); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Keep only controllers series when supported and needed
|
||||
|
if(result.supportsControllersDiscrimination && showControllersOnly){ |
||||
|
result.series = $.grep(result.series, function(series, index){ |
||||
|
return series.isController; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Sort data and mark series
|
||||
|
$.each(result.series, function(index, series) { |
||||
|
series.data.sort(compareByXCoordinate); |
||||
|
if(!(noMatchColor && noMatchColor===true)) { |
||||
|
series.color = index; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Set the zoom on the specified plot object
|
||||
|
function zoomPlot(plot, xmin, xmax, ymin, ymax){ |
||||
|
var axes = plot.getAxes(); |
||||
|
// Override axes min and max options
|
||||
|
$.extend(true, axes, { |
||||
|
xaxis: { |
||||
|
options : { min: xmin, max: xmax } |
||||
|
}, |
||||
|
yaxis: { |
||||
|
options : { min: ymin, max: ymax } |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Redraw the plot
|
||||
|
plot.setupGrid(); |
||||
|
plot.draw(); |
||||
|
} |
||||
|
|
||||
|
// Prepares DOM items to add zoom function on the specified graph
|
||||
|
function setGraphZoomable(graphSelector, overviewSelector){ |
||||
|
var graph = $(graphSelector); |
||||
|
var overview = $(overviewSelector); |
||||
|
|
||||
|
// Ignore mouse down event
|
||||
|
graph.bind("mousedown", function() { return false; }); |
||||
|
overview.bind("mousedown", function() { return false; }); |
||||
|
|
||||
|
// Zoom on selection
|
||||
|
graph.bind("plotselected", function (event, ranges) { |
||||
|
// clamp the zooming to prevent infinite zoom
|
||||
|
if (ranges.xaxis.to - ranges.xaxis.from < 0.00001) { |
||||
|
ranges.xaxis.to = ranges.xaxis.from + 0.00001; |
||||
|
} |
||||
|
if (ranges.yaxis.to - ranges.yaxis.from < 0.00001) { |
||||
|
ranges.yaxis.to = ranges.yaxis.from + 0.00001; |
||||
|
} |
||||
|
|
||||
|
// Do the zooming
|
||||
|
var plot = graph.data('plot'); |
||||
|
zoomPlot(plot, ranges.xaxis.from, ranges.xaxis.to, ranges.yaxis.from, ranges.yaxis.to); |
||||
|
plot.clearSelection(); |
||||
|
|
||||
|
// Synchronize overview selection
|
||||
|
overview.data('plot').setSelection(ranges, true); |
||||
|
}); |
||||
|
|
||||
|
// Zoom linked graph on overview selection
|
||||
|
overview.bind("plotselected", function (event, ranges) { |
||||
|
graph.data('plot').setSelection(ranges); |
||||
|
}); |
||||
|
|
||||
|
// Reset linked graph zoom when reseting overview selection
|
||||
|
overview.bind("plotunselected", function () { |
||||
|
var overviewAxes = overview.data('plot').getAxes(); |
||||
|
zoomPlot(graph.data('plot'), overviewAxes.xaxis.min, overviewAxes.xaxis.max, overviewAxes.yaxis.min, overviewAxes.yaxis.max); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Prepares data to be consumed by plot plugins
|
||||
|
function prepareData(series, choiceContainer, customizeSeries){ |
||||
|
var datasets = []; |
||||
|
|
||||
|
// Add only selected series to the data set
|
||||
|
choiceContainer.find("input:checked").each(function (index, item) { |
||||
|
var key = $(item).attr("name"); |
||||
|
var i = 0; |
||||
|
var size = series.length; |
||||
|
while(i < size && series[i].label != key) |
||||
|
i++; |
||||
|
if(i < size){ |
||||
|
var currentSeries = series[i]; |
||||
|
datasets.push(currentSeries); |
||||
|
if(customizeSeries) |
||||
|
customizeSeries(currentSeries); |
||||
|
} |
||||
|
}); |
||||
|
return datasets; |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
* Ignore case comparator |
||||
|
*/ |
||||
|
function sortAlphaCaseless(a,b){ |
||||
|
return a.toLowerCase() > b.toLowerCase() ? 1 : -1; |
||||
|
}; |
||||
|
|
||||
|
function createLegend(choiceContainer, infos) { |
||||
|
// Sort series by name
|
||||
|
var keys = []; |
||||
|
$.each(infos.data.result.series, function(index, series){ |
||||
|
keys.push(series.label); |
||||
|
}); |
||||
|
keys.sort(sortAlphaCaseless); |
||||
|
|
||||
|
// Create list of series with support of activation/deactivation
|
||||
|
$.each(keys, function(index, key) { |
||||
|
var id = choiceContainer.attr('id') + index; |
||||
|
$('<li />') |
||||
|
.append($('<input id="' + id + '" name="' + key + '" type="checkbox" checked="checked" hidden />')) |
||||
|
.append($('<label />', { 'text': key , 'for': id })) |
||||
|
.appendTo(choiceContainer); |
||||
|
}); |
||||
|
choiceContainer.find("label").click( function(){ |
||||
|
if (this.style.color !== "rgb(129, 129, 129)" ){ |
||||
|
this.style.color="#818181"; |
||||
|
}else { |
||||
|
this.style.color="black"; |
||||
|
} |
||||
|
$(this).parent().children().children().toggleClass("legend-disabled"); |
||||
|
}); |
||||
|
choiceContainer.find("label").mousedown( function(event){ |
||||
|
event.preventDefault(); |
||||
|
}); |
||||
|
choiceContainer.find("label").mouseenter(function(){ |
||||
|
this.style.cursor="pointer"; |
||||
|
}); |
||||
|
|
||||
|
// Recreate graphe on series activation toggle
|
||||
|
choiceContainer.find("input").click(function(){ |
||||
|
infos.createGraph(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Unchecks all boxes for "Hide all samples" functionality
|
||||
|
function uncheckAll(id){ |
||||
|
toggleAll(id, false); |
||||
|
} |
||||
|
|
||||
|
// Checks all boxes for "Show all samples" functionality
|
||||
|
function checkAll(id){ |
||||
|
toggleAll(id, true); |
||||
|
} |
||||
@ -0,0 +1,235 @@ |
|||||
|
/* |
||||
|
Licensed to the Apache Software Foundation (ASF) under one or more |
||||
|
contributor license agreements. See the NOTICE file distributed with |
||||
|
this work for additional information regarding copyright ownership. |
||||
|
The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
|
(the "License"); you may not use this file except in compliance with |
||||
|
the License. You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
*/ |
||||
|
var showControllersOnly = false; |
||||
|
var seriesFilter = ""; |
||||
|
var filtersOnlySampleSeries = true; |
||||
|
|
||||
|
/* |
||||
|
* Add header in statistics table to group metrics by category |
||||
|
* format |
||||
|
* |
||||
|
*/ |
||||
|
function summaryTableHeader(header) { |
||||
|
var newRow = header.insertRow(-1); |
||||
|
newRow.className = "tablesorter-no-sort"; |
||||
|
var cell = document.createElement('th'); |
||||
|
cell.setAttribute("data-sorter", false); |
||||
|
cell.colSpan = 1; |
||||
|
cell.innerHTML = "Requests"; |
||||
|
newRow.appendChild(cell); |
||||
|
|
||||
|
cell = document.createElement('th'); |
||||
|
cell.setAttribute("data-sorter", false); |
||||
|
cell.colSpan = 3; |
||||
|
cell.innerHTML = "Executions"; |
||||
|
newRow.appendChild(cell); |
||||
|
|
||||
|
cell = document.createElement('th'); |
||||
|
cell.setAttribute("data-sorter", false); |
||||
|
cell.colSpan = 7; |
||||
|
cell.innerHTML = "Response Times (ms)"; |
||||
|
newRow.appendChild(cell); |
||||
|
|
||||
|
cell = document.createElement('th'); |
||||
|
cell.setAttribute("data-sorter", false); |
||||
|
cell.colSpan = 1; |
||||
|
cell.innerHTML = "Throughput"; |
||||
|
newRow.appendChild(cell); |
||||
|
|
||||
|
cell = document.createElement('th'); |
||||
|
cell.setAttribute("data-sorter", false); |
||||
|
cell.colSpan = 2; |
||||
|
cell.innerHTML = "Network (KB/sec)"; |
||||
|
newRow.appendChild(cell); |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
* Populates the table identified by id parameter with the specified data and |
||||
|
* format |
||||
|
* |
||||
|
*/ |
||||
|
function createTable(table, info, formatter, defaultSorts, seriesIndex, headerCreator) { |
||||
|
var tableRef = table[0]; |
||||
|
|
||||
|
// Create header and populate it with data.titles array
|
||||
|
var header = tableRef.createTHead(); |
||||
|
|
||||
|
// Call callback is available
|
||||
|
if(headerCreator) { |
||||
|
headerCreator(header); |
||||
|
} |
||||
|
|
||||
|
var newRow = header.insertRow(-1); |
||||
|
for (var index = 0; index < info.titles.length; index++) { |
||||
|
var cell = document.createElement('th'); |
||||
|
cell.innerHTML = info.titles[index]; |
||||
|
newRow.appendChild(cell); |
||||
|
} |
||||
|
|
||||
|
var tBody; |
||||
|
|
||||
|
// Create overall body if defined
|
||||
|
if(info.overall){ |
||||
|
tBody = document.createElement('tbody'); |
||||
|
tBody.className = "tablesorter-no-sort"; |
||||
|
tableRef.appendChild(tBody); |
||||
|
var newRow = tBody.insertRow(-1); |
||||
|
var data = info.overall.data; |
||||
|
for(var index=0;index < data.length; index++){ |
||||
|
var cell = newRow.insertCell(-1); |
||||
|
cell.innerHTML = formatter ? formatter(index, data[index]): data[index]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Create regular body
|
||||
|
tBody = document.createElement('tbody'); |
||||
|
tableRef.appendChild(tBody); |
||||
|
|
||||
|
var regexp; |
||||
|
if(seriesFilter) { |
||||
|
regexp = new RegExp(seriesFilter, 'i'); |
||||
|
} |
||||
|
// Populate body with data.items array
|
||||
|
for(var index=0; index < info.items.length; index++){ |
||||
|
var item = info.items[index]; |
||||
|
if((!regexp || filtersOnlySampleSeries && !info.supportsControllersDiscrimination || regexp.test(item.data[seriesIndex])) |
||||
|
&& |
||||
|
(!showControllersOnly || !info.supportsControllersDiscrimination || item.isController)){ |
||||
|
if(item.data.length > 0) { |
||||
|
var newRow = tBody.insertRow(-1); |
||||
|
for(var col=0; col < item.data.length; col++){ |
||||
|
var cell = newRow.insertCell(-1); |
||||
|
cell.innerHTML = formatter ? formatter(col, item.data[col]) : item.data[col]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Add support of columns sort
|
||||
|
table.tablesorter({sortList : defaultSorts}); |
||||
|
} |
||||
|
|
||||
|
$(document).ready(function() { |
||||
|
|
||||
|
// Customize table sorter default options
|
||||
|
$.extend( $.tablesorter.defaults, { |
||||
|
theme: 'blue', |
||||
|
cssInfoBlock: "tablesorter-no-sort", |
||||
|
widthFixed: true, |
||||
|
widgets: ['zebra'] |
||||
|
}); |
||||
|
|
||||
|
var data = {"OkPercent": 100.0, "KoPercent": 0.0}; |
||||
|
var dataset = [ |
||||
|
{ |
||||
|
"label" : "FAIL", |
||||
|
"data" : data.KoPercent, |
||||
|
"color" : "#FF6347" |
||||
|
}, |
||||
|
{ |
||||
|
"label" : "PASS", |
||||
|
"data" : data.OkPercent, |
||||
|
"color" : "#9ACD32" |
||||
|
}]; |
||||
|
$.plot($("#flot-requests-summary"), dataset, { |
||||
|
series : { |
||||
|
pie : { |
||||
|
show : true, |
||||
|
radius : 1, |
||||
|
label : { |
||||
|
show : true, |
||||
|
radius : 3 / 4, |
||||
|
formatter : function(label, series) { |
||||
|
return '<div style="font-size:8pt;text-align:center;padding:2px;color:white;">' |
||||
|
+ label |
||||
|
+ '<br/>' |
||||
|
+ Math.round10(series.percent, -2) |
||||
|
+ '%</div>'; |
||||
|
}, |
||||
|
background : { |
||||
|
opacity : 0.5, |
||||
|
color : '#000' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
legend : { |
||||
|
show : true |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Creates APDEX table
|
||||
|
createTable($("#apdexTable"), {"supportsControllersDiscrimination": true, "overall": {"data": [0.999465625, 500, 1500, "Total"], "isController": false}, "titles": ["Apdex", "T (Toleration threshold)", "F (Frustration threshold)", "Label"], "items": [{"data": [0.99893125, 500, 1500, "Create a book"], "isController": false}, {"data": [1.0, 500, 1500, "Get book by id"], "isController": false}]}, function(index, item){ |
||||
|
switch(index){ |
||||
|
case 0: |
||||
|
item = item.toFixed(3); |
||||
|
break; |
||||
|
case 1: |
||||
|
case 2: |
||||
|
item = formatDuration(item); |
||||
|
break; |
||||
|
} |
||||
|
return item; |
||||
|
}, [[0, 0]], 3); |
||||
|
|
||||
|
// Create statistics table
|
||||
|
createTable($("#statisticsTable"), {"supportsControllersDiscrimination": true, "overall": {"data": ["Total", 160000, 0, 0.0, 47.38238750000128, 0, 1628, 1.0, 6.0, 8.0, 10.0, 8074.690890739339, 1761.9741889824627, 1742.2605881749935], "isController": false}, "titles": ["Label", "#Samples", "FAIL", "Error %", "Average", "Min", "Max", "Median", "90th pct", "95th pct", "99th pct", "Transactions/s", "Received", "Sent"], "items": [{"data": ["Create a book", 80000, 0, 0.0, 51.883337500000096, 1, 1628, 8.0, 21.0, 24.0, 28.0, 4037.549207630968, 776.7550721711921, 965.5934467895175], "isController": false}, {"data": ["Get book by id", 80000, 0, 0.0, 42.8814375, 0, 311, 4.0, 15.0, 18.0, 22.0, 4440.74382459062, 1083.7021123889813, 854.3227865667499], "isController": false}]}, function(index, item){ |
||||
|
switch(index){ |
||||
|
// Errors pct
|
||||
|
case 3: |
||||
|
item = item.toFixed(2) + '%'; |
||||
|
break; |
||||
|
// Mean
|
||||
|
case 4: |
||||
|
// Mean
|
||||
|
case 7: |
||||
|
// Median
|
||||
|
case 8: |
||||
|
// Percentile 1
|
||||
|
case 9: |
||||
|
// Percentile 2
|
||||
|
case 10: |
||||
|
// Percentile 3
|
||||
|
case 11: |
||||
|
// Throughput
|
||||
|
case 12: |
||||
|
// Kbytes/s
|
||||
|
case 13: |
||||
|
// Sent Kbytes/s
|
||||
|
item = item.toFixed(2); |
||||
|
break; |
||||
|
} |
||||
|
return item; |
||||
|
}, [[0, 0]], 0, summaryTableHeader); |
||||
|
|
||||
|
// Create error table
|
||||
|
createTable($("#errorsTable"), {"supportsControllersDiscrimination": false, "titles": ["Type of error", "Number of errors", "% in errors", "% in all samples"], "items": []}, function(index, item){ |
||||
|
switch(index){ |
||||
|
case 2: |
||||
|
case 3: |
||||
|
item = item.toFixed(2) + '%'; |
||||
|
break; |
||||
|
} |
||||
|
return item; |
||||
|
}, [[1, 1]]); |
||||
|
|
||||
|
// Create top5 errors by sampler
|
||||
|
createTable($("#top5ErrorsBySamplerTable"), {"supportsControllersDiscrimination": false, "overall": {"data": ["Total", 160000, 0, "", "", "", "", "", "", "", "", "", ""], "isController": false}, "titles": ["Sample", "#Samples", "#Errors", "Error", "#Errors", "Error", "#Errors", "Error", "#Errors", "Error", "#Errors", "Error", "#Errors"], "items": [{"data": [], "isController": false}, {"data": [], "isController": false}]}, function(index, item){ |
||||
|
return item; |
||||
|
}, [[0, 0]], 0); |
||||
|
|
||||
|
}); |
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,402 @@ |
|||||
|
/** |
||||
|
* @license jahashtable, a JavaScript implementation of a hash table. It creates a single constructor function called |
||||
|
* Hashtable in the global scope. |
||||
|
* |
||||
|
* http://www.timdown.co.uk/jshashtable/
|
||||
|
* Copyright %%build:year%% Tim Down. |
||||
|
* Version: %%build:version%% |
||||
|
* Build date: %%build:date%% |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
var Hashtable = (function(UNDEFINED) { |
||||
|
var FUNCTION = "function", STRING = "string", UNDEF = "undefined"; |
||||
|
|
||||
|
// Require Array.prototype.splice, Object.prototype.hasOwnProperty and encodeURIComponent. In environments not
|
||||
|
// having these (e.g. IE <= 5), we bail out now and leave Hashtable null.
|
||||
|
if (typeof encodeURIComponent == UNDEF || |
||||
|
Array.prototype.splice === UNDEFINED || |
||||
|
Object.prototype.hasOwnProperty === UNDEFINED) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
function toStr(obj) { |
||||
|
return (typeof obj == STRING) ? obj : "" + obj; |
||||
|
} |
||||
|
|
||||
|
function hashObject(obj) { |
||||
|
var hashCode; |
||||
|
if (typeof obj == STRING) { |
||||
|
return obj; |
||||
|
} else if (typeof obj.hashCode == FUNCTION) { |
||||
|
// Check the hashCode method really has returned a string
|
||||
|
hashCode = obj.hashCode(); |
||||
|
return (typeof hashCode == STRING) ? hashCode : hashObject(hashCode); |
||||
|
} else { |
||||
|
return toStr(obj); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function merge(o1, o2) { |
||||
|
for (var i in o2) { |
||||
|
if (o2.hasOwnProperty(i)) { |
||||
|
o1[i] = o2[i]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function equals_fixedValueHasEquals(fixedValue, variableValue) { |
||||
|
return fixedValue.equals(variableValue); |
||||
|
} |
||||
|
|
||||
|
function equals_fixedValueNoEquals(fixedValue, variableValue) { |
||||
|
return (typeof variableValue.equals == FUNCTION) ? |
||||
|
variableValue.equals(fixedValue) : (fixedValue === variableValue); |
||||
|
} |
||||
|
|
||||
|
function createKeyValCheck(kvStr) { |
||||
|
return function(kv) { |
||||
|
if (kv === null) { |
||||
|
throw new Error("null is not a valid " + kvStr); |
||||
|
} else if (kv === UNDEFINED) { |
||||
|
throw new Error(kvStr + " must not be undefined"); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value"); |
||||
|
|
||||
|
/*----------------------------------------------------------------------------------------------------------------*/ |
||||
|
|
||||
|
function Bucket(hash, firstKey, firstValue, equalityFunction) { |
||||
|
this[0] = hash; |
||||
|
this.entries = []; |
||||
|
this.addEntry(firstKey, firstValue); |
||||
|
|
||||
|
if (equalityFunction !== null) { |
||||
|
this.getEqualityFunction = function() { |
||||
|
return equalityFunction; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2; |
||||
|
|
||||
|
function createBucketSearcher(mode) { |
||||
|
return function(key) { |
||||
|
var i = this.entries.length, entry, equals = this.getEqualityFunction(key); |
||||
|
while (i--) { |
||||
|
entry = this.entries[i]; |
||||
|
if ( equals(key, entry[0]) ) { |
||||
|
switch (mode) { |
||||
|
case EXISTENCE: |
||||
|
return true; |
||||
|
case ENTRY: |
||||
|
return entry; |
||||
|
case ENTRY_INDEX_AND_VALUE: |
||||
|
return [ i, entry[1] ]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function createBucketLister(entryProperty) { |
||||
|
return function(aggregatedArr) { |
||||
|
var startIndex = aggregatedArr.length; |
||||
|
for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) { |
||||
|
aggregatedArr[startIndex + i] = entries[i][entryProperty]; |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
Bucket.prototype = { |
||||
|
getEqualityFunction: function(searchValue) { |
||||
|
return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals; |
||||
|
}, |
||||
|
|
||||
|
getEntryForKey: createBucketSearcher(ENTRY), |
||||
|
|
||||
|
getEntryAndIndexForKey: createBucketSearcher(ENTRY_INDEX_AND_VALUE), |
||||
|
|
||||
|
removeEntryForKey: function(key) { |
||||
|
var result = this.getEntryAndIndexForKey(key); |
||||
|
if (result) { |
||||
|
this.entries.splice(result[0], 1); |
||||
|
return result[1]; |
||||
|
} |
||||
|
return null; |
||||
|
}, |
||||
|
|
||||
|
addEntry: function(key, value) { |
||||
|
this.entries.push( [key, value] ); |
||||
|
}, |
||||
|
|
||||
|
keys: createBucketLister(0), |
||||
|
|
||||
|
values: createBucketLister(1), |
||||
|
|
||||
|
getEntries: function(destEntries) { |
||||
|
var startIndex = destEntries.length; |
||||
|
for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) { |
||||
|
// Clone the entry stored in the bucket before adding to array
|
||||
|
destEntries[startIndex + i] = entries[i].slice(0); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
containsKey: createBucketSearcher(EXISTENCE), |
||||
|
|
||||
|
containsValue: function(value) { |
||||
|
var entries = this.entries, i = entries.length; |
||||
|
while (i--) { |
||||
|
if ( value === entries[i][1] ) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/*----------------------------------------------------------------------------------------------------------------*/ |
||||
|
|
||||
|
// Supporting functions for searching hashtable buckets
|
||||
|
|
||||
|
function searchBuckets(buckets, hash) { |
||||
|
var i = buckets.length, bucket; |
||||
|
while (i--) { |
||||
|
bucket = buckets[i]; |
||||
|
if (hash === bucket[0]) { |
||||
|
return i; |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
function getBucketForHash(bucketsByHash, hash) { |
||||
|
var bucket = bucketsByHash[hash]; |
||||
|
|
||||
|
// Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype
|
||||
|
return ( bucket && (bucket instanceof Bucket) ) ? bucket : null; |
||||
|
} |
||||
|
|
||||
|
/*----------------------------------------------------------------------------------------------------------------*/ |
||||
|
|
||||
|
function Hashtable() { |
||||
|
var buckets = []; |
||||
|
var bucketsByHash = {}; |
||||
|
var properties = { |
||||
|
replaceDuplicateKey: true, |
||||
|
hashCode: hashObject, |
||||
|
equals: null |
||||
|
}; |
||||
|
|
||||
|
var arg0 = arguments[0], arg1 = arguments[1]; |
||||
|
if (arg1 !== UNDEFINED) { |
||||
|
properties.hashCode = arg0; |
||||
|
properties.equals = arg1; |
||||
|
} else if (arg0 !== UNDEFINED) { |
||||
|
merge(properties, arg0); |
||||
|
} |
||||
|
|
||||
|
var hashCode = properties.hashCode, equals = properties.equals; |
||||
|
|
||||
|
this.properties = properties; |
||||
|
|
||||
|
this.put = function(key, value) { |
||||
|
checkKey(key); |
||||
|
checkValue(value); |
||||
|
var hash = hashCode(key), bucket, bucketEntry, oldValue = null; |
||||
|
|
||||
|
// Check if a bucket exists for the bucket key
|
||||
|
bucket = getBucketForHash(bucketsByHash, hash); |
||||
|
if (bucket) { |
||||
|
// Check this bucket to see if it already contains this key
|
||||
|
bucketEntry = bucket.getEntryForKey(key); |
||||
|
if (bucketEntry) { |
||||
|
// This bucket entry is the current mapping of key to value, so replace the old value.
|
||||
|
// Also, we optionally replace the key so that the latest key is stored.
|
||||
|
if (properties.replaceDuplicateKey) { |
||||
|
bucketEntry[0] = key; |
||||
|
} |
||||
|
oldValue = bucketEntry[1]; |
||||
|
bucketEntry[1] = value; |
||||
|
} else { |
||||
|
// The bucket does not contain an entry for this key, so add one
|
||||
|
bucket.addEntry(key, value); |
||||
|
} |
||||
|
} else { |
||||
|
// No bucket exists for the key, so create one and put our key/value mapping in
|
||||
|
bucket = new Bucket(hash, key, value, equals); |
||||
|
buckets.push(bucket); |
||||
|
bucketsByHash[hash] = bucket; |
||||
|
} |
||||
|
return oldValue; |
||||
|
}; |
||||
|
|
||||
|
this.get = function(key) { |
||||
|
checkKey(key); |
||||
|
|
||||
|
var hash = hashCode(key); |
||||
|
|
||||
|
// Check if a bucket exists for the bucket key
|
||||
|
var bucket = getBucketForHash(bucketsByHash, hash); |
||||
|
if (bucket) { |
||||
|
// Check this bucket to see if it contains this key
|
||||
|
var bucketEntry = bucket.getEntryForKey(key); |
||||
|
if (bucketEntry) { |
||||
|
// This bucket entry is the current mapping of key to value, so return the value.
|
||||
|
return bucketEntry[1]; |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
}; |
||||
|
|
||||
|
this.containsKey = function(key) { |
||||
|
checkKey(key); |
||||
|
var bucketKey = hashCode(key); |
||||
|
|
||||
|
// Check if a bucket exists for the bucket key
|
||||
|
var bucket = getBucketForHash(bucketsByHash, bucketKey); |
||||
|
|
||||
|
return bucket ? bucket.containsKey(key) : false; |
||||
|
}; |
||||
|
|
||||
|
this.containsValue = function(value) { |
||||
|
checkValue(value); |
||||
|
var i = buckets.length; |
||||
|
while (i--) { |
||||
|
if (buckets[i].containsValue(value)) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
}; |
||||
|
|
||||
|
this.clear = function() { |
||||
|
buckets.length = 0; |
||||
|
bucketsByHash = {}; |
||||
|
}; |
||||
|
|
||||
|
this.isEmpty = function() { |
||||
|
return !buckets.length; |
||||
|
}; |
||||
|
|
||||
|
var createBucketAggregator = function(bucketFuncName) { |
||||
|
return function() { |
||||
|
var aggregated = [], i = buckets.length; |
||||
|
while (i--) { |
||||
|
buckets[i][bucketFuncName](aggregated); |
||||
|
} |
||||
|
return aggregated; |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
this.keys = createBucketAggregator("keys"); |
||||
|
this.values = createBucketAggregator("values"); |
||||
|
this.entries = createBucketAggregator("getEntries"); |
||||
|
|
||||
|
this.remove = function(key) { |
||||
|
checkKey(key); |
||||
|
|
||||
|
var hash = hashCode(key), bucketIndex, oldValue = null; |
||||
|
|
||||
|
// Check if a bucket exists for the bucket key
|
||||
|
var bucket = getBucketForHash(bucketsByHash, hash); |
||||
|
|
||||
|
if (bucket) { |
||||
|
// Remove entry from this bucket for this key
|
||||
|
oldValue = bucket.removeEntryForKey(key); |
||||
|
if (oldValue !== null) { |
||||
|
// Entry was removed, so check if bucket is empty
|
||||
|
if (bucket.entries.length == 0) { |
||||
|
// Bucket is empty, so remove it from the bucket collections
|
||||
|
bucketIndex = searchBuckets(buckets, hash); |
||||
|
buckets.splice(bucketIndex, 1); |
||||
|
delete bucketsByHash[hash]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return oldValue; |
||||
|
}; |
||||
|
|
||||
|
this.size = function() { |
||||
|
var total = 0, i = buckets.length; |
||||
|
while (i--) { |
||||
|
total += buckets[i].entries.length; |
||||
|
} |
||||
|
return total; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
Hashtable.prototype = { |
||||
|
each: function(callback) { |
||||
|
var entries = this.entries(), i = entries.length, entry; |
||||
|
while (i--) { |
||||
|
entry = entries[i]; |
||||
|
callback(entry[0], entry[1]); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
equals: function(hashtable) { |
||||
|
var keys, key, val, count = this.size(); |
||||
|
if (count == hashtable.size()) { |
||||
|
keys = this.keys(); |
||||
|
while (count--) { |
||||
|
key = keys[count]; |
||||
|
val = hashtable.get(key); |
||||
|
if (val === null || val !== this.get(key)) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
}, |
||||
|
|
||||
|
putAll: function(hashtable, conflictCallback) { |
||||
|
var entries = hashtable.entries(); |
||||
|
var entry, key, value, thisValue, i = entries.length; |
||||
|
var hasConflictCallback = (typeof conflictCallback == FUNCTION); |
||||
|
while (i--) { |
||||
|
entry = entries[i]; |
||||
|
key = entry[0]; |
||||
|
value = entry[1]; |
||||
|
|
||||
|
// Check for a conflict. The default behaviour is to overwrite the value for an existing key
|
||||
|
if ( hasConflictCallback && (thisValue = this.get(key)) ) { |
||||
|
value = conflictCallback(key, thisValue, value); |
||||
|
} |
||||
|
this.put(key, value); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
clone: function() { |
||||
|
var clone = new Hashtable(this.properties); |
||||
|
clone.putAll(this); |
||||
|
return clone; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
Hashtable.prototype.toQueryString = function() { |
||||
|
var entries = this.entries(), i = entries.length, entry; |
||||
|
var parts = []; |
||||
|
while (i--) { |
||||
|
entry = entries[i]; |
||||
|
parts[i] = encodeURIComponent( toStr(entry[0]) ) + "=" + encodeURIComponent( toStr(entry[1]) ); |
||||
|
} |
||||
|
return parts.join("&"); |
||||
|
}; |
||||
|
|
||||
|
return Hashtable; |
||||
|
})(); |
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,117 @@ |
|||||
|
/*! |
||||
|
* jQuery Cookie Plugin v1.4.1 |
||||
|
* https://github.com/carhartl/jquery-cookie
|
||||
|
* |
||||
|
* Copyright 2013 Klaus Hartl |
||||
|
* Released under the MIT license |
||||
|
*/ |
||||
|
(function (factory) { |
||||
|
if (typeof define === 'function' && define.amd) { |
||||
|
// AMD
|
||||
|
define(['jquery'], factory); |
||||
|
} else if (typeof exports === 'object') { |
||||
|
// CommonJS
|
||||
|
factory(require('jquery')); |
||||
|
} else { |
||||
|
// Browser globals
|
||||
|
factory(jQuery); |
||||
|
} |
||||
|
}(function ($) { |
||||
|
|
||||
|
var pluses = /\+/g; |
||||
|
|
||||
|
function encode(s) { |
||||
|
return config.raw ? s : encodeURIComponent(s); |
||||
|
} |
||||
|
|
||||
|
function decode(s) { |
||||
|
return config.raw ? s : decodeURIComponent(s); |
||||
|
} |
||||
|
|
||||
|
function stringifyCookieValue(value) { |
||||
|
return encode(config.json ? JSON.stringify(value) : String(value)); |
||||
|
} |
||||
|
|
||||
|
function parseCookieValue(s) { |
||||
|
if (s.indexOf('"') === 0) { |
||||
|
// This is a quoted cookie as according to RFC2068, unescape...
|
||||
|
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// Replace server-side written pluses with spaces.
|
||||
|
// If we can't decode the cookie, ignore it, it's unusable.
|
||||
|
// If we can't parse the cookie, ignore it, it's unusable.
|
||||
|
s = decodeURIComponent(s.replace(pluses, ' ')); |
||||
|
return config.json ? JSON.parse(s) : s; |
||||
|
} catch(e) {} |
||||
|
} |
||||
|
|
||||
|
function read(s, converter) { |
||||
|
var value = config.raw ? s : parseCookieValue(s); |
||||
|
return $.isFunction(converter) ? converter(value) : value; |
||||
|
} |
||||
|
|
||||
|
var config = $.cookie = function (key, value, options) { |
||||
|
|
||||
|
// Write
|
||||
|
|
||||
|
if (value !== undefined && !$.isFunction(value)) { |
||||
|
options = $.extend({}, config.defaults, options); |
||||
|
|
||||
|
if (typeof options.expires === 'number') { |
||||
|
var days = options.expires, t = options.expires = new Date(); |
||||
|
t.setTime(+t + days * 864e+5); |
||||
|
} |
||||
|
|
||||
|
return (document.cookie = [ |
||||
|
encode(key), '=', stringifyCookieValue(value), |
||||
|
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
|
||||
|
options.path ? '; path=' + options.path : '', |
||||
|
options.domain ? '; domain=' + options.domain : '', |
||||
|
options.secure ? '; secure' : '' |
||||
|
].join('')); |
||||
|
} |
||||
|
|
||||
|
// Read
|
||||
|
|
||||
|
var result = key ? undefined : {}; |
||||
|
|
||||
|
// To prevent the for loop in the first place assign an empty array
|
||||
|
// in case there are no cookies at all. Also prevents odd result when
|
||||
|
// calling $.cookie().
|
||||
|
var cookies = document.cookie ? document.cookie.split('; ') : []; |
||||
|
|
||||
|
for (var i = 0, l = cookies.length; i < l; i++) { |
||||
|
var parts = cookies[i].split('='); |
||||
|
var name = decode(parts.shift()); |
||||
|
var cookie = parts.join('='); |
||||
|
|
||||
|
if (key && key === name) { |
||||
|
// If second argument (value) is a function it's a converter...
|
||||
|
result = read(cookie, value); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
// Prevent storing a cookie that we couldn't decode.
|
||||
|
if (!key && (cookie = read(cookie)) !== undefined) { |
||||
|
result[name] = cookie; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
}; |
||||
|
|
||||
|
config.defaults = {}; |
||||
|
|
||||
|
$.removeCookie = function (key, options) { |
||||
|
if ($.cookie(key) === undefined) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// Must not alter options, thus extending a fresh object...
|
||||
|
$.cookie(key, '', $.extend({}, options, { expires: -1 })); |
||||
|
return !$.cookie(key); |
||||
|
}; |
||||
|
|
||||
|
})); |
||||
@ -0,0 +1,83 @@ |
|||||
|
/* Flot plugin for stacking data sets rather than overlyaing them. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
The plugin assumes the data is sorted on x (or y if stacking horizontally). |
||||
|
For line charts, it is assumed that if a line has an undefined gap (from a |
||||
|
null point), then the line above it should have the same gap - insert zeros |
||||
|
instead of "null" if you want another behaviour. This also holds for the start |
||||
|
and end of the chart. Note that stacking a mix of positive and negative values |
||||
|
in most instances doesn't make sense (so it looks weird). |
||||
|
|
||||
|
Two or more series are stacked when their "stack" attribute is set to the same |
||||
|
key (which can be any number or string or just "true"). To specify the default |
||||
|
stack, you can set the stack option like this: |
||||
|
|
||||
|
series: { |
||||
|
stack: null/false, true, or a key (number/string) |
||||
|
} |
||||
|
|
||||
|
You can also specify it for a single series, like this: |
||||
|
|
||||
|
$.plot( $("#placeholder"), [{ |
||||
|
data: [ ... ], |
||||
|
stack: true |
||||
|
}]) |
||||
|
|
||||
|
The stacking order is determined by the order of the data series in the array |
||||
|
(later series end up on top of the previous). |
||||
|
|
||||
|
Internally, the plugin modifies the datapoints in each series, adding an |
||||
|
offset to the y value. For line series, extra data points are inserted through |
||||
|
interpolation. If there's a second y value, it's also adjusted (e.g for bar |
||||
|
charts or filled areas). |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
/** |
||||
|
* Patched version as per https://github.com/flot/flot/issues/326
|
||||
|
*/ |
||||
|
(function ($) { |
||||
|
var options = { |
||||
|
series: { stack: null } // or number/string
|
||||
|
}; |
||||
|
|
||||
|
function init(plot) { |
||||
|
|
||||
|
// will be built up dynamically as a hash from x-value to
|
||||
|
var stackBases = {}; |
||||
|
|
||||
|
function stackData(plot, s, datapoints) { |
||||
|
if (s.stack == null || s.stack === false) |
||||
|
return; |
||||
|
|
||||
|
var newPoints = []; |
||||
|
|
||||
|
for (var i=0; i < datapoints.points.length; i += 3) { |
||||
|
|
||||
|
if (!stackBases[datapoints.points[i]]) { |
||||
|
stackBases[datapoints.points[i]] = 0; |
||||
|
} |
||||
|
|
||||
|
// note that the values need to be turned into absolute y-values.
|
||||
|
// in other words, if you were to stack (x, y1), (x, y2), and (x, y3), (each from different series, which is where stackBases comes in),
|
||||
|
// you'd want the new points to be (x, y1, 0), (x, y1+y2, y1), (x,y1+y2+y3, y1+y2)
|
||||
|
// generally, (x, thisValue + (base up to this point), + (base up tothis point))
|
||||
|
newPoints[i] = datapoints.points[i]; |
||||
|
newPoints[i+1] = datapoints.points[i+1] + stackBases[datapoints.points[i]]; |
||||
|
newPoints[i+2] = stackBases[datapoints.points[i]]; |
||||
|
stackBases[datapoints.points[i]] += datapoints.points[i+1]; |
||||
|
} |
||||
|
datapoints.points = newPoints; |
||||
|
} |
||||
|
plot.hooks.processDatapoints.push(stackData); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: 'stack', |
||||
|
version: '1.2-patch-326' |
||||
|
}); |
||||
|
})(jQuery); |
||||
@ -0,0 +1 @@ |
|||||
|
(function(k){var a=new Hashtable();var f=["ae","au","ca","cn","eg","gb","hk","il","in","jp","sk","th","tw","us"];var b=["at","br","de","dk","es","gr","it","nl","pt","tr","vn"];var i=["cz","fi","fr","ru","se","pl"];var d=["ch"];var g=[[".",","],[",","."],[","," "],[".","'"]];var c=[f,b,i,d];function j(n,l,m){this.dec=n;this.group=l;this.neg=m}function h(){for(var l=0;l<c.length;l++){localeGroup=c[l];for(var m=0;m<localeGroup.length;m++){a.put(localeGroup[m],l)}}}function e(l,r){if(a.size()==0){h()}var q=".";var o=",";var p="-";if(r==false){if(l.indexOf("_")!=-1){l=l.split("_")[1].toLowerCase()}else{if(l.indexOf("-")!=-1){l=l.split("-")[1].toLowerCase()}}}var n=a.get(l);if(n){var m=g[n];if(m){q=m[0];o=m[1]}}return new j(q,o,p)}k.fn.formatNumber=function(l,m,n){return this.each(function(){if(m==null){m=true}if(n==null){n=true}var p;if(k(this).is(":input")){p=new String(k(this).val())}else{p=new String(k(this).text())}var o=k.formatNumber(p,l);if(m){if(k(this).is(":input")){k(this).val(o)}else{k(this).text(o)}}if(n){return o}})};k.formatNumber=function(q,w){var w=k.extend({},k.fn.formatNumber.defaults,w);var l=e(w.locale.toLowerCase(),w.isFullLocale);var n=l.dec;var u=l.group;var o=l.neg;var m="0#-,.";var t="";var s=false;for(var r=0;r<w.format.length;r++){if(m.indexOf(w.format.charAt(r))==-1){t=t+w.format.charAt(r)}else{if(r==0&&w.format.charAt(r)=="-"){s=true;continue}else{break}}}var v="";for(var r=w.format.length-1;r>=0;r--){if(m.indexOf(w.format.charAt(r))==-1){v=w.format.charAt(r)+v}else{break}}w.format=w.format.substring(t.length);w.format=w.format.substring(0,w.format.length-v.length);var p=new Number(q);return k._formatNumber(p,w,v,t,s)};k._formatNumber=function(m,q,n,I,t){var q=k.extend({},k.fn.formatNumber.defaults,q);var G=e(q.locale.toLowerCase(),q.isFullLocale);var F=G.dec;var w=G.group;var l=G.neg;var z=false;if(isNaN(m)){if(q.nanForceZero==true){m=0;z=true}else{return null}}if(n=="%"){m=m*100}var B="";if(q.format.indexOf(".")>-1){var H=F;var u=q.format.substring(q.format.lastIndexOf(".")+1);if(q.round==true){m=new Number(m.toFixed(u.length))}else{var M=m.toString();M=M.substring(0,M.lastIndexOf(".")+u.length+1);m=new Number(M)}var A=m%1;var C=new String(A.toFixed(u.length));C=C.substring(C.lastIndexOf(".")+1);for(var J=0;J<u.length;J++){if(u.charAt(J)=="#"&&C.charAt(J)!="0"){H+=C.charAt(J);continue}else{if(u.charAt(J)=="#"&&C.charAt(J)=="0"){var r=C.substring(J);if(r.match("[1-9]")){H+=C.charAt(J);continue}else{break}}else{if(u.charAt(J)=="0"){H+=C.charAt(J)}}}}B+=H}else{m=Math.round(m)}var v=Math.floor(m);if(m<0){v=Math.ceil(m)}var E="";if(q.format.indexOf(".")==-1){E=q.format}else{E=q.format.substring(0,q.format.indexOf("."))}var L="";if(!(v==0&&E.substr(E.length-1)=="#")||z){var x=new String(Math.abs(v));var p=9999;if(E.lastIndexOf(",")!=-1){p=E.length-E.lastIndexOf(",")-1}var o=0;for(var J=x.length-1;J>-1;J--){L=x.charAt(J)+L;o++;if(o==p&&J!=0){L=w+L;o=0}}if(E.length>L.length){var K=E.indexOf("0");if(K!=-1){var D=E.length-K;var s=E.length-L.length-1;while(L.length<D){var y=E.charAt(s);if(y==","){y=w}L=y+L;s--}}}}if(!L&&E.indexOf("0",E.length-1)!==-1){L="0"}B=L+B;if(m<0&&t&&I.length>0){I=l+I}else{if(m<0){B=l+B}}if(!q.decimalSeparatorAlwaysShown){if(B.lastIndexOf(F)==B.length-1){B=B.substring(0,B.length-1)}}B=I+B+n;return B};k.fn.parseNumber=function(l,m,o){if(m==null){m=true}if(o==null){o=true}var p;if(k(this).is(":input")){p=new String(k(this).val())}else{p=new String(k(this).text())}var n=k.parseNumber(p,l);if(n){if(m){if(k(this).is(":input")){k(this).val(n.toString())}else{k(this).text(n.toString())}}if(o){return n}}};k.parseNumber=function(s,x){var x=k.extend({},k.fn.parseNumber.defaults,x);var m=e(x.locale.toLowerCase(),x.isFullLocale);var p=m.dec;var v=m.group;var q=m.neg;var l="1234567890.-";while(s.indexOf(v)>-1){s=s.replace(v,"")}s=s.replace(p,".").replace(q,"-");var w="";var o=false;if(s.charAt(s.length-1)=="%"||x.isPercentage==true){o=true}for(var t=0;t<s.length;t++){if(l.indexOf(s.charAt(t))>-1){w=w+s.charAt(t)}}var r=new Number(w);if(o){r=r/100;var u=w.indexOf(".");if(u!=-1){var n=w.length-u-1;r=r.toFixed(n+2)}else{r=r.toFixed(w.length-1)}}return r};k.fn.parseNumber.defaults={locale:"us",decimalSeparatorAlwaysShown:false,isPercentage:false,isFullLocale:false};k.fn.formatNumber.defaults={format:"#,###.00",locale:"us",decimalSeparatorAlwaysShown:false,nanForceZero:true,round:true,isFullLocale:false};Number.prototype.toFixed=function(l){return k._roundNumber(this,l)};k._roundNumber=function(n,m){var l=Math.pow(10,m||0);var o=String(Math.round(n*l)/l);if(m>0){var p=o.indexOf(".");if(p==-1){o+=".";p=0}else{p=o.length-(p+1)}while(p<m){o+="0";p++}}return o}})(jQuery); |
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,176 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
|
||||
|
<head> |
||||
|
|
||||
|
<meta charset="utf-8"> |
||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
<meta name="description" content=""> |
||||
|
<meta name="author" content=""> |
||||
|
|
||||
|
<title>Apache JMeter Dashboard</title> |
||||
|
|
||||
|
<!-- Bootstrap Core CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- icone onglet --> |
||||
|
<link rel="icon" type="image/png" href="icon-apache.png" /> |
||||
|
|
||||
|
<!-- MetisMenu CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Legends CSS --> |
||||
|
<link href="../css/legends.css" rel="stylesheet"> |
||||
|
|
||||
|
|
||||
|
<!-- Custom CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/dist/css/sb-admin-2.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Custom Fonts --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"> |
||||
|
|
||||
|
<!-- JQuery UI style --> |
||||
|
<link href="../css/jquery-ui.min.css" rel="stylesheet"> |
||||
|
<link href="../css/jquery-ui.structure.min.css" rel="stylesheet"> |
||||
|
<link href="../css/jquery-ui.theme.min.css" rel="stylesheet"> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
|
||||
|
<div id="wrapper"> |
||||
|
|
||||
|
<!-- Navigation --> |
||||
|
<nav class="navbar navbar-default navbar-static-top" role="navigation" style="margin-bottom: 0"> |
||||
|
<div class="navbar-header"> |
||||
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> |
||||
|
<span class="sr-only">Toggle navigation</span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
</button> |
||||
|
<a class="navbar-brand" href="../../index.html">Apache JMeter Dashboard</a> |
||||
|
</div> |
||||
|
<!-- /.navbar-header --> |
||||
|
|
||||
|
|
||||
|
<div class="navbar-default sidebar" role="navigation"> |
||||
|
<div class="sidebar-nav navbar-collapse"> |
||||
|
<ul class="nav" id="side-menu"> |
||||
|
|
||||
|
<li> |
||||
|
<a href="../../index.html"><i class="fa fa-dashboard fa-fw"></i> Dashboard</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Charts<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-second-level"> |
||||
|
<li> |
||||
|
<a href="OverTime.html">Over Time</a> |
||||
|
|
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="Throughput.html">Throughput</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="ResponseTimes.html">Response Times</a> |
||||
|
</li> |
||||
|
|
||||
|
</ul> |
||||
|
<!-- /.nav-second-level --> |
||||
|
</li> |
||||
|
|
||||
|
<li> |
||||
|
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Customs Graphs<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-second-level"> |
||||
|
<li> |
||||
|
<a href="CustomsGraphs.html">Over Time<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-third-level in" id="submenu"> |
||||
|
</ul> |
||||
|
<li> |
||||
|
</ul> |
||||
|
</li> |
||||
|
|
||||
|
</ul> |
||||
|
</div> |
||||
|
<!-- /.sidebar-collapse --> |
||||
|
</div> |
||||
|
<!-- /.navbar-static-side --> |
||||
|
</nav> |
||||
|
|
||||
|
<div id="page-wrapper"> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-12"> |
||||
|
<div class="panel panel-default" > |
||||
|
<div class="panel-heading" style="text-align:center;"> |
||||
|
<p class="dashboard-title">Test and Report information</p> |
||||
|
</div> |
||||
|
<div class="panel-body"> |
||||
|
<table id="generalInfos" class="table table-bordered table-condensed " > |
||||
|
<tr> |
||||
|
<td>File:</td> |
||||
|
<td>"WithAbp.jtl"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>Start Time:</td> |
||||
|
<td>"12/31/23, 11:45 AM"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>End Time:</td> |
||||
|
<td>"12/31/23, 11:46 AM"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>Filter for display:</td> |
||||
|
<td>""</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.row --> |
||||
|
<div class="row" id="graphContainer"> |
||||
|
|
||||
|
</div> |
||||
|
<!-- /.row --> |
||||
|
</div> |
||||
|
<!-- /#page-wrapper --> |
||||
|
|
||||
|
</div> |
||||
|
<!-- /#wrapper --> |
||||
|
|
||||
|
<!-- jQuery --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/jquery/dist/jquery.min.js"></script> |
||||
|
|
||||
|
<!-- Bootstrap Core JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/bootstrap/dist/js/bootstrap.min.js"></script> |
||||
|
|
||||
|
<!-- Metis Menu Plugin JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.js"></script> |
||||
|
|
||||
|
<!-- Flot Charts JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/excanvas.min.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.pie.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.resize.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.canvas.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.navigate.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.time.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.selection.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot.tooltip/js/jquery.flot.tooltip.min.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot-axislabels/jquery.flot.axislabels.js"></script> |
||||
|
<script src="../js/hashtable.js"></script> |
||||
|
<script src="../js/jquery.numberformatter-1.2.3.min.js"></script> |
||||
|
<script src="../js/curvedLines.js"></script> |
||||
|
<script src="../js/dashboard-commons.js"></script> |
||||
|
<script src="../js/customGraph.js"></script> |
||||
|
<script src="../js/jquery-ui.min.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.threshold.js"></script> |
||||
|
<!-- Custom Theme JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/dist/js/sb-admin-2.js"></script> |
||||
|
<script src="../js/jquery.cookie.js"></script> |
||||
|
|
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.symbol.js"></script> |
||||
|
|
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
@ -0,0 +1,512 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
|
||||
|
<head> |
||||
|
|
||||
|
<meta charset="utf-8"> |
||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
<meta name="description" content=""> |
||||
|
<meta name="author" content=""> |
||||
|
|
||||
|
<title>Apache JMeter Dashboard</title> |
||||
|
|
||||
|
<!-- Bootstrap Core CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- icone onglet --> |
||||
|
<link rel="icon" type="image/png" href="icon-apache.png" /> |
||||
|
|
||||
|
<!-- MetisMenu CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Legends CSS --> |
||||
|
<link href="../css/legends.css" rel="stylesheet"> |
||||
|
|
||||
|
|
||||
|
<!-- Custom CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/dist/css/sb-admin-2.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Custom Fonts --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"> |
||||
|
|
||||
|
<!-- JQuery UI style --> |
||||
|
<link href="../css/jquery-ui.min.css" rel="stylesheet"> |
||||
|
<link href="../css/jquery-ui.structure.min.css" rel="stylesheet"> |
||||
|
<link href="../css/jquery-ui.theme.min.css" rel="stylesheet"> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
|
||||
|
<div id="wrapper"> |
||||
|
|
||||
|
<!-- Navigation --> |
||||
|
<nav class="navbar navbar-default navbar-static-top" role="navigation" style="margin-bottom: 0"> |
||||
|
<div class="navbar-header"> |
||||
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> |
||||
|
<span class="sr-only">Toggle navigation</span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
</button> |
||||
|
<a class="navbar-brand" href="../../index.html">Apache JMeter Dashboard</a> |
||||
|
</div> |
||||
|
<!-- /.navbar-header --> |
||||
|
|
||||
|
|
||||
|
<div class="navbar-default sidebar" role="navigation"> |
||||
|
<div class="sidebar-nav navbar-collapse"> |
||||
|
<ul class="nav" id="side-menu"> |
||||
|
|
||||
|
<li> |
||||
|
<a href="../../index.html"><i class="fa fa-dashboard fa-fw"></i> Dashboard</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Charts<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-second-level"> |
||||
|
<li> |
||||
|
<a href="OverTime.html">Over Time<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-third-level in" id="submenu"> |
||||
|
<li> |
||||
|
<a href="OverTime.html#responseTimesOverTime" onclick="$('#bodyResponseTimeOverTime').collapse('show');"> |
||||
|
Response Times Over Time |
||||
|
</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="OverTime.html#responseTimePercentilesOverTime" onclick="$('#bodyResponseTimePercentilesOverTime').collapse('show');"> |
||||
|
Response Time Percentiles Over Time (successful responses) |
||||
|
</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="OverTime.html#activeThreadsOverTime" onclick="$('#bodyActiveThreadsOverTime').collapse('show');"> |
||||
|
Active Threads Over Time |
||||
|
</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="OverTime.html#bytesThroughputOverTime" onclick="$('#bodyBytesThroughputOverTime').collapse('show');"> |
||||
|
Bytes Throughput Over Time |
||||
|
</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="OverTime.html#latenciesOverTime" onclick="$('#bodyLatenciesOverTime').collapse('show');"> |
||||
|
Latencies Over Time |
||||
|
</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="OverTime.html#connectTimeOverTime" onclick="$('#bodyConnectTimeOverTime').collapse('show');"> |
||||
|
Connect Time Over Time |
||||
|
</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="Throughput.html">Throughput</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="ResponseTimes.html">Response Times</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
<!-- /.nav-second-level --> |
||||
|
</li> |
||||
|
|
||||
|
<li> |
||||
|
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i>Customs Graphs<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-second-level"> |
||||
|
<li> |
||||
|
<a href="CustomsGraphs.html">Over Time</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</li> |
||||
|
|
||||
|
</ul> |
||||
|
</div> |
||||
|
<!-- /.sidebar-collapse --> |
||||
|
</div> |
||||
|
<!-- /.navbar-static-side --> |
||||
|
</nav> |
||||
|
<!--modal--> |
||||
|
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> |
||||
|
<div class="modal-dialog" style="width:90%;height: 5%;"> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> |
||||
|
<h4 class="modal-title" id="myModalLabel">Zoom</h4> |
||||
|
</div> |
||||
|
<div class="modal-body"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flot-modal-content"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="modal-footer" id="modalFooter"> |
||||
|
<p id="legendModal" hidden></p> |
||||
|
<ul id="choicesModal" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="page-wrapper"> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-12"> |
||||
|
<div class="panel panel-default" > |
||||
|
<div class="panel-heading" style="text-align:center;"> |
||||
|
<p class="dashboard-title">Test and Report information</p> |
||||
|
</div> |
||||
|
<div class="panel-body"> |
||||
|
<table id="generalInfos" class="table table-bordered table-condensed " > |
||||
|
<tr> |
||||
|
<td>File:</td> |
||||
|
<td>"WithAbp.jtl"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>Start Time:</td> |
||||
|
<td>"12/31/23, 11:45 AM"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>End Time:</td> |
||||
|
<td>"12/31/23, 11:46 AM"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>Filter for display:</td> |
||||
|
<td>""</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.row --> |
||||
|
<div class="row" id="graphContainer"> |
||||
|
|
||||
|
<!-- /.col-lg-6 --> |
||||
|
<div class="col-lg-12 portlet" id="responseTimesOverTime"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw" ></i> |
||||
|
<span type="button" class="span-title dropdown-toggle click-title" data-toggle="collapse" href="#bodyResponseTimeOverTime" aria-expanded="true" aria-controls="bodyResponseTimeOverTime">Response Times Over Time</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#bodyResponseTimeOverTime" onClick="checkAll('choicesResponseTimesOverTime');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#bodyResponseTimeOverTime" onClick="uncheckAll('choicesResponseTimesOverTime');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#bodyResponseTimeOverTime" onclick="exportToPNG('flotResponseTimesOverTime', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyResponseTimeOverTime" aria-expanded="true" aria-controls="bodyResponseTimeOverTime"> |
||||
|
<i class="fa fa-chevron-up"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse in portlet-content" id="bodyResponseTimeOverTime"> |
||||
|
<div class="panel-body" id="collapseResponseTimesOverTime"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotResponseTimesOverTime" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewResponseTimesOverTime" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerResponseTimesOverTime"> |
||||
|
<p id="legendResponseTimesOverTime" hidden></p> |
||||
|
<ul id="choicesResponseTimesOverTime" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
<!-- /.col-lg-12 --> |
||||
|
|
||||
|
<!-- /.col-lg-12 --> |
||||
|
<div class="col-lg-12 portlet" id="responseTimePercentilesOverTime"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"></i> |
||||
|
<span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyResponseTimePercentilesOverTime" aria-expanded="true" aria-controls="bodyResponseTimePercentilesOverTime">Response Time Percentiles Over Time (successful responses)</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#responseTimePercentilesOverTime" onClick="checkAll('choicesResponseTimePercentilesOverTime');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#responseTimePercentilesOverTime" onClick="uncheckAll('choicesResponseTimePercentilesOverTime');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#responseTimePercentilesOverTime" onclick="exportToPNG('flotResponseTimePercentilesOverTime', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyResponseTimePercentilesOverTime" aria-expanded="true" aria-controls="bodyResponseTimePercentilesOverTime"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodyResponseTimePercentilesOverTime"> |
||||
|
<div class="panel-body" id="collapseConnectTime"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotResponseTimePercentilesOverTime" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewResponseTimePercentilesOverTime" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerResponseTimePercentilesOverTime"> |
||||
|
<p id="legendResponseTimePercentilesOverTime" hidden></p> |
||||
|
<ul id="choicesResponseTimePercentilesOverTime" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
|
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
<!-- /.col-lg-12 --> |
||||
|
|
||||
|
<div class="col-lg-12 portlet" id="activeThreadsOverTime"> |
||||
|
<div class="panel panel-default" > |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"> </i> <span type="button" class="span-title dropdown-toggle click-title" data-toggle="collapse" href="#bodyActiveThreadsOverTime" aria-expanded="true" aria-controls="bodyActiveThreadsOverTime">Active Threads Over Time</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="drag btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#activeThreadsOverTime" onClick="checkAll('choicesActiveThreadsOverTime');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#activeThreadsOverTime" onClick="uncheckAll('choicesActiveThreadsOverTime');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#activeThreadsOverTime" onclick="exportToPNG('flotActiveThreadsOverTime', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyActiveThreadsOverTime" aria-expanded="true" aria-controls="bodyActiveThreadsOverTime"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodyActiveThreadsOverTime"> |
||||
|
<div class="panel-body" id="collapseActiveThreadsOverTime"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotActiveThreadsOverTime" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewActiveThreadsOverTime" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerActiveThreadsOverTime"> |
||||
|
<p id="legendActiveThreadsOverTime" hidden></p> |
||||
|
<ul id="choicesActiveThreadsOverTime" class="legend"> |
||||
|
|
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div class="col-lg-12 portlet" id="bytesThroughputOverTime"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw" ></i> <span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyBytesThroughputOverTime" aria-expanded="true" aria-controls="bodyBytesThroughputOverTime">Bytes Throughput Over Time</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#bytesThroughputOverTime" onClick="checkAll('choicesBytesThroughputOverTime');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#bytesThroughputOverTime" onClick="uncheckAll('choicesBytesThroughputOverTime');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#bytesThroughputOverTime" onclick="exportToPNG('flotBytesThroughputOverTime', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyBytesThroughputOverTime" aria-expanded="true" aria-controls="bodyBytesThroughputOverTime"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodyBytesThroughputOverTime"> |
||||
|
<div class="panel-body " id="collapseBytes"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotBytesThroughputOverTime" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewBytesThroughputOverTime" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
<div class="panel-footer" id="footerBytesThroughputOverTime"> |
||||
|
<p id="legendBytesThroughputOverTime" hidden></p> |
||||
|
<ul id="choicesBytesThroughputOverTime" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
|
||||
|
<!-- /.col-lg-6 --> |
||||
|
<div class="col-lg-12 portlet" id="latenciesOverTime"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"></i> |
||||
|
<span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyLatenciesOverTime" aria-expanded="true" aria-controls="bodyLatenciesOverTime">Latencies Over Time</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#latenciesOverTime" onClick="checkAll('choicesLatenciesOverTime');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#latenciesOverTime" onClick="uncheckAll('choicesLatenciesOverTime');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#latenciesOverTime" onclick="exportToPNG('flotLatenciesOverTime', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyLatenciesOverTime" aria-expanded="true" aria-controls="bodyLatenciesOverTime"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodyLatenciesOverTime"> |
||||
|
<div class="panel-body" id="collapseLatencies"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotLatenciesOverTime" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewLatenciesOverTime" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerLatenciesOverTime"> |
||||
|
<p id="legendLatenciesOverTime" hidden></p> |
||||
|
<ul id="choicesLatenciesOverTime" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
|
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
|
||||
|
<!-- /.col-lg-6 --> |
||||
|
<div class="col-lg-12 portlet" id="connectTimeOverTime"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"></i> |
||||
|
<span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyConnectTimeOverTime" aria-expanded="true" aria-controls="bodyConnectTimeOverTime">Connect Time Over Time</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#connectTimeOverTime" onClick="checkAll('choicesConnectTimeOverTime');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#connectTimeOverTime" onClick="uncheckAll('choicesConnectTimeOverTime');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#connectTimeOverTime" onclick="exportToPNG('flotConnectTimeOverTime', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyConnectTimeOverTime" aria-expanded="true" aria-controls="bodyConnectTimeOverTime"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodyConnectTimeOverTime"> |
||||
|
<div class="panel-body" id="collapseConnectTime"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotConnectTimeOverTime" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewConnectTimeOverTime" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerConnectTimeOverTime"> |
||||
|
<p id="legendConnectTimeOverTime" hidden></p> |
||||
|
<ul id="choicesConnectTimeOverTime" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
|
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
<!-- /.col-lg-6 --> |
||||
|
</div> |
||||
|
<!-- /.row --> |
||||
|
</div> |
||||
|
<!-- /#page-wrapper --> |
||||
|
|
||||
|
</div> |
||||
|
<!-- /#wrapper --> |
||||
|
|
||||
|
<!-- jQuery --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/jquery/dist/jquery.min.js"></script> |
||||
|
|
||||
|
<!-- Bootstrap Core JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/bootstrap/dist/js/bootstrap.min.js"></script> |
||||
|
|
||||
|
<!-- Metis Menu Plugin JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.js"></script> |
||||
|
|
||||
|
<!-- Flot Charts JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/excanvas.min.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.pie.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.resize.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.canvas.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.time.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.selection.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot.tooltip/js/jquery.flot.tooltip.min.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot-axislabels/jquery.flot.axislabels.js"></script> |
||||
|
<script src="../js/jquery.flot.stack.js"></script> |
||||
|
<script src="../js/hashtable.js"></script> |
||||
|
<script src="../js/jquery.numberformatter-1.2.3.min.js"></script> |
||||
|
<script src="../js/curvedLines.js"></script> |
||||
|
<script src="../js/dashboard-commons.js"></script> |
||||
|
<script src="../js/graph.js"></script> |
||||
|
<script src="../js/jquery-ui.min.js"></script> |
||||
|
<script src="../js/jquery.cookie.js"></script> |
||||
|
<!-- Custom Theme JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/dist/js/sb-admin-2.js"></script> |
||||
|
|
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
@ -0,0 +1,373 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
|
||||
|
<head> |
||||
|
|
||||
|
<meta charset="utf-8"> |
||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
<meta name="description" content=""> |
||||
|
<meta name="author" content=""> |
||||
|
|
||||
|
<title>Apache JMeter Dashboard</title> |
||||
|
|
||||
|
<!-- Bootstrap Core CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- icone onglet --> |
||||
|
<link rel="icon" type="image/png" href="icon-apache.png" /> |
||||
|
|
||||
|
<!-- MetisMenu CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Legends CSS --> |
||||
|
<link href="../css/legends.css" rel="stylesheet"> |
||||
|
|
||||
|
|
||||
|
<!-- Custom CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/dist/css/sb-admin-2.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Custom Fonts --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"> |
||||
|
|
||||
|
<!-- JQuery UI style --> |
||||
|
<link href="../css/jquery-ui.min.css" rel="stylesheet"> |
||||
|
<link href="../css/jquery-ui.structure.min.css" rel="stylesheet"> |
||||
|
<link href="../css/jquery-ui.theme.min.css" rel="stylesheet"> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
|
||||
|
<div id="wrapper"> |
||||
|
|
||||
|
<!-- Navigation --> |
||||
|
<nav class="navbar navbar-default navbar-static-top" role="navigation" style="margin-bottom: 0"> |
||||
|
<div class="navbar-header"> |
||||
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> |
||||
|
<span class="sr-only">Toggle navigation</span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
</button> |
||||
|
<a class="navbar-brand" href="../../index.html">Apache JMeter Dashboard</a> |
||||
|
</div> |
||||
|
<!-- /.navbar-header --> |
||||
|
|
||||
|
|
||||
|
<div class="navbar-default sidebar" role="navigation"> |
||||
|
<div class="sidebar-nav navbar-collapse"> |
||||
|
<ul class="nav" id="side-menu"> |
||||
|
|
||||
|
<li> |
||||
|
<a href="../../index.html"><i class="fa fa-dashboard fa-fw"></i> Dashboard</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Charts<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-second-level"> |
||||
|
<li> |
||||
|
<a href="OverTime.html">Over Time</a> |
||||
|
|
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="Throughput.html">Throughput</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="ResponseTimes.html">Response Times<span class="fa arrow"></span></a> |
||||
|
|
||||
|
<ul class="nav nav-third-level in" id="submenu"> |
||||
|
<li> |
||||
|
<a href="ResponseTimes.html#responseTimePercentiles" onclick="$('#bodyResponseTimePercentiles').collapse('show');"> |
||||
|
Response Time Percentiles |
||||
|
</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="ResponseTimes.html#syntheticResponseTimeDistribution" onclick="$('#bodySyntheticResponseTimeDistribution').collapse('show');"> |
||||
|
Response Time Overview |
||||
|
</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="ResponseTimes.html#timeVsThreads" onclick="$('#bodyTimeVsThreads').collapse('show');">Time Vs Threads</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="ResponseTimes.html#responseTimeDistribution" onclick="$('#bodyResponseTimeDistribution').collapse('show');"> |
||||
|
Response Time Distribution |
||||
|
</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
|
||||
|
</li> |
||||
|
</ul> |
||||
|
<!-- /.nav-second-level --> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Customs Graphs<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-second-level"> |
||||
|
<li> |
||||
|
<a href="CustomsGraphs.html">Over Time</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<!-- /.sidebar-collapse --> |
||||
|
</div> |
||||
|
<!-- /.navbar-static-side --> |
||||
|
</nav> |
||||
|
|
||||
|
<div id="page-wrapper"> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-12"> |
||||
|
<div class="panel panel-default" > |
||||
|
<div class="panel-heading" style="text-align:center;"> |
||||
|
<p class="dashboard-title">Test and Report information</p> |
||||
|
</div> |
||||
|
<div class="panel-body"> |
||||
|
<table id="generalInfos" class="table table-bordered table-condensed " > |
||||
|
<tr> |
||||
|
<td>File:</td> |
||||
|
<td>"WithAbp.jtl"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>Start Time:</td> |
||||
|
<td>"12/31/23, 11:45 AM"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>End Time:</td> |
||||
|
<td>"12/31/23, 11:46 AM"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>Filter for display:</td> |
||||
|
<td>""</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.row --> |
||||
|
<div class="row" id="graphContainer"> |
||||
|
<div class="col-lg-12 portlet" id="responseTimePercentiles"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"></i> <span type="button" class="span-title dropdown-toggle click-title" data-toggle="collapse" href="#bodyResponseTimePercentiles" aria-expanded="true" aria-controls="bodyResponseTimePercentiles">Response Time Percentiles</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="drag btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#responseTimePercentiles" onClick="checkAll('choicesResponseTimePercentiles');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#responseTimePercentiles" onClick="uncheckAll('choicesResponseTimePercentiles');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#responseTimePercentiles" onclick="exportToPNG('flotResponseTimesPercentiles', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyResponseTimePercentiles" aria-expanded="true" aria-controls="bodyResponseTimePercentiles"> |
||||
|
<i class="fa fa-chevron-up"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse in portlet-content" id="bodyResponseTimePercentiles"> |
||||
|
<div class="panel-body" id="collapseResponseTimePercentiles"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotResponseTimesPercentiles" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewResponseTimesPercentiles" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerPercentiles"> |
||||
|
<p id="legendResponseTimePercentiles" hidden></p> |
||||
|
<ul id="choicesResponseTimePercentiles" class="legend"> |
||||
|
|
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
<div class="col-lg-12 portlet" id="syntheticResponseTimeDistribution"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"></i> <span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodySyntheticResponseTimeDistribution" aria-expanded="true" aria-controls="bodySyntheticResponseTimeDistribution">Response Time Overview</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="drag btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#syntheticResponseTimeDistribution" onClick="checkAll('choicesSyntheticResponseTimeDistribution');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#syntheticResponseTimeDistribution" onClick="uncheckAll('choicesSyntheticResponseTimeDistribution');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#syntheticResponseTimeDistribution" onclick="exportToPNG('flotSyntheticResponseTimesDistribution', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodySyntheticResponseTimeDistribution" aria-expanded="true" aria-controls="bodySyntheticResponseTimeDistribution"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodySyntheticResponseTimeDistribution"> |
||||
|
<div class="panel-body" id="collapseSyntheticResponseTimeDistribution"> |
||||
|
|
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotSyntheticResponseTimeDistribution"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerSyntheticResponseTimeDistribution"> |
||||
|
<p id="legendSyntheticResponseTimeDistribution" hidden></p> |
||||
|
<ul id="choicesSyntheticResponseTimeDistribution" class="legend"> |
||||
|
|
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
<div class="col-lg-12 portlet" id="timeVsThreads"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"></i><span type="button" class="span-title dropdown-toggle click-title" data-toggle="collapse" href="#bodyTimeVsThreads" aria-expanded="true" aria-controls="bodyTimeVsThreads">Time Vs Threads</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="drag btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#timeVsThreads" onClick="checkAll('choicesTimeVsThreads');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#timeVsThreads" onClick="uncheckAll('choicesTimeVsThreads');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#timeVsThreads" onclick="exportToPNG('flotTimesVsThreads', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyTimeVsThreads" aria-expanded="true" aria-controls="bodyTimeVsThreads"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodyTimeVsThreads"> |
||||
|
<div class="panel-body" id="collapseTimeVsThreads"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotTimesVsThreads" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewTimesVsThreads" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerTimeVsThreads"> |
||||
|
<p id="legendTimeVsThreads" hidden></p> |
||||
|
<ul id="choicesTimeVsThreads" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
<div class="col-lg-12 portlet" id="responseTimeDistribution"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"></i> <span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyResponseTimeDistribution" aria-expanded="true" aria-controls="bodyResponseTimeDistribution">Response Time Distribution</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="drag btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#responseTimeDistribution" onClick="checkAll('choicesResponseTimeDistribution');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#responseTimeDistribution" onClick="uncheckAll('choicesResponseTimeDistribution');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#responseTimeDistribution" onclick="exportToPNG('flotResponseTimeDistribution', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyResponseTimeDistribution" aria-expanded="true" aria-controls="bodyResponseTimeDistribution"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodyResponseTimeDistribution"> |
||||
|
<div class="panel-body" id="collapseResponseTimeDistribution"> |
||||
|
|
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotResponseTimeDistribution"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerResponseTimeDistribution"> |
||||
|
<p id="legendResponseTimeDistribution" hidden></p> |
||||
|
<ul id="choicesResponseTimeDistribution" class="legend"> |
||||
|
|
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
|
||||
|
<!-- /.col-lg-6 --> |
||||
|
</div> |
||||
|
<!-- /.row --> |
||||
|
</div> |
||||
|
<!-- /#page-wrapper --> |
||||
|
|
||||
|
</div> |
||||
|
<!-- /#wrapper --> |
||||
|
|
||||
|
<!-- jQuery --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/jquery/dist/jquery.min.js"></script> |
||||
|
|
||||
|
<!-- Bootstrap Core JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/bootstrap/dist/js/bootstrap.min.js"></script> |
||||
|
|
||||
|
<!-- Metis Menu Plugin JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.js"></script> |
||||
|
|
||||
|
<!-- Flot Charts JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/excanvas.min.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.pie.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.resize.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.canvas.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.navigate.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.time.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.selection.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot.tooltip/js/jquery.flot.tooltip.min.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot-axislabels/jquery.flot.axislabels.js"></script> |
||||
|
<script src="../js/hashtable.js"></script> |
||||
|
<script src="../js/jquery.numberformatter-1.2.3.min.js"></script> |
||||
|
<script src="../js/curvedLines.js"></script> |
||||
|
<script src="../js/dashboard-commons.js"></script> |
||||
|
<script src="../js/graph.js"></script> |
||||
|
<script src="../js/jquery-ui.min.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.threshold.js"></script> |
||||
|
<!-- Custom Theme JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/dist/js/sb-admin-2.js"></script> |
||||
|
<script src="../js/jquery.cookie.js"></script> |
||||
|
|
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.symbol.js"></script> |
||||
|
|
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
@ -0,0 +1,463 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
|
||||
|
<head> |
||||
|
|
||||
|
<meta charset="utf-8"> |
||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
<meta name="description" content=""> |
||||
|
<meta name="author" content=""> |
||||
|
|
||||
|
<title>Apache JMeter Dashboard</title> |
||||
|
|
||||
|
<!-- Bootstrap Core CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- icon --> |
||||
|
<link rel="icon" type="image/png" href="icon-apache.png" /> |
||||
|
|
||||
|
<!-- MetisMenu CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Legends CSS --> |
||||
|
<link href="../css/legends.css" rel="stylesheet"> |
||||
|
|
||||
|
|
||||
|
<!-- Custom CSS --> |
||||
|
<link href="../../sbadmin2-1.0.7/dist/css/sb-admin-2.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Custom Fonts --> |
||||
|
<link href="../../sbadmin2-1.0.7/bower_components/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"> |
||||
|
|
||||
|
<!-- JQuery UI style --> |
||||
|
<link href="../css/jquery-ui.min.css" rel="stylesheet"> |
||||
|
<link href="../css/jquery-ui.structure.min.css" rel="stylesheet"> |
||||
|
<link href="../css/jquery-ui.theme.min.css" rel="stylesheet"> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
|
||||
|
<div id="wrapper"> |
||||
|
|
||||
|
<!-- Navigation --> |
||||
|
<nav class="navbar navbar-default navbar-static-top" role="navigation" style="margin-bottom: 0"> |
||||
|
<div class="navbar-header"> |
||||
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> |
||||
|
<span class="sr-only">Toggle navigation</span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
</button> |
||||
|
<a class="navbar-brand" href="../../index.html">Apache JMeter Dashboard</a> |
||||
|
</div> |
||||
|
<!-- /.navbar-header --> |
||||
|
|
||||
|
|
||||
|
<div class="navbar-default sidebar" role="navigation"> |
||||
|
<div class="sidebar-nav navbar-collapse"> |
||||
|
<ul class="nav" id="side-menu"> |
||||
|
|
||||
|
<li> |
||||
|
<a href="../../index.html"><i class="fa fa-dashboard fa-fw"></i> Dashboard</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Charts<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-second-level"> |
||||
|
<li> |
||||
|
<a href="OverTime.html">Over Time</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="Throughput.html">Throughput<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-third-level in" id="submenu"> |
||||
|
<li> |
||||
|
<a href="Throughput.html#hitsPerSecond" onclick="$('#bodyHitsPerSecond').collapse('show');">Hits Per Second</a> |
||||
|
</li> |
||||
|
|
||||
|
<li> |
||||
|
<a href="Throughput.html#codesPerSecond" onclick="$('#bodyCodesPerSecond').collapse('show');">Codes Per Second</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="Throughput.html#transactionsPerSecond" onclick="$('#bodyTransactionsPerSecond').collapse('show');">Transactions Per Second</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="Throughput.html#totalTPS" onclick="$('#bodyTotalTPS').collapse('show');">Total Transactions Per Second</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="Throughput.html#responseTimeVsRequest" onclick="$('#bodyResponseTimeVsRequest').collapse('show');">Response Time Vs Request</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="Throughput.html#latencyVsRequest" onclick="$('#bodyLatenciesVsRequest').collapse('show');">Latency Vs Request</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="ResponseTimes.html">Response Times</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
<!-- /.nav-second-level --> |
||||
|
</li> |
||||
|
|
||||
|
<li> |
||||
|
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Customs Graphs<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-second-level"> |
||||
|
<li> |
||||
|
<a href="CustomsGraphs.html">Over Time</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</li> |
||||
|
|
||||
|
</ul> |
||||
|
</div> |
||||
|
<!-- /.sidebar-collapse --> |
||||
|
</div> |
||||
|
<!-- /.navbar-static-side --> |
||||
|
</nav> |
||||
|
|
||||
|
<div id="page-wrapper"> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-12"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading" style="text-align:center;"> |
||||
|
<p class="dashboard-title">Test and Report information</p> |
||||
|
</div> |
||||
|
<div class="panel-body"> |
||||
|
<table id="generalInfos" class="table table-bordered table-condensed " > |
||||
|
<tr> |
||||
|
<td>File:</td> |
||||
|
<td>"WithAbp.jtl"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>Start Time:</td> |
||||
|
<td>"12/31/23, 11:45 AM"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>End Time:</td> |
||||
|
<td>"12/31/23, 11:46 AM"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>Filter for display:</td> |
||||
|
<td>""</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.row --> |
||||
|
<div class="row" id="graphContainer"> |
||||
|
|
||||
|
<!-- /.col-lg-12 --> |
||||
|
|
||||
|
<div class="col-lg-12 portlet" id="hitsPerSecond"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"> </i> <span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyHitsPerSecond" aria-expanded="true" aria-controls="bodyHitsPerSecond">Hits Per Second</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#hitsPerSecond" onClick="checkAll('choicesHitsPerSecond');">Display samples</a> |
||||
|
</li> |
||||
|
<li><a href="#hitsPerSecond" onClick="uncheckAll('choicesHitsPerSecond');">Hide samples</a> |
||||
|
</li> |
||||
|
<li><a href="#hitsPerSecond" onclick="exportToPNG('flotHitsPerSecond', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyHitsPerSecond" aria-expanded="true" aria-controls="bodyHitsPerSecond"> |
||||
|
<i class="fa fa-chevron-up"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse in portlet-content" id="bodyHitsPerSecond"> |
||||
|
<div class="panel-body" id="collapseHitsPerSecond" > |
||||
|
<div class="flot-chart" > |
||||
|
<div class="flot-chart-content" id="flotHitsPerSecond" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewHitsPerSecond" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerHitsPerSecond"> |
||||
|
<p id="legendHitsPerSecond" hidden></p> |
||||
|
<ul id="choicesHitsPerSecond" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- codesPerSecond --> |
||||
|
|
||||
|
<div class="col-lg-12 portlet" id="codesPerSecond"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"></i> |
||||
|
<span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyCodesPerSecond" aria-expanded="true" aria-controls="bodyCodesPerSecond">Codes Per Second</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#codesPerSecond" onClick="checkAll('choicesCodesPerSecond');">Display samples</a> |
||||
|
</li> |
||||
|
<li><a href="#codesPerSecond" onClick="uncheckAll('choicesCodesPerSecond');">Hide samples</a> |
||||
|
</li> |
||||
|
<li><a href="#codesPerSecond" onclick="exportToPNG('flotCodesPerSecond', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyCodesPerSecond" aria-expanded="true" aria-controls="bodyCodesPerSecond"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
|
||||
|
<div class="collapse out portlet-content" id="bodyCodesPerSecond"> |
||||
|
<div class="panel-body" id="collapseCodesPerSecond"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotCodesPerSecond" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewCodesPerSecond" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerCodesPerSecond"> |
||||
|
<p id="legendCodesPerSecond" hidden></p> |
||||
|
<ul id="choicesCodesPerSecond" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-12 portlet" id="transactionsPerSecond"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"> </i> <span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyTransactionsPerSecond" aria-expanded="true" aria-controls="bodyTransactionsPerSecond">Transactions Per Second</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#transactionsPerSecond" onClick="checkAll('choicesTransactionsPerSecond');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#transactionsPerSecond" onClick="uncheckAll('choicesTransactionsPerSecond');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#transactionsPerSecond" onclick="exportToPNG('flotTransactionsPerSecond', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyTransactionsPerSecond" aria-expanded="true" aria-controls="bodyTransactionsPerSecond"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodyTransactionsPerSecond"> |
||||
|
<div class="panel-body" id="collapseTransactionsPerSecond"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotTransactionsPerSecond" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewTransactionsPerSecond" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerTransactionsPerSecond"> |
||||
|
<p id="legendTransactionsPerSecond" hidden></p> |
||||
|
<ul id="choicesTransactionsPerSecond" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-12 portlet" id="totalTPS"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"> </i> <span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyTotalTPS" aria-expanded="true" aria-controls="bodyTotalTPS">Total Transactions Per Second</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#totalTPS" onClick="checkAll('choicesTotalTPS');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#totalTPS" onClick="uncheckAll('choicesTotalTPS');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#totalTPS" onclick="exportToPNG('flotTotalTPS', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyTotalTPS" aria-expanded="true" aria-controls="bodyTotalTPS"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodyTotalTPS"> |
||||
|
<div class="panel-body" id="collapseTotalTPS"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotTotalTPS" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewTotalTPS" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerTotalTPS"> |
||||
|
<p id="legendTotalTPS" hidden></p> |
||||
|
<ul id="choicesTotalTPS" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-lg-12 portlet" id="responseTimeVsRequest"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"> </i> <span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyResponseTimeVsRequest" aria-expanded="true" aria-controls="bodyResponseTimeVsRequest">Response Time Vs Request</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#responseTimeVsRequest" onClick="checkAll('choicesResponseTimeVsRequest');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#responseTimeVsRequest" onClick="uncheckAll('choicesResponseTimeVsRequest');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#responseTimeVsRequest" onclick="exportToPNG('flotResponseTimeVsRequest', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyResponseTimeVsRequest" aria-expanded="true" aria-controls="bodyResponseTimeVsRequest"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodyResponseTimeVsRequest"> |
||||
|
<div class="panel-body" id="collapseResponseTimeVsRequest"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotResponseTimeVsRequest" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewResponseTimeVsRequest" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerResponseRimeVsRequest"> |
||||
|
<p id="legendResponseTimeVsRequest" hidden></p> |
||||
|
<ul id="choicesResponseTimeVsRequest" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
<div class="col-lg-12 portlet" id="latencyVsRequest"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading portlet-header"> |
||||
|
<i class="fa fa-bar-chart-o fa-fw"> </i> <span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyLatenciesVsRequest" aria-expanded="true" aria-controls="bodyLatenciesVsRequest">Latency Vs Request</span> |
||||
|
<div class="pull-right"> |
||||
|
<div class="btn-group"> |
||||
|
<a class="btn btn-link btn-xs"> |
||||
|
<i class="glyphicon glyphicon-resize-vertical"></i> |
||||
|
</a> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown"> |
||||
|
<i class="fa fa-wrench"></i> |
||||
|
</button> |
||||
|
<ul class="dropdown-menu dropdown-user"> |
||||
|
<li><a href="#latencyVsRequest" onClick="checkAll('choicesLatencyVsRequest');">Display all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#latencyVsRequest" onClick="uncheckAll('choicesLatencyVsRequest');">Hide all samples</a> |
||||
|
</li> |
||||
|
<li><a href="#latencyVsRequest" onclick="exportToPNG('flotLatenciesVsRequest', this);">Save as PNG</a></li> |
||||
|
</ul> |
||||
|
<button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyLatenciesVsRequest" aria-expanded="true" aria-controls="bodyLatenciesVsRequest"> |
||||
|
<i class="fa fa-chevron-down"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-heading --> |
||||
|
<div class="collapse out portlet-content" id="bodyLatenciesVsRequest"> |
||||
|
<div class="panel-body" id="collapseLatencyVsRequest"> |
||||
|
<div class="flot-chart"> |
||||
|
<div class="flot-chart-content" id="flotLatenciesVsRequest" style="float: left; width:80%;"></div> |
||||
|
<div style="float:left;margin-left:5px"> |
||||
|
<p>Zoom :</p> |
||||
|
<div id="overviewLatenciesVsRequest" style="width:190px;height:100px;"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="panel-footer" id="footerLatenciesVsRequest"> |
||||
|
<p id="legendLatencyVsRequest" hidden></p> |
||||
|
<ul id="choicesLatencyVsRequest" class="legend"></ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- /.panel-body --> |
||||
|
</div> |
||||
|
<!-- /.panel --> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
<!-- /.row --> |
||||
|
</div> |
||||
|
<!-- /#page-wrapper --> |
||||
|
|
||||
|
</div> |
||||
|
<!-- /#wrapper --> |
||||
|
|
||||
|
<!-- jQuery --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/jquery/dist/jquery.min.js"></script> |
||||
|
|
||||
|
<!-- Bootstrap Core JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/bootstrap/dist/js/bootstrap.min.js"></script> |
||||
|
|
||||
|
<!-- Metis Menu Plugin JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.js"></script> |
||||
|
|
||||
|
<!-- Flot Charts JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/excanvas.min.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.pie.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.resize.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.canvas.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.time.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.selection.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot.tooltip/js/jquery.flot.tooltip.min.js"></script> |
||||
|
<script src="../../sbadmin2-1.0.7/bower_components/flot-axislabels/jquery.flot.axislabels.js"></script> |
||||
|
<script src="../js/hashtable.js"></script> |
||||
|
<script src="../js/jquery.numberformatter-1.2.3.min.js"></script> |
||||
|
<script src="../js/curvedLines.js"></script> |
||||
|
<script src="../js/dashboard-commons.js"></script> |
||||
|
<script src="../js/graph.js"></script> |
||||
|
<script src="../js/jquery-ui.min.js"></script> |
||||
|
<!-- Custom Theme JavaScript --> |
||||
|
<script src="../../sbadmin2-1.0.7/dist/js/sb-admin-2.js"></script> |
||||
|
<script src="../js/jquery.cookie.js"></script> |
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
|
After Width: | Height: | Size: 4.8 KiB |
@ -0,0 +1,203 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
|
||||
|
<head> |
||||
|
|
||||
|
<meta charset="utf-8"> |
||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
<meta name="description" content=""> |
||||
|
<meta name="author" content=""> |
||||
|
|
||||
|
<title>Apache JMeter Dashboard</title> |
||||
|
|
||||
|
<!-- Bootstrap Core CSS --> |
||||
|
<link href="sbadmin2-1.0.7/bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- MetisMenu CSS --> |
||||
|
<link href="sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Custom CSS --> |
||||
|
<link href="sbadmin2-1.0.7/dist/css/sb-admin-2.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Dashboard css --> |
||||
|
<link href="content/css/dashboard.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- Table sorter --> |
||||
|
<link href="content/css/theme.blue.css" rel="stylesheet"> |
||||
|
|
||||
|
<!-- icon --> |
||||
|
<link rel="icon" type="image/png" href="content/pages/icon-apache.png" /> |
||||
|
|
||||
|
<!-- Custom Fonts --> |
||||
|
<link href="sbadmin2-1.0.7/bower_components/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
<div id="wrapper"> |
||||
|
<nav class="navbar navbar-default navbar-static-top" role="navigation" style="margin-bottom: 0"> |
||||
|
<div class="navbar-header"> |
||||
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> |
||||
|
<span class="sr-only">Toggle navigation</span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
<span class="icon-bar"></span> |
||||
|
</button> |
||||
|
<a class="navbar-brand" href="index.html">Apache JMeter Dashboard</a> |
||||
|
</div> |
||||
|
<div class="navbar-default sidebar" role="navigation"> |
||||
|
<div class="sidebar-nav navbar-collapse"> |
||||
|
<ul class="nav" id="side-menu"> |
||||
|
<li> |
||||
|
<a href="index.html"><i class="fa fa-dashboard fa-fw"></i> Dashboard</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Charts<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-second-level"> |
||||
|
<li> |
||||
|
<a href="content/pages/OverTime.html">Over Time</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="content/pages/Throughput.html">Throughput</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="content/pages/ResponseTimes.html">Response Times</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
<!-- /.nav-second-level --> |
||||
|
</li> |
||||
|
|
||||
|
<li> |
||||
|
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Customs Graphs<span class="fa arrow"></span></a> |
||||
|
<ul class="nav nav-second-level"> |
||||
|
<li> |
||||
|
<a href="content/pages/CustomsGraphs.html">Over Time</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</nav> |
||||
|
<div id="page-wrapper"> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-12"> |
||||
|
<div class="panel panel-default" > |
||||
|
<div class="panel-heading" style="text-align:center;"> |
||||
|
<p class="dashboard-title">Test and Report information</p> |
||||
|
</div> |
||||
|
<div class="panel-body"> |
||||
|
<table id="generalInfos" class="table table-bordered table-condensed " > |
||||
|
<tr> |
||||
|
<td>Source file</td> |
||||
|
<td>"WithAbp.jtl"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>Start Time</td> |
||||
|
<td>"12/31/23, 11:45 AM"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>End Time</td> |
||||
|
<td>"12/31/23, 11:46 AM"</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td>Filter for display</td> |
||||
|
<td>""</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-lg-6"> |
||||
|
<div class="panel panel-default" style="text-align:center;"> |
||||
|
<div class="panel-heading"> |
||||
|
<p class="dashboard-title"><a href="https://en.wikipedia.org/wiki/Apdex" target="_blank">APDEX (Application Performance Index)</a></p> |
||||
|
</div> |
||||
|
<div class="panel-body"> |
||||
|
<section id="apdex" class="col-md-12 table-responsive"> |
||||
|
<table id="apdexTable" class="table table-bordered table-condensed tablesorter "> |
||||
|
</table> |
||||
|
</section> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-6" > |
||||
|
<div class="panel panel-default" style="text-align:center;"> |
||||
|
<div class="panel-heading"> |
||||
|
<p class="dashboard-title">Requests Summary</p> |
||||
|
</div> |
||||
|
<div class="panel-body"> |
||||
|
<div class="flot-chart" style="height:200px;"> |
||||
|
<div class="flot-chart-content" id="flot-requests-summary" style="height:200px;"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-12"> |
||||
|
<div class="panel panel-default" style="text-align:center;"> |
||||
|
<div class="panel-heading" > |
||||
|
<p class="dashboard-title">Statistics</p> |
||||
|
</div> |
||||
|
<div class="panel-body "> |
||||
|
<section class="col-md-12 table-responsive"> |
||||
|
<table id="statisticsTable" class="table table-bordered table-condensed tablesorter " > |
||||
|
</table> |
||||
|
</section> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-12"> |
||||
|
<div class="panel panel-default" style="text-align:center;"> |
||||
|
<div class="panel-heading"> |
||||
|
<p class="dashboard-title">Errors</p> |
||||
|
</div> |
||||
|
<div class="panel-body"> |
||||
|
<section class="col-md-12 table-responsive"> |
||||
|
<table id="errorsTable" class="table table-bordered table-condensed tablesorter "> |
||||
|
</table> |
||||
|
</section> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-12"> |
||||
|
<div class="panel panel-default" style="text-align:center;"> |
||||
|
<div class="panel-heading"> |
||||
|
<p class="dashboard-title">Top 5 Errors by sampler</p> |
||||
|
</div> |
||||
|
<div class="panel-body"> |
||||
|
<section class="col-md-12 table-responsive"> |
||||
|
<table id="top5ErrorsBySamplerTable" class="table table-bordered table-condensed tablesorter "> |
||||
|
</table> |
||||
|
</section> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- jQuery --> |
||||
|
<script src="sbadmin2-1.0.7/bower_components/jquery/dist/jquery.min.js"></script> |
||||
|
|
||||
|
<!-- Bootstrap Core JavaScript --> |
||||
|
<script src="sbadmin2-1.0.7/bower_components/bootstrap/dist/js/bootstrap.min.js"></script> |
||||
|
<script src="sbadmin2-1.0.7/bower_components/flot/excanvas.min.js"></script> |
||||
|
<script src="sbadmin2-1.0.7/bower_components/flot/jquery.flot.js"></script> |
||||
|
<script src="sbadmin2-1.0.7/bower_components/flot/jquery.flot.pie.js"></script> |
||||
|
<script src="sbadmin2-1.0.7/bower_components/flot/jquery.flot.resize.js"></script> |
||||
|
<script src="sbadmin2-1.0.7/bower_components/flot/jquery.flot.time.js"></script> |
||||
|
<script src="sbadmin2-1.0.7/bower_components/flot.tooltip/js/jquery.flot.tooltip.min.js"></script> |
||||
|
<script src="sbadmin2-1.0.7/bower_components/flot-axislabels/jquery.flot.axislabels.js"></script> |
||||
|
<!-- Metis Menu Plugin JavaScript --> |
||||
|
<script src="sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.js"></script> |
||||
|
|
||||
|
<script src="content/js/dashboard-commons.js"></script> |
||||
|
<script src="content/js/dashboard.js"></script> |
||||
|
|
||||
|
<!-- Custom Theme JavaScript --> |
||||
|
<script src="sbadmin2-1.0.7/dist/js/sb-admin-2.js"></script> |
||||
|
<script type="text/javascript" src="content/js/jquery.tablesorter.min.js"></script> |
||||
|
|
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
@ -0,0 +1,27 @@ |
|||||
|
# [Start Bootstrap](http://startbootstrap.com/) - [SB Admin 2](http://startbootstrap.com/template-overviews/sb-admin-2/) |
||||
|
|
||||
|
[SB Admin 2](http://startbootstrap.com/template-overviews/sb-admin-2/) is an open source, admin dashboard template for [Bootstrap](http://getbootstrap.com/) created by [Start Bootstrap](http://startbootstrap.com/). |
||||
|
|
||||
|
## Getting Started |
||||
|
|
||||
|
To use this template, choose one of the following options to get started: |
||||
|
* Download the latest release on Start Bootstrap |
||||
|
* Fork this repository on GitHub |
||||
|
* Install via bower using `bower install startbootstrap-sb-admin-2` |
||||
|
|
||||
|
## Bugs and Issues |
||||
|
|
||||
|
Have a bug or an issue with this template? [Open a new issue](https://github.com/IronSummitMedia/startbootstrap-sb-admin-2/issues) here on GitHub or leave a comment on the [template overview page at Start Bootstrap](http://startbootstrap.com/template-overviews/sb-admin-2/). |
||||
|
|
||||
|
## Creator |
||||
|
|
||||
|
Start Bootstrap was created by and is maintained by **David Miller**, Managing Parter at [Iron Summit Media Strategies](http://www.ironsummitmedia.com/). |
||||
|
|
||||
|
* https://twitter.com/davidmillerskt |
||||
|
* https://github.com/davidtmiller |
||||
|
|
||||
|
Start Bootstrap is based on the [Bootstrap](http://getbootstrap.com/) framework created by [Mark Otto](https://twitter.com/mdo) and [Jacob Thorton](https://twitter.com/fat). |
||||
|
|
||||
|
## Copyright and License |
||||
|
|
||||
|
Copyright 2013-2015 Iron Summit Media Strategies, LLC. Code released under the [Apache 2.0](https://github.com/IronSummitMedia/startbootstrap-sb-admin-2/blob/gh-pages/LICENSE) license. |
||||
@ -0,0 +1,39 @@ |
|||||
|
{ |
||||
|
"name": "startbootstrap-sb-admin-2", |
||||
|
"version": "1.0.7", |
||||
|
"homepage": "http://startbootstrap.com/template-overviews/sb-admin-2/", |
||||
|
"authors": [ |
||||
|
"David Miller" |
||||
|
], |
||||
|
"description": "A free, open source, Bootstrap admin theme created by Start Bootstrap", |
||||
|
"keywords": [ |
||||
|
"bootstrap", |
||||
|
"theme" |
||||
|
], |
||||
|
"license": "Apache", |
||||
|
"ignore": [ |
||||
|
"**/.*", |
||||
|
"node_modules", |
||||
|
"bower_components", |
||||
|
"test", |
||||
|
"tests", |
||||
|
"pages", |
||||
|
"index.html", |
||||
|
"/js" |
||||
|
], |
||||
|
"main": [ |
||||
|
"dist/css/sb-admin-2.css", |
||||
|
"dist/js/sb-admin-2.js" |
||||
|
], |
||||
|
"dependencies": { |
||||
|
"bootstrap": "~3.3.1", |
||||
|
"datatables": "~1.10.4", |
||||
|
"datatables-plugins": "~1.0.1", |
||||
|
"flot": "~0.8.3", |
||||
|
"font-awesome": "~4.2.0", |
||||
|
"metisMenu": "~1.1.3", |
||||
|
"datatables-responsive": "~1.0.3", |
||||
|
"bootstrap-social": "~4.8.0", |
||||
|
"flot.tooltip": "~0.8.4" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
{ |
||||
|
"name": "bootstrap", |
||||
|
"description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", |
||||
|
"version": "3.3.4", |
||||
|
"keywords": [ |
||||
|
"css", |
||||
|
"js", |
||||
|
"less", |
||||
|
"mobile-first", |
||||
|
"responsive", |
||||
|
"front-end", |
||||
|
"framework", |
||||
|
"web" |
||||
|
], |
||||
|
"homepage": "http://getbootstrap.com", |
||||
|
"main": [ |
||||
|
"less/bootstrap.less", |
||||
|
"dist/css/bootstrap.css", |
||||
|
"dist/js/bootstrap.js", |
||||
|
"dist/fonts/glyphicons-halflings-regular.eot", |
||||
|
"dist/fonts/glyphicons-halflings-regular.svg", |
||||
|
"dist/fonts/glyphicons-halflings-regular.ttf", |
||||
|
"dist/fonts/glyphicons-halflings-regular.woff", |
||||
|
"dist/fonts/glyphicons-halflings-regular.woff2" |
||||
|
], |
||||
|
"ignore": [ |
||||
|
"/.*", |
||||
|
"_config.yml", |
||||
|
"CNAME", |
||||
|
"composer.json", |
||||
|
"CONTRIBUTING.md", |
||||
|
"docs", |
||||
|
"js/tests", |
||||
|
"test-infra" |
||||
|
], |
||||
|
"dependencies": { |
||||
|
"jquery": ">= 1.9.1" |
||||
|
}, |
||||
|
"_release": "3.3.4", |
||||
|
"_resolution": { |
||||
|
"type": "version", |
||||
|
"tag": "v3.3.4", |
||||
|
"commit": "a10eb60bc0b07b747fa0c4ebd8821eb7307bd07f" |
||||
|
}, |
||||
|
"_source": "git://github.com/twbs/bootstrap.git", |
||||
|
"_target": "~3.3.1", |
||||
|
"_originalSource": "bootstrap" |
||||
|
} |
||||
@ -0,0 +1,134 @@ |
|||||
|
# [Bootstrap](http://getbootstrap.com) |
||||
|
 |
||||
|
[](https://www.npmjs.com/package/bootstrap) |
||||
|
[](https://travis-ci.org/twbs/bootstrap) |
||||
|
[](https://david-dm.org/twbs/bootstrap#info=devDependencies) |
||||
|
[](https://saucelabs.com/u/bootstrap) |
||||
|
|
||||
|
Bootstrap is a sleek, intuitive, and powerful front-end framework for faster and easier web development, created by [Mark Otto](https://twitter.com/mdo) and [Jacob Thornton](https://twitter.com/fat), and maintained by the [core team](https://github.com/orgs/twbs/people) with the massive support and involvement of the community. |
||||
|
|
||||
|
To get started, check out <http://getbootstrap.com>! |
||||
|
|
||||
|
## Table of contents |
||||
|
|
||||
|
- [Quick start](#quick-start) |
||||
|
- [Bugs and feature requests](#bugs-and-feature-requests) |
||||
|
- [Documentation](#documentation) |
||||
|
- [Contributing](#contributing) |
||||
|
- [Community](#community) |
||||
|
- [Versioning](#versioning) |
||||
|
- [Creators](#creators) |
||||
|
- [Copyright and license](#copyright-and-license) |
||||
|
|
||||
|
## Quick start |
||||
|
|
||||
|
Five quick start options are available: |
||||
|
|
||||
|
- [Download the latest release](https://github.com/twbs/bootstrap/archive/v3.3.4.zip). |
||||
|
- Clone the repo: `git clone https://github.com/twbs/bootstrap.git`. |
||||
|
- Install with [Bower](http://bower.io): `bower install bootstrap`. |
||||
|
- Install with [npm](https://www.npmjs.com): `npm install bootstrap`. |
||||
|
- Install with [Meteor](https://www.meteor.com/): `meteor add twbs:bootstrap`. |
||||
|
|
||||
|
Read the [Getting started page](http://getbootstrap.com/getting-started/) for information on the framework contents, templates and examples, and more. |
||||
|
|
||||
|
### What's included |
||||
|
|
||||
|
Within the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations. You'll see something like this: |
||||
|
|
||||
|
``` |
||||
|
bootstrap/ |
||||
|
├── css/ |
||||
|
│ ├── bootstrap.css |
||||
|
│ ├── bootstrap.css.map |
||||
|
│ ├── bootstrap.min.css |
||||
|
│ ├── bootstrap-theme.css |
||||
|
│ ├── bootstrap-theme.css.map |
||||
|
│ └── bootstrap-theme.min.css |
||||
|
├── js/ |
||||
|
│ ├── bootstrap.js |
||||
|
│ └── bootstrap.min.js |
||||
|
└── fonts/ |
||||
|
├── glyphicons-halflings-regular.eot |
||||
|
├── glyphicons-halflings-regular.svg |
||||
|
├── glyphicons-halflings-regular.ttf |
||||
|
├── glyphicons-halflings-regular.woff |
||||
|
└── glyphicons-halflings-regular.woff2 |
||||
|
``` |
||||
|
|
||||
|
We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). CSS [source maps](https://developers.google.com/chrome-developer-tools/docs/css-preprocessors) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Fonts from Glyphicons are included, as is the optional Bootstrap theme. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Bugs and feature requests |
||||
|
|
||||
|
Have a bug or a feature request? Please first read the [issue guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md#using-the-issue-tracker) and search for existing and closed issues. If your problem or idea is not addressed yet, [please open a new issue](https://github.com/twbs/bootstrap/issues/new). |
||||
|
|
||||
|
|
||||
|
## Documentation |
||||
|
|
||||
|
Bootstrap's documentation, included in this repo in the root directory, is built with [Jekyll](http://jekyllrb.com) and publicly hosted on GitHub Pages at <http://getbootstrap.com>. The docs may also be run locally. |
||||
|
|
||||
|
### Running documentation locally |
||||
|
|
||||
|
1. If necessary, [install Jekyll](http://jekyllrb.com/docs/installation) (requires v2.5.x). |
||||
|
- **Windows users:** Read [this unofficial guide](http://jekyll-windows.juthilo.com/) to get Jekyll up and running without problems. |
||||
|
2. Install the Ruby-based syntax highlighter, [Rouge](https://github.com/jneen/rouge), with `gem install rouge`. |
||||
|
3. From the root `/bootstrap` directory, run `jekyll serve` in the command line. |
||||
|
4. Open <http://localhost:9001> in your browser, and voilà. |
||||
|
|
||||
|
Learn more about using Jekyll by reading its [documentation](http://jekyllrb.com/docs/home/). |
||||
|
|
||||
|
### Documentation for previous releases |
||||
|
|
||||
|
Documentation for v2.3.2 has been made available for the time being at <http://getbootstrap.com/2.3.2/> while folks transition to Bootstrap 3. |
||||
|
|
||||
|
[Previous releases](https://github.com/twbs/bootstrap/releases) and their documentation are also available for download. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Contributing |
||||
|
|
||||
|
Please read through our [contributing guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development. |
||||
|
|
||||
|
Moreover, if your pull request contains JavaScript patches or features, you must include [relevant unit tests](https://github.com/twbs/bootstrap/tree/master/js/tests). All HTML and CSS should conform to the [Code Guide](https://github.com/mdo/code-guide), maintained by [Mark Otto](https://github.com/mdo). |
||||
|
|
||||
|
Editor preferences are available in the [editor config](https://github.com/twbs/bootstrap/blob/master/.editorconfig) for easy use in common text editors. Read more and download plugins at <http://editorconfig.org>. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Community |
||||
|
|
||||
|
Keep track of development and community news. |
||||
|
|
||||
|
- Follow [@getbootstrap on Twitter](https://twitter.com/getbootstrap). |
||||
|
- Read and subscribe to [The Official Bootstrap Blog](http://blog.getbootstrap.com). |
||||
|
- Chat with fellow Bootstrappers in IRC. On the `irc.freenode.net` server, in the `##bootstrap` channel. |
||||
|
- Implementation help may be found at Stack Overflow (tagged [`twitter-bootstrap-3`](http://stackoverflow.com/questions/tagged/twitter-bootstrap-3)). |
||||
|
- Developers should use the keyword `bootstrap` on packages which modify or add to the functionality of Bootstrap when distributing through [npm](https://www.npmjs.com/browse/keyword/bootstrap) or similar delivery mechanisms for maximum discoverability. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Versioning |
||||
|
|
||||
|
For transparency into our release cycle and in striving to maintain backward compatibility, Bootstrap is maintained under [the Semantic Versioning guidelines](http://semver.org/). Sometimes we screw up, but we'll adhere to those rules whenever possible. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Creators |
||||
|
|
||||
|
**Mark Otto** |
||||
|
|
||||
|
- <https://twitter.com/mdo> |
||||
|
- <https://github.com/mdo> |
||||
|
|
||||
|
**Jacob Thornton** |
||||
|
|
||||
|
- <https://twitter.com/fat> |
||||
|
- <https://github.com/fat> |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Copyright and license |
||||
|
|
||||
|
Code and documentation copyright 2011-2015 Twitter, Inc. Code released under [the MIT license](https://github.com/twbs/bootstrap/blob/master/LICENSE). Docs released under [Creative Commons](https://github.com/twbs/bootstrap/blob/master/docs/LICENSE). |
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -0,0 +1,14 @@ |
|||||
|
{ |
||||
|
"name": "flot-axislabels", |
||||
|
"homepage": "https://github.com/markrcote/flot-axislabels", |
||||
|
"_release": "a181e09d04", |
||||
|
"_resolution": { |
||||
|
"type": "branch", |
||||
|
"branch": "master", |
||||
|
"commit": "a181e09d04d120d05e5bc2baaa8738b5b3670428" |
||||
|
}, |
||||
|
"_source": "git://github.com/markrcote/flot-axislabels.git", |
||||
|
"_target": "*", |
||||
|
"_originalSource": "flot-axislabels", |
||||
|
"_direct": true |
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
flot-axislabels: Axis Labels plugin for flot |
||||
|
============================================ |
||||
|
|
||||
|
Originally written by Xuan Luo. Maintained by Mark Cote. |
||||
|
|
||||
|
Contributions: |
||||
|
|
||||
|
* Xuan Luo |
||||
|
* Mark Cote |
||||
|
* stdexcept |
||||
|
* Clemens Stolle |
||||
|
* Michael Haddon |
||||
|
* andig |
||||
|
* Alex Pinkney |
||||
|
|
||||
|
[flot-axislabels](https://github.com/markrcote/flot-axislabels) provides |
||||
|
flot with the ability to label axes. It supports any number of axes. It |
||||
|
can render the labels with CSS transforms, in canvas, or with traditional |
||||
|
CSS positioning ("HTML" mode). flot-axislabels attempts a graceful fallback |
||||
|
from CSS to canvas to HTML if some modes are not supported. You can also |
||||
|
force a particular lesser mode (canvas or HTML). |
||||
|
|
||||
|
In both CSS and canvas modes, the y-axis labels are rotated to face the |
||||
|
graph (90 degrees counter-clockwise for left-hand labels, and 90 degrees |
||||
|
clockwise for right-hand labels). In HTML mode, y-axis labels are left |
||||
|
horizontal (warning: this takes up a lot of space). |
||||
|
|
||||
|
In CSS and HTML modes, each axis label belongs to the classes "axisLabels" |
||||
|
and "[axisName]Label" (e.g. .xaxisLabel, .y2axisLabel, etc). You can use |
||||
|
standard CSS properties to customize their appearance. |
||||
|
|
||||
|
In canvas mode, you can set font size, family, and colour through flot |
||||
|
options (see below). |
||||
|
|
||||
|
|
||||
|
Example |
||||
|
------- |
||||
|
|
||||
|
$(function () { |
||||
|
var options = { |
||||
|
axisLabels: { |
||||
|
show: true |
||||
|
}, |
||||
|
xaxes: [{ |
||||
|
axisLabel: 'foo', |
||||
|
}], |
||||
|
yaxes: [{ |
||||
|
position: 'left', |
||||
|
axisLabel: 'bar', |
||||
|
}, { |
||||
|
position: 'right', |
||||
|
axisLabel: 'bleem' |
||||
|
}] |
||||
|
}; |
||||
|
|
||||
|
$.plot($("#placeholder"), |
||||
|
yourData, |
||||
|
options); |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
Usage |
||||
|
----- |
||||
|
|
||||
|
flot-axislabel adds an axisLabels object to the global options object. |
||||
|
It supports one option: |
||||
|
|
||||
|
* show (bool): display all axis labels (default: true) |
||||
|
|
||||
|
There are also several options added to the axis objects. The two main ones |
||||
|
are |
||||
|
|
||||
|
* axisLabel (string): the text you want displayed as the label |
||||
|
* axisLabelPadding (int): padding, in pixels, between the tick labels and the |
||||
|
axis label (default: 2) |
||||
|
|
||||
|
By default, if supported, flot-axislabels uses CSS transforms. You can force |
||||
|
either canvas or HTML mode by setting axisLabelUseCanvas or axisLabelUseHtml, |
||||
|
respectively, to true. |
||||
|
|
||||
|
Canvas mode supports several other options: |
||||
|
|
||||
|
* axisLabelFontSizePixels (int): the size, in pixels, of the font (default: 14) |
||||
|
* axisLabelFontFamily (string): the font family of the font (default: |
||||
|
sans-serif) |
||||
|
* axisLabelColour (string): the font colour (default: black) |
||||
|
|
||||
|
|
||||
|
Compatibility |
||||
|
------------- |
||||
|
|
||||
|
flot-axislabels should work with recent versions of Firefox, Chrome, Opera, |
||||
|
and Safari. It also works with IE 8 and 9. The canvas option does *not* |
||||
|
seem to work with IE 8, even with excanvas. |
||||
|
|
||||
|
|
||||
|
License |
||||
|
------- |
||||
|
|
||||
|
flot-axislabels is released under the terms of [the MIT License](http://www.opensource.org/licenses/MIT). |
||||
@ -0,0 +1,466 @@ |
|||||
|
/* |
||||
|
Axis Labels Plugin for flot. |
||||
|
http://github.com/markrcote/flot-axislabels
|
||||
|
|
||||
|
Original code is Copyright (c) 2010 Xuan Luo. |
||||
|
Original code was released under the GPLv3 license by Xuan Luo, September 2010. |
||||
|
Original code was rereleased under the MIT license by Xuan Luo, April 2012. |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining |
||||
|
a copy of this software and associated documentation files (the |
||||
|
"Software"), to deal in the Software without restriction, including |
||||
|
without limitation the rights to use, copy, modify, merge, publish, |
||||
|
distribute, sublicense, and/or sell copies of the Software, and to |
||||
|
permit persons to whom the Software is furnished to do so, subject to |
||||
|
the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice shall be |
||||
|
included in all copies or substantial portions of the Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
*/ |
||||
|
|
||||
|
(function ($) { |
||||
|
var options = { |
||||
|
axisLabels: { |
||||
|
show: true |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
function canvasSupported() { |
||||
|
return !!document.createElement('canvas').getContext; |
||||
|
} |
||||
|
|
||||
|
function canvasTextSupported() { |
||||
|
if (!canvasSupported()) { |
||||
|
return false; |
||||
|
} |
||||
|
var dummy_canvas = document.createElement('canvas'); |
||||
|
var context = dummy_canvas.getContext('2d'); |
||||
|
return typeof context.fillText == 'function'; |
||||
|
} |
||||
|
|
||||
|
function css3TransitionSupported() { |
||||
|
var div = document.createElement('div'); |
||||
|
return typeof div.style.MozTransition != 'undefined' // Gecko
|
||||
|
|| typeof div.style.OTransition != 'undefined' // Opera
|
||||
|
|| typeof div.style.webkitTransition != 'undefined' // WebKit
|
||||
|
|| typeof div.style.transition != 'undefined'; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function AxisLabel(axisName, position, padding, plot, opts) { |
||||
|
this.axisName = axisName; |
||||
|
this.position = position; |
||||
|
this.padding = padding; |
||||
|
this.plot = plot; |
||||
|
this.opts = opts; |
||||
|
this.width = 0; |
||||
|
this.height = 0; |
||||
|
} |
||||
|
|
||||
|
AxisLabel.prototype.cleanup = function() { |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
CanvasAxisLabel.prototype = new AxisLabel(); |
||||
|
CanvasAxisLabel.prototype.constructor = CanvasAxisLabel; |
||||
|
function CanvasAxisLabel(axisName, position, padding, plot, opts) { |
||||
|
AxisLabel.prototype.constructor.call(this, axisName, position, padding, |
||||
|
plot, opts); |
||||
|
} |
||||
|
|
||||
|
CanvasAxisLabel.prototype.calculateSize = function() { |
||||
|
if (!this.opts.axisLabelFontSizePixels) |
||||
|
this.opts.axisLabelFontSizePixels = 14; |
||||
|
if (!this.opts.axisLabelFontFamily) |
||||
|
this.opts.axisLabelFontFamily = 'sans-serif'; |
||||
|
|
||||
|
var textWidth = this.opts.axisLabelFontSizePixels + this.padding; |
||||
|
var textHeight = this.opts.axisLabelFontSizePixels + this.padding; |
||||
|
if (this.position == 'left' || this.position == 'right') { |
||||
|
this.width = this.opts.axisLabelFontSizePixels + this.padding; |
||||
|
this.height = 0; |
||||
|
} else { |
||||
|
this.width = 0; |
||||
|
this.height = this.opts.axisLabelFontSizePixels + this.padding; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
CanvasAxisLabel.prototype.draw = function(box) { |
||||
|
if (!this.opts.axisLabelColour) |
||||
|
this.opts.axisLabelColour = 'black'; |
||||
|
var ctx = this.plot.getCanvas().getContext('2d'); |
||||
|
ctx.save(); |
||||
|
ctx.font = this.opts.axisLabelFontSizePixels + 'px ' + |
||||
|
this.opts.axisLabelFontFamily; |
||||
|
ctx.fillStyle = this.opts.axisLabelColour; |
||||
|
var width = ctx.measureText(this.opts.axisLabel).width; |
||||
|
var height = this.opts.axisLabelFontSizePixels; |
||||
|
var x, y, angle = 0; |
||||
|
if (this.position == 'top') { |
||||
|
x = box.left + box.width/2 - width/2; |
||||
|
y = box.top + height*0.72; |
||||
|
} else if (this.position == 'bottom') { |
||||
|
x = box.left + box.width/2 - width/2; |
||||
|
y = box.top + box.height - height*0.72; |
||||
|
} else if (this.position == 'left') { |
||||
|
x = box.left + height*0.72; |
||||
|
y = box.height/2 + box.top + width/2; |
||||
|
angle = -Math.PI/2; |
||||
|
} else if (this.position == 'right') { |
||||
|
x = box.left + box.width - height*0.72; |
||||
|
y = box.height/2 + box.top - width/2; |
||||
|
angle = Math.PI/2; |
||||
|
} |
||||
|
ctx.translate(x, y); |
||||
|
ctx.rotate(angle); |
||||
|
ctx.fillText(this.opts.axisLabel, 0, 0); |
||||
|
ctx.restore(); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
HtmlAxisLabel.prototype = new AxisLabel(); |
||||
|
HtmlAxisLabel.prototype.constructor = HtmlAxisLabel; |
||||
|
function HtmlAxisLabel(axisName, position, padding, plot, opts) { |
||||
|
AxisLabel.prototype.constructor.call(this, axisName, position, |
||||
|
padding, plot, opts); |
||||
|
this.elem = null; |
||||
|
} |
||||
|
|
||||
|
HtmlAxisLabel.prototype.calculateSize = function() { |
||||
|
var elem = $('<div class="axisLabels" style="position:absolute;">' + |
||||
|
this.opts.axisLabel + '</div>'); |
||||
|
this.plot.getPlaceholder().append(elem); |
||||
|
// store height and width of label itself, for use in draw()
|
||||
|
this.labelWidth = elem.outerWidth(true); |
||||
|
this.labelHeight = elem.outerHeight(true); |
||||
|
elem.remove(); |
||||
|
|
||||
|
this.width = this.height = 0; |
||||
|
if (this.position == 'left' || this.position == 'right') { |
||||
|
this.width = this.labelWidth + this.padding; |
||||
|
} else { |
||||
|
this.height = this.labelHeight + this.padding; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
HtmlAxisLabel.prototype.cleanup = function() { |
||||
|
if (this.elem) { |
||||
|
this.elem.remove(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
HtmlAxisLabel.prototype.draw = function(box) { |
||||
|
this.plot.getPlaceholder().find('#' + this.axisName + 'Label').remove(); |
||||
|
this.elem = $('<div id="' + this.axisName + |
||||
|
'Label" " class="axisLabels" style="position:absolute;">' |
||||
|
+ this.opts.axisLabel + '</div>'); |
||||
|
this.plot.getPlaceholder().append(this.elem); |
||||
|
if (this.position == 'top') { |
||||
|
this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + |
||||
|
'px'); |
||||
|
this.elem.css('top', box.top + 'px'); |
||||
|
} else if (this.position == 'bottom') { |
||||
|
this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + |
||||
|
'px'); |
||||
|
this.elem.css('top', box.top + box.height - this.labelHeight + |
||||
|
'px'); |
||||
|
} else if (this.position == 'left') { |
||||
|
this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + |
||||
|
'px'); |
||||
|
this.elem.css('left', box.left + 'px'); |
||||
|
} else if (this.position == 'right') { |
||||
|
this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + |
||||
|
'px'); |
||||
|
this.elem.css('left', box.left + box.width - this.labelWidth + |
||||
|
'px'); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
CssTransformAxisLabel.prototype = new HtmlAxisLabel(); |
||||
|
CssTransformAxisLabel.prototype.constructor = CssTransformAxisLabel; |
||||
|
function CssTransformAxisLabel(axisName, position, padding, plot, opts) { |
||||
|
HtmlAxisLabel.prototype.constructor.call(this, axisName, position, |
||||
|
padding, plot, opts); |
||||
|
} |
||||
|
|
||||
|
CssTransformAxisLabel.prototype.calculateSize = function() { |
||||
|
HtmlAxisLabel.prototype.calculateSize.call(this); |
||||
|
this.width = this.height = 0; |
||||
|
if (this.position == 'left' || this.position == 'right') { |
||||
|
this.width = this.labelHeight + this.padding; |
||||
|
} else { |
||||
|
this.height = this.labelHeight + this.padding; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
CssTransformAxisLabel.prototype.transforms = function(degrees, x, y) { |
||||
|
var stransforms = { |
||||
|
'-moz-transform': '', |
||||
|
'-webkit-transform': '', |
||||
|
'-o-transform': '', |
||||
|
'-ms-transform': '' |
||||
|
}; |
||||
|
if (x != 0 || y != 0) { |
||||
|
var stdTranslate = ' translate(' + x + 'px, ' + y + 'px)'; |
||||
|
stransforms['-moz-transform'] += stdTranslate; |
||||
|
stransforms['-webkit-transform'] += stdTranslate; |
||||
|
stransforms['-o-transform'] += stdTranslate; |
||||
|
stransforms['-ms-transform'] += stdTranslate; |
||||
|
} |
||||
|
if (degrees != 0) { |
||||
|
var rotation = degrees / 90; |
||||
|
var stdRotate = ' rotate(' + degrees + 'deg)'; |
||||
|
stransforms['-moz-transform'] += stdRotate; |
||||
|
stransforms['-webkit-transform'] += stdRotate; |
||||
|
stransforms['-o-transform'] += stdRotate; |
||||
|
stransforms['-ms-transform'] += stdRotate; |
||||
|
} |
||||
|
var s = 'top: 0; left: 0; '; |
||||
|
for (var prop in stransforms) { |
||||
|
if (stransforms[prop]) { |
||||
|
s += prop + ':' + stransforms[prop] + ';'; |
||||
|
} |
||||
|
} |
||||
|
s += ';'; |
||||
|
return s; |
||||
|
}; |
||||
|
|
||||
|
CssTransformAxisLabel.prototype.calculateOffsets = function(box) { |
||||
|
var offsets = { x: 0, y: 0, degrees: 0 }; |
||||
|
if (this.position == 'bottom') { |
||||
|
offsets.x = box.left + box.width/2 - this.labelWidth/2; |
||||
|
offsets.y = box.top + box.height - this.labelHeight; |
||||
|
} else if (this.position == 'top') { |
||||
|
offsets.x = box.left + box.width/2 - this.labelWidth/2; |
||||
|
offsets.y = box.top; |
||||
|
} else if (this.position == 'left') { |
||||
|
offsets.degrees = -90; |
||||
|
offsets.x = box.left - this.labelWidth/2 + this.labelHeight/2; |
||||
|
offsets.y = box.height/2 + box.top; |
||||
|
} else if (this.position == 'right') { |
||||
|
offsets.degrees = 90; |
||||
|
offsets.x = box.left + box.width - this.labelWidth/2 |
||||
|
- this.labelHeight/2; |
||||
|
offsets.y = box.height/2 + box.top; |
||||
|
} |
||||
|
offsets.x = Math.round(offsets.x); |
||||
|
offsets.y = Math.round(offsets.y); |
||||
|
|
||||
|
return offsets; |
||||
|
}; |
||||
|
|
||||
|
CssTransformAxisLabel.prototype.draw = function(box) { |
||||
|
this.plot.getPlaceholder().find("." + this.axisName + "Label").remove(); |
||||
|
var offsets = this.calculateOffsets(box); |
||||
|
this.elem = $('<div class="axisLabels ' + this.axisName + |
||||
|
'Label" style="position:absolute; ' + |
||||
|
this.transforms(offsets.degrees, offsets.x, offsets.y) + |
||||
|
'">' + this.opts.axisLabel + '</div>'); |
||||
|
this.plot.getPlaceholder().append(this.elem); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
IeTransformAxisLabel.prototype = new CssTransformAxisLabel(); |
||||
|
IeTransformAxisLabel.prototype.constructor = IeTransformAxisLabel; |
||||
|
function IeTransformAxisLabel(axisName, position, padding, plot, opts) { |
||||
|
CssTransformAxisLabel.prototype.constructor.call(this, axisName, |
||||
|
position, padding, |
||||
|
plot, opts); |
||||
|
this.requiresResize = false; |
||||
|
} |
||||
|
|
||||
|
IeTransformAxisLabel.prototype.transforms = function(degrees, x, y) { |
||||
|
// I didn't feel like learning the crazy Matrix stuff, so this uses
|
||||
|
// a combination of the rotation transform and CSS positioning.
|
||||
|
var s = ''; |
||||
|
if (degrees != 0) { |
||||
|
var rotation = degrees/90; |
||||
|
while (rotation < 0) { |
||||
|
rotation += 4; |
||||
|
} |
||||
|
s += ' filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=' + rotation + '); '; |
||||
|
// see below
|
||||
|
this.requiresResize = (this.position == 'right'); |
||||
|
} |
||||
|
if (x != 0) { |
||||
|
s += 'left: ' + x + 'px; '; |
||||
|
} |
||||
|
if (y != 0) { |
||||
|
s += 'top: ' + y + 'px; '; |
||||
|
} |
||||
|
return s; |
||||
|
}; |
||||
|
|
||||
|
IeTransformAxisLabel.prototype.calculateOffsets = function(box) { |
||||
|
var offsets = CssTransformAxisLabel.prototype.calculateOffsets.call( |
||||
|
this, box); |
||||
|
// adjust some values to take into account differences between
|
||||
|
// CSS and IE rotations.
|
||||
|
if (this.position == 'top') { |
||||
|
// FIXME: not sure why, but placing this exactly at the top causes
|
||||
|
// the top axis label to flip to the bottom...
|
||||
|
offsets.y = box.top + 1; |
||||
|
} else if (this.position == 'left') { |
||||
|
offsets.x = box.left; |
||||
|
offsets.y = box.height/2 + box.top - this.labelWidth/2; |
||||
|
} else if (this.position == 'right') { |
||||
|
offsets.x = box.left + box.width - this.labelHeight; |
||||
|
offsets.y = box.height/2 + box.top - this.labelWidth/2; |
||||
|
} |
||||
|
return offsets; |
||||
|
}; |
||||
|
|
||||
|
IeTransformAxisLabel.prototype.draw = function(box) { |
||||
|
CssTransformAxisLabel.prototype.draw.call(this, box); |
||||
|
if (this.requiresResize) { |
||||
|
this.elem = this.plot.getPlaceholder().find("." + this.axisName + |
||||
|
"Label"); |
||||
|
// Since we used CSS positioning instead of transforms for
|
||||
|
// translating the element, and since the positioning is done
|
||||
|
// before any rotations, we have to reset the width and height
|
||||
|
// in case the browser wrapped the text (specifically for the
|
||||
|
// y2axis).
|
||||
|
this.elem.css('width', this.labelWidth); |
||||
|
this.elem.css('height', this.labelHeight); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
function init(plot) { |
||||
|
plot.hooks.processOptions.push(function (plot, options) { |
||||
|
|
||||
|
if (!options.axisLabels.show) |
||||
|
return; |
||||
|
|
||||
|
// This is kind of a hack. There are no hooks in Flot between
|
||||
|
// the creation and measuring of the ticks (setTicks, measureTickLabels
|
||||
|
// in setupGrid() ) and the drawing of the ticks and plot box
|
||||
|
// (insertAxisLabels in setupGrid() ).
|
||||
|
//
|
||||
|
// Therefore, we use a trick where we run the draw routine twice:
|
||||
|
// the first time to get the tick measurements, so that we can change
|
||||
|
// them, and then have it draw it again.
|
||||
|
var secondPass = false; |
||||
|
|
||||
|
var axisLabels = {}; |
||||
|
var axisOffsetCounts = { left: 0, right: 0, top: 0, bottom: 0 }; |
||||
|
|
||||
|
var defaultPadding = 2; // padding between axis and tick labels
|
||||
|
plot.hooks.draw.push(function (plot, ctx) { |
||||
|
var hasAxisLabels = false; |
||||
|
if (!secondPass) { |
||||
|
// MEASURE AND SET OPTIONS
|
||||
|
$.each(plot.getAxes(), function(axisName, axis) { |
||||
|
var opts = axis.options // Flot 0.7
|
||||
|
|| plot.getOptions()[axisName]; // Flot 0.6
|
||||
|
|
||||
|
// Handle redraws initiated outside of this plug-in.
|
||||
|
if (axisName in axisLabels) { |
||||
|
axis.labelHeight = axis.labelHeight - |
||||
|
axisLabels[axisName].height; |
||||
|
axis.labelWidth = axis.labelWidth - |
||||
|
axisLabels[axisName].width; |
||||
|
opts.labelHeight = axis.labelHeight; |
||||
|
opts.labelWidth = axis.labelWidth; |
||||
|
axisLabels[axisName].cleanup(); |
||||
|
delete axisLabels[axisName]; |
||||
|
} |
||||
|
|
||||
|
if (!opts || !opts.axisLabel || !axis.show) |
||||
|
return; |
||||
|
|
||||
|
hasAxisLabels = true; |
||||
|
var renderer = null; |
||||
|
|
||||
|
if (!opts.axisLabelUseHtml && |
||||
|
navigator.appName == 'Microsoft Internet Explorer') { |
||||
|
var ua = navigator.userAgent; |
||||
|
var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); |
||||
|
if (re.exec(ua) != null) { |
||||
|
rv = parseFloat(RegExp.$1); |
||||
|
} |
||||
|
if (rv >= 9 && !opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { |
||||
|
renderer = CssTransformAxisLabel; |
||||
|
} else if (!opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { |
||||
|
renderer = IeTransformAxisLabel; |
||||
|
} else if (opts.axisLabelUseCanvas) { |
||||
|
renderer = CanvasAxisLabel; |
||||
|
} else { |
||||
|
renderer = HtmlAxisLabel; |
||||
|
} |
||||
|
} else { |
||||
|
if (opts.axisLabelUseHtml || (!css3TransitionSupported() && !canvasTextSupported()) && !opts.axisLabelUseCanvas) { |
||||
|
renderer = HtmlAxisLabel; |
||||
|
} else if (opts.axisLabelUseCanvas || !css3TransitionSupported()) { |
||||
|
renderer = CanvasAxisLabel; |
||||
|
} else { |
||||
|
renderer = CssTransformAxisLabel; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var padding = opts.axisLabelPadding === undefined ? |
||||
|
defaultPadding : opts.axisLabelPadding; |
||||
|
|
||||
|
axisLabels[axisName] = new renderer(axisName, |
||||
|
axis.position, padding, |
||||
|
plot, opts); |
||||
|
|
||||
|
// flot interprets axis.labelHeight and .labelWidth as
|
||||
|
// the height and width of the tick labels. We increase
|
||||
|
// these values to make room for the axis label and
|
||||
|
// padding.
|
||||
|
|
||||
|
axisLabels[axisName].calculateSize(); |
||||
|
|
||||
|
// AxisLabel.height and .width are the size of the
|
||||
|
// axis label and padding.
|
||||
|
// Just set opts here because axis will be sorted out on
|
||||
|
// the redraw.
|
||||
|
|
||||
|
opts.labelHeight = axis.labelHeight + |
||||
|
axisLabels[axisName].height; |
||||
|
opts.labelWidth = axis.labelWidth + |
||||
|
axisLabels[axisName].width; |
||||
|
}); |
||||
|
|
||||
|
// If there are axis labels, re-draw with new label widths and
|
||||
|
// heights.
|
||||
|
|
||||
|
if (hasAxisLabels) { |
||||
|
secondPass = true; |
||||
|
plot.setupGrid(); |
||||
|
plot.draw(); |
||||
|
} |
||||
|
} else { |
||||
|
secondPass = false; |
||||
|
// DRAW
|
||||
|
$.each(plot.getAxes(), function(axisName, axis) { |
||||
|
var opts = axis.options // Flot 0.7
|
||||
|
|| plot.getOptions()[axisName]; // Flot 0.6
|
||||
|
if (!opts || !opts.axisLabel || !axis.show) |
||||
|
return; |
||||
|
|
||||
|
axisLabels[axisName].draw(axis.box); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: 'axisLabels', |
||||
|
version: '2.0' |
||||
|
}); |
||||
|
})(jQuery); |
||||
@ -0,0 +1,24 @@ |
|||||
|
{ |
||||
|
"name": "flot.tooltip", |
||||
|
"version": "0.8.4", |
||||
|
"main": "js/jquery.flot.tooltip.js", |
||||
|
"ignore": [ |
||||
|
".gitignore", |
||||
|
"Gruntfile.js", |
||||
|
"README.md", |
||||
|
"package.json", |
||||
|
"examples", |
||||
|
"js/old" |
||||
|
], |
||||
|
"homepage": "https://github.com/krzysu/flot.tooltip", |
||||
|
"_release": "0.8.4", |
||||
|
"_resolution": { |
||||
|
"type": "version", |
||||
|
"tag": "0.8.4", |
||||
|
"commit": "312a7570f6dfeff00d9c0ce6f6263160b84ee652" |
||||
|
}, |
||||
|
"_source": "git://github.com/krzysu/flot.tooltip.git", |
||||
|
"_target": "~0.8.4", |
||||
|
"_originalSource": "flot.tooltip", |
||||
|
"_direct": true |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
{ |
||||
|
"name": "flot.tooltip", |
||||
|
"version": "0.7.1", |
||||
|
"main": "js/jquery.flot.tooltip.js", |
||||
|
"ignore": [ |
||||
|
".gitignore", |
||||
|
"Gruntfile.js", |
||||
|
"README.md", |
||||
|
"package.json", |
||||
|
"examples", |
||||
|
"js/old" |
||||
|
] |
||||
|
} |
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
@ -0,0 +1,490 @@ |
|||||
|
/* |
||||
|
* jquery.flot.tooltip |
||||
|
* |
||||
|
* description: easy-to-use tooltips for Flot charts |
||||
|
* version: 0.8.4 |
||||
|
* authors: Krzysztof Urbas @krzysu [myviews.pl],Evan Steinkerchner @Roundaround |
||||
|
* website: https://github.com/krzysu/flot.tooltip
|
||||
|
* |
||||
|
* build on 2014-08-04 |
||||
|
* released under MIT License, 2012 |
||||
|
*/ |
||||
|
(function ($) { |
||||
|
// plugin options, default values
|
||||
|
var defaultOptions = { |
||||
|
tooltip: false, |
||||
|
tooltipOpts: { |
||||
|
id: "flotTip", |
||||
|
content: "%s | X: %x | Y: %y", |
||||
|
// allowed templates are:
|
||||
|
// %s -> series label,
|
||||
|
// %lx -> x axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
|
||||
|
// %ly -> y axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
|
||||
|
// %x -> X value,
|
||||
|
// %y -> Y value,
|
||||
|
// %x.2 -> precision of X value,
|
||||
|
// %p -> percent
|
||||
|
xDateFormat: null, |
||||
|
yDateFormat: null, |
||||
|
monthNames: null, |
||||
|
dayNames: null, |
||||
|
shifts: { |
||||
|
x: 10, |
||||
|
y: 20 |
||||
|
}, |
||||
|
defaultTheme: true, |
||||
|
lines: { |
||||
|
track: false, |
||||
|
threshold: 0.05 |
||||
|
}, |
||||
|
|
||||
|
// callbacks
|
||||
|
onHover: function (flotItem, $tooltipEl) {}, |
||||
|
|
||||
|
$compat: false |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// object
|
||||
|
var FlotTooltip = function (plot) { |
||||
|
// variables
|
||||
|
this.tipPosition = {x: 0, y: 0}; |
||||
|
|
||||
|
this.init(plot); |
||||
|
}; |
||||
|
|
||||
|
// main plugin function
|
||||
|
FlotTooltip.prototype.init = function (plot) { |
||||
|
var that = this; |
||||
|
|
||||
|
// detect other flot plugins
|
||||
|
var plotPluginsLength = $.plot.plugins.length; |
||||
|
this.plotPlugins = []; |
||||
|
|
||||
|
if (plotPluginsLength) { |
||||
|
for (var p = 0; p < plotPluginsLength; p++) { |
||||
|
this.plotPlugins.push($.plot.plugins[p].name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
plot.hooks.bindEvents.push(function (plot, eventHolder) { |
||||
|
|
||||
|
// get plot options
|
||||
|
that.plotOptions = plot.getOptions(); |
||||
|
|
||||
|
// if not enabled return
|
||||
|
if (that.plotOptions.tooltip === false || typeof that.plotOptions.tooltip === 'undefined') return; |
||||
|
|
||||
|
// shortcut to access tooltip options
|
||||
|
that.tooltipOptions = that.plotOptions.tooltipOpts; |
||||
|
|
||||
|
if (that.tooltipOptions.$compat) { |
||||
|
that.wfunc = 'width'; |
||||
|
that.hfunc = 'height'; |
||||
|
} else { |
||||
|
that.wfunc = 'innerWidth'; |
||||
|
that.hfunc = 'innerHeight'; |
||||
|
} |
||||
|
|
||||
|
// create tooltip DOM element
|
||||
|
var $tip = that.getDomElement(); |
||||
|
|
||||
|
// bind event
|
||||
|
$( plot.getPlaceholder() ).bind("plothover", plothover); |
||||
|
|
||||
|
$(eventHolder).bind('mousemove', mouseMove); |
||||
|
}); |
||||
|
|
||||
|
plot.hooks.shutdown.push(function (plot, eventHolder){ |
||||
|
$(plot.getPlaceholder()).unbind("plothover", plothover); |
||||
|
$(eventHolder).unbind("mousemove", mouseMove); |
||||
|
}); |
||||
|
|
||||
|
function mouseMove(e){ |
||||
|
var pos = {}; |
||||
|
pos.x = e.pageX; |
||||
|
pos.y = e.pageY; |
||||
|
plot.setTooltipPosition(pos); |
||||
|
} |
||||
|
|
||||
|
function plothover(event, pos, item) { |
||||
|
// Simple distance formula.
|
||||
|
var lineDistance = function (p1x, p1y, p2x, p2y) { |
||||
|
return Math.sqrt((p2x - p1x) * (p2x - p1x) + (p2y - p1y) * (p2y - p1y)); |
||||
|
}; |
||||
|
|
||||
|
// Here is some voodoo magic for determining the distance to a line form a given point {x, y}.
|
||||
|
var dotLineLength = function (x, y, x0, y0, x1, y1, o) { |
||||
|
if (o && !(o = |
||||
|
function (x, y, x0, y0, x1, y1) { |
||||
|
if (typeof x0 !== 'undefined') return { x: x0, y: y }; |
||||
|
else if (typeof y0 !== 'undefined') return { x: x, y: y0 }; |
||||
|
|
||||
|
var left, |
||||
|
tg = -1 / ((y1 - y0) / (x1 - x0)); |
||||
|
|
||||
|
return { |
||||
|
x: left = (x1 * (x * tg - y + y0) + x0 * (x * -tg + y - y1)) / (tg * (x1 - x0) + y0 - y1), |
||||
|
y: tg * left - tg * x + y |
||||
|
}; |
||||
|
} (x, y, x0, y0, x1, y1), |
||||
|
o.x >= Math.min(x0, x1) && o.x <= Math.max(x0, x1) && o.y >= Math.min(y0, y1) && o.y <= Math.max(y0, y1)) |
||||
|
) { |
||||
|
var l1 = lineDistance(x, y, x0, y0), l2 = lineDistance(x, y, x1, y1); |
||||
|
return l1 > l2 ? l2 : l1; |
||||
|
} else { |
||||
|
var a = y0 - y1, b = x1 - x0, c = x0 * y1 - y0 * x1; |
||||
|
return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
if (item) { |
||||
|
plot.showTooltip(item, pos); |
||||
|
} else if (that.plotOptions.series.lines.show && that.tooltipOptions.lines.track === true) { |
||||
|
var closestTrace = { |
||||
|
distance: -1 |
||||
|
}; |
||||
|
|
||||
|
$.each(plot.getData(), function (i, series) { |
||||
|
var xBeforeIndex = 0, |
||||
|
xAfterIndex = -1; |
||||
|
|
||||
|
// Our search here assumes our data is sorted via the x-axis.
|
||||
|
// TODO: Improve efficiency somehow - search smaller sets of data.
|
||||
|
for (var j = 1; j < series.data.length; j++) { |
||||
|
if (series.data[j - 1][0] <= pos.x && series.data[j][0] >= pos.x) { |
||||
|
xBeforeIndex = j - 1; |
||||
|
xAfterIndex = j; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (xAfterIndex === -1) { |
||||
|
plot.hideTooltip(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var pointPrev = { x: series.data[xBeforeIndex][0], y: series.data[xBeforeIndex][1] }, |
||||
|
pointNext = { x: series.data[xAfterIndex][0], y: series.data[xAfterIndex][1] }; |
||||
|
|
||||
|
var distToLine = dotLineLength(pos.x, pos.y, pointPrev.x, pointPrev.y, pointNext.x, pointNext.y, false); |
||||
|
|
||||
|
if (distToLine < that.tooltipOptions.lines.threshold) { |
||||
|
|
||||
|
var closestIndex = lineDistance(pointPrev.x, pointPrev.y, pos.x, pos.y) < |
||||
|
lineDistance(pos.x, pos.y, pointNext.x, pointNext.y) ? xBeforeIndex : xAfterIndex; |
||||
|
|
||||
|
var pointSize = series.datapoints.pointsize; |
||||
|
|
||||
|
// Calculate the point on the line vertically closest to our cursor.
|
||||
|
var pointOnLine = [ |
||||
|
pos.x, |
||||
|
pointPrev.y + ((pointNext.y - pointPrev.y) * ((pos.x - pointPrev.x) / (pointNext.x - pointPrev.x))) |
||||
|
]; |
||||
|
|
||||
|
var item = { |
||||
|
datapoint: pointOnLine, |
||||
|
dataIndex: closestIndex, |
||||
|
series: series, |
||||
|
seriesIndex: i |
||||
|
}; |
||||
|
|
||||
|
if (closestTrace.distance === -1 || distToLine < closestTrace.distance) { |
||||
|
closestTrace = { |
||||
|
distance: distToLine, |
||||
|
item: item |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (closestTrace.distance !== -1) |
||||
|
plot.showTooltip(closestTrace.item, pos); |
||||
|
else |
||||
|
plot.hideTooltip(); |
||||
|
} else { |
||||
|
plot.hideTooltip(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Quick little function for setting the tooltip position.
|
||||
|
plot.setTooltipPosition = function (pos) { |
||||
|
var $tip = that.getDomElement(); |
||||
|
|
||||
|
var totalTipWidth = $tip.outerWidth() + that.tooltipOptions.shifts.x; |
||||
|
var totalTipHeight = $tip.outerHeight() + that.tooltipOptions.shifts.y; |
||||
|
if ((pos.x - $(window).scrollLeft()) > ($(window)[that.wfunc]() - totalTipWidth)) { |
||||
|
pos.x -= totalTipWidth; |
||||
|
} |
||||
|
if ((pos.y - $(window).scrollTop()) > ($(window)[that.hfunc]() - totalTipHeight)) { |
||||
|
pos.y -= totalTipHeight; |
||||
|
} |
||||
|
that.tipPosition.x = pos.x; |
||||
|
that.tipPosition.y = pos.y; |
||||
|
}; |
||||
|
|
||||
|
// Quick little function for showing the tooltip.
|
||||
|
plot.showTooltip = function (target, position) { |
||||
|
var $tip = that.getDomElement(); |
||||
|
|
||||
|
// convert tooltip content template to real tipText
|
||||
|
var tipText = that.stringFormat(that.tooltipOptions.content, target); |
||||
|
|
||||
|
$tip.html(tipText); |
||||
|
plot.setTooltipPosition({ x: position.pageX, y: position.pageY }); |
||||
|
$tip.css({ |
||||
|
left: that.tipPosition.x + that.tooltipOptions.shifts.x, |
||||
|
top: that.tipPosition.y + that.tooltipOptions.shifts.y |
||||
|
}).show(); |
||||
|
|
||||
|
// run callback
|
||||
|
if (typeof that.tooltipOptions.onHover === 'function') { |
||||
|
that.tooltipOptions.onHover(target, $tip); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Quick little function for hiding the tooltip.
|
||||
|
plot.hideTooltip = function () { |
||||
|
that.getDomElement().hide().html(''); |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* get or create tooltip DOM element |
||||
|
* @return jQuery object |
||||
|
*/ |
||||
|
FlotTooltip.prototype.getDomElement = function () { |
||||
|
var $tip = $('#' + this.tooltipOptions.id); |
||||
|
|
||||
|
if( $tip.length === 0 ){ |
||||
|
$tip = $('<div />').attr('id', this.tooltipOptions.id); |
||||
|
$tip.appendTo('body').hide().css({position: 'absolute'}); |
||||
|
|
||||
|
if(this.tooltipOptions.defaultTheme) { |
||||
|
$tip.css({ |
||||
|
'background': '#fff', |
||||
|
'z-index': '1040', |
||||
|
'padding': '0.4em 0.6em', |
||||
|
'border-radius': '0.5em', |
||||
|
'font-size': '0.8em', |
||||
|
'border': '1px solid #111', |
||||
|
'display': 'none', |
||||
|
'white-space': 'nowrap' |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return $tip; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* core function, create tooltip content |
||||
|
* @param {string} content - template with tooltip content |
||||
|
* @param {object} item - Flot item |
||||
|
* @return {string} real tooltip content for current item |
||||
|
*/ |
||||
|
FlotTooltip.prototype.stringFormat = function (content, item) { |
||||
|
|
||||
|
var percentPattern = /%p\.{0,1}(\d{0,})/; |
||||
|
var seriesPattern = /%s/; |
||||
|
var xLabelPattern = /%lx/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
|
||||
|
var yLabelPattern = /%ly/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
|
||||
|
var xPattern = /%x\.{0,1}(\d{0,})/; |
||||
|
var yPattern = /%y\.{0,1}(\d{0,})/; |
||||
|
var xPatternWithoutPrecision = "%x"; |
||||
|
var yPatternWithoutPrecision = "%y"; |
||||
|
var customTextPattern = "%ct"; |
||||
|
|
||||
|
var x, y, customText, p; |
||||
|
|
||||
|
// for threshold plugin we need to read data from different place
|
||||
|
if (typeof item.series.threshold !== "undefined") { |
||||
|
x = item.datapoint[0]; |
||||
|
y = item.datapoint[1]; |
||||
|
customText = item.datapoint[2]; |
||||
|
} else if (typeof item.series.lines !== "undefined" && item.series.lines.steps) { |
||||
|
x = item.series.datapoints.points[item.dataIndex * 2]; |
||||
|
y = item.series.datapoints.points[item.dataIndex * 2 + 1]; |
||||
|
// TODO: where to find custom text in this variant?
|
||||
|
customText = ""; |
||||
|
} else { |
||||
|
x = item.series.data[item.dataIndex][0]; |
||||
|
y = item.series.data[item.dataIndex][1]; |
||||
|
customText = item.series.data[item.dataIndex][2]; |
||||
|
} |
||||
|
|
||||
|
// I think this is only in case of threshold plugin
|
||||
|
if (item.series.label === null && item.series.originSeries) { |
||||
|
item.series.label = item.series.originSeries.label; |
||||
|
} |
||||
|
|
||||
|
// if it is a function callback get the content string
|
||||
|
if (typeof(content) === 'function') { |
||||
|
content = content(item.series.label, x, y, item); |
||||
|
} |
||||
|
|
||||
|
// percent match for pie charts and stacked percent
|
||||
|
if (typeof (item.series.percent) !== 'undefined') { |
||||
|
p = item.series.percent; |
||||
|
} else if (typeof (item.series.percents) !== 'undefined') { |
||||
|
p = item.series.percents[item.dataIndex]; |
||||
|
} |
||||
|
if (typeof p === 'number') { |
||||
|
content = this.adjustValPrecision(percentPattern, content, p); |
||||
|
} |
||||
|
|
||||
|
// series match
|
||||
|
if (typeof(item.series.label) !== 'undefined') { |
||||
|
content = content.replace(seriesPattern, item.series.label); |
||||
|
} else { |
||||
|
//remove %s if label is undefined
|
||||
|
content = content.replace(seriesPattern, ""); |
||||
|
} |
||||
|
|
||||
|
// x axis label match
|
||||
|
if (this.hasAxisLabel('xaxis', item)) { |
||||
|
content = content.replace(xLabelPattern, item.series.xaxis.options.axisLabel); |
||||
|
} else { |
||||
|
//remove %lx if axis label is undefined or axislabels plugin not present
|
||||
|
content = content.replace(xLabelPattern, ""); |
||||
|
} |
||||
|
|
||||
|
// y axis label match
|
||||
|
if (this.hasAxisLabel('yaxis', item)) { |
||||
|
content = content.replace(yLabelPattern, item.series.yaxis.options.axisLabel); |
||||
|
} else { |
||||
|
//remove %ly if axis label is undefined or axislabels plugin not present
|
||||
|
content = content.replace(yLabelPattern, ""); |
||||
|
} |
||||
|
|
||||
|
// time mode axes with custom dateFormat
|
||||
|
if (this.isTimeMode('xaxis', item) && this.isXDateFormat(item)) { |
||||
|
content = content.replace(xPattern, this.timestampToDate(x, this.tooltipOptions.xDateFormat, item.series.xaxis.options)); |
||||
|
} |
||||
|
if (this.isTimeMode('yaxis', item) && this.isYDateFormat(item)) { |
||||
|
content = content.replace(yPattern, this.timestampToDate(y, this.tooltipOptions.yDateFormat, item.series.yaxis.options)); |
||||
|
} |
||||
|
|
||||
|
// set precision if defined
|
||||
|
if (typeof x === 'number') { |
||||
|
content = this.adjustValPrecision(xPattern, content, x); |
||||
|
} |
||||
|
if (typeof y === 'number') { |
||||
|
content = this.adjustValPrecision(yPattern, content, y); |
||||
|
} |
||||
|
|
||||
|
// change x from number to given label, if given
|
||||
|
if (typeof item.series.xaxis.ticks !== 'undefined') { |
||||
|
|
||||
|
var ticks; |
||||
|
if (this.hasRotatedXAxisTicks(item)) { |
||||
|
// xaxis.ticks will be an empty array if tickRotor is being used, but the values are available in rotatedTicks
|
||||
|
ticks = 'rotatedTicks'; |
||||
|
} else { |
||||
|
ticks = 'ticks'; |
||||
|
} |
||||
|
|
||||
|
// see https://github.com/krzysu/flot.tooltip/issues/65
|
||||
|
var tickIndex = item.dataIndex + item.seriesIndex; |
||||
|
|
||||
|
if (item.series.xaxis[ticks].length > tickIndex && !this.isTimeMode('xaxis', item)) { |
||||
|
var valueX = (this.isCategoriesMode('xaxis', item)) ? item.series.xaxis[ticks][tickIndex].label : item.series.xaxis[ticks][tickIndex].v; |
||||
|
if (valueX === x) { |
||||
|
content = content.replace(xPattern, item.series.xaxis[ticks][tickIndex].label); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// change y from number to given label, if given
|
||||
|
if (typeof item.series.yaxis.ticks !== 'undefined') { |
||||
|
for (var index in item.series.yaxis.ticks) { |
||||
|
if (item.series.yaxis.ticks.hasOwnProperty(index)) { |
||||
|
var valueY = (this.isCategoriesMode('yaxis', item)) ? item.series.yaxis.ticks[index].label : item.series.yaxis.ticks[index].v; |
||||
|
if (valueY === y) { |
||||
|
content = content.replace(yPattern, item.series.yaxis.ticks[index].label); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// if no value customization, use tickFormatter by default
|
||||
|
if (typeof item.series.xaxis.tickFormatter !== 'undefined') { |
||||
|
//escape dollar
|
||||
|
content = content.replace(xPatternWithoutPrecision, item.series.xaxis.tickFormatter(x, item.series.xaxis).replace(/\$/g, '$$')); |
||||
|
} |
||||
|
if (typeof item.series.yaxis.tickFormatter !== 'undefined') { |
||||
|
//escape dollar
|
||||
|
content = content.replace(yPatternWithoutPrecision, item.series.yaxis.tickFormatter(y, item.series.yaxis).replace(/\$/g, '$$')); |
||||
|
} |
||||
|
|
||||
|
if (customText) |
||||
|
content = content.replace(customTextPattern, customText); |
||||
|
|
||||
|
return content; |
||||
|
}; |
||||
|
|
||||
|
// helpers just for readability
|
||||
|
FlotTooltip.prototype.isTimeMode = function (axisName, item) { |
||||
|
return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'time'); |
||||
|
}; |
||||
|
|
||||
|
FlotTooltip.prototype.isXDateFormat = function (item) { |
||||
|
return (typeof this.tooltipOptions.xDateFormat !== 'undefined' && this.tooltipOptions.xDateFormat !== null); |
||||
|
}; |
||||
|
|
||||
|
FlotTooltip.prototype.isYDateFormat = function (item) { |
||||
|
return (typeof this.tooltipOptions.yDateFormat !== 'undefined' && this.tooltipOptions.yDateFormat !== null); |
||||
|
}; |
||||
|
|
||||
|
FlotTooltip.prototype.isCategoriesMode = function (axisName, item) { |
||||
|
return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'categories'); |
||||
|
}; |
||||
|
|
||||
|
//
|
||||
|
FlotTooltip.prototype.timestampToDate = function (tmst, dateFormat, options) { |
||||
|
var theDate = $.plot.dateGenerator(tmst, options); |
||||
|
return $.plot.formatDate(theDate, dateFormat, this.tooltipOptions.monthNames, this.tooltipOptions.dayNames); |
||||
|
}; |
||||
|
|
||||
|
//
|
||||
|
FlotTooltip.prototype.adjustValPrecision = function (pattern, content, value) { |
||||
|
|
||||
|
var precision; |
||||
|
var matchResult = content.match(pattern); |
||||
|
if( matchResult !== null ) { |
||||
|
if(RegExp.$1 !== '') { |
||||
|
precision = RegExp.$1; |
||||
|
value = value.toFixed(precision); |
||||
|
|
||||
|
// only replace content if precision exists, in other case use thickformater
|
||||
|
content = content.replace(pattern, value); |
||||
|
} |
||||
|
} |
||||
|
return content; |
||||
|
}; |
||||
|
|
||||
|
// other plugins detection below
|
||||
|
|
||||
|
// check if flot-axislabels plugin (https://github.com/markrcote/flot-axislabels) is used and that an axis label is given
|
||||
|
FlotTooltip.prototype.hasAxisLabel = function (axisName, item) { |
||||
|
return ($.inArray(this.plotPlugins, 'axisLabels') !== -1 && typeof item.series[axisName].options.axisLabel !== 'undefined' && item.series[axisName].options.axisLabel.length > 0); |
||||
|
}; |
||||
|
|
||||
|
// check whether flot-tickRotor, a plugin which allows rotation of X-axis ticks, is being used
|
||||
|
FlotTooltip.prototype.hasRotatedXAxisTicks = function (item) { |
||||
|
return ($.inArray(this.plotPlugins, 'tickRotor') !== -1 && typeof item.series.xaxis.rotatedTicks !== 'undefined'); |
||||
|
}; |
||||
|
|
||||
|
//
|
||||
|
var init = function (plot) { |
||||
|
new FlotTooltip(plot); |
||||
|
}; |
||||
|
|
||||
|
// define Flot plugin
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: defaultOptions, |
||||
|
name: 'tooltip', |
||||
|
version: '0.8.4' |
||||
|
}); |
||||
|
|
||||
|
})(jQuery); |
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,479 @@ |
|||||
|
(function ($) { |
||||
|
// plugin options, default values
|
||||
|
var defaultOptions = { |
||||
|
tooltip: false, |
||||
|
tooltipOpts: { |
||||
|
id: "flotTip", |
||||
|
content: "%s | X: %x | Y: %y", |
||||
|
// allowed templates are:
|
||||
|
// %s -> series label,
|
||||
|
// %lx -> x axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
|
||||
|
// %ly -> y axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
|
||||
|
// %x -> X value,
|
||||
|
// %y -> Y value,
|
||||
|
// %x.2 -> precision of X value,
|
||||
|
// %p -> percent
|
||||
|
xDateFormat: null, |
||||
|
yDateFormat: null, |
||||
|
monthNames: null, |
||||
|
dayNames: null, |
||||
|
shifts: { |
||||
|
x: 10, |
||||
|
y: 20 |
||||
|
}, |
||||
|
defaultTheme: true, |
||||
|
lines: { |
||||
|
track: false, |
||||
|
threshold: 0.05 |
||||
|
}, |
||||
|
|
||||
|
// callbacks
|
||||
|
onHover: function (flotItem, $tooltipEl) {}, |
||||
|
|
||||
|
$compat: false |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// object
|
||||
|
var FlotTooltip = function (plot) { |
||||
|
// variables
|
||||
|
this.tipPosition = {x: 0, y: 0}; |
||||
|
|
||||
|
this.init(plot); |
||||
|
}; |
||||
|
|
||||
|
// main plugin function
|
||||
|
FlotTooltip.prototype.init = function (plot) { |
||||
|
var that = this; |
||||
|
|
||||
|
// detect other flot plugins
|
||||
|
var plotPluginsLength = $.plot.plugins.length; |
||||
|
this.plotPlugins = []; |
||||
|
|
||||
|
if (plotPluginsLength) { |
||||
|
for (var p = 0; p < plotPluginsLength; p++) { |
||||
|
this.plotPlugins.push($.plot.plugins[p].name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
plot.hooks.bindEvents.push(function (plot, eventHolder) { |
||||
|
|
||||
|
// get plot options
|
||||
|
that.plotOptions = plot.getOptions(); |
||||
|
|
||||
|
// if not enabled return
|
||||
|
if (that.plotOptions.tooltip === false || typeof that.plotOptions.tooltip === 'undefined') return; |
||||
|
|
||||
|
// shortcut to access tooltip options
|
||||
|
that.tooltipOptions = that.plotOptions.tooltipOpts; |
||||
|
|
||||
|
if (that.tooltipOptions.$compat) { |
||||
|
that.wfunc = 'width'; |
||||
|
that.hfunc = 'height'; |
||||
|
} else { |
||||
|
that.wfunc = 'innerWidth'; |
||||
|
that.hfunc = 'innerHeight'; |
||||
|
} |
||||
|
|
||||
|
// create tooltip DOM element
|
||||
|
var $tip = that.getDomElement(); |
||||
|
|
||||
|
// bind event
|
||||
|
$( plot.getPlaceholder() ).bind("plothover", plothover); |
||||
|
|
||||
|
$(eventHolder).bind('mousemove', mouseMove); |
||||
|
}); |
||||
|
|
||||
|
plot.hooks.shutdown.push(function (plot, eventHolder){ |
||||
|
$(plot.getPlaceholder()).unbind("plothover", plothover); |
||||
|
$(eventHolder).unbind("mousemove", mouseMove); |
||||
|
}); |
||||
|
|
||||
|
function mouseMove(e){ |
||||
|
var pos = {}; |
||||
|
pos.x = e.pageX; |
||||
|
pos.y = e.pageY; |
||||
|
plot.setTooltipPosition(pos); |
||||
|
} |
||||
|
|
||||
|
function plothover(event, pos, item) { |
||||
|
// Simple distance formula.
|
||||
|
var lineDistance = function (p1x, p1y, p2x, p2y) { |
||||
|
return Math.sqrt((p2x - p1x) * (p2x - p1x) + (p2y - p1y) * (p2y - p1y)); |
||||
|
}; |
||||
|
|
||||
|
// Here is some voodoo magic for determining the distance to a line form a given point {x, y}.
|
||||
|
var dotLineLength = function (x, y, x0, y0, x1, y1, o) { |
||||
|
if (o && !(o = |
||||
|
function (x, y, x0, y0, x1, y1) { |
||||
|
if (typeof x0 !== 'undefined') return { x: x0, y: y }; |
||||
|
else if (typeof y0 !== 'undefined') return { x: x, y: y0 }; |
||||
|
|
||||
|
var left, |
||||
|
tg = -1 / ((y1 - y0) / (x1 - x0)); |
||||
|
|
||||
|
return { |
||||
|
x: left = (x1 * (x * tg - y + y0) + x0 * (x * -tg + y - y1)) / (tg * (x1 - x0) + y0 - y1), |
||||
|
y: tg * left - tg * x + y |
||||
|
}; |
||||
|
} (x, y, x0, y0, x1, y1), |
||||
|
o.x >= Math.min(x0, x1) && o.x <= Math.max(x0, x1) && o.y >= Math.min(y0, y1) && o.y <= Math.max(y0, y1)) |
||||
|
) { |
||||
|
var l1 = lineDistance(x, y, x0, y0), l2 = lineDistance(x, y, x1, y1); |
||||
|
return l1 > l2 ? l2 : l1; |
||||
|
} else { |
||||
|
var a = y0 - y1, b = x1 - x0, c = x0 * y1 - y0 * x1; |
||||
|
return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
if (item) { |
||||
|
plot.showTooltip(item, pos); |
||||
|
} else if (that.plotOptions.series.lines.show && that.tooltipOptions.lines.track === true) { |
||||
|
var closestTrace = { |
||||
|
distance: -1 |
||||
|
}; |
||||
|
|
||||
|
$.each(plot.getData(), function (i, series) { |
||||
|
var xBeforeIndex = 0, |
||||
|
xAfterIndex = -1; |
||||
|
|
||||
|
// Our search here assumes our data is sorted via the x-axis.
|
||||
|
// TODO: Improve efficiency somehow - search smaller sets of data.
|
||||
|
for (var j = 1; j < series.data.length; j++) { |
||||
|
if (series.data[j - 1][0] <= pos.x && series.data[j][0] >= pos.x) { |
||||
|
xBeforeIndex = j - 1; |
||||
|
xAfterIndex = j; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (xAfterIndex === -1) { |
||||
|
plot.hideTooltip(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var pointPrev = { x: series.data[xBeforeIndex][0], y: series.data[xBeforeIndex][1] }, |
||||
|
pointNext = { x: series.data[xAfterIndex][0], y: series.data[xAfterIndex][1] }; |
||||
|
|
||||
|
var distToLine = dotLineLength(pos.x, pos.y, pointPrev.x, pointPrev.y, pointNext.x, pointNext.y, false); |
||||
|
|
||||
|
if (distToLine < that.tooltipOptions.lines.threshold) { |
||||
|
|
||||
|
var closestIndex = lineDistance(pointPrev.x, pointPrev.y, pos.x, pos.y) < |
||||
|
lineDistance(pos.x, pos.y, pointNext.x, pointNext.y) ? xBeforeIndex : xAfterIndex; |
||||
|
|
||||
|
var pointSize = series.datapoints.pointsize; |
||||
|
|
||||
|
// Calculate the point on the line vertically closest to our cursor.
|
||||
|
var pointOnLine = [ |
||||
|
pos.x, |
||||
|
pointPrev.y + ((pointNext.y - pointPrev.y) * ((pos.x - pointPrev.x) / (pointNext.x - pointPrev.x))) |
||||
|
]; |
||||
|
|
||||
|
var item = { |
||||
|
datapoint: pointOnLine, |
||||
|
dataIndex: closestIndex, |
||||
|
series: series, |
||||
|
seriesIndex: i |
||||
|
}; |
||||
|
|
||||
|
if (closestTrace.distance === -1 || distToLine < closestTrace.distance) { |
||||
|
closestTrace = { |
||||
|
distance: distToLine, |
||||
|
item: item |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (closestTrace.distance !== -1) |
||||
|
plot.showTooltip(closestTrace.item, pos); |
||||
|
else |
||||
|
plot.hideTooltip(); |
||||
|
} else { |
||||
|
plot.hideTooltip(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Quick little function for setting the tooltip position.
|
||||
|
plot.setTooltipPosition = function (pos) { |
||||
|
var $tip = that.getDomElement(); |
||||
|
|
||||
|
var totalTipWidth = $tip.outerWidth() + that.tooltipOptions.shifts.x; |
||||
|
var totalTipHeight = $tip.outerHeight() + that.tooltipOptions.shifts.y; |
||||
|
if ((pos.x - $(window).scrollLeft()) > ($(window)[that.wfunc]() - totalTipWidth)) { |
||||
|
pos.x -= totalTipWidth; |
||||
|
} |
||||
|
if ((pos.y - $(window).scrollTop()) > ($(window)[that.hfunc]() - totalTipHeight)) { |
||||
|
pos.y -= totalTipHeight; |
||||
|
} |
||||
|
that.tipPosition.x = pos.x; |
||||
|
that.tipPosition.y = pos.y; |
||||
|
}; |
||||
|
|
||||
|
// Quick little function for showing the tooltip.
|
||||
|
plot.showTooltip = function (target, position) { |
||||
|
var $tip = that.getDomElement(); |
||||
|
|
||||
|
// convert tooltip content template to real tipText
|
||||
|
var tipText = that.stringFormat(that.tooltipOptions.content, target); |
||||
|
|
||||
|
$tip.html(tipText); |
||||
|
plot.setTooltipPosition({ x: position.pageX, y: position.pageY }); |
||||
|
$tip.css({ |
||||
|
left: that.tipPosition.x + that.tooltipOptions.shifts.x, |
||||
|
top: that.tipPosition.y + that.tooltipOptions.shifts.y |
||||
|
}).show(); |
||||
|
|
||||
|
// run callback
|
||||
|
if (typeof that.tooltipOptions.onHover === 'function') { |
||||
|
that.tooltipOptions.onHover(target, $tip); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Quick little function for hiding the tooltip.
|
||||
|
plot.hideTooltip = function () { |
||||
|
that.getDomElement().hide().html(''); |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* get or create tooltip DOM element |
||||
|
* @return jQuery object |
||||
|
*/ |
||||
|
FlotTooltip.prototype.getDomElement = function () { |
||||
|
var $tip = $('#' + this.tooltipOptions.id); |
||||
|
|
||||
|
if( $tip.length === 0 ){ |
||||
|
$tip = $('<div />').attr('id', this.tooltipOptions.id); |
||||
|
$tip.appendTo('body').hide().css({position: 'absolute'}); |
||||
|
|
||||
|
if(this.tooltipOptions.defaultTheme) { |
||||
|
$tip.css({ |
||||
|
'background': '#fff', |
||||
|
'z-index': '1040', |
||||
|
'padding': '0.4em 0.6em', |
||||
|
'border-radius': '0.5em', |
||||
|
'font-size': '0.8em', |
||||
|
'border': '1px solid #111', |
||||
|
'display': 'none', |
||||
|
'white-space': 'nowrap' |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return $tip; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* core function, create tooltip content |
||||
|
* @param {string} content - template with tooltip content |
||||
|
* @param {object} item - Flot item |
||||
|
* @return {string} real tooltip content for current item |
||||
|
*/ |
||||
|
FlotTooltip.prototype.stringFormat = function (content, item) { |
||||
|
|
||||
|
var percentPattern = /%p\.{0,1}(\d{0,})/; |
||||
|
var seriesPattern = /%s/; |
||||
|
var xLabelPattern = /%lx/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
|
||||
|
var yLabelPattern = /%ly/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
|
||||
|
var xPattern = /%x\.{0,1}(\d{0,})/; |
||||
|
var yPattern = /%y\.{0,1}(\d{0,})/; |
||||
|
var xPatternWithoutPrecision = "%x"; |
||||
|
var yPatternWithoutPrecision = "%y"; |
||||
|
var customTextPattern = "%ct"; |
||||
|
|
||||
|
var x, y, customText, p; |
||||
|
|
||||
|
// for threshold plugin we need to read data from different place
|
||||
|
if (typeof item.series.threshold !== "undefined") { |
||||
|
x = item.datapoint[0]; |
||||
|
y = item.datapoint[1]; |
||||
|
customText = item.datapoint[2]; |
||||
|
} else if (typeof item.series.lines !== "undefined" && item.series.lines.steps) { |
||||
|
x = item.series.datapoints.points[item.dataIndex * 2]; |
||||
|
y = item.series.datapoints.points[item.dataIndex * 2 + 1]; |
||||
|
// TODO: where to find custom text in this variant?
|
||||
|
customText = ""; |
||||
|
} else { |
||||
|
x = item.series.data[item.dataIndex][0]; |
||||
|
y = item.series.data[item.dataIndex][1]; |
||||
|
customText = item.series.data[item.dataIndex][2]; |
||||
|
} |
||||
|
|
||||
|
// I think this is only in case of threshold plugin
|
||||
|
if (item.series.label === null && item.series.originSeries) { |
||||
|
item.series.label = item.series.originSeries.label; |
||||
|
} |
||||
|
|
||||
|
// if it is a function callback get the content string
|
||||
|
if (typeof(content) === 'function') { |
||||
|
content = content(item.series.label, x, y, item); |
||||
|
} |
||||
|
|
||||
|
// percent match for pie charts and stacked percent
|
||||
|
if (typeof (item.series.percent) !== 'undefined') { |
||||
|
p = item.series.percent; |
||||
|
} else if (typeof (item.series.percents) !== 'undefined') { |
||||
|
p = item.series.percents[item.dataIndex]; |
||||
|
} |
||||
|
if (typeof p === 'number') { |
||||
|
content = this.adjustValPrecision(percentPattern, content, p); |
||||
|
} |
||||
|
|
||||
|
// series match
|
||||
|
if (typeof(item.series.label) !== 'undefined') { |
||||
|
content = content.replace(seriesPattern, item.series.label); |
||||
|
} else { |
||||
|
//remove %s if label is undefined
|
||||
|
content = content.replace(seriesPattern, ""); |
||||
|
} |
||||
|
|
||||
|
// x axis label match
|
||||
|
if (this.hasAxisLabel('xaxis', item)) { |
||||
|
content = content.replace(xLabelPattern, item.series.xaxis.options.axisLabel); |
||||
|
} else { |
||||
|
//remove %lx if axis label is undefined or axislabels plugin not present
|
||||
|
content = content.replace(xLabelPattern, ""); |
||||
|
} |
||||
|
|
||||
|
// y axis label match
|
||||
|
if (this.hasAxisLabel('yaxis', item)) { |
||||
|
content = content.replace(yLabelPattern, item.series.yaxis.options.axisLabel); |
||||
|
} else { |
||||
|
//remove %ly if axis label is undefined or axislabels plugin not present
|
||||
|
content = content.replace(yLabelPattern, ""); |
||||
|
} |
||||
|
|
||||
|
// time mode axes with custom dateFormat
|
||||
|
if (this.isTimeMode('xaxis', item) && this.isXDateFormat(item)) { |
||||
|
content = content.replace(xPattern, this.timestampToDate(x, this.tooltipOptions.xDateFormat, item.series.xaxis.options)); |
||||
|
} |
||||
|
if (this.isTimeMode('yaxis', item) && this.isYDateFormat(item)) { |
||||
|
content = content.replace(yPattern, this.timestampToDate(y, this.tooltipOptions.yDateFormat, item.series.yaxis.options)); |
||||
|
} |
||||
|
|
||||
|
// set precision if defined
|
||||
|
if (typeof x === 'number') { |
||||
|
content = this.adjustValPrecision(xPattern, content, x); |
||||
|
} |
||||
|
if (typeof y === 'number') { |
||||
|
content = this.adjustValPrecision(yPattern, content, y); |
||||
|
} |
||||
|
|
||||
|
// change x from number to given label, if given
|
||||
|
if (typeof item.series.xaxis.ticks !== 'undefined') { |
||||
|
|
||||
|
var ticks; |
||||
|
if (this.hasRotatedXAxisTicks(item)) { |
||||
|
// xaxis.ticks will be an empty array if tickRotor is being used, but the values are available in rotatedTicks
|
||||
|
ticks = 'rotatedTicks'; |
||||
|
} else { |
||||
|
ticks = 'ticks'; |
||||
|
} |
||||
|
|
||||
|
// see https://github.com/krzysu/flot.tooltip/issues/65
|
||||
|
var tickIndex = item.dataIndex + item.seriesIndex; |
||||
|
|
||||
|
if (item.series.xaxis[ticks].length > tickIndex && !this.isTimeMode('xaxis', item)) { |
||||
|
var valueX = (this.isCategoriesMode('xaxis', item)) ? item.series.xaxis[ticks][tickIndex].label : item.series.xaxis[ticks][tickIndex].v; |
||||
|
if (valueX === x) { |
||||
|
content = content.replace(xPattern, item.series.xaxis[ticks][tickIndex].label); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// change y from number to given label, if given
|
||||
|
if (typeof item.series.yaxis.ticks !== 'undefined') { |
||||
|
for (var index in item.series.yaxis.ticks) { |
||||
|
if (item.series.yaxis.ticks.hasOwnProperty(index)) { |
||||
|
var valueY = (this.isCategoriesMode('yaxis', item)) ? item.series.yaxis.ticks[index].label : item.series.yaxis.ticks[index].v; |
||||
|
if (valueY === y) { |
||||
|
content = content.replace(yPattern, item.series.yaxis.ticks[index].label); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// if no value customization, use tickFormatter by default
|
||||
|
if (typeof item.series.xaxis.tickFormatter !== 'undefined') { |
||||
|
//escape dollar
|
||||
|
content = content.replace(xPatternWithoutPrecision, item.series.xaxis.tickFormatter(x, item.series.xaxis).replace(/\$/g, '$$')); |
||||
|
} |
||||
|
if (typeof item.series.yaxis.tickFormatter !== 'undefined') { |
||||
|
//escape dollar
|
||||
|
content = content.replace(yPatternWithoutPrecision, item.series.yaxis.tickFormatter(y, item.series.yaxis).replace(/\$/g, '$$')); |
||||
|
} |
||||
|
|
||||
|
if (customText) |
||||
|
content = content.replace(customTextPattern, customText); |
||||
|
|
||||
|
return content; |
||||
|
}; |
||||
|
|
||||
|
// helpers just for readability
|
||||
|
FlotTooltip.prototype.isTimeMode = function (axisName, item) { |
||||
|
return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'time'); |
||||
|
}; |
||||
|
|
||||
|
FlotTooltip.prototype.isXDateFormat = function (item) { |
||||
|
return (typeof this.tooltipOptions.xDateFormat !== 'undefined' && this.tooltipOptions.xDateFormat !== null); |
||||
|
}; |
||||
|
|
||||
|
FlotTooltip.prototype.isYDateFormat = function (item) { |
||||
|
return (typeof this.tooltipOptions.yDateFormat !== 'undefined' && this.tooltipOptions.yDateFormat !== null); |
||||
|
}; |
||||
|
|
||||
|
FlotTooltip.prototype.isCategoriesMode = function (axisName, item) { |
||||
|
return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'categories'); |
||||
|
}; |
||||
|
|
||||
|
//
|
||||
|
FlotTooltip.prototype.timestampToDate = function (tmst, dateFormat, options) { |
||||
|
var theDate = $.plot.dateGenerator(tmst, options); |
||||
|
return $.plot.formatDate(theDate, dateFormat, this.tooltipOptions.monthNames, this.tooltipOptions.dayNames); |
||||
|
}; |
||||
|
|
||||
|
//
|
||||
|
FlotTooltip.prototype.adjustValPrecision = function (pattern, content, value) { |
||||
|
|
||||
|
var precision; |
||||
|
var matchResult = content.match(pattern); |
||||
|
if( matchResult !== null ) { |
||||
|
if(RegExp.$1 !== '') { |
||||
|
precision = RegExp.$1; |
||||
|
value = value.toFixed(precision); |
||||
|
|
||||
|
// only replace content if precision exists, in other case use thickformater
|
||||
|
content = content.replace(pattern, value); |
||||
|
} |
||||
|
} |
||||
|
return content; |
||||
|
}; |
||||
|
|
||||
|
// other plugins detection below
|
||||
|
|
||||
|
// check if flot-axislabels plugin (https://github.com/markrcote/flot-axislabels) is used and that an axis label is given
|
||||
|
FlotTooltip.prototype.hasAxisLabel = function (axisName, item) { |
||||
|
return ($.inArray(this.plotPlugins, 'axisLabels') !== -1 && typeof item.series[axisName].options.axisLabel !== 'undefined' && item.series[axisName].options.axisLabel.length > 0); |
||||
|
}; |
||||
|
|
||||
|
// check whether flot-tickRotor, a plugin which allows rotation of X-axis ticks, is being used
|
||||
|
FlotTooltip.prototype.hasRotatedXAxisTicks = function (item) { |
||||
|
return ($.inArray(this.plotPlugins, 'tickRotor') !== -1 && typeof item.series.xaxis.rotatedTicks !== 'undefined'); |
||||
|
}; |
||||
|
|
||||
|
//
|
||||
|
var init = function (plot) { |
||||
|
new FlotTooltip(plot); |
||||
|
}; |
||||
|
|
||||
|
// define Flot plugin
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: defaultOptions, |
||||
|
name: 'tooltip', |
||||
|
version: '0.8.4' |
||||
|
}); |
||||
|
|
||||
|
})(jQuery); |
||||
@ -0,0 +1,19 @@ |
|||||
|
{ |
||||
|
"name": "Flot", |
||||
|
"version": "0.8.3", |
||||
|
"main": "jquery.flot.js", |
||||
|
"dependencies": { |
||||
|
"jquery": ">= 1.2.6" |
||||
|
}, |
||||
|
"homepage": "https://github.com/flot/flot", |
||||
|
"_release": "0.8.3", |
||||
|
"_resolution": { |
||||
|
"type": "version", |
||||
|
"tag": "v0.8.3", |
||||
|
"commit": "453b017cc5acfd75e252b93e8635f57f4196d45d" |
||||
|
}, |
||||
|
"_source": "git://github.com/flot/flot.git", |
||||
|
"_target": "~0.8.3", |
||||
|
"_originalSource": "flot", |
||||
|
"_direct": true |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
language: node_js |
||||
|
node_js: |
||||
|
- 0.8 |
||||
File diff suppressed because it is too large
@ -0,0 +1,98 @@ |
|||||
|
## Contributing to Flot ## |
||||
|
|
||||
|
We welcome all contributions, but following these guidelines results in less |
||||
|
work for us, and a faster and better response. |
||||
|
|
||||
|
### Issues ### |
||||
|
|
||||
|
Issues are not a way to ask general questions about Flot. If you see unexpected |
||||
|
behavior but are not 100% certain that it is a bug, please try posting to the |
||||
|
[forum](http://groups.google.com/group/flot-graphs) first, and confirm that |
||||
|
what you see is really a Flot problem before creating a new issue for it. When |
||||
|
reporting a bug, please include a working demonstration of the problem, if |
||||
|
possible, or at least a clear description of the options you're using and the |
||||
|
environment (browser and version, jQuery version, other libraries) that you're |
||||
|
running under. |
||||
|
|
||||
|
If you have suggestions for new features, or changes to existing ones, we'd |
||||
|
love to hear them! Please submit each suggestion as a separate new issue. |
||||
|
|
||||
|
If you would like to work on an existing issue, please make sure it is not |
||||
|
already assigned to someone else. If an issue is assigned to someone, that |
||||
|
person has already started working on it. So, pick unassigned issues to prevent |
||||
|
duplicated effort. |
||||
|
|
||||
|
### Pull Requests ### |
||||
|
|
||||
|
To make merging as easy as possible, please keep these rules in mind: |
||||
|
|
||||
|
1. Submit new features or architectural changes to the *<version>-work* |
||||
|
branch for the next major release. Submit bug fixes to the master branch. |
||||
|
|
||||
|
2. Divide larger changes into a series of small, logical commits with |
||||
|
descriptive messages. |
||||
|
|
||||
|
3. Rebase, if necessary, before submitting your pull request, to reduce the |
||||
|
work we need to do to merge it. |
||||
|
|
||||
|
4. Format your code according to the style guidelines below. |
||||
|
|
||||
|
### Flot Style Guidelines ### |
||||
|
|
||||
|
Flot follows the [jQuery Core Style Guidelines](http://docs.jquery.com/JQuery_Core_Style_Guidelines), |
||||
|
with the following updates and exceptions: |
||||
|
|
||||
|
#### Spacing #### |
||||
|
|
||||
|
Use four-space indents, no tabs. Do not add horizontal space around parameter |
||||
|
lists, loop definitions, or array/object indices. For example: |
||||
|
|
||||
|
```js |
||||
|
for ( var i = 0; i < data.length; i++ ) { // This block is wrong! |
||||
|
if ( data[ i ] > 1 ) { |
||||
|
data[ i ] = 2; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (var i = 0; i < data.length; i++) { // This block is correct! |
||||
|
if (data[i] > 1) { |
||||
|
data[i] = 2; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### Comments #### |
||||
|
|
||||
|
Use [jsDoc](http://usejsdoc.org) comments for all file and function headers. |
||||
|
Use // for all inline and block comments, regardless of length. |
||||
|
|
||||
|
All // comment blocks should have an empty line above *and* below them. For |
||||
|
example: |
||||
|
|
||||
|
```js |
||||
|
var a = 5; |
||||
|
|
||||
|
// We're going to loop here |
||||
|
// TODO: Make this loop faster, better, stronger! |
||||
|
|
||||
|
for (var x = 0; x < 10; x++) {} |
||||
|
``` |
||||
|
|
||||
|
#### Wrapping #### |
||||
|
|
||||
|
Block comments should be wrapped at 80 characters. |
||||
|
|
||||
|
Code should attempt to wrap at 80 characters, but may run longer if wrapping |
||||
|
would hurt readability more than having to scroll horizontally. This is a |
||||
|
judgement call made on a situational basis. |
||||
|
|
||||
|
Statements containing complex logic should not be wrapped arbitrarily if they |
||||
|
do not exceed 80 characters. For example: |
||||
|
|
||||
|
```js |
||||
|
if (a == 1 && // This block is wrong! |
||||
|
b == 2 && |
||||
|
c == 3) {} |
||||
|
|
||||
|
if (a == 1 && b == 2 && c == 3) {} // This block is correct! |
||||
|
``` |
||||
@ -0,0 +1,75 @@ |
|||||
|
## Frequently asked questions ## |
||||
|
|
||||
|
#### How much data can Flot cope with? #### |
||||
|
|
||||
|
Flot will happily draw everything you send to it so the answer |
||||
|
depends on the browser. The excanvas emulation used for IE (built with |
||||
|
VML) makes IE by far the slowest browser so be sure to test with that |
||||
|
if IE users are in your target group (for large plots in IE, you can |
||||
|
also check out Flashcanvas which may be faster). |
||||
|
|
||||
|
1000 points is not a problem, but as soon as you start having more |
||||
|
points than the pixel width, you should probably start thinking about |
||||
|
downsampling/aggregation as this is near the resolution limit of the |
||||
|
chart anyway. If you downsample server-side, you also save bandwidth. |
||||
|
|
||||
|
|
||||
|
#### Flot isn't working when I'm using JSON data as source! #### |
||||
|
|
||||
|
Actually, Flot loves JSON data, you just got the format wrong. |
||||
|
Double check that you're not inputting strings instead of numbers, |
||||
|
like [["0", "-2.13"], ["5", "4.3"]]. This is most common mistake, and |
||||
|
the error might not show up immediately because Javascript can do some |
||||
|
conversion automatically. |
||||
|
|
||||
|
|
||||
|
#### Can I export the graph? #### |
||||
|
|
||||
|
You can grab the image rendered by the canvas element used by Flot |
||||
|
as a PNG or JPEG (remember to set a background). Note that it won't |
||||
|
include anything not drawn in the canvas (such as the legend). And it |
||||
|
doesn't work with excanvas which uses VML, but you could try |
||||
|
Flashcanvas. |
||||
|
|
||||
|
|
||||
|
#### The bars are all tiny in time mode? #### |
||||
|
|
||||
|
It's not really possible to determine the bar width automatically. |
||||
|
So you have to set the width with the barWidth option which is NOT in |
||||
|
pixels, but in the units of the x axis (or the y axis for horizontal |
||||
|
bars). For time mode that's milliseconds so the default value of 1 |
||||
|
makes the bars 1 millisecond wide. |
||||
|
|
||||
|
|
||||
|
#### Can I use Flot with libraries like Mootools or Prototype? #### |
||||
|
|
||||
|
Yes, Flot supports it out of the box and it's easy! Just use jQuery |
||||
|
instead of $, e.g. call jQuery.plot instead of $.plot and use |
||||
|
jQuery(something) instead of $(something). As a convenience, you can |
||||
|
put in a DOM element for the graph placeholder where the examples and |
||||
|
the API documentation are using jQuery objects. |
||||
|
|
||||
|
Depending on how you include jQuery, you may have to add one line of |
||||
|
code to prevent jQuery from overwriting functions from the other |
||||
|
libraries, see the documentation in jQuery ("Using jQuery with other |
||||
|
libraries") for details. |
||||
|
|
||||
|
|
||||
|
#### Flot doesn't work with [insert name of Javascript UI framework]! #### |
||||
|
|
||||
|
Flot is using standard HTML to make charts. If this is not working, |
||||
|
it's probably because the framework you're using is doing something |
||||
|
weird with the DOM or with the CSS that is interfering with Flot. |
||||
|
|
||||
|
A common problem is that there's display:none on a container until the |
||||
|
user does something. Many tab widgets work this way, and there's |
||||
|
nothing wrong with it - you just can't call Flot inside a display:none |
||||
|
container as explained in the README so you need to hold off the Flot |
||||
|
call until the container is actually displayed (or use |
||||
|
visibility:hidden instead of display:none or move the container |
||||
|
off-screen). |
||||
|
|
||||
|
If you find there's a specific thing we can do to Flot to help, feel |
||||
|
free to submit a bug report. Otherwise, you're welcome to ask for help |
||||
|
on the forum/mailing list, but please don't submit a bug report to |
||||
|
Flot. |
||||
@ -0,0 +1,12 @@ |
|||||
|
# Makefile for generating minified files
|
||||
|
|
||||
|
.PHONY: all |
||||
|
|
||||
|
# we cheat and process all .js files instead of an exhaustive list
|
||||
|
all: $(patsubst %.js,%.min.js,$(filter-out %.min.js,$(wildcard *.js))) |
||||
|
|
||||
|
%.min.js: %.js |
||||
|
yui-compressor $< -o $@ |
||||
|
|
||||
|
test: |
||||
|
./node_modules/.bin/jshint *jquery.flot.js |
||||
File diff suppressed because it is too large
@ -0,0 +1,143 @@ |
|||||
|
## Writing plugins ## |
||||
|
|
||||
|
All you need to do to make a new plugin is creating an init function |
||||
|
and a set of options (if needed), stuffing it into an object and |
||||
|
putting it in the $.plot.plugins array. For example: |
||||
|
|
||||
|
```js |
||||
|
function myCoolPluginInit(plot) { |
||||
|
plot.coolstring = "Hello!"; |
||||
|
}; |
||||
|
|
||||
|
$.plot.plugins.push({ init: myCoolPluginInit, options: { ... } }); |
||||
|
|
||||
|
// if $.plot is called, it will return a plot object with the |
||||
|
// attribute "coolstring" |
||||
|
``` |
||||
|
|
||||
|
Now, given that the plugin might run in many different places, it's |
||||
|
a good idea to avoid leaking names. The usual trick here is wrap the |
||||
|
above lines in an anonymous function which is called immediately, like |
||||
|
this: (function () { inner code ... })(). To make it even more robust |
||||
|
in case $ is not bound to jQuery but some other Javascript library, we |
||||
|
can write it as |
||||
|
|
||||
|
```js |
||||
|
(function ($) { |
||||
|
// plugin definition |
||||
|
// ... |
||||
|
})(jQuery); |
||||
|
``` |
||||
|
|
||||
|
There's a complete example below, but you should also check out the |
||||
|
plugins bundled with Flot. |
||||
|
|
||||
|
|
||||
|
## Complete example ## |
||||
|
|
||||
|
Here is a simple debug plugin which alerts each of the series in the |
||||
|
plot. It has a single option that control whether it is enabled and |
||||
|
how much info to output: |
||||
|
|
||||
|
```js |
||||
|
(function ($) { |
||||
|
function init(plot) { |
||||
|
var debugLevel = 1; |
||||
|
|
||||
|
function checkDebugEnabled(plot, options) { |
||||
|
if (options.debug) { |
||||
|
debugLevel = options.debug; |
||||
|
plot.hooks.processDatapoints.push(alertSeries); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function alertSeries(plot, series, datapoints) { |
||||
|
var msg = "series " + series.label; |
||||
|
if (debugLevel > 1) { |
||||
|
msg += " with " + series.data.length + " points"; |
||||
|
alert(msg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
plot.hooks.processOptions.push(checkDebugEnabled); |
||||
|
} |
||||
|
|
||||
|
var options = { debug: 0 }; |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: "simpledebug", |
||||
|
version: "0.1" |
||||
|
}); |
||||
|
})(jQuery); |
||||
|
``` |
||||
|
|
||||
|
We also define "name" and "version". It's not used by Flot, but might |
||||
|
be helpful for other plugins in resolving dependencies. |
||||
|
|
||||
|
Put the above in a file named "jquery.flot.debug.js", include it in an |
||||
|
HTML page and then it can be used with: |
||||
|
|
||||
|
```js |
||||
|
$.plot($("#placeholder"), [...], { debug: 2 }); |
||||
|
``` |
||||
|
|
||||
|
This simple plugin illustrates a couple of points: |
||||
|
|
||||
|
- It uses the anonymous function trick to avoid name pollution. |
||||
|
- It can be enabled/disabled through an option. |
||||
|
- Variables in the init function can be used to store plot-specific |
||||
|
state between the hooks. |
||||
|
|
||||
|
The two last points are important because there may be multiple plots |
||||
|
on the same page, and you'd want to make sure they are not mixed up. |
||||
|
|
||||
|
|
||||
|
## Shutting down a plugin ## |
||||
|
|
||||
|
Each plot object has a shutdown hook which is run when plot.shutdown() |
||||
|
is called. This usually mostly happens in case another plot is made on |
||||
|
top of an existing one. |
||||
|
|
||||
|
The purpose of the hook is to give you a chance to unbind any event |
||||
|
handlers you've registered and remove any extra DOM things you've |
||||
|
inserted. |
||||
|
|
||||
|
The problem with event handlers is that you can have registered a |
||||
|
handler which is run in some point in the future, e.g. with |
||||
|
setTimeout(). Meanwhile, the plot may have been shutdown and removed, |
||||
|
but because your event handler is still referencing it, it can't be |
||||
|
garbage collected yet, and worse, if your handler eventually runs, it |
||||
|
may overwrite stuff on a completely different plot. |
||||
|
|
||||
|
|
||||
|
## Some hints on the options ## |
||||
|
|
||||
|
Plugins should always support appropriate options to enable/disable |
||||
|
them because the plugin user may have several plots on the same page |
||||
|
where only one should use the plugin. In most cases it's probably a |
||||
|
good idea if the plugin is turned off rather than on per default, just |
||||
|
like most of the powerful features in Flot. |
||||
|
|
||||
|
If the plugin needs options that are specific to each series, like the |
||||
|
points or lines options in core Flot, you can put them in "series" in |
||||
|
the options object, e.g. |
||||
|
|
||||
|
```js |
||||
|
var options = { |
||||
|
series: { |
||||
|
downsample: { |
||||
|
algorithm: null, |
||||
|
maxpoints: 1000 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Then they will be copied by Flot into each series, providing default |
||||
|
values in case none are specified. |
||||
|
|
||||
|
Think hard and long about naming the options. These names are going to |
||||
|
be public API, and code is going to depend on them if the plugin is |
||||
|
successful. |
||||
@ -0,0 +1,110 @@ |
|||||
|
# Flot [](https://travis-ci.org/flot/flot) |
||||
|
|
||||
|
## About ## |
||||
|
|
||||
|
Flot is a Javascript plotting library for jQuery. |
||||
|
Read more at the website: <http://www.flotcharts.org/> |
||||
|
|
||||
|
Take a look at the the examples in examples/index.html; they should give a good |
||||
|
impression of what Flot can do, and the source code of the examples is probably |
||||
|
the fastest way to learn how to use Flot. |
||||
|
|
||||
|
|
||||
|
## Installation ## |
||||
|
|
||||
|
Just include the Javascript file after you've included jQuery. |
||||
|
|
||||
|
Generally, all browsers that support the HTML5 canvas tag are |
||||
|
supported. |
||||
|
|
||||
|
For support for Internet Explorer < 9, you can use [Excanvas] |
||||
|
[excanvas], a canvas emulator; this is used in the examples bundled |
||||
|
with Flot. You just include the excanvas script like this: |
||||
|
|
||||
|
```html |
||||
|
<!--[if lte IE 8]><script language="javascript" type="text/javascript" src="excanvas.min.js"></script><![endif]--> |
||||
|
``` |
||||
|
|
||||
|
If it's not working on your development IE 6.0, check that it has |
||||
|
support for VML which Excanvas is relying on. It appears that some |
||||
|
stripped down versions used for test environments on virtual machines |
||||
|
lack the VML support. |
||||
|
|
||||
|
You can also try using [Flashcanvas][flashcanvas], which uses Flash to |
||||
|
do the emulation. Although Flash can be a bit slower to load than VML, |
||||
|
if you've got a lot of points, the Flash version can be much faster |
||||
|
overall. Flot contains some wrapper code for activating Excanvas which |
||||
|
Flashcanvas is compatible with. |
||||
|
|
||||
|
You need at least jQuery 1.2.6, but try at least 1.3.2 for interactive |
||||
|
charts because of performance improvements in event handling. |
||||
|
|
||||
|
|
||||
|
## Basic usage ## |
||||
|
|
||||
|
Create a placeholder div to put the graph in: |
||||
|
|
||||
|
```html |
||||
|
<div id="placeholder"></div> |
||||
|
``` |
||||
|
|
||||
|
You need to set the width and height of this div, otherwise the plot |
||||
|
library doesn't know how to scale the graph. You can do it inline like |
||||
|
this: |
||||
|
|
||||
|
```html |
||||
|
<div id="placeholder" style="width:600px;height:300px"></div> |
||||
|
``` |
||||
|
|
||||
|
You can also do it with an external stylesheet. Make sure that the |
||||
|
placeholder isn't within something with a display:none CSS property - |
||||
|
in that case, Flot has trouble measuring label dimensions which |
||||
|
results in garbled looks and might have trouble measuring the |
||||
|
placeholder dimensions which is fatal (it'll throw an exception). |
||||
|
|
||||
|
Then when the div is ready in the DOM, which is usually on document |
||||
|
ready, run the plot function: |
||||
|
|
||||
|
```js |
||||
|
$.plot($("#placeholder"), data, options); |
||||
|
``` |
||||
|
|
||||
|
Here, data is an array of data series and options is an object with |
||||
|
settings if you want to customize the plot. Take a look at the |
||||
|
examples for some ideas of what to put in or look at the |
||||
|
[API reference](API.md). Here's a quick example that'll draw a line |
||||
|
from (0, 0) to (1, 1): |
||||
|
|
||||
|
```js |
||||
|
$.plot($("#placeholder"), [ [[0, 0], [1, 1]] ], { yaxis: { max: 1 } }); |
||||
|
``` |
||||
|
|
||||
|
The plot function immediately draws the chart and then returns a plot |
||||
|
object with a couple of methods. |
||||
|
|
||||
|
|
||||
|
## What's with the name? ## |
||||
|
|
||||
|
First: it's pronounced with a short o, like "plot". Not like "flawed". |
||||
|
|
||||
|
So "Flot" rhymes with "plot". |
||||
|
|
||||
|
And if you look up "flot" in a Danish-to-English dictionary, some of |
||||
|
the words that come up are "good-looking", "attractive", "stylish", |
||||
|
"smart", "impressive", "extravagant". One of the main goals with Flot |
||||
|
is pretty looks. |
||||
|
|
||||
|
|
||||
|
## Notes about the examples ## |
||||
|
|
||||
|
In order to have a useful, functional example of time-series plots using time |
||||
|
zones, date.js from [timezone-js][timezone-js] (released under the Apache 2.0 |
||||
|
license) and the [Olson][olson] time zone database (released to the public |
||||
|
domain) have been included in the examples directory. They are used in |
||||
|
examples/axes-time-zones/index.html. |
||||
|
|
||||
|
|
||||
|
[excanvas]: http://code.google.com/p/explorercanvas/ |
||||
|
[flashcanvas]: http://code.google.com/p/flashcanvas/ |
||||
|
[timezone-js]: https://github.com/mde/timezone-js |
||||
|
[olson]: http://ftp.iana.org/time-zones |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"name": "Flot", |
||||
|
"version": "0.8.3", |
||||
|
"main": "jquery.flot.js", |
||||
|
"dependencies": { |
||||
|
"jquery": ">= 1.2.6" |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
@ -0,0 +1,27 @@ |
|||||
|
{ |
||||
|
"name": "flot", |
||||
|
"version": "0.8.3", |
||||
|
"title": "Flot", |
||||
|
"author": { |
||||
|
"name": "Ole Laursen", |
||||
|
"url": "https://github.com/OleLaursen" |
||||
|
}, |
||||
|
"licenses": [{ |
||||
|
"type": "MIT", |
||||
|
"url": "http://github.com/flot/flot/blob/master/LICENSE.txt" |
||||
|
}], |
||||
|
"dependencies": { |
||||
|
"jquery": ">=1.2.6" |
||||
|
}, |
||||
|
"description": "Flot is a pure JavaScript plotting library for jQuery, with a focus on simple usage, attractive looks and interactive features.", |
||||
|
"keywords": ["plot", "chart", "graph", "visualization", "canvas", "graphics"], |
||||
|
"homepage": "http://www.flotcharts.org", |
||||
|
"docs": "http://github.com/flot/flot/blob/master/API.md", |
||||
|
"demo": "http://www.flotcharts.org/flot/examples/", |
||||
|
"bugs": "http://github.com/flot/flot/issues", |
||||
|
"maintainers": [{ |
||||
|
"name": "David Schnur", |
||||
|
"email": "dnschnur@gmail.com", |
||||
|
"url": "http://github.com/dnschnur" |
||||
|
}] |
||||
|
} |
||||
@ -0,0 +1,180 @@ |
|||||
|
/* Plugin for jQuery for working with colors. |
||||
|
* |
||||
|
* Version 1.1. |
||||
|
* |
||||
|
* Inspiration from jQuery color animation plugin by John Resig. |
||||
|
* |
||||
|
* Released under the MIT license by Ole Laursen, October 2009. |
||||
|
* |
||||
|
* Examples: |
||||
|
* |
||||
|
* $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() |
||||
|
* var c = $.color.extract($("#mydiv"), 'background-color'); |
||||
|
* console.log(c.r, c.g, c.b, c.a); |
||||
|
* $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
|
||||
|
* |
||||
|
* Note that .scale() and .add() return the same modified object |
||||
|
* instead of making a new one. |
||||
|
* |
||||
|
* V. 1.1: Fix error handling so e.g. parsing an empty string does |
||||
|
* produce a color rather than just crashing. |
||||
|
*/ |
||||
|
|
||||
|
(function($) { |
||||
|
$.color = {}; |
||||
|
|
||||
|
// construct color object with some convenient chainable helpers
|
||||
|
$.color.make = function (r, g, b, a) { |
||||
|
var o = {}; |
||||
|
o.r = r || 0; |
||||
|
o.g = g || 0; |
||||
|
o.b = b || 0; |
||||
|
o.a = a != null ? a : 1; |
||||
|
|
||||
|
o.add = function (c, d) { |
||||
|
for (var i = 0; i < c.length; ++i) |
||||
|
o[c.charAt(i)] += d; |
||||
|
return o.normalize(); |
||||
|
}; |
||||
|
|
||||
|
o.scale = function (c, f) { |
||||
|
for (var i = 0; i < c.length; ++i) |
||||
|
o[c.charAt(i)] *= f; |
||||
|
return o.normalize(); |
||||
|
}; |
||||
|
|
||||
|
o.toString = function () { |
||||
|
if (o.a >= 1.0) { |
||||
|
return "rgb("+[o.r, o.g, o.b].join(",")+")"; |
||||
|
} else { |
||||
|
return "rgba("+[o.r, o.g, o.b, o.a].join(",")+")"; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
o.normalize = function () { |
||||
|
function clamp(min, value, max) { |
||||
|
return value < min ? min: (value > max ? max: value); |
||||
|
} |
||||
|
|
||||
|
o.r = clamp(0, parseInt(o.r), 255); |
||||
|
o.g = clamp(0, parseInt(o.g), 255); |
||||
|
o.b = clamp(0, parseInt(o.b), 255); |
||||
|
o.a = clamp(0, o.a, 1); |
||||
|
return o; |
||||
|
}; |
||||
|
|
||||
|
o.clone = function () { |
||||
|
return $.color.make(o.r, o.b, o.g, o.a); |
||||
|
}; |
||||
|
|
||||
|
return o.normalize(); |
||||
|
} |
||||
|
|
||||
|
// extract CSS color property from element, going up in the DOM
|
||||
|
// if it's "transparent"
|
||||
|
$.color.extract = function (elem, css) { |
||||
|
var c; |
||||
|
|
||||
|
do { |
||||
|
c = elem.css(css).toLowerCase(); |
||||
|
// keep going until we find an element that has color, or
|
||||
|
// we hit the body or root (have no parent)
|
||||
|
if (c != '' && c != 'transparent') |
||||
|
break; |
||||
|
elem = elem.parent(); |
||||
|
} while (elem.length && !$.nodeName(elem.get(0), "body")); |
||||
|
|
||||
|
// catch Safari's way of signalling transparent
|
||||
|
if (c == "rgba(0, 0, 0, 0)") |
||||
|
c = "transparent"; |
||||
|
|
||||
|
return $.color.parse(c); |
||||
|
} |
||||
|
|
||||
|
// parse CSS color string (like "rgb(10, 32, 43)" or "#fff"),
|
||||
|
// returns color object, if parsing failed, you get black (0, 0,
|
||||
|
// 0) out
|
||||
|
$.color.parse = function (str) { |
||||
|
var res, m = $.color.make; |
||||
|
|
||||
|
// Look for rgb(num,num,num)
|
||||
|
if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)) |
||||
|
return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10)); |
||||
|
|
||||
|
// Look for rgba(num,num,num,num)
|
||||
|
if (res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) |
||||
|
return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4])); |
||||
|
|
||||
|
// Look for rgb(num%,num%,num%)
|
||||
|
if (res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)) |
||||
|
return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55); |
||||
|
|
||||
|
// Look for rgba(num%,num%,num%,num)
|
||||
|
if (res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) |
||||
|
return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55, parseFloat(res[4])); |
||||
|
|
||||
|
// Look for #a0b1c2
|
||||
|
if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)) |
||||
|
return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16)); |
||||
|
|
||||
|
// Look for #fff
|
||||
|
if (res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)) |
||||
|
return m(parseInt(res[1]+res[1], 16), parseInt(res[2]+res[2], 16), parseInt(res[3]+res[3], 16)); |
||||
|
|
||||
|
// Otherwise, we're most likely dealing with a named color
|
||||
|
var name = $.trim(str).toLowerCase(); |
||||
|
if (name == "transparent") |
||||
|
return m(255, 255, 255, 0); |
||||
|
else { |
||||
|
// default to black
|
||||
|
res = lookupColors[name] || [0, 0, 0]; |
||||
|
return m(res[0], res[1], res[2]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var lookupColors = { |
||||
|
aqua:[0,255,255], |
||||
|
azure:[240,255,255], |
||||
|
beige:[245,245,220], |
||||
|
black:[0,0,0], |
||||
|
blue:[0,0,255], |
||||
|
brown:[165,42,42], |
||||
|
cyan:[0,255,255], |
||||
|
darkblue:[0,0,139], |
||||
|
darkcyan:[0,139,139], |
||||
|
darkgrey:[169,169,169], |
||||
|
darkgreen:[0,100,0], |
||||
|
darkkhaki:[189,183,107], |
||||
|
darkmagenta:[139,0,139], |
||||
|
darkolivegreen:[85,107,47], |
||||
|
darkorange:[255,140,0], |
||||
|
darkorchid:[153,50,204], |
||||
|
darkred:[139,0,0], |
||||
|
darksalmon:[233,150,122], |
||||
|
darkviolet:[148,0,211], |
||||
|
fuchsia:[255,0,255], |
||||
|
gold:[255,215,0], |
||||
|
green:[0,128,0], |
||||
|
indigo:[75,0,130], |
||||
|
khaki:[240,230,140], |
||||
|
lightblue:[173,216,230], |
||||
|
lightcyan:[224,255,255], |
||||
|
lightgreen:[144,238,144], |
||||
|
lightgrey:[211,211,211], |
||||
|
lightpink:[255,182,193], |
||||
|
lightyellow:[255,255,224], |
||||
|
lime:[0,255,0], |
||||
|
magenta:[255,0,255], |
||||
|
maroon:[128,0,0], |
||||
|
navy:[0,0,128], |
||||
|
olive:[128,128,0], |
||||
|
orange:[255,165,0], |
||||
|
pink:[255,192,203], |
||||
|
purple:[128,0,128], |
||||
|
violet:[128,0,128], |
||||
|
red:[255,0,0], |
||||
|
silver:[192,192,192], |
||||
|
white:[255,255,255], |
||||
|
yellow:[255,255,0] |
||||
|
}; |
||||
|
})(jQuery); |
||||
@ -0,0 +1,345 @@ |
|||||
|
/* Flot plugin for drawing all elements of a plot on the canvas. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
Flot normally produces certain elements, like axis labels and the legend, using |
||||
|
HTML elements. This permits greater interactivity and customization, and often |
||||
|
looks better, due to cross-browser canvas text inconsistencies and limitations. |
||||
|
|
||||
|
It can also be desirable to render the plot entirely in canvas, particularly |
||||
|
if the goal is to save it as an image, or if Flot is being used in a context |
||||
|
where the HTML DOM does not exist, as is the case within Node.js. This plugin |
||||
|
switches out Flot's standard drawing operations for canvas-only replacements. |
||||
|
|
||||
|
Currently the plugin supports only axis labels, but it will eventually allow |
||||
|
every element of the plot to be rendered directly to canvas. |
||||
|
|
||||
|
The plugin supports these options: |
||||
|
|
||||
|
{ |
||||
|
canvas: boolean |
||||
|
} |
||||
|
|
||||
|
The "canvas" option controls whether full canvas drawing is enabled, making it |
||||
|
possible to toggle on and off. This is useful when a plot uses HTML text in the |
||||
|
browser, but needs to redraw with canvas text when exporting as an image. |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
(function($) { |
||||
|
|
||||
|
var options = { |
||||
|
canvas: true |
||||
|
}; |
||||
|
|
||||
|
var render, getTextInfo, addText; |
||||
|
|
||||
|
// Cache the prototype hasOwnProperty for faster access
|
||||
|
|
||||
|
var hasOwnProperty = Object.prototype.hasOwnProperty; |
||||
|
|
||||
|
function init(plot, classes) { |
||||
|
|
||||
|
var Canvas = classes.Canvas; |
||||
|
|
||||
|
// We only want to replace the functions once; the second time around
|
||||
|
// we would just get our new function back. This whole replacing of
|
||||
|
// prototype functions is a disaster, and needs to be changed ASAP.
|
||||
|
|
||||
|
if (render == null) { |
||||
|
getTextInfo = Canvas.prototype.getTextInfo, |
||||
|
addText = Canvas.prototype.addText, |
||||
|
render = Canvas.prototype.render; |
||||
|
} |
||||
|
|
||||
|
// Finishes rendering the canvas, including overlaid text
|
||||
|
|
||||
|
Canvas.prototype.render = function() { |
||||
|
|
||||
|
if (!plot.getOptions().canvas) { |
||||
|
return render.call(this); |
||||
|
} |
||||
|
|
||||
|
var context = this.context, |
||||
|
cache = this._textCache; |
||||
|
|
||||
|
// For each text layer, render elements marked as active
|
||||
|
|
||||
|
context.save(); |
||||
|
context.textBaseline = "middle"; |
||||
|
|
||||
|
for (var layerKey in cache) { |
||||
|
if (hasOwnProperty.call(cache, layerKey)) { |
||||
|
var layerCache = cache[layerKey]; |
||||
|
for (var styleKey in layerCache) { |
||||
|
if (hasOwnProperty.call(layerCache, styleKey)) { |
||||
|
var styleCache = layerCache[styleKey], |
||||
|
updateStyles = true; |
||||
|
for (var key in styleCache) { |
||||
|
if (hasOwnProperty.call(styleCache, key)) { |
||||
|
|
||||
|
var info = styleCache[key], |
||||
|
positions = info.positions, |
||||
|
lines = info.lines; |
||||
|
|
||||
|
// Since every element at this level of the cache have the
|
||||
|
// same font and fill styles, we can just change them once
|
||||
|
// using the values from the first element.
|
||||
|
|
||||
|
if (updateStyles) { |
||||
|
context.fillStyle = info.font.color; |
||||
|
context.font = info.font.definition; |
||||
|
updateStyles = false; |
||||
|
} |
||||
|
|
||||
|
for (var i = 0, position; position = positions[i]; i++) { |
||||
|
if (position.active) { |
||||
|
for (var j = 0, line; line = position.lines[j]; j++) { |
||||
|
context.fillText(lines[j].text, line[0], line[1]); |
||||
|
} |
||||
|
} else { |
||||
|
positions.splice(i--, 1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (positions.length == 0) { |
||||
|
delete styleCache[key]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
context.restore(); |
||||
|
}; |
||||
|
|
||||
|
// Creates (if necessary) and returns a text info object.
|
||||
|
//
|
||||
|
// When the canvas option is set, the object looks like this:
|
||||
|
//
|
||||
|
// {
|
||||
|
// width: Width of the text's bounding box.
|
||||
|
// height: Height of the text's bounding box.
|
||||
|
// positions: Array of positions at which this text is drawn.
|
||||
|
// lines: [{
|
||||
|
// height: Height of this line.
|
||||
|
// widths: Width of this line.
|
||||
|
// text: Text on this line.
|
||||
|
// }],
|
||||
|
// font: {
|
||||
|
// definition: Canvas font property string.
|
||||
|
// color: Color of the text.
|
||||
|
// },
|
||||
|
// }
|
||||
|
//
|
||||
|
// The positions array contains objects that look like this:
|
||||
|
//
|
||||
|
// {
|
||||
|
// active: Flag indicating whether the text should be visible.
|
||||
|
// lines: Array of [x, y] coordinates at which to draw the line.
|
||||
|
// x: X coordinate at which to draw the text.
|
||||
|
// y: Y coordinate at which to draw the text.
|
||||
|
// }
|
||||
|
|
||||
|
Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { |
||||
|
|
||||
|
if (!plot.getOptions().canvas) { |
||||
|
return getTextInfo.call(this, layer, text, font, angle, width); |
||||
|
} |
||||
|
|
||||
|
var textStyle, layerCache, styleCache, info; |
||||
|
|
||||
|
// Cast the value to a string, in case we were given a number
|
||||
|
|
||||
|
text = "" + text; |
||||
|
|
||||
|
// If the font is a font-spec object, generate a CSS definition
|
||||
|
|
||||
|
if (typeof font === "object") { |
||||
|
textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family; |
||||
|
} else { |
||||
|
textStyle = font; |
||||
|
} |
||||
|
|
||||
|
// Retrieve (or create) the cache for the text's layer and styles
|
||||
|
|
||||
|
layerCache = this._textCache[layer]; |
||||
|
|
||||
|
if (layerCache == null) { |
||||
|
layerCache = this._textCache[layer] = {}; |
||||
|
} |
||||
|
|
||||
|
styleCache = layerCache[textStyle]; |
||||
|
|
||||
|
if (styleCache == null) { |
||||
|
styleCache = layerCache[textStyle] = {}; |
||||
|
} |
||||
|
|
||||
|
info = styleCache[text]; |
||||
|
|
||||
|
if (info == null) { |
||||
|
|
||||
|
var context = this.context; |
||||
|
|
||||
|
// If the font was provided as CSS, create a div with those
|
||||
|
// classes and examine it to generate a canvas font spec.
|
||||
|
|
||||
|
if (typeof font !== "object") { |
||||
|
|
||||
|
var element = $("<div> </div>") |
||||
|
.css("position", "absolute") |
||||
|
.addClass(typeof font === "string" ? font : null) |
||||
|
.appendTo(this.getTextLayer(layer)); |
||||
|
|
||||
|
font = { |
||||
|
lineHeight: element.height(), |
||||
|
style: element.css("font-style"), |
||||
|
variant: element.css("font-variant"), |
||||
|
weight: element.css("font-weight"), |
||||
|
family: element.css("font-family"), |
||||
|
color: element.css("color") |
||||
|
}; |
||||
|
|
||||
|
// Setting line-height to 1, without units, sets it equal
|
||||
|
// to the font-size, even if the font-size is abstract,
|
||||
|
// like 'smaller'. This enables us to read the real size
|
||||
|
// via the element's height, working around browsers that
|
||||
|
// return the literal 'smaller' value.
|
||||
|
|
||||
|
font.size = element.css("line-height", 1).height(); |
||||
|
|
||||
|
element.remove(); |
||||
|
} |
||||
|
|
||||
|
textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family; |
||||
|
|
||||
|
// Create a new info object, initializing the dimensions to
|
||||
|
// zero so we can count them up line-by-line.
|
||||
|
|
||||
|
info = styleCache[text] = { |
||||
|
width: 0, |
||||
|
height: 0, |
||||
|
positions: [], |
||||
|
lines: [], |
||||
|
font: { |
||||
|
definition: textStyle, |
||||
|
color: font.color |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
context.save(); |
||||
|
context.font = textStyle; |
||||
|
|
||||
|
// Canvas can't handle multi-line strings; break on various
|
||||
|
// newlines, including HTML brs, to build a list of lines.
|
||||
|
// Note that we could split directly on regexps, but IE < 9 is
|
||||
|
// broken; revisit when we drop IE 7/8 support.
|
||||
|
|
||||
|
var lines = (text + "").replace(/<br ?\/?>|\r\n|\r/g, "\n").split("\n"); |
||||
|
|
||||
|
for (var i = 0; i < lines.length; ++i) { |
||||
|
|
||||
|
var lineText = lines[i], |
||||
|
measured = context.measureText(lineText); |
||||
|
|
||||
|
info.width = Math.max(measured.width, info.width); |
||||
|
info.height += font.lineHeight; |
||||
|
|
||||
|
info.lines.push({ |
||||
|
text: lineText, |
||||
|
width: measured.width, |
||||
|
height: font.lineHeight |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
context.restore(); |
||||
|
} |
||||
|
|
||||
|
return info; |
||||
|
}; |
||||
|
|
||||
|
// Adds a text string to the canvas text overlay.
|
||||
|
|
||||
|
Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { |
||||
|
|
||||
|
if (!plot.getOptions().canvas) { |
||||
|
return addText.call(this, layer, x, y, text, font, angle, width, halign, valign); |
||||
|
} |
||||
|
|
||||
|
var info = this.getTextInfo(layer, text, font, angle, width), |
||||
|
positions = info.positions, |
||||
|
lines = info.lines; |
||||
|
|
||||
|
// Text is drawn with baseline 'middle', which we need to account
|
||||
|
// for by adding half a line's height to the y position.
|
||||
|
|
||||
|
y += info.height / lines.length / 2; |
||||
|
|
||||
|
// Tweak the initial y-position to match vertical alignment
|
||||
|
|
||||
|
if (valign == "middle") { |
||||
|
y = Math.round(y - info.height / 2); |
||||
|
} else if (valign == "bottom") { |
||||
|
y = Math.round(y - info.height); |
||||
|
} else { |
||||
|
y = Math.round(y); |
||||
|
} |
||||
|
|
||||
|
// FIXME: LEGACY BROWSER FIX
|
||||
|
// AFFECTS: Opera < 12.00
|
||||
|
|
||||
|
// Offset the y coordinate, since Opera is off pretty
|
||||
|
// consistently compared to the other browsers.
|
||||
|
|
||||
|
if (!!(window.opera && window.opera.version().split(".")[0] < 12)) { |
||||
|
y -= 2; |
||||
|
} |
||||
|
|
||||
|
// Determine whether this text already exists at this position.
|
||||
|
// If so, mark it for inclusion in the next render pass.
|
||||
|
|
||||
|
for (var i = 0, position; position = positions[i]; i++) { |
||||
|
if (position.x == x && position.y == y) { |
||||
|
position.active = true; |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// If the text doesn't exist at this position, create a new entry
|
||||
|
|
||||
|
position = { |
||||
|
active: true, |
||||
|
lines: [], |
||||
|
x: x, |
||||
|
y: y |
||||
|
}; |
||||
|
|
||||
|
positions.push(position); |
||||
|
|
||||
|
// Fill in the x & y positions of each line, adjusting them
|
||||
|
// individually for horizontal alignment.
|
||||
|
|
||||
|
for (var i = 0, line; line = lines[i]; i++) { |
||||
|
if (halign == "center") { |
||||
|
position.lines.push([Math.round(x - line.width / 2), y]); |
||||
|
} else if (halign == "right") { |
||||
|
position.lines.push([Math.round(x - line.width), y]); |
||||
|
} else { |
||||
|
position.lines.push([Math.round(x), y]); |
||||
|
} |
||||
|
y += line.height; |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: "canvas", |
||||
|
version: "1.0" |
||||
|
}); |
||||
|
|
||||
|
})(jQuery); |
||||
@ -0,0 +1,190 @@ |
|||||
|
/* Flot plugin for plotting textual data or categories. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin |
||||
|
allows you to plot such a dataset directly. |
||||
|
|
||||
|
To enable it, you must specify mode: "categories" on the axis with the textual |
||||
|
labels, e.g. |
||||
|
|
||||
|
$.plot("#placeholder", data, { xaxis: { mode: "categories" } }); |
||||
|
|
||||
|
By default, the labels are ordered as they are met in the data series. If you |
||||
|
need a different ordering, you can specify "categories" on the axis options |
||||
|
and list the categories there: |
||||
|
|
||||
|
xaxis: { |
||||
|
mode: "categories", |
||||
|
categories: ["February", "March", "April"] |
||||
|
} |
||||
|
|
||||
|
If you need to customize the distances between the categories, you can specify |
||||
|
"categories" as an object mapping labels to values |
||||
|
|
||||
|
xaxis: { |
||||
|
mode: "categories", |
||||
|
categories: { "February": 1, "March": 3, "April": 4 } |
||||
|
} |
||||
|
|
||||
|
If you don't specify all categories, the remaining categories will be numbered |
||||
|
from the max value plus 1 (with a spacing of 1 between each). |
||||
|
|
||||
|
Internally, the plugin works by transforming the input data through an auto- |
||||
|
generated mapping where the first category becomes 0, the second 1, etc. |
||||
|
Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this |
||||
|
is visible in hover and click events that return numbers rather than the |
||||
|
category labels). The plugin also overrides the tick generator to spit out the |
||||
|
categories as ticks instead of the values. |
||||
|
|
||||
|
If you need to map a value back to its label, the mapping is always accessible |
||||
|
as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories. |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
(function ($) { |
||||
|
var options = { |
||||
|
xaxis: { |
||||
|
categories: null |
||||
|
}, |
||||
|
yaxis: { |
||||
|
categories: null |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
function processRawData(plot, series, data, datapoints) { |
||||
|
// if categories are enabled, we need to disable
|
||||
|
// auto-transformation to numbers so the strings are intact
|
||||
|
// for later processing
|
||||
|
|
||||
|
var xCategories = series.xaxis.options.mode == "categories", |
||||
|
yCategories = series.yaxis.options.mode == "categories"; |
||||
|
|
||||
|
if (!(xCategories || yCategories)) |
||||
|
return; |
||||
|
|
||||
|
var format = datapoints.format; |
||||
|
|
||||
|
if (!format) { |
||||
|
// FIXME: auto-detection should really not be defined here
|
||||
|
var s = series; |
||||
|
format = []; |
||||
|
format.push({ x: true, number: true, required: true }); |
||||
|
format.push({ y: true, number: true, required: true }); |
||||
|
|
||||
|
if (s.bars.show || (s.lines.show && s.lines.fill)) { |
||||
|
var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); |
||||
|
format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); |
||||
|
if (s.bars.horizontal) { |
||||
|
delete format[format.length - 1].y; |
||||
|
format[format.length - 1].x = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
datapoints.format = format; |
||||
|
} |
||||
|
|
||||
|
for (var m = 0; m < format.length; ++m) { |
||||
|
if (format[m].x && xCategories) |
||||
|
format[m].number = false; |
||||
|
|
||||
|
if (format[m].y && yCategories) |
||||
|
format[m].number = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function getNextIndex(categories) { |
||||
|
var index = -1; |
||||
|
|
||||
|
for (var v in categories) |
||||
|
if (categories[v] > index) |
||||
|
index = categories[v]; |
||||
|
|
||||
|
return index + 1; |
||||
|
} |
||||
|
|
||||
|
function categoriesTickGenerator(axis) { |
||||
|
var res = []; |
||||
|
for (var label in axis.categories) { |
||||
|
var v = axis.categories[label]; |
||||
|
if (v >= axis.min && v <= axis.max) |
||||
|
res.push([v, label]); |
||||
|
} |
||||
|
|
||||
|
res.sort(function (a, b) { return a[0] - b[0]; }); |
||||
|
|
||||
|
return res; |
||||
|
} |
||||
|
|
||||
|
function setupCategoriesForAxis(series, axis, datapoints) { |
||||
|
if (series[axis].options.mode != "categories") |
||||
|
return; |
||||
|
|
||||
|
if (!series[axis].categories) { |
||||
|
// parse options
|
||||
|
var c = {}, o = series[axis].options.categories || {}; |
||||
|
if ($.isArray(o)) { |
||||
|
for (var i = 0; i < o.length; ++i) |
||||
|
c[o[i]] = i; |
||||
|
} |
||||
|
else { |
||||
|
for (var v in o) |
||||
|
c[v] = o[v]; |
||||
|
} |
||||
|
|
||||
|
series[axis].categories = c; |
||||
|
} |
||||
|
|
||||
|
// fix ticks
|
||||
|
if (!series[axis].options.ticks) |
||||
|
series[axis].options.ticks = categoriesTickGenerator; |
||||
|
|
||||
|
transformPointsOnAxis(datapoints, axis, series[axis].categories); |
||||
|
} |
||||
|
|
||||
|
function transformPointsOnAxis(datapoints, axis, categories) { |
||||
|
// go through the points, transforming them
|
||||
|
var points = datapoints.points, |
||||
|
ps = datapoints.pointsize, |
||||
|
format = datapoints.format, |
||||
|
formatColumn = axis.charAt(0), |
||||
|
index = getNextIndex(categories); |
||||
|
|
||||
|
for (var i = 0; i < points.length; i += ps) { |
||||
|
if (points[i] == null) |
||||
|
continue; |
||||
|
|
||||
|
for (var m = 0; m < ps; ++m) { |
||||
|
var val = points[i + m]; |
||||
|
|
||||
|
if (val == null || !format[m][formatColumn]) |
||||
|
continue; |
||||
|
|
||||
|
if (!(val in categories)) { |
||||
|
categories[val] = index; |
||||
|
++index; |
||||
|
} |
||||
|
|
||||
|
points[i + m] = categories[val]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function processDatapoints(plot, series, datapoints) { |
||||
|
setupCategoriesForAxis(series, "xaxis", datapoints); |
||||
|
setupCategoriesForAxis(series, "yaxis", datapoints); |
||||
|
} |
||||
|
|
||||
|
function init(plot) { |
||||
|
plot.hooks.processRawData.push(processRawData); |
||||
|
plot.hooks.processDatapoints.push(processDatapoints); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: 'categories', |
||||
|
version: '1.0' |
||||
|
}); |
||||
|
})(jQuery); |
||||
@ -0,0 +1,176 @@ |
|||||
|
/* Flot plugin for showing crosshairs when the mouse hovers over the plot. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
The plugin supports these options: |
||||
|
|
||||
|
crosshair: { |
||||
|
mode: null or "x" or "y" or "xy" |
||||
|
color: color |
||||
|
lineWidth: number |
||||
|
} |
||||
|
|
||||
|
Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical |
||||
|
crosshair that lets you trace the values on the x axis, "y" enables a |
||||
|
horizontal crosshair and "xy" enables them both. "color" is the color of the |
||||
|
crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of |
||||
|
the drawn lines (default is 1). |
||||
|
|
||||
|
The plugin also adds four public methods: |
||||
|
|
||||
|
- setCrosshair( pos ) |
||||
|
|
||||
|
Set the position of the crosshair. Note that this is cleared if the user |
||||
|
moves the mouse. "pos" is in coordinates of the plot and should be on the |
||||
|
form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple |
||||
|
axes), which is coincidentally the same format as what you get from a |
||||
|
"plothover" event. If "pos" is null, the crosshair is cleared. |
||||
|
|
||||
|
- clearCrosshair() |
||||
|
|
||||
|
Clear the crosshair. |
||||
|
|
||||
|
- lockCrosshair(pos) |
||||
|
|
||||
|
Cause the crosshair to lock to the current location, no longer updating if |
||||
|
the user moves the mouse. Optionally supply a position (passed on to |
||||
|
setCrosshair()) to move it to. |
||||
|
|
||||
|
Example usage: |
||||
|
|
||||
|
var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } }; |
||||
|
$("#graph").bind( "plothover", function ( evt, position, item ) { |
||||
|
if ( item ) { |
||||
|
// Lock the crosshair to the data point being hovered
|
||||
|
myFlot.lockCrosshair({ |
||||
|
x: item.datapoint[ 0 ], |
||||
|
y: item.datapoint[ 1 ] |
||||
|
}); |
||||
|
} else { |
||||
|
// Return normal crosshair operation
|
||||
|
myFlot.unlockCrosshair(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
- unlockCrosshair() |
||||
|
|
||||
|
Free the crosshair to move again after locking it. |
||||
|
*/ |
||||
|
|
||||
|
(function ($) { |
||||
|
var options = { |
||||
|
crosshair: { |
||||
|
mode: null, // one of null, "x", "y" or "xy",
|
||||
|
color: "rgba(170, 0, 0, 0.80)", |
||||
|
lineWidth: 1 |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
function init(plot) { |
||||
|
// position of crosshair in pixels
|
||||
|
var crosshair = { x: -1, y: -1, locked: false }; |
||||
|
|
||||
|
plot.setCrosshair = function setCrosshair(pos) { |
||||
|
if (!pos) |
||||
|
crosshair.x = -1; |
||||
|
else { |
||||
|
var o = plot.p2c(pos); |
||||
|
crosshair.x = Math.max(0, Math.min(o.left, plot.width())); |
||||
|
crosshair.y = Math.max(0, Math.min(o.top, plot.height())); |
||||
|
} |
||||
|
|
||||
|
plot.triggerRedrawOverlay(); |
||||
|
}; |
||||
|
|
||||
|
plot.clearCrosshair = plot.setCrosshair; // passes null for pos
|
||||
|
|
||||
|
plot.lockCrosshair = function lockCrosshair(pos) { |
||||
|
if (pos) |
||||
|
plot.setCrosshair(pos); |
||||
|
crosshair.locked = true; |
||||
|
}; |
||||
|
|
||||
|
plot.unlockCrosshair = function unlockCrosshair() { |
||||
|
crosshair.locked = false; |
||||
|
}; |
||||
|
|
||||
|
function onMouseOut(e) { |
||||
|
if (crosshair.locked) |
||||
|
return; |
||||
|
|
||||
|
if (crosshair.x != -1) { |
||||
|
crosshair.x = -1; |
||||
|
plot.triggerRedrawOverlay(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onMouseMove(e) { |
||||
|
if (crosshair.locked) |
||||
|
return; |
||||
|
|
||||
|
if (plot.getSelection && plot.getSelection()) { |
||||
|
crosshair.x = -1; // hide the crosshair while selecting
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var offset = plot.offset(); |
||||
|
crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); |
||||
|
crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); |
||||
|
plot.triggerRedrawOverlay(); |
||||
|
} |
||||
|
|
||||
|
plot.hooks.bindEvents.push(function (plot, eventHolder) { |
||||
|
if (!plot.getOptions().crosshair.mode) |
||||
|
return; |
||||
|
|
||||
|
eventHolder.mouseout(onMouseOut); |
||||
|
eventHolder.mousemove(onMouseMove); |
||||
|
}); |
||||
|
|
||||
|
plot.hooks.drawOverlay.push(function (plot, ctx) { |
||||
|
var c = plot.getOptions().crosshair; |
||||
|
if (!c.mode) |
||||
|
return; |
||||
|
|
||||
|
var plotOffset = plot.getPlotOffset(); |
||||
|
|
||||
|
ctx.save(); |
||||
|
ctx.translate(plotOffset.left, plotOffset.top); |
||||
|
|
||||
|
if (crosshair.x != -1) { |
||||
|
var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0; |
||||
|
|
||||
|
ctx.strokeStyle = c.color; |
||||
|
ctx.lineWidth = c.lineWidth; |
||||
|
ctx.lineJoin = "round"; |
||||
|
|
||||
|
ctx.beginPath(); |
||||
|
if (c.mode.indexOf("x") != -1) { |
||||
|
var drawX = Math.floor(crosshair.x) + adj; |
||||
|
ctx.moveTo(drawX, 0); |
||||
|
ctx.lineTo(drawX, plot.height()); |
||||
|
} |
||||
|
if (c.mode.indexOf("y") != -1) { |
||||
|
var drawY = Math.floor(crosshair.y) + adj; |
||||
|
ctx.moveTo(0, drawY); |
||||
|
ctx.lineTo(plot.width(), drawY); |
||||
|
} |
||||
|
ctx.stroke(); |
||||
|
} |
||||
|
ctx.restore(); |
||||
|
}); |
||||
|
|
||||
|
plot.hooks.shutdown.push(function (plot, eventHolder) { |
||||
|
eventHolder.unbind("mouseout", onMouseOut); |
||||
|
eventHolder.unbind("mousemove", onMouseMove); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: 'crosshair', |
||||
|
version: '1.0' |
||||
|
}); |
||||
|
})(jQuery); |
||||
@ -0,0 +1,353 @@ |
|||||
|
/* Flot plugin for plotting error bars. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
Error bars are used to show standard deviation and other statistical |
||||
|
properties in a plot. |
||||
|
|
||||
|
* Created by Rui Pereira - rui (dot) pereira (at) gmail (dot) com |
||||
|
|
||||
|
This plugin allows you to plot error-bars over points. Set "errorbars" inside |
||||
|
the points series to the axis name over which there will be error values in |
||||
|
your data array (*even* if you do not intend to plot them later, by setting |
||||
|
"show: null" on xerr/yerr). |
||||
|
|
||||
|
The plugin supports these options: |
||||
|
|
||||
|
series: { |
||||
|
points: { |
||||
|
errorbars: "x" or "y" or "xy", |
||||
|
xerr: { |
||||
|
show: null/false or true, |
||||
|
asymmetric: null/false or true, |
||||
|
upperCap: null or "-" or function, |
||||
|
lowerCap: null or "-" or function, |
||||
|
color: null or color, |
||||
|
radius: null or number |
||||
|
}, |
||||
|
yerr: { same options as xerr } |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Each data point array is expected to be of the type: |
||||
|
|
||||
|
"x" [ x, y, xerr ] |
||||
|
"y" [ x, y, yerr ] |
||||
|
"xy" [ x, y, xerr, yerr ] |
||||
|
|
||||
|
Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and |
||||
|
equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric |
||||
|
error-bars on X and asymmetric on Y would be: |
||||
|
|
||||
|
[ x, y, xerr, yerr_lower, yerr_upper ] |
||||
|
|
||||
|
By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will |
||||
|
draw a small cap perpendicular to the error bar. They can also be set to a |
||||
|
user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg. |
||||
|
|
||||
|
function drawSemiCircle( ctx, x, y, radius ) { |
||||
|
ctx.beginPath(); |
||||
|
ctx.arc( x, y, radius, 0, Math.PI, false ); |
||||
|
ctx.moveTo( x - radius, y ); |
||||
|
ctx.lineTo( x + radius, y ); |
||||
|
ctx.stroke(); |
||||
|
} |
||||
|
|
||||
|
Color and radius both default to the same ones of the points series if not |
||||
|
set. The independent radius parameter on xerr/yerr is useful for the case when |
||||
|
we may want to add error-bars to a line, without showing the interconnecting |
||||
|
points (with radius: 0), and still showing end caps on the error-bars. |
||||
|
shadowSize and lineWidth are derived as well from the points series. |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
(function ($) { |
||||
|
var options = { |
||||
|
series: { |
||||
|
points: { |
||||
|
errorbars: null, //should be 'x', 'y' or 'xy'
|
||||
|
xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}, |
||||
|
yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
function processRawData(plot, series, data, datapoints){ |
||||
|
if (!series.points.errorbars) |
||||
|
return; |
||||
|
|
||||
|
// x,y values
|
||||
|
var format = [ |
||||
|
{ x: true, number: true, required: true }, |
||||
|
{ y: true, number: true, required: true } |
||||
|
]; |
||||
|
|
||||
|
var errors = series.points.errorbars; |
||||
|
// error bars - first X then Y
|
||||
|
if (errors == 'x' || errors == 'xy') { |
||||
|
// lower / upper error
|
||||
|
if (series.points.xerr.asymmetric) { |
||||
|
format.push({ x: true, number: true, required: true }); |
||||
|
format.push({ x: true, number: true, required: true }); |
||||
|
} else |
||||
|
format.push({ x: true, number: true, required: true }); |
||||
|
} |
||||
|
if (errors == 'y' || errors == 'xy') { |
||||
|
// lower / upper error
|
||||
|
if (series.points.yerr.asymmetric) { |
||||
|
format.push({ y: true, number: true, required: true }); |
||||
|
format.push({ y: true, number: true, required: true }); |
||||
|
} else |
||||
|
format.push({ y: true, number: true, required: true }); |
||||
|
} |
||||
|
datapoints.format = format; |
||||
|
} |
||||
|
|
||||
|
function parseErrors(series, i){ |
||||
|
|
||||
|
var points = series.datapoints.points; |
||||
|
|
||||
|
// read errors from points array
|
||||
|
var exl = null, |
||||
|
exu = null, |
||||
|
eyl = null, |
||||
|
eyu = null; |
||||
|
var xerr = series.points.xerr, |
||||
|
yerr = series.points.yerr; |
||||
|
|
||||
|
var eb = series.points.errorbars; |
||||
|
// error bars - first X
|
||||
|
if (eb == 'x' || eb == 'xy') { |
||||
|
if (xerr.asymmetric) { |
||||
|
exl = points[i + 2]; |
||||
|
exu = points[i + 3]; |
||||
|
if (eb == 'xy') |
||||
|
if (yerr.asymmetric){ |
||||
|
eyl = points[i + 4]; |
||||
|
eyu = points[i + 5]; |
||||
|
} else eyl = points[i + 4]; |
||||
|
} else { |
||||
|
exl = points[i + 2]; |
||||
|
if (eb == 'xy') |
||||
|
if (yerr.asymmetric) { |
||||
|
eyl = points[i + 3]; |
||||
|
eyu = points[i + 4]; |
||||
|
} else eyl = points[i + 3]; |
||||
|
} |
||||
|
// only Y
|
||||
|
} else if (eb == 'y') |
||||
|
if (yerr.asymmetric) { |
||||
|
eyl = points[i + 2]; |
||||
|
eyu = points[i + 3]; |
||||
|
} else eyl = points[i + 2]; |
||||
|
|
||||
|
// symmetric errors?
|
||||
|
if (exu == null) exu = exl; |
||||
|
if (eyu == null) eyu = eyl; |
||||
|
|
||||
|
var errRanges = [exl, exu, eyl, eyu]; |
||||
|
// nullify if not showing
|
||||
|
if (!xerr.show){ |
||||
|
errRanges[0] = null; |
||||
|
errRanges[1] = null; |
||||
|
} |
||||
|
if (!yerr.show){ |
||||
|
errRanges[2] = null; |
||||
|
errRanges[3] = null; |
||||
|
} |
||||
|
return errRanges; |
||||
|
} |
||||
|
|
||||
|
function drawSeriesErrors(plot, ctx, s){ |
||||
|
|
||||
|
var points = s.datapoints.points, |
||||
|
ps = s.datapoints.pointsize, |
||||
|
ax = [s.xaxis, s.yaxis], |
||||
|
radius = s.points.radius, |
||||
|
err = [s.points.xerr, s.points.yerr]; |
||||
|
|
||||
|
//sanity check, in case some inverted axis hack is applied to flot
|
||||
|
var invertX = false; |
||||
|
if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) { |
||||
|
invertX = true; |
||||
|
var tmp = err[0].lowerCap; |
||||
|
err[0].lowerCap = err[0].upperCap; |
||||
|
err[0].upperCap = tmp; |
||||
|
} |
||||
|
|
||||
|
var invertY = false; |
||||
|
if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) { |
||||
|
invertY = true; |
||||
|
var tmp = err[1].lowerCap; |
||||
|
err[1].lowerCap = err[1].upperCap; |
||||
|
err[1].upperCap = tmp; |
||||
|
} |
||||
|
|
||||
|
for (var i = 0; i < s.datapoints.points.length; i += ps) { |
||||
|
|
||||
|
//parse
|
||||
|
var errRanges = parseErrors(s, i); |
||||
|
|
||||
|
//cycle xerr & yerr
|
||||
|
for (var e = 0; e < err.length; e++){ |
||||
|
|
||||
|
var minmax = [ax[e].min, ax[e].max]; |
||||
|
|
||||
|
//draw this error?
|
||||
|
if (errRanges[e * err.length]){ |
||||
|
|
||||
|
//data coordinates
|
||||
|
var x = points[i], |
||||
|
y = points[i + 1]; |
||||
|
|
||||
|
//errorbar ranges
|
||||
|
var upper = [x, y][e] + errRanges[e * err.length + 1], |
||||
|
lower = [x, y][e] - errRanges[e * err.length]; |
||||
|
|
||||
|
//points outside of the canvas
|
||||
|
if (err[e].err == 'x') |
||||
|
if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max) |
||||
|
continue; |
||||
|
if (err[e].err == 'y') |
||||
|
if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max) |
||||
|
continue; |
||||
|
|
||||
|
// prevent errorbars getting out of the canvas
|
||||
|
var drawUpper = true, |
||||
|
drawLower = true; |
||||
|
|
||||
|
if (upper > minmax[1]) { |
||||
|
drawUpper = false; |
||||
|
upper = minmax[1]; |
||||
|
} |
||||
|
if (lower < minmax[0]) { |
||||
|
drawLower = false; |
||||
|
lower = minmax[0]; |
||||
|
} |
||||
|
|
||||
|
//sanity check, in case some inverted axis hack is applied to flot
|
||||
|
if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) { |
||||
|
//swap coordinates
|
||||
|
var tmp = lower; |
||||
|
lower = upper; |
||||
|
upper = tmp; |
||||
|
tmp = drawLower; |
||||
|
drawLower = drawUpper; |
||||
|
drawUpper = tmp; |
||||
|
tmp = minmax[0]; |
||||
|
minmax[0] = minmax[1]; |
||||
|
minmax[1] = tmp; |
||||
|
} |
||||
|
|
||||
|
// convert to pixels
|
||||
|
x = ax[0].p2c(x), |
||||
|
y = ax[1].p2c(y), |
||||
|
upper = ax[e].p2c(upper); |
||||
|
lower = ax[e].p2c(lower); |
||||
|
minmax[0] = ax[e].p2c(minmax[0]); |
||||
|
minmax[1] = ax[e].p2c(minmax[1]); |
||||
|
|
||||
|
//same style as points by default
|
||||
|
var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth, |
||||
|
sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize; |
||||
|
|
||||
|
//shadow as for points
|
||||
|
if (lw > 0 && sw > 0) { |
||||
|
var w = sw / 2; |
||||
|
ctx.lineWidth = w; |
||||
|
ctx.strokeStyle = "rgba(0,0,0,0.1)"; |
||||
|
drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax); |
||||
|
|
||||
|
ctx.strokeStyle = "rgba(0,0,0,0.2)"; |
||||
|
drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax); |
||||
|
} |
||||
|
|
||||
|
ctx.strokeStyle = err[e].color? err[e].color: s.color; |
||||
|
ctx.lineWidth = lw; |
||||
|
//draw it
|
||||
|
drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){ |
||||
|
|
||||
|
//shadow offset
|
||||
|
y += offset; |
||||
|
upper += offset; |
||||
|
lower += offset; |
||||
|
|
||||
|
// error bar - avoid plotting over circles
|
||||
|
if (err.err == 'x'){ |
||||
|
if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]); |
||||
|
else drawUpper = false; |
||||
|
if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] ); |
||||
|
else drawLower = false; |
||||
|
} |
||||
|
else { |
||||
|
if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] ); |
||||
|
else drawUpper = false; |
||||
|
if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] ); |
||||
|
else drawLower = false; |
||||
|
} |
||||
|
|
||||
|
//internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps
|
||||
|
//this is a way to get errorbars on lines without visible connecting dots
|
||||
|
radius = err.radius != null? err.radius: radius; |
||||
|
|
||||
|
// upper cap
|
||||
|
if (drawUpper) { |
||||
|
if (err.upperCap == '-'){ |
||||
|
if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] ); |
||||
|
else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] ); |
||||
|
} else if ($.isFunction(err.upperCap)){ |
||||
|
if (err.err=='x') err.upperCap(ctx, upper, y, radius); |
||||
|
else err.upperCap(ctx, x, upper, radius); |
||||
|
} |
||||
|
} |
||||
|
// lower cap
|
||||
|
if (drawLower) { |
||||
|
if (err.lowerCap == '-'){ |
||||
|
if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] ); |
||||
|
else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] ); |
||||
|
} else if ($.isFunction(err.lowerCap)){ |
||||
|
if (err.err=='x') err.lowerCap(ctx, lower, y, radius); |
||||
|
else err.lowerCap(ctx, x, lower, radius); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function drawPath(ctx, pts){ |
||||
|
ctx.beginPath(); |
||||
|
ctx.moveTo(pts[0][0], pts[0][1]); |
||||
|
for (var p=1; p < pts.length; p++) |
||||
|
ctx.lineTo(pts[p][0], pts[p][1]); |
||||
|
ctx.stroke(); |
||||
|
} |
||||
|
|
||||
|
function draw(plot, ctx){ |
||||
|
var plotOffset = plot.getPlotOffset(); |
||||
|
|
||||
|
ctx.save(); |
||||
|
ctx.translate(plotOffset.left, plotOffset.top); |
||||
|
$.each(plot.getData(), function (i, s) { |
||||
|
if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show)) |
||||
|
drawSeriesErrors(plot, ctx, s); |
||||
|
}); |
||||
|
ctx.restore(); |
||||
|
} |
||||
|
|
||||
|
function init(plot) { |
||||
|
plot.hooks.processRawData.push(processRawData); |
||||
|
plot.hooks.draw.push(draw); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: 'errorbars', |
||||
|
version: '1.0' |
||||
|
}); |
||||
|
})(jQuery); |
||||
@ -0,0 +1,226 @@ |
|||||
|
/* Flot plugin for computing bottoms for filled line and bar charts. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
The case: you've got two series that you want to fill the area between. In Flot |
||||
|
terms, you need to use one as the fill bottom of the other. You can specify the |
||||
|
bottom of each data point as the third coordinate manually, or you can use this |
||||
|
plugin to compute it for you. |
||||
|
|
||||
|
In order to name the other series, you need to give it an id, like this: |
||||
|
|
||||
|
var dataset = [ |
||||
|
{ data: [ ... ], id: "foo" } , // use default bottom
|
||||
|
{ data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
|
||||
|
]; |
||||
|
|
||||
|
$.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }}); |
||||
|
|
||||
|
As a convenience, if the id given is a number that doesn't appear as an id in |
||||
|
the series, it is interpreted as the index in the array instead (so fillBetween: |
||||
|
0 can also mean the first series). |
||||
|
|
||||
|
Internally, the plugin modifies the datapoints in each series. For line series, |
||||
|
extra data points might be inserted through interpolation. Note that at points |
||||
|
where the bottom line is not defined (due to a null point or start/end of line), |
||||
|
the current line will show a gap too. The algorithm comes from the |
||||
|
jquery.flot.stack.js plugin, possibly some code could be shared. |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
(function ( $ ) { |
||||
|
|
||||
|
var options = { |
||||
|
series: { |
||||
|
fillBetween: null // or number
|
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
function init( plot ) { |
||||
|
|
||||
|
function findBottomSeries( s, allseries ) { |
||||
|
|
||||
|
var i; |
||||
|
|
||||
|
for ( i = 0; i < allseries.length; ++i ) { |
||||
|
if ( allseries[ i ].id === s.fillBetween ) { |
||||
|
return allseries[ i ]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if ( typeof s.fillBetween === "number" ) { |
||||
|
if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) { |
||||
|
return null; |
||||
|
} |
||||
|
return allseries[ s.fillBetween ]; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
function computeFillBottoms( plot, s, datapoints ) { |
||||
|
|
||||
|
if ( s.fillBetween == null ) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var other = findBottomSeries( s, plot.getData() ); |
||||
|
|
||||
|
if ( !other ) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var ps = datapoints.pointsize, |
||||
|
points = datapoints.points, |
||||
|
otherps = other.datapoints.pointsize, |
||||
|
otherpoints = other.datapoints.points, |
||||
|
newpoints = [], |
||||
|
px, py, intery, qx, qy, bottom, |
||||
|
withlines = s.lines.show, |
||||
|
withbottom = ps > 2 && datapoints.format[2].y, |
||||
|
withsteps = withlines && s.lines.steps, |
||||
|
fromgap = true, |
||||
|
i = 0, |
||||
|
j = 0, |
||||
|
l, m; |
||||
|
|
||||
|
while ( true ) { |
||||
|
|
||||
|
if ( i >= points.length ) { |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
l = newpoints.length; |
||||
|
|
||||
|
if ( points[ i ] == null ) { |
||||
|
|
||||
|
// copy gaps
|
||||
|
|
||||
|
for ( m = 0; m < ps; ++m ) { |
||||
|
newpoints.push( points[ i + m ] ); |
||||
|
} |
||||
|
|
||||
|
i += ps; |
||||
|
|
||||
|
} else if ( j >= otherpoints.length ) { |
||||
|
|
||||
|
// for lines, we can't use the rest of the points
|
||||
|
|
||||
|
if ( !withlines ) { |
||||
|
for ( m = 0; m < ps; ++m ) { |
||||
|
newpoints.push( points[ i + m ] ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
i += ps; |
||||
|
|
||||
|
} else if ( otherpoints[ j ] == null ) { |
||||
|
|
||||
|
// oops, got a gap
|
||||
|
|
||||
|
for ( m = 0; m < ps; ++m ) { |
||||
|
newpoints.push( null ); |
||||
|
} |
||||
|
|
||||
|
fromgap = true; |
||||
|
j += otherps; |
||||
|
|
||||
|
} else { |
||||
|
|
||||
|
// cases where we actually got two points
|
||||
|
|
||||
|
px = points[ i ]; |
||||
|
py = points[ i + 1 ]; |
||||
|
qx = otherpoints[ j ]; |
||||
|
qy = otherpoints[ j + 1 ]; |
||||
|
bottom = 0; |
||||
|
|
||||
|
if ( px === qx ) { |
||||
|
|
||||
|
for ( m = 0; m < ps; ++m ) { |
||||
|
newpoints.push( points[ i + m ] ); |
||||
|
} |
||||
|
|
||||
|
//newpoints[ l + 1 ] += qy;
|
||||
|
bottom = qy; |
||||
|
|
||||
|
i += ps; |
||||
|
j += otherps; |
||||
|
|
||||
|
} else if ( px > qx ) { |
||||
|
|
||||
|
// we got past point below, might need to
|
||||
|
// insert interpolated extra point
|
||||
|
|
||||
|
if ( withlines && i > 0 && points[ i - ps ] != null ) { |
||||
|
intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px ); |
||||
|
newpoints.push( qx ); |
||||
|
newpoints.push( intery ); |
||||
|
for ( m = 2; m < ps; ++m ) { |
||||
|
newpoints.push( points[ i + m ] ); |
||||
|
} |
||||
|
bottom = qy; |
||||
|
} |
||||
|
|
||||
|
j += otherps; |
||||
|
|
||||
|
} else { // px < qx
|
||||
|
|
||||
|
// if we come from a gap, we just skip this point
|
||||
|
|
||||
|
if ( fromgap && withlines ) { |
||||
|
i += ps; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
for ( m = 0; m < ps; ++m ) { |
||||
|
newpoints.push( points[ i + m ] ); |
||||
|
} |
||||
|
|
||||
|
// we might be able to interpolate a point below,
|
||||
|
// this can give us a better y
|
||||
|
|
||||
|
if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) { |
||||
|
bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx ); |
||||
|
} |
||||
|
|
||||
|
//newpoints[l + 1] += bottom;
|
||||
|
|
||||
|
i += ps; |
||||
|
} |
||||
|
|
||||
|
fromgap = false; |
||||
|
|
||||
|
if ( l !== newpoints.length && withbottom ) { |
||||
|
newpoints[ l + 2 ] = bottom; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// maintain the line steps invariant
|
||||
|
|
||||
|
if ( withsteps && l !== newpoints.length && l > 0 && |
||||
|
newpoints[ l ] !== null && |
||||
|
newpoints[ l ] !== newpoints[ l - ps ] && |
||||
|
newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) { |
||||
|
for (m = 0; m < ps; ++m) { |
||||
|
newpoints[ l + ps + m ] = newpoints[ l + m ]; |
||||
|
} |
||||
|
newpoints[ l + 1 ] = newpoints[ l - ps + 1 ]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
datapoints.points = newpoints; |
||||
|
} |
||||
|
|
||||
|
plot.hooks.processDatapoints.push( computeFillBottoms ); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: "fillbetween", |
||||
|
version: "1.0" |
||||
|
}); |
||||
|
|
||||
|
})(jQuery); |
||||
@ -0,0 +1,241 @@ |
|||||
|
/* Flot plugin for plotting images. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
The data syntax is [ [ image, x1, y1, x2, y2 ], ... ] where (x1, y1) and |
||||
|
(x2, y2) are where you intend the two opposite corners of the image to end up |
||||
|
in the plot. Image must be a fully loaded Javascript image (you can make one |
||||
|
with new Image()). If the image is not complete, it's skipped when plotting. |
||||
|
|
||||
|
There are two helpers included for retrieving images. The easiest work the way |
||||
|
that you put in URLs instead of images in the data, like this: |
||||
|
|
||||
|
[ "myimage.png", 0, 0, 10, 10 ] |
||||
|
|
||||
|
Then call $.plot.image.loadData( data, options, callback ) where data and |
||||
|
options are the same as you pass in to $.plot. This loads the images, replaces |
||||
|
the URLs in the data with the corresponding images and calls "callback" when |
||||
|
all images are loaded (or failed loading). In the callback, you can then call |
||||
|
$.plot with the data set. See the included example. |
||||
|
|
||||
|
A more low-level helper, $.plot.image.load(urls, callback) is also included. |
||||
|
Given a list of URLs, it calls callback with an object mapping from URL to |
||||
|
Image object when all images are loaded or have failed loading. |
||||
|
|
||||
|
The plugin supports these options: |
||||
|
|
||||
|
series: { |
||||
|
images: { |
||||
|
show: boolean |
||||
|
anchor: "corner" or "center" |
||||
|
alpha: [ 0, 1 ] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
They can be specified for a specific series: |
||||
|
|
||||
|
$.plot( $("#placeholder"), [{ |
||||
|
data: [ ... ], |
||||
|
images: { ... } |
||||
|
]) |
||||
|
|
||||
|
Note that because the data format is different from usual data points, you |
||||
|
can't use images with anything else in a specific data series. |
||||
|
|
||||
|
Setting "anchor" to "center" causes the pixels in the image to be anchored at |
||||
|
the corner pixel centers inside of at the pixel corners, effectively letting |
||||
|
half a pixel stick out to each side in the plot. |
||||
|
|
||||
|
A possible future direction could be support for tiling for large images (like |
||||
|
Google Maps). |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
(function ($) { |
||||
|
var options = { |
||||
|
series: { |
||||
|
images: { |
||||
|
show: false, |
||||
|
alpha: 1, |
||||
|
anchor: "corner" // or "center"
|
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
$.plot.image = {}; |
||||
|
|
||||
|
$.plot.image.loadDataImages = function (series, options, callback) { |
||||
|
var urls = [], points = []; |
||||
|
|
||||
|
var defaultShow = options.series.images.show; |
||||
|
|
||||
|
$.each(series, function (i, s) { |
||||
|
if (!(defaultShow || s.images.show)) |
||||
|
return; |
||||
|
|
||||
|
if (s.data) |
||||
|
s = s.data; |
||||
|
|
||||
|
$.each(s, function (i, p) { |
||||
|
if (typeof p[0] == "string") { |
||||
|
urls.push(p[0]); |
||||
|
points.push(p); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
$.plot.image.load(urls, function (loadedImages) { |
||||
|
$.each(points, function (i, p) { |
||||
|
var url = p[0]; |
||||
|
if (loadedImages[url]) |
||||
|
p[0] = loadedImages[url]; |
||||
|
}); |
||||
|
|
||||
|
callback(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
$.plot.image.load = function (urls, callback) { |
||||
|
var missing = urls.length, loaded = {}; |
||||
|
if (missing == 0) |
||||
|
callback({}); |
||||
|
|
||||
|
$.each(urls, function (i, url) { |
||||
|
var handler = function () { |
||||
|
--missing; |
||||
|
|
||||
|
loaded[url] = this; |
||||
|
|
||||
|
if (missing == 0) |
||||
|
callback(loaded); |
||||
|
}; |
||||
|
|
||||
|
$('<img />').load(handler).error(handler).attr('src', url); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
function drawSeries(plot, ctx, series) { |
||||
|
var plotOffset = plot.getPlotOffset(); |
||||
|
|
||||
|
if (!series.images || !series.images.show) |
||||
|
return; |
||||
|
|
||||
|
var points = series.datapoints.points, |
||||
|
ps = series.datapoints.pointsize; |
||||
|
|
||||
|
for (var i = 0; i < points.length; i += ps) { |
||||
|
var img = points[i], |
||||
|
x1 = points[i + 1], y1 = points[i + 2], |
||||
|
x2 = points[i + 3], y2 = points[i + 4], |
||||
|
xaxis = series.xaxis, yaxis = series.yaxis, |
||||
|
tmp; |
||||
|
|
||||
|
// actually we should check img.complete, but it
|
||||
|
// appears to be a somewhat unreliable indicator in
|
||||
|
// IE6 (false even after load event)
|
||||
|
if (!img || img.width <= 0 || img.height <= 0) |
||||
|
continue; |
||||
|
|
||||
|
if (x1 > x2) { |
||||
|
tmp = x2; |
||||
|
x2 = x1; |
||||
|
x1 = tmp; |
||||
|
} |
||||
|
if (y1 > y2) { |
||||
|
tmp = y2; |
||||
|
y2 = y1; |
||||
|
y1 = tmp; |
||||
|
} |
||||
|
|
||||
|
// if the anchor is at the center of the pixel, expand the
|
||||
|
// image by 1/2 pixel in each direction
|
||||
|
if (series.images.anchor == "center") { |
||||
|
tmp = 0.5 * (x2-x1) / (img.width - 1); |
||||
|
x1 -= tmp; |
||||
|
x2 += tmp; |
||||
|
tmp = 0.5 * (y2-y1) / (img.height - 1); |
||||
|
y1 -= tmp; |
||||
|
y2 += tmp; |
||||
|
} |
||||
|
|
||||
|
// clip
|
||||
|
if (x1 == x2 || y1 == y2 || |
||||
|
x1 >= xaxis.max || x2 <= xaxis.min || |
||||
|
y1 >= yaxis.max || y2 <= yaxis.min) |
||||
|
continue; |
||||
|
|
||||
|
var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height; |
||||
|
if (x1 < xaxis.min) { |
||||
|
sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1); |
||||
|
x1 = xaxis.min; |
||||
|
} |
||||
|
|
||||
|
if (x2 > xaxis.max) { |
||||
|
sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1); |
||||
|
x2 = xaxis.max; |
||||
|
} |
||||
|
|
||||
|
if (y1 < yaxis.min) { |
||||
|
sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1); |
||||
|
y1 = yaxis.min; |
||||
|
} |
||||
|
|
||||
|
if (y2 > yaxis.max) { |
||||
|
sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1); |
||||
|
y2 = yaxis.max; |
||||
|
} |
||||
|
|
||||
|
x1 = xaxis.p2c(x1); |
||||
|
x2 = xaxis.p2c(x2); |
||||
|
y1 = yaxis.p2c(y1); |
||||
|
y2 = yaxis.p2c(y2); |
||||
|
|
||||
|
// the transformation may have swapped us
|
||||
|
if (x1 > x2) { |
||||
|
tmp = x2; |
||||
|
x2 = x1; |
||||
|
x1 = tmp; |
||||
|
} |
||||
|
if (y1 > y2) { |
||||
|
tmp = y2; |
||||
|
y2 = y1; |
||||
|
y1 = tmp; |
||||
|
} |
||||
|
|
||||
|
tmp = ctx.globalAlpha; |
||||
|
ctx.globalAlpha *= series.images.alpha; |
||||
|
ctx.drawImage(img, |
||||
|
sx1, sy1, sx2 - sx1, sy2 - sy1, |
||||
|
x1 + plotOffset.left, y1 + plotOffset.top, |
||||
|
x2 - x1, y2 - y1); |
||||
|
ctx.globalAlpha = tmp; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function processRawData(plot, series, data, datapoints) { |
||||
|
if (!series.images.show) |
||||
|
return; |
||||
|
|
||||
|
// format is Image, x1, y1, x2, y2 (opposite corners)
|
||||
|
datapoints.format = [ |
||||
|
{ required: true }, |
||||
|
{ x: true, number: true, required: true }, |
||||
|
{ y: true, number: true, required: true }, |
||||
|
{ x: true, number: true, required: true }, |
||||
|
{ y: true, number: true, required: true } |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
function init(plot) { |
||||
|
plot.hooks.processRawData.push(processRawData); |
||||
|
plot.hooks.drawSeries.push(drawSeries); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: 'image', |
||||
|
version: '1.1' |
||||
|
}); |
||||
|
})(jQuery); |
||||
File diff suppressed because it is too large
@ -0,0 +1,346 @@ |
|||||
|
/* Flot plugin for adding the ability to pan and zoom the plot. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
The default behaviour is double click and scrollwheel up/down to zoom in, drag |
||||
|
to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and |
||||
|
plot.pan( offset ) so you easily can add custom controls. It also fires |
||||
|
"plotpan" and "plotzoom" events, useful for synchronizing plots. |
||||
|
|
||||
|
The plugin supports these options: |
||||
|
|
||||
|
zoom: { |
||||
|
interactive: false |
||||
|
trigger: "dblclick" // or "click" for single click
|
||||
|
amount: 1.5 // 2 = 200% (zoom in), 0.5 = 50% (zoom out)
|
||||
|
} |
||||
|
|
||||
|
pan: { |
||||
|
interactive: false |
||||
|
cursor: "move" // CSS mouse cursor value used when dragging, e.g. "pointer"
|
||||
|
frameRate: 20 |
||||
|
} |
||||
|
|
||||
|
xaxis, yaxis, x2axis, y2axis: { |
||||
|
zoomRange: null // or [ number, number ] (min range, max range) or false
|
||||
|
panRange: null // or [ number, number ] (min, max) or false
|
||||
|
} |
||||
|
|
||||
|
"interactive" enables the built-in drag/click behaviour. If you enable |
||||
|
interactive for pan, then you'll have a basic plot that supports moving |
||||
|
around; the same for zoom. |
||||
|
|
||||
|
"amount" specifies the default amount to zoom in (so 1.5 = 150%) relative to |
||||
|
the current viewport. |
||||
|
|
||||
|
"cursor" is a standard CSS mouse cursor string used for visual feedback to the |
||||
|
user when dragging. |
||||
|
|
||||
|
"frameRate" specifies the maximum number of times per second the plot will |
||||
|
update itself while the user is panning around on it (set to null to disable |
||||
|
intermediate pans, the plot will then not update until the mouse button is |
||||
|
released). |
||||
|
|
||||
|
"zoomRange" is the interval in which zooming can happen, e.g. with zoomRange: |
||||
|
[1, 100] the zoom will never scale the axis so that the difference between min |
||||
|
and max is smaller than 1 or larger than 100. You can set either end to null |
||||
|
to ignore, e.g. [1, null]. If you set zoomRange to false, zooming on that axis |
||||
|
will be disabled. |
||||
|
|
||||
|
"panRange" confines the panning to stay within a range, e.g. with panRange: |
||||
|
[-10, 20] panning stops at -10 in one end and at 20 in the other. Either can |
||||
|
be null, e.g. [-10, null]. If you set panRange to false, panning on that axis |
||||
|
will be disabled. |
||||
|
|
||||
|
Example API usage: |
||||
|
|
||||
|
plot = $.plot(...); |
||||
|
|
||||
|
// zoom default amount in on the pixel ( 10, 20 )
|
||||
|
plot.zoom({ center: { left: 10, top: 20 } }); |
||||
|
|
||||
|
// zoom out again
|
||||
|
plot.zoomOut({ center: { left: 10, top: 20 } }); |
||||
|
|
||||
|
// zoom 200% in on the pixel (10, 20)
|
||||
|
plot.zoom({ amount: 2, center: { left: 10, top: 20 } }); |
||||
|
|
||||
|
// pan 100 pixels to the left and 20 down
|
||||
|
plot.pan({ left: -100, top: 20 }) |
||||
|
|
||||
|
Here, "center" specifies where the center of the zooming should happen. Note |
||||
|
that this is defined in pixel space, not the space of the data points (you can |
||||
|
use the p2c helpers on the axes in Flot to help you convert between these). |
||||
|
|
||||
|
"amount" is the amount to zoom the viewport relative to the current range, so |
||||
|
1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You |
||||
|
can set the default in the options. |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
// First two dependencies, jquery.event.drag.js and
|
||||
|
// jquery.mousewheel.js, we put them inline here to save people the
|
||||
|
// effort of downloading them.
|
||||
|
|
||||
|
/* |
||||
|
jquery.event.drag.js ~ v1.5 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)
|
||||
|
Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt
|
||||
|
*/ |
||||
|
(function(a){function e(h){var k,j=this,l=h.data||{};if(l.elem)j=h.dragTarget=l.elem,h.dragProxy=d.proxy||j,h.cursorOffsetX=l.pageX-l.left,h.cursorOffsetY=l.pageY-l.top,h.offsetX=h.pageX-h.cursorOffsetX,h.offsetY=h.pageY-h.cursorOffsetY;else if(d.dragging||l.which>0&&h.which!=l.which||a(h.target).is(l.not))return;switch(h.type){case"mousedown":return a.extend(l,a(j).offset(),{elem:j,target:h.target,pageX:h.pageX,pageY:h.pageY}),b.add(document,"mousemove mouseup",e,l),i(j,!1),d.dragging=null,!1;case!d.dragging&&"mousemove":if(g(h.pageX-l.pageX)+g(h.pageY-l.pageY)<l.distance)break;h.target=l.target,k=f(h,"dragstart",j),k!==!1&&(d.dragging=j,d.proxy=h.dragProxy=a(k||j)[0]);case"mousemove":if(d.dragging){if(k=f(h,"drag",j),c.drop&&(c.drop.allowed=k!==!1,c.drop.handler(h)),k!==!1)break;h.type="mouseup"}case"mouseup":b.remove(document,"mousemove mouseup",e),d.dragging&&(c.drop&&c.drop.handler(h),f(h,"dragend",j)),i(j,!0),d.dragging=d.proxy=l.elem=!1}return!0}function f(b,c,d){b.type=c;var e=a.event.dispatch.call(d,b);return e===!1?!1:e||b.result}function g(a){return Math.pow(a,2)}function h(){return d.dragging===!1}function i(a,b){a&&(a.unselectable=b?"off":"on",a.onselectstart=function(){return b},a.style&&(a.style.MozUserSelect=b?"":"none"))}a.fn.drag=function(a,b,c){return b&&this.bind("dragstart",a),c&&this.bind("dragend",c),a?this.bind("drag",b?b:a):this.trigger("drag")};var b=a.event,c=b.special,d=c.drag={not:":input",distance:0,which:1,dragging:!1,setup:function(c){c=a.extend({distance:d.distance,which:d.which,not:d.not},c||{}),c.distance=g(c.distance),b.add(this,"mousedown",e,c),this.attachEvent&&this.attachEvent("ondragstart",h)},teardown:function(){b.remove(this,"mousedown",e),this===d.dragging&&(d.dragging=d.proxy=!1),i(this,!0),this.detachEvent&&this.detachEvent("ondragstart",h)}};c.dragstart=c.dragend={setup:function(){},teardown:function(){}}})(jQuery); |
||||
|
|
||||
|
/* jquery.mousewheel.min.js |
||||
|
* Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
|
||||
|
* Licensed under the MIT License (LICENSE.txt). |
||||
|
* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
|
||||
|
* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
|
||||
|
* Thanks to: Seamus Leahy for adding deltaX and deltaY |
||||
|
* |
||||
|
* Version: 3.0.6 |
||||
|
* |
||||
|
* Requires: 1.2.2+ |
||||
|
*/ |
||||
|
(function(d){function e(a){var b=a||window.event,c=[].slice.call(arguments,1),f=0,e=0,g=0,a=d.event.fix(b);a.type="mousewheel";b.wheelDelta&&(f=b.wheelDelta/120);b.detail&&(f=-b.detail/3);g=f;void 0!==b.axis&&b.axis===b.HORIZONTAL_AXIS&&(g=0,e=-1*f);void 0!==b.wheelDeltaY&&(g=b.wheelDeltaY/120);void 0!==b.wheelDeltaX&&(e=-1*b.wheelDeltaX/120);c.unshift(a,f,e,g);return(d.event.dispatch||d.event.handle).apply(this,c)}var c=["DOMMouseScroll","mousewheel"];if(d.event.fixHooks)for(var h=c.length;h;)d.event.fixHooks[c[--h]]=d.event.mouseHooks;d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=c.length;a;)this.addEventListener(c[--a],e,!1);else this.onmousewheel=e},teardown:function(){if(this.removeEventListener)for(var a=c.length;a;)this.removeEventListener(c[--a],e,!1);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery); |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
(function ($) { |
||||
|
var options = { |
||||
|
xaxis: { |
||||
|
zoomRange: null, // or [number, number] (min range, max range)
|
||||
|
panRange: null // or [number, number] (min, max)
|
||||
|
}, |
||||
|
zoom: { |
||||
|
interactive: false, |
||||
|
trigger: "dblclick", // or "click" for single click
|
||||
|
amount: 1.5 // how much to zoom relative to current position, 2 = 200% (zoom in), 0.5 = 50% (zoom out)
|
||||
|
}, |
||||
|
pan: { |
||||
|
interactive: false, |
||||
|
cursor: "move", |
||||
|
frameRate: 20 |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
function init(plot) { |
||||
|
function onZoomClick(e, zoomOut) { |
||||
|
var c = plot.offset(); |
||||
|
c.left = e.pageX - c.left; |
||||
|
c.top = e.pageY - c.top; |
||||
|
if (zoomOut) |
||||
|
plot.zoomOut({ center: c }); |
||||
|
else |
||||
|
plot.zoom({ center: c }); |
||||
|
} |
||||
|
|
||||
|
function onMouseWheel(e, delta) { |
||||
|
e.preventDefault(); |
||||
|
onZoomClick(e, delta < 0); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var prevCursor = 'default', prevPageX = 0, prevPageY = 0, |
||||
|
panTimeout = null; |
||||
|
|
||||
|
function onDragStart(e) { |
||||
|
if (e.which != 1) // only accept left-click
|
||||
|
return false; |
||||
|
var c = plot.getPlaceholder().css('cursor'); |
||||
|
if (c) |
||||
|
prevCursor = c; |
||||
|
plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor); |
||||
|
prevPageX = e.pageX; |
||||
|
prevPageY = e.pageY; |
||||
|
} |
||||
|
|
||||
|
function onDrag(e) { |
||||
|
var frameRate = plot.getOptions().pan.frameRate; |
||||
|
if (panTimeout || !frameRate) |
||||
|
return; |
||||
|
|
||||
|
panTimeout = setTimeout(function () { |
||||
|
plot.pan({ left: prevPageX - e.pageX, |
||||
|
top: prevPageY - e.pageY }); |
||||
|
prevPageX = e.pageX; |
||||
|
prevPageY = e.pageY; |
||||
|
|
||||
|
panTimeout = null; |
||||
|
}, 1 / frameRate * 1000); |
||||
|
} |
||||
|
|
||||
|
function onDragEnd(e) { |
||||
|
if (panTimeout) { |
||||
|
clearTimeout(panTimeout); |
||||
|
panTimeout = null; |
||||
|
} |
||||
|
|
||||
|
plot.getPlaceholder().css('cursor', prevCursor); |
||||
|
plot.pan({ left: prevPageX - e.pageX, |
||||
|
top: prevPageY - e.pageY }); |
||||
|
} |
||||
|
|
||||
|
function bindEvents(plot, eventHolder) { |
||||
|
var o = plot.getOptions(); |
||||
|
if (o.zoom.interactive) { |
||||
|
eventHolder[o.zoom.trigger](onZoomClick); |
||||
|
eventHolder.mousewheel(onMouseWheel); |
||||
|
} |
||||
|
|
||||
|
if (o.pan.interactive) { |
||||
|
eventHolder.bind("dragstart", { distance: 10 }, onDragStart); |
||||
|
eventHolder.bind("drag", onDrag); |
||||
|
eventHolder.bind("dragend", onDragEnd); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
plot.zoomOut = function (args) { |
||||
|
if (!args) |
||||
|
args = {}; |
||||
|
|
||||
|
if (!args.amount) |
||||
|
args.amount = plot.getOptions().zoom.amount; |
||||
|
|
||||
|
args.amount = 1 / args.amount; |
||||
|
plot.zoom(args); |
||||
|
}; |
||||
|
|
||||
|
plot.zoom = function (args) { |
||||
|
if (!args) |
||||
|
args = {}; |
||||
|
|
||||
|
var c = args.center, |
||||
|
amount = args.amount || plot.getOptions().zoom.amount, |
||||
|
w = plot.width(), h = plot.height(); |
||||
|
|
||||
|
if (!c) |
||||
|
c = { left: w / 2, top: h / 2 }; |
||||
|
|
||||
|
var xf = c.left / w, |
||||
|
yf = c.top / h, |
||||
|
minmax = { |
||||
|
x: { |
||||
|
min: c.left - xf * w / amount, |
||||
|
max: c.left + (1 - xf) * w / amount |
||||
|
}, |
||||
|
y: { |
||||
|
min: c.top - yf * h / amount, |
||||
|
max: c.top + (1 - yf) * h / amount |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
$.each(plot.getAxes(), function(_, axis) { |
||||
|
var opts = axis.options, |
||||
|
min = minmax[axis.direction].min, |
||||
|
max = minmax[axis.direction].max, |
||||
|
zr = opts.zoomRange, |
||||
|
pr = opts.panRange; |
||||
|
|
||||
|
if (zr === false) // no zooming on this axis
|
||||
|
return; |
||||
|
|
||||
|
min = axis.c2p(min); |
||||
|
max = axis.c2p(max); |
||||
|
if (min > max) { |
||||
|
// make sure min < max
|
||||
|
var tmp = min; |
||||
|
min = max; |
||||
|
max = tmp; |
||||
|
} |
||||
|
|
||||
|
//Check that we are in panRange
|
||||
|
if (pr) { |
||||
|
if (pr[0] != null && min < pr[0]) { |
||||
|
min = pr[0]; |
||||
|
} |
||||
|
if (pr[1] != null && max > pr[1]) { |
||||
|
max = pr[1]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var range = max - min; |
||||
|
if (zr && |
||||
|
((zr[0] != null && range < zr[0] && amount >1) || |
||||
|
(zr[1] != null && range > zr[1] && amount <1))) |
||||
|
return; |
||||
|
|
||||
|
opts.min = min; |
||||
|
opts.max = max; |
||||
|
}); |
||||
|
|
||||
|
plot.setupGrid(); |
||||
|
plot.draw(); |
||||
|
|
||||
|
if (!args.preventEvent) |
||||
|
plot.getPlaceholder().trigger("plotzoom", [ plot, args ]); |
||||
|
}; |
||||
|
|
||||
|
plot.pan = function (args) { |
||||
|
var delta = { |
||||
|
x: +args.left, |
||||
|
y: +args.top |
||||
|
}; |
||||
|
|
||||
|
if (isNaN(delta.x)) |
||||
|
delta.x = 0; |
||||
|
if (isNaN(delta.y)) |
||||
|
delta.y = 0; |
||||
|
|
||||
|
$.each(plot.getAxes(), function (_, axis) { |
||||
|
var opts = axis.options, |
||||
|
min, max, d = delta[axis.direction]; |
||||
|
|
||||
|
min = axis.c2p(axis.p2c(axis.min) + d), |
||||
|
max = axis.c2p(axis.p2c(axis.max) + d); |
||||
|
|
||||
|
var pr = opts.panRange; |
||||
|
if (pr === false) // no panning on this axis
|
||||
|
return; |
||||
|
|
||||
|
if (pr) { |
||||
|
// check whether we hit the wall
|
||||
|
if (pr[0] != null && pr[0] > min) { |
||||
|
d = pr[0] - min; |
||||
|
min += d; |
||||
|
max += d; |
||||
|
} |
||||
|
|
||||
|
if (pr[1] != null && pr[1] < max) { |
||||
|
d = pr[1] - max; |
||||
|
min += d; |
||||
|
max += d; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
opts.min = min; |
||||
|
opts.max = max; |
||||
|
}); |
||||
|
|
||||
|
plot.setupGrid(); |
||||
|
plot.draw(); |
||||
|
|
||||
|
if (!args.preventEvent) |
||||
|
plot.getPlaceholder().trigger("plotpan", [ plot, args ]); |
||||
|
}; |
||||
|
|
||||
|
function shutdown(plot, eventHolder) { |
||||
|
eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick); |
||||
|
eventHolder.unbind("mousewheel", onMouseWheel); |
||||
|
eventHolder.unbind("dragstart", onDragStart); |
||||
|
eventHolder.unbind("drag", onDrag); |
||||
|
eventHolder.unbind("dragend", onDragEnd); |
||||
|
if (panTimeout) |
||||
|
clearTimeout(panTimeout); |
||||
|
} |
||||
|
|
||||
|
plot.hooks.bindEvents.push(bindEvents); |
||||
|
plot.hooks.shutdown.push(shutdown); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: 'navigate', |
||||
|
version: '1.3' |
||||
|
}); |
||||
|
})(jQuery); |
||||
@ -0,0 +1,820 @@ |
|||||
|
/* Flot plugin for rendering pie charts. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
The plugin assumes that each series has a single data value, and that each |
||||
|
value is a positive integer or zero. Negative numbers don't make sense for a |
||||
|
pie chart, and have unpredictable results. The values do NOT need to be |
||||
|
passed in as percentages; the plugin will calculate the total and per-slice |
||||
|
percentages internally. |
||||
|
|
||||
|
* Created by Brian Medendorp |
||||
|
|
||||
|
* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars |
||||
|
|
||||
|
The plugin supports these options: |
||||
|
|
||||
|
series: { |
||||
|
pie: { |
||||
|
show: true/false |
||||
|
radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto' |
||||
|
innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect |
||||
|
startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result |
||||
|
tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show) |
||||
|
offset: { |
||||
|
top: integer value to move the pie up or down |
||||
|
left: integer value to move the pie left or right, or 'auto' |
||||
|
}, |
||||
|
stroke: { |
||||
|
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF') |
||||
|
width: integer pixel width of the stroke |
||||
|
}, |
||||
|
label: { |
||||
|
show: true/false, or 'auto' |
||||
|
formatter: a user-defined function that modifies the text/style of the label text |
||||
|
radius: 0-1 for percentage of fullsize, or a specified pixel length |
||||
|
background: { |
||||
|
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000') |
||||
|
opacity: 0-1 |
||||
|
}, |
||||
|
threshold: 0-1 for the percentage value at which to hide labels (if they're too small) |
||||
|
}, |
||||
|
combine: { |
||||
|
threshold: 0-1 for the percentage value at which to combine slices (if they're too small) |
||||
|
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined |
||||
|
label: any text value of what the combined slice should be labeled |
||||
|
} |
||||
|
highlight: { |
||||
|
opacity: 0-1 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
More detail and specific examples can be found in the included HTML file. |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
(function($) { |
||||
|
|
||||
|
// Maximum redraw attempts when fitting labels within the plot
|
||||
|
|
||||
|
var REDRAW_ATTEMPTS = 10; |
||||
|
|
||||
|
// Factor by which to shrink the pie when fitting labels within the plot
|
||||
|
|
||||
|
var REDRAW_SHRINK = 0.95; |
||||
|
|
||||
|
function init(plot) { |
||||
|
|
||||
|
var canvas = null, |
||||
|
target = null, |
||||
|
options = null, |
||||
|
maxRadius = null, |
||||
|
centerLeft = null, |
||||
|
centerTop = null, |
||||
|
processed = false, |
||||
|
ctx = null; |
||||
|
|
||||
|
// interactive variables
|
||||
|
|
||||
|
var highlights = []; |
||||
|
|
||||
|
// add hook to determine if pie plugin in enabled, and then perform necessary operations
|
||||
|
|
||||
|
plot.hooks.processOptions.push(function(plot, options) { |
||||
|
if (options.series.pie.show) { |
||||
|
|
||||
|
options.grid.show = false; |
||||
|
|
||||
|
// set labels.show
|
||||
|
|
||||
|
if (options.series.pie.label.show == "auto") { |
||||
|
if (options.legend.show) { |
||||
|
options.series.pie.label.show = false; |
||||
|
} else { |
||||
|
options.series.pie.label.show = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// set radius
|
||||
|
|
||||
|
if (options.series.pie.radius == "auto") { |
||||
|
if (options.series.pie.label.show) { |
||||
|
options.series.pie.radius = 3/4; |
||||
|
} else { |
||||
|
options.series.pie.radius = 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ensure sane tilt
|
||||
|
|
||||
|
if (options.series.pie.tilt > 1) { |
||||
|
options.series.pie.tilt = 1; |
||||
|
} else if (options.series.pie.tilt < 0) { |
||||
|
options.series.pie.tilt = 0; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
plot.hooks.bindEvents.push(function(plot, eventHolder) { |
||||
|
var options = plot.getOptions(); |
||||
|
if (options.series.pie.show) { |
||||
|
if (options.grid.hoverable) { |
||||
|
eventHolder.unbind("mousemove").mousemove(onMouseMove); |
||||
|
} |
||||
|
if (options.grid.clickable) { |
||||
|
eventHolder.unbind("click").click(onClick); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) { |
||||
|
var options = plot.getOptions(); |
||||
|
if (options.series.pie.show) { |
||||
|
processDatapoints(plot, series, data, datapoints); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
plot.hooks.drawOverlay.push(function(plot, octx) { |
||||
|
var options = plot.getOptions(); |
||||
|
if (options.series.pie.show) { |
||||
|
drawOverlay(plot, octx); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
plot.hooks.draw.push(function(plot, newCtx) { |
||||
|
var options = plot.getOptions(); |
||||
|
if (options.series.pie.show) { |
||||
|
draw(plot, newCtx); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function processDatapoints(plot, series, datapoints) { |
||||
|
if (!processed) { |
||||
|
processed = true; |
||||
|
canvas = plot.getCanvas(); |
||||
|
target = $(canvas).parent(); |
||||
|
options = plot.getOptions(); |
||||
|
plot.setData(combine(plot.getData())); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function combine(data) { |
||||
|
|
||||
|
var total = 0, |
||||
|
combined = 0, |
||||
|
numCombined = 0, |
||||
|
color = options.series.pie.combine.color, |
||||
|
newdata = []; |
||||
|
|
||||
|
// Fix up the raw data from Flot, ensuring the data is numeric
|
||||
|
|
||||
|
for (var i = 0; i < data.length; ++i) { |
||||
|
|
||||
|
var value = data[i].data; |
||||
|
|
||||
|
// If the data is an array, we'll assume that it's a standard
|
||||
|
// Flot x-y pair, and are concerned only with the second value.
|
||||
|
|
||||
|
// Note how we use the original array, rather than creating a
|
||||
|
// new one; this is more efficient and preserves any extra data
|
||||
|
// that the user may have stored in higher indexes.
|
||||
|
|
||||
|
if ($.isArray(value) && value.length == 1) { |
||||
|
value = value[0]; |
||||
|
} |
||||
|
|
||||
|
if ($.isArray(value)) { |
||||
|
// Equivalent to $.isNumeric() but compatible with jQuery < 1.7
|
||||
|
if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) { |
||||
|
value[1] = +value[1]; |
||||
|
} else { |
||||
|
value[1] = 0; |
||||
|
} |
||||
|
} else if (!isNaN(parseFloat(value)) && isFinite(value)) { |
||||
|
value = [1, +value]; |
||||
|
} else { |
||||
|
value = [1, 0]; |
||||
|
} |
||||
|
|
||||
|
data[i].data = [value]; |
||||
|
} |
||||
|
|
||||
|
// Sum up all the slices, so we can calculate percentages for each
|
||||
|
|
||||
|
for (var i = 0; i < data.length; ++i) { |
||||
|
total += data[i].data[0][1]; |
||||
|
} |
||||
|
|
||||
|
// Count the number of slices with percentages below the combine
|
||||
|
// threshold; if it turns out to be just one, we won't combine.
|
||||
|
|
||||
|
for (var i = 0; i < data.length; ++i) { |
||||
|
var value = data[i].data[0][1]; |
||||
|
if (value / total <= options.series.pie.combine.threshold) { |
||||
|
combined += value; |
||||
|
numCombined++; |
||||
|
if (!color) { |
||||
|
color = data[i].color; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (var i = 0; i < data.length; ++i) { |
||||
|
var value = data[i].data[0][1]; |
||||
|
if (numCombined < 2 || value / total > options.series.pie.combine.threshold) { |
||||
|
newdata.push( |
||||
|
$.extend(data[i], { /* extend to allow keeping all other original data values |
||||
|
and using them e.g. in labelFormatter. */ |
||||
|
data: [[1, value]], |
||||
|
color: data[i].color, |
||||
|
label: data[i].label, |
||||
|
angle: value * Math.PI * 2 / total, |
||||
|
percent: value / (total / 100) |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (numCombined > 1) { |
||||
|
newdata.push({ |
||||
|
data: [[1, combined]], |
||||
|
color: color, |
||||
|
label: options.series.pie.combine.label, |
||||
|
angle: combined * Math.PI * 2 / total, |
||||
|
percent: combined / (total / 100) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return newdata; |
||||
|
} |
||||
|
|
||||
|
function draw(plot, newCtx) { |
||||
|
|
||||
|
if (!target) { |
||||
|
return; // if no series were passed
|
||||
|
} |
||||
|
|
||||
|
var canvasWidth = plot.getPlaceholder().width(), |
||||
|
canvasHeight = plot.getPlaceholder().height(), |
||||
|
legendWidth = target.children().filter(".legend").children().width() || 0; |
||||
|
|
||||
|
ctx = newCtx; |
||||
|
|
||||
|
// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
|
||||
|
|
||||
|
// When combining smaller slices into an 'other' slice, we need to
|
||||
|
// add a new series. Since Flot gives plugins no way to modify the
|
||||
|
// list of series, the pie plugin uses a hack where the first call
|
||||
|
// to processDatapoints results in a call to setData with the new
|
||||
|
// list of series, then subsequent processDatapoints do nothing.
|
||||
|
|
||||
|
// The plugin-global 'processed' flag is used to control this hack;
|
||||
|
// it starts out false, and is set to true after the first call to
|
||||
|
// processDatapoints.
|
||||
|
|
||||
|
// Unfortunately this turns future setData calls into no-ops; they
|
||||
|
// call processDatapoints, the flag is true, and nothing happens.
|
||||
|
|
||||
|
// To fix this we'll set the flag back to false here in draw, when
|
||||
|
// all series have been processed, so the next sequence of calls to
|
||||
|
// processDatapoints once again starts out with a slice-combine.
|
||||
|
// This is really a hack; in 0.9 we need to give plugins a proper
|
||||
|
// way to modify series before any processing begins.
|
||||
|
|
||||
|
processed = false; |
||||
|
|
||||
|
// calculate maximum radius and center point
|
||||
|
|
||||
|
maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2; |
||||
|
centerTop = canvasHeight / 2 + options.series.pie.offset.top; |
||||
|
centerLeft = canvasWidth / 2; |
||||
|
|
||||
|
if (options.series.pie.offset.left == "auto") { |
||||
|
if (options.legend.position.match("w")) { |
||||
|
centerLeft += legendWidth / 2; |
||||
|
} else { |
||||
|
centerLeft -= legendWidth / 2; |
||||
|
} |
||||
|
if (centerLeft < maxRadius) { |
||||
|
centerLeft = maxRadius; |
||||
|
} else if (centerLeft > canvasWidth - maxRadius) { |
||||
|
centerLeft = canvasWidth - maxRadius; |
||||
|
} |
||||
|
} else { |
||||
|
centerLeft += options.series.pie.offset.left; |
||||
|
} |
||||
|
|
||||
|
var slices = plot.getData(), |
||||
|
attempts = 0; |
||||
|
|
||||
|
// Keep shrinking the pie's radius until drawPie returns true,
|
||||
|
// indicating that all the labels fit, or we try too many times.
|
||||
|
|
||||
|
do { |
||||
|
if (attempts > 0) { |
||||
|
maxRadius *= REDRAW_SHRINK; |
||||
|
} |
||||
|
attempts += 1; |
||||
|
clear(); |
||||
|
if (options.series.pie.tilt <= 0.8) { |
||||
|
drawShadow(); |
||||
|
} |
||||
|
} while (!drawPie() && attempts < REDRAW_ATTEMPTS) |
||||
|
|
||||
|
if (attempts >= REDRAW_ATTEMPTS) { |
||||
|
clear(); |
||||
|
target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>"); |
||||
|
} |
||||
|
|
||||
|
if (plot.setSeries && plot.insertLegend) { |
||||
|
plot.setSeries(slices); |
||||
|
plot.insertLegend(); |
||||
|
} |
||||
|
|
||||
|
// we're actually done at this point, just defining internal functions at this point
|
||||
|
|
||||
|
function clear() { |
||||
|
ctx.clearRect(0, 0, canvasWidth, canvasHeight); |
||||
|
target.children().filter(".pieLabel, .pieLabelBackground").remove(); |
||||
|
} |
||||
|
|
||||
|
function drawShadow() { |
||||
|
|
||||
|
var shadowLeft = options.series.pie.shadow.left; |
||||
|
var shadowTop = options.series.pie.shadow.top; |
||||
|
var edge = 10; |
||||
|
var alpha = options.series.pie.shadow.alpha; |
||||
|
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; |
||||
|
|
||||
|
if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) { |
||||
|
return; // shadow would be outside canvas, so don't draw it
|
||||
|
} |
||||
|
|
||||
|
ctx.save(); |
||||
|
ctx.translate(shadowLeft,shadowTop); |
||||
|
ctx.globalAlpha = alpha; |
||||
|
ctx.fillStyle = "#000"; |
||||
|
|
||||
|
// center and rotate to starting position
|
||||
|
|
||||
|
ctx.translate(centerLeft,centerTop); |
||||
|
ctx.scale(1, options.series.pie.tilt); |
||||
|
|
||||
|
//radius -= edge;
|
||||
|
|
||||
|
for (var i = 1; i <= edge; i++) { |
||||
|
ctx.beginPath(); |
||||
|
ctx.arc(0, 0, radius, 0, Math.PI * 2, false); |
||||
|
ctx.fill(); |
||||
|
radius -= i; |
||||
|
} |
||||
|
|
||||
|
ctx.restore(); |
||||
|
} |
||||
|
|
||||
|
function drawPie() { |
||||
|
|
||||
|
var startAngle = Math.PI * options.series.pie.startAngle; |
||||
|
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; |
||||
|
|
||||
|
// center and rotate to starting position
|
||||
|
|
||||
|
ctx.save(); |
||||
|
ctx.translate(centerLeft,centerTop); |
||||
|
ctx.scale(1, options.series.pie.tilt); |
||||
|
//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
|
||||
|
|
||||
|
// draw slices
|
||||
|
|
||||
|
ctx.save(); |
||||
|
var currentAngle = startAngle; |
||||
|
for (var i = 0; i < slices.length; ++i) { |
||||
|
slices[i].startAngle = currentAngle; |
||||
|
drawSlice(slices[i].angle, slices[i].color, true); |
||||
|
} |
||||
|
ctx.restore(); |
||||
|
|
||||
|
// draw slice outlines
|
||||
|
|
||||
|
if (options.series.pie.stroke.width > 0) { |
||||
|
ctx.save(); |
||||
|
ctx.lineWidth = options.series.pie.stroke.width; |
||||
|
currentAngle = startAngle; |
||||
|
for (var i = 0; i < slices.length; ++i) { |
||||
|
drawSlice(slices[i].angle, options.series.pie.stroke.color, false); |
||||
|
} |
||||
|
ctx.restore(); |
||||
|
} |
||||
|
|
||||
|
// draw donut hole
|
||||
|
|
||||
|
drawDonutHole(ctx); |
||||
|
|
||||
|
ctx.restore(); |
||||
|
|
||||
|
// Draw the labels, returning true if they fit within the plot
|
||||
|
|
||||
|
if (options.series.pie.label.show) { |
||||
|
return drawLabels(); |
||||
|
} else return true; |
||||
|
|
||||
|
function drawSlice(angle, color, fill) { |
||||
|
|
||||
|
if (angle <= 0 || isNaN(angle)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (fill) { |
||||
|
ctx.fillStyle = color; |
||||
|
} else { |
||||
|
ctx.strokeStyle = color; |
||||
|
ctx.lineJoin = "round"; |
||||
|
} |
||||
|
|
||||
|
ctx.beginPath(); |
||||
|
if (Math.abs(angle - Math.PI * 2) > 0.000000001) { |
||||
|
ctx.moveTo(0, 0); // Center of the pie
|
||||
|
} |
||||
|
|
||||
|
//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
|
||||
|
ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false); |
||||
|
ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false); |
||||
|
ctx.closePath(); |
||||
|
//ctx.rotate(angle); // This doesn't work properly in Opera
|
||||
|
currentAngle += angle; |
||||
|
|
||||
|
if (fill) { |
||||
|
ctx.fill(); |
||||
|
} else { |
||||
|
ctx.stroke(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function drawLabels() { |
||||
|
|
||||
|
var currentAngle = startAngle; |
||||
|
var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius; |
||||
|
|
||||
|
for (var i = 0; i < slices.length; ++i) { |
||||
|
if (slices[i].percent >= options.series.pie.label.threshold * 100) { |
||||
|
if (!drawLabel(slices[i], currentAngle, i)) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
currentAngle += slices[i].angle; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
|
||||
|
function drawLabel(slice, startAngle, index) { |
||||
|
|
||||
|
if (slice.data[0][1] == 0) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// format label text
|
||||
|
|
||||
|
var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; |
||||
|
|
||||
|
if (lf) { |
||||
|
text = lf(slice.label, slice); |
||||
|
} else { |
||||
|
text = slice.label; |
||||
|
} |
||||
|
|
||||
|
if (plf) { |
||||
|
text = plf(text, slice); |
||||
|
} |
||||
|
|
||||
|
var halfAngle = ((startAngle + slice.angle) + startAngle) / 2; |
||||
|
var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); |
||||
|
var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; |
||||
|
|
||||
|
var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>"; |
||||
|
target.append(html); |
||||
|
|
||||
|
var label = target.children("#pieLabel" + index); |
||||
|
var labelTop = (y - label.height() / 2); |
||||
|
var labelLeft = (x - label.width() / 2); |
||||
|
|
||||
|
label.css("top", labelTop); |
||||
|
label.css("left", labelLeft); |
||||
|
|
||||
|
// check to make sure that the label is not outside the canvas
|
||||
|
|
||||
|
if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (options.series.pie.label.background.opacity != 0) { |
||||
|
|
||||
|
// put in the transparent background separately to avoid blended labels and label boxes
|
||||
|
|
||||
|
var c = options.series.pie.label.background.color; |
||||
|
|
||||
|
if (c == null) { |
||||
|
c = slice.color; |
||||
|
} |
||||
|
|
||||
|
var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;"; |
||||
|
$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>") |
||||
|
.css("opacity", options.series.pie.label.background.opacity) |
||||
|
.insertBefore(label); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} // end individual label function
|
||||
|
} // end drawLabels function
|
||||
|
} // end drawPie function
|
||||
|
} // end draw function
|
||||
|
|
||||
|
// Placed here because it needs to be accessed from multiple locations
|
||||
|
|
||||
|
function drawDonutHole(layer) { |
||||
|
if (options.series.pie.innerRadius > 0) { |
||||
|
|
||||
|
// subtract the center
|
||||
|
|
||||
|
layer.save(); |
||||
|
var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; |
||||
|
layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
|
||||
|
layer.beginPath(); |
||||
|
layer.fillStyle = options.series.pie.stroke.color; |
||||
|
layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); |
||||
|
layer.fill(); |
||||
|
layer.closePath(); |
||||
|
layer.restore(); |
||||
|
|
||||
|
// add inner stroke
|
||||
|
|
||||
|
layer.save(); |
||||
|
layer.beginPath(); |
||||
|
layer.strokeStyle = options.series.pie.stroke.color; |
||||
|
layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); |
||||
|
layer.stroke(); |
||||
|
layer.closePath(); |
||||
|
layer.restore(); |
||||
|
|
||||
|
// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//-- Additional Interactive related functions --
|
||||
|
|
||||
|
function isPointInPoly(poly, pt) { |
||||
|
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) |
||||
|
((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) |
||||
|
&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) |
||||
|
&& (c = !c); |
||||
|
return c; |
||||
|
} |
||||
|
|
||||
|
function findNearbySlice(mouseX, mouseY) { |
||||
|
|
||||
|
var slices = plot.getData(), |
||||
|
options = plot.getOptions(), |
||||
|
radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius, |
||||
|
x, y; |
||||
|
|
||||
|
for (var i = 0; i < slices.length; ++i) { |
||||
|
|
||||
|
var s = slices[i]; |
||||
|
|
||||
|
if (s.pie.show) { |
||||
|
|
||||
|
ctx.save(); |
||||
|
ctx.beginPath(); |
||||
|
ctx.moveTo(0, 0); // Center of the pie
|
||||
|
//ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here.
|
||||
|
ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false); |
||||
|
ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false); |
||||
|
ctx.closePath(); |
||||
|
x = mouseX - centerLeft; |
||||
|
y = mouseY - centerTop; |
||||
|
|
||||
|
if (ctx.isPointInPath) { |
||||
|
if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) { |
||||
|
ctx.restore(); |
||||
|
return { |
||||
|
datapoint: [s.percent, s.data], |
||||
|
dataIndex: 0, |
||||
|
series: s, |
||||
|
seriesIndex: i |
||||
|
}; |
||||
|
} |
||||
|
} else { |
||||
|
|
||||
|
// excanvas for IE doesn;t support isPointInPath, this is a workaround.
|
||||
|
|
||||
|
var p1X = radius * Math.cos(s.startAngle), |
||||
|
p1Y = radius * Math.sin(s.startAngle), |
||||
|
p2X = radius * Math.cos(s.startAngle + s.angle / 4), |
||||
|
p2Y = radius * Math.sin(s.startAngle + s.angle / 4), |
||||
|
p3X = radius * Math.cos(s.startAngle + s.angle / 2), |
||||
|
p3Y = radius * Math.sin(s.startAngle + s.angle / 2), |
||||
|
p4X = radius * Math.cos(s.startAngle + s.angle / 1.5), |
||||
|
p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5), |
||||
|
p5X = radius * Math.cos(s.startAngle + s.angle), |
||||
|
p5Y = radius * Math.sin(s.startAngle + s.angle), |
||||
|
arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]], |
||||
|
arrPoint = [x, y]; |
||||
|
|
||||
|
// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
|
||||
|
|
||||
|
if (isPointInPoly(arrPoly, arrPoint)) { |
||||
|
ctx.restore(); |
||||
|
return { |
||||
|
datapoint: [s.percent, s.data], |
||||
|
dataIndex: 0, |
||||
|
series: s, |
||||
|
seriesIndex: i |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ctx.restore(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
function onMouseMove(e) { |
||||
|
triggerClickHoverEvent("plothover", e); |
||||
|
} |
||||
|
|
||||
|
function onClick(e) { |
||||
|
triggerClickHoverEvent("plotclick", e); |
||||
|
} |
||||
|
|
||||
|
// trigger click or hover event (they send the same parameters so we share their code)
|
||||
|
|
||||
|
function triggerClickHoverEvent(eventname, e) { |
||||
|
|
||||
|
var offset = plot.offset(); |
||||
|
var canvasX = parseInt(e.pageX - offset.left); |
||||
|
var canvasY = parseInt(e.pageY - offset.top); |
||||
|
var item = findNearbySlice(canvasX, canvasY); |
||||
|
|
||||
|
if (options.grid.autoHighlight) { |
||||
|
|
||||
|
// clear auto-highlights
|
||||
|
|
||||
|
for (var i = 0; i < highlights.length; ++i) { |
||||
|
var h = highlights[i]; |
||||
|
if (h.auto == eventname && !(item && h.series == item.series)) { |
||||
|
unhighlight(h.series); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// highlight the slice
|
||||
|
|
||||
|
if (item) { |
||||
|
highlight(item.series, eventname); |
||||
|
} |
||||
|
|
||||
|
// trigger any hover bind events
|
||||
|
|
||||
|
var pos = { pageX: e.pageX, pageY: e.pageY }; |
||||
|
target.trigger(eventname, [pos, item]); |
||||
|
} |
||||
|
|
||||
|
function highlight(s, auto) { |
||||
|
//if (typeof s == "number") {
|
||||
|
// s = series[s];
|
||||
|
//}
|
||||
|
|
||||
|
var i = indexOfHighlight(s); |
||||
|
|
||||
|
if (i == -1) { |
||||
|
highlights.push({ series: s, auto: auto }); |
||||
|
plot.triggerRedrawOverlay(); |
||||
|
} else if (!auto) { |
||||
|
highlights[i].auto = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function unhighlight(s) { |
||||
|
if (s == null) { |
||||
|
highlights = []; |
||||
|
plot.triggerRedrawOverlay(); |
||||
|
} |
||||
|
|
||||
|
//if (typeof s == "number") {
|
||||
|
// s = series[s];
|
||||
|
//}
|
||||
|
|
||||
|
var i = indexOfHighlight(s); |
||||
|
|
||||
|
if (i != -1) { |
||||
|
highlights.splice(i, 1); |
||||
|
plot.triggerRedrawOverlay(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function indexOfHighlight(s) { |
||||
|
for (var i = 0; i < highlights.length; ++i) { |
||||
|
var h = highlights[i]; |
||||
|
if (h.series == s) |
||||
|
return i; |
||||
|
} |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
function drawOverlay(plot, octx) { |
||||
|
|
||||
|
var options = plot.getOptions(); |
||||
|
|
||||
|
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; |
||||
|
|
||||
|
octx.save(); |
||||
|
octx.translate(centerLeft, centerTop); |
||||
|
octx.scale(1, options.series.pie.tilt); |
||||
|
|
||||
|
for (var i = 0; i < highlights.length; ++i) { |
||||
|
drawHighlight(highlights[i].series); |
||||
|
} |
||||
|
|
||||
|
drawDonutHole(octx); |
||||
|
|
||||
|
octx.restore(); |
||||
|
|
||||
|
function drawHighlight(series) { |
||||
|
|
||||
|
if (series.angle <= 0 || isNaN(series.angle)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
|
||||
|
octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
|
||||
|
octx.beginPath(); |
||||
|
if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) { |
||||
|
octx.moveTo(0, 0); // Center of the pie
|
||||
|
} |
||||
|
octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false); |
||||
|
octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false); |
||||
|
octx.closePath(); |
||||
|
octx.fill(); |
||||
|
} |
||||
|
} |
||||
|
} // end init (plugin body)
|
||||
|
|
||||
|
// define pie specific options and their default values
|
||||
|
|
||||
|
var options = { |
||||
|
series: { |
||||
|
pie: { |
||||
|
show: false, |
||||
|
radius: "auto", // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
|
||||
|
innerRadius: 0, /* for donut */ |
||||
|
startAngle: 3/2, |
||||
|
tilt: 1, |
||||
|
shadow: { |
||||
|
left: 5, // shadow left offset
|
||||
|
top: 15, // shadow top offset
|
||||
|
alpha: 0.02 // shadow alpha
|
||||
|
}, |
||||
|
offset: { |
||||
|
top: 0, |
||||
|
left: "auto" |
||||
|
}, |
||||
|
stroke: { |
||||
|
color: "#fff", |
||||
|
width: 1 |
||||
|
}, |
||||
|
label: { |
||||
|
show: "auto", |
||||
|
formatter: function(label, slice) { |
||||
|
return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>"; |
||||
|
}, // formatter function
|
||||
|
radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
|
||||
|
background: { |
||||
|
color: null, |
||||
|
opacity: 0 |
||||
|
}, |
||||
|
threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow)
|
||||
|
}, |
||||
|
combine: { |
||||
|
threshold: -1, // percentage at which to combine little slices into one larger slice
|
||||
|
color: null, // color to give the new slice (auto-generated if null)
|
||||
|
label: "Other" // label to give the new slice
|
||||
|
}, |
||||
|
highlight: { |
||||
|
//color: "#fff", // will add this functionality once parseColor is available
|
||||
|
opacity: 0.5 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: "pie", |
||||
|
version: "1.1" |
||||
|
}); |
||||
|
|
||||
|
})(jQuery); |
||||
@ -0,0 +1,59 @@ |
|||||
|
/* Flot plugin for automatically redrawing plots as the placeholder resizes. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
It works by listening for changes on the placeholder div (through the jQuery |
||||
|
resize event plugin) - if the size changes, it will redraw the plot. |
||||
|
|
||||
|
There are no options. If you need to disable the plugin for some plots, you |
||||
|
can just fix the size of their placeholders. |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
/* Inline dependency: |
||||
|
* jQuery resize event - v1.1 - 3/14/2010 |
||||
|
* http://benalman.com/projects/jquery-resize-plugin/
|
||||
|
* |
||||
|
* Copyright (c) 2010 "Cowboy" Ben Alman |
||||
|
* Dual licensed under the MIT and GPL licenses. |
||||
|
* http://benalman.com/about/license/
|
||||
|
*/ |
||||
|
(function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this); |
||||
|
|
||||
|
(function ($) { |
||||
|
var options = { }; // no options
|
||||
|
|
||||
|
function init(plot) { |
||||
|
function onResize() { |
||||
|
var placeholder = plot.getPlaceholder(); |
||||
|
|
||||
|
// somebody might have hidden us and we can't plot
|
||||
|
// when we don't have the dimensions
|
||||
|
if (placeholder.width() == 0 || placeholder.height() == 0) |
||||
|
return; |
||||
|
|
||||
|
plot.resize(); |
||||
|
plot.setupGrid(); |
||||
|
plot.draw(); |
||||
|
} |
||||
|
|
||||
|
function bindEvents(plot, eventHolder) { |
||||
|
plot.getPlaceholder().resize(onResize); |
||||
|
} |
||||
|
|
||||
|
function shutdown(plot, eventHolder) { |
||||
|
plot.getPlaceholder().unbind("resize", onResize); |
||||
|
} |
||||
|
|
||||
|
plot.hooks.bindEvents.push(bindEvents); |
||||
|
plot.hooks.shutdown.push(shutdown); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: 'resize', |
||||
|
version: '1.0' |
||||
|
}); |
||||
|
})(jQuery); |
||||
@ -0,0 +1,360 @@ |
|||||
|
/* Flot plugin for selecting regions of a plot. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
The plugin supports these options: |
||||
|
|
||||
|
selection: { |
||||
|
mode: null or "x" or "y" or "xy", |
||||
|
color: color, |
||||
|
shape: "round" or "miter" or "bevel", |
||||
|
minSize: number of pixels |
||||
|
} |
||||
|
|
||||
|
Selection support is enabled by setting the mode to one of "x", "y" or "xy". |
||||
|
In "x" mode, the user will only be able to specify the x range, similarly for |
||||
|
"y" mode. For "xy", the selection becomes a rectangle where both ranges can be |
||||
|
specified. "color" is color of the selection (if you need to change the color |
||||
|
later on, you can get to it with plot.getOptions().selection.color). "shape" |
||||
|
is the shape of the corners of the selection. |
||||
|
|
||||
|
"minSize" is the minimum size a selection can be in pixels. This value can |
||||
|
be customized to determine the smallest size a selection can be and still |
||||
|
have the selection rectangle be displayed. When customizing this value, the |
||||
|
fact that it refers to pixels, not axis units must be taken into account. |
||||
|
Thus, for example, if there is a bar graph in time mode with BarWidth set to 1 |
||||
|
minute, setting "minSize" to 1 will not make the minimum selection size 1 |
||||
|
minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent |
||||
|
"plotunselected" events from being fired when the user clicks the mouse without |
||||
|
dragging. |
||||
|
|
||||
|
When selection support is enabled, a "plotselected" event will be emitted on |
||||
|
the DOM element you passed into the plot function. The event handler gets a |
||||
|
parameter with the ranges selected on the axes, like this: |
||||
|
|
||||
|
placeholder.bind( "plotselected", function( event, ranges ) { |
||||
|
alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) |
||||
|
// similar for yaxis - with multiple axes, the extra ones are in
|
||||
|
// x2axis, x3axis, ...
|
||||
|
}); |
||||
|
|
||||
|
The "plotselected" event is only fired when the user has finished making the |
||||
|
selection. A "plotselecting" event is fired during the process with the same |
||||
|
parameters as the "plotselected" event, in case you want to know what's |
||||
|
happening while it's happening, |
||||
|
|
||||
|
A "plotunselected" event with no arguments is emitted when the user clicks the |
||||
|
mouse to remove the selection. As stated above, setting "minSize" to 0 will |
||||
|
destroy this behavior. |
||||
|
|
||||
|
The plugin allso adds the following methods to the plot object: |
||||
|
|
||||
|
- setSelection( ranges, preventEvent ) |
||||
|
|
||||
|
Set the selection rectangle. The passed in ranges is on the same form as |
||||
|
returned in the "plotselected" event. If the selection mode is "x", you |
||||
|
should put in either an xaxis range, if the mode is "y" you need to put in |
||||
|
an yaxis range and both xaxis and yaxis if the selection mode is "xy", like |
||||
|
this: |
||||
|
|
||||
|
setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); |
||||
|
|
||||
|
setSelection will trigger the "plotselected" event when called. If you don't |
||||
|
want that to happen, e.g. if you're inside a "plotselected" handler, pass |
||||
|
true as the second parameter. If you are using multiple axes, you can |
||||
|
specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of |
||||
|
xaxis, the plugin picks the first one it sees. |
||||
|
|
||||
|
- clearSelection( preventEvent ) |
||||
|
|
||||
|
Clear the selection rectangle. Pass in true to avoid getting a |
||||
|
"plotunselected" event. |
||||
|
|
||||
|
- getSelection() |
||||
|
|
||||
|
Returns the current selection in the same format as the "plotselected" |
||||
|
event. If there's currently no selection, the function returns null. |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
(function ($) { |
||||
|
function init(plot) { |
||||
|
var selection = { |
||||
|
first: { x: -1, y: -1}, second: { x: -1, y: -1}, |
||||
|
show: false, |
||||
|
active: false |
||||
|
}; |
||||
|
|
||||
|
// FIXME: The drag handling implemented here should be
|
||||
|
// abstracted out, there's some similar code from a library in
|
||||
|
// the navigation plugin, this should be massaged a bit to fit
|
||||
|
// the Flot cases here better and reused. Doing this would
|
||||
|
// make this plugin much slimmer.
|
||||
|
var savedhandlers = {}; |
||||
|
|
||||
|
var mouseUpHandler = null; |
||||
|
|
||||
|
function onMouseMove(e) { |
||||
|
if (selection.active) { |
||||
|
updateSelection(e); |
||||
|
|
||||
|
plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onMouseDown(e) { |
||||
|
if (e.which != 1) // only accept left-click
|
||||
|
return; |
||||
|
|
||||
|
// cancel out any text selections
|
||||
|
document.body.focus(); |
||||
|
|
||||
|
// prevent text selection and drag in old-school browsers
|
||||
|
if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) { |
||||
|
savedhandlers.onselectstart = document.onselectstart; |
||||
|
document.onselectstart = function () { return false; }; |
||||
|
} |
||||
|
if (document.ondrag !== undefined && savedhandlers.ondrag == null) { |
||||
|
savedhandlers.ondrag = document.ondrag; |
||||
|
document.ondrag = function () { return false; }; |
||||
|
} |
||||
|
|
||||
|
setSelectionPos(selection.first, e); |
||||
|
|
||||
|
selection.active = true; |
||||
|
|
||||
|
// this is a bit silly, but we have to use a closure to be
|
||||
|
// able to whack the same handler again
|
||||
|
mouseUpHandler = function (e) { onMouseUp(e); }; |
||||
|
|
||||
|
$(document).one("mouseup", mouseUpHandler); |
||||
|
} |
||||
|
|
||||
|
function onMouseUp(e) { |
||||
|
mouseUpHandler = null; |
||||
|
|
||||
|
// revert drag stuff for old-school browsers
|
||||
|
if (document.onselectstart !== undefined) |
||||
|
document.onselectstart = savedhandlers.onselectstart; |
||||
|
if (document.ondrag !== undefined) |
||||
|
document.ondrag = savedhandlers.ondrag; |
||||
|
|
||||
|
// no more dragging
|
||||
|
selection.active = false; |
||||
|
updateSelection(e); |
||||
|
|
||||
|
if (selectionIsSane()) |
||||
|
triggerSelectedEvent(); |
||||
|
else { |
||||
|
// this counts as a clear
|
||||
|
plot.getPlaceholder().trigger("plotunselected", [ ]); |
||||
|
plot.getPlaceholder().trigger("plotselecting", [ null ]); |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
function getSelection() { |
||||
|
if (!selectionIsSane()) |
||||
|
return null; |
||||
|
|
||||
|
if (!selection.show) return null; |
||||
|
|
||||
|
var r = {}, c1 = selection.first, c2 = selection.second; |
||||
|
$.each(plot.getAxes(), function (name, axis) { |
||||
|
if (axis.used) { |
||||
|
var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]); |
||||
|
r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) }; |
||||
|
} |
||||
|
}); |
||||
|
return r; |
||||
|
} |
||||
|
|
||||
|
function triggerSelectedEvent() { |
||||
|
var r = getSelection(); |
||||
|
|
||||
|
plot.getPlaceholder().trigger("plotselected", [ r ]); |
||||
|
|
||||
|
// backwards-compat stuff, to be removed in future
|
||||
|
if (r.xaxis && r.yaxis) |
||||
|
plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); |
||||
|
} |
||||
|
|
||||
|
function clamp(min, value, max) { |
||||
|
return value < min ? min: (value > max ? max: value); |
||||
|
} |
||||
|
|
||||
|
function setSelectionPos(pos, e) { |
||||
|
var o = plot.getOptions(); |
||||
|
var offset = plot.getPlaceholder().offset(); |
||||
|
var plotOffset = plot.getPlotOffset(); |
||||
|
pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width()); |
||||
|
pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height()); |
||||
|
|
||||
|
if (o.selection.mode == "y") |
||||
|
pos.x = pos == selection.first ? 0 : plot.width(); |
||||
|
|
||||
|
if (o.selection.mode == "x") |
||||
|
pos.y = pos == selection.first ? 0 : plot.height(); |
||||
|
} |
||||
|
|
||||
|
function updateSelection(pos) { |
||||
|
if (pos.pageX == null) |
||||
|
return; |
||||
|
|
||||
|
setSelectionPos(selection.second, pos); |
||||
|
if (selectionIsSane()) { |
||||
|
selection.show = true; |
||||
|
plot.triggerRedrawOverlay(); |
||||
|
} |
||||
|
else |
||||
|
clearSelection(true); |
||||
|
} |
||||
|
|
||||
|
function clearSelection(preventEvent) { |
||||
|
if (selection.show) { |
||||
|
selection.show = false; |
||||
|
plot.triggerRedrawOverlay(); |
||||
|
if (!preventEvent) |
||||
|
plot.getPlaceholder().trigger("plotunselected", [ ]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// function taken from markings support in Flot
|
||||
|
function extractRange(ranges, coord) { |
||||
|
var axis, from, to, key, axes = plot.getAxes(); |
||||
|
|
||||
|
for (var k in axes) { |
||||
|
axis = axes[k]; |
||||
|
if (axis.direction == coord) { |
||||
|
key = coord + axis.n + "axis"; |
||||
|
if (!ranges[key] && axis.n == 1) |
||||
|
key = coord + "axis"; // support x1axis as xaxis
|
||||
|
if (ranges[key]) { |
||||
|
from = ranges[key].from; |
||||
|
to = ranges[key].to; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// backwards-compat stuff - to be removed in future
|
||||
|
if (!ranges[key]) { |
||||
|
axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0]; |
||||
|
from = ranges[coord + "1"]; |
||||
|
to = ranges[coord + "2"]; |
||||
|
} |
||||
|
|
||||
|
// auto-reverse as an added bonus
|
||||
|
if (from != null && to != null && from > to) { |
||||
|
var tmp = from; |
||||
|
from = to; |
||||
|
to = tmp; |
||||
|
} |
||||
|
|
||||
|
return { from: from, to: to, axis: axis }; |
||||
|
} |
||||
|
|
||||
|
function setSelection(ranges, preventEvent) { |
||||
|
var axis, range, o = plot.getOptions(); |
||||
|
|
||||
|
if (o.selection.mode == "y") { |
||||
|
selection.first.x = 0; |
||||
|
selection.second.x = plot.width(); |
||||
|
} |
||||
|
else { |
||||
|
range = extractRange(ranges, "x"); |
||||
|
|
||||
|
selection.first.x = range.axis.p2c(range.from); |
||||
|
selection.second.x = range.axis.p2c(range.to); |
||||
|
} |
||||
|
|
||||
|
if (o.selection.mode == "x") { |
||||
|
selection.first.y = 0; |
||||
|
selection.second.y = plot.height(); |
||||
|
} |
||||
|
else { |
||||
|
range = extractRange(ranges, "y"); |
||||
|
|
||||
|
selection.first.y = range.axis.p2c(range.from); |
||||
|
selection.second.y = range.axis.p2c(range.to); |
||||
|
} |
||||
|
|
||||
|
selection.show = true; |
||||
|
plot.triggerRedrawOverlay(); |
||||
|
if (!preventEvent && selectionIsSane()) |
||||
|
triggerSelectedEvent(); |
||||
|
} |
||||
|
|
||||
|
function selectionIsSane() { |
||||
|
var minSize = plot.getOptions().selection.minSize; |
||||
|
return Math.abs(selection.second.x - selection.first.x) >= minSize && |
||||
|
Math.abs(selection.second.y - selection.first.y) >= minSize; |
||||
|
} |
||||
|
|
||||
|
plot.clearSelection = clearSelection; |
||||
|
plot.setSelection = setSelection; |
||||
|
plot.getSelection = getSelection; |
||||
|
|
||||
|
plot.hooks.bindEvents.push(function(plot, eventHolder) { |
||||
|
var o = plot.getOptions(); |
||||
|
if (o.selection.mode != null) { |
||||
|
eventHolder.mousemove(onMouseMove); |
||||
|
eventHolder.mousedown(onMouseDown); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
plot.hooks.drawOverlay.push(function (plot, ctx) { |
||||
|
// draw selection
|
||||
|
if (selection.show && selectionIsSane()) { |
||||
|
var plotOffset = plot.getPlotOffset(); |
||||
|
var o = plot.getOptions(); |
||||
|
|
||||
|
ctx.save(); |
||||
|
ctx.translate(plotOffset.left, plotOffset.top); |
||||
|
|
||||
|
var c = $.color.parse(o.selection.color); |
||||
|
|
||||
|
ctx.strokeStyle = c.scale('a', 0.8).toString(); |
||||
|
ctx.lineWidth = 1; |
||||
|
ctx.lineJoin = o.selection.shape; |
||||
|
ctx.fillStyle = c.scale('a', 0.4).toString(); |
||||
|
|
||||
|
var x = Math.min(selection.first.x, selection.second.x) + 0.5, |
||||
|
y = Math.min(selection.first.y, selection.second.y) + 0.5, |
||||
|
w = Math.abs(selection.second.x - selection.first.x) - 1, |
||||
|
h = Math.abs(selection.second.y - selection.first.y) - 1; |
||||
|
|
||||
|
ctx.fillRect(x, y, w, h); |
||||
|
ctx.strokeRect(x, y, w, h); |
||||
|
|
||||
|
ctx.restore(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
plot.hooks.shutdown.push(function (plot, eventHolder) { |
||||
|
eventHolder.unbind("mousemove", onMouseMove); |
||||
|
eventHolder.unbind("mousedown", onMouseDown); |
||||
|
|
||||
|
if (mouseUpHandler) |
||||
|
$(document).unbind("mouseup", mouseUpHandler); |
||||
|
}); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: { |
||||
|
selection: { |
||||
|
mode: null, // one of null, "x", "y" or "xy"
|
||||
|
color: "#e8cfac", |
||||
|
shape: "round", // one of "round", "miter", or "bevel"
|
||||
|
minSize: 5 // minimum number of pixels
|
||||
|
} |
||||
|
}, |
||||
|
name: 'selection', |
||||
|
version: '1.1' |
||||
|
}); |
||||
|
})(jQuery); |
||||
@ -0,0 +1,188 @@ |
|||||
|
/* Flot plugin for stacking data sets rather than overlyaing them. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
The plugin assumes the data is sorted on x (or y if stacking horizontally). |
||||
|
For line charts, it is assumed that if a line has an undefined gap (from a |
||||
|
null point), then the line above it should have the same gap - insert zeros |
||||
|
instead of "null" if you want another behaviour. This also holds for the start |
||||
|
and end of the chart. Note that stacking a mix of positive and negative values |
||||
|
in most instances doesn't make sense (so it looks weird). |
||||
|
|
||||
|
Two or more series are stacked when their "stack" attribute is set to the same |
||||
|
key (which can be any number or string or just "true"). To specify the default |
||||
|
stack, you can set the stack option like this: |
||||
|
|
||||
|
series: { |
||||
|
stack: null/false, true, or a key (number/string) |
||||
|
} |
||||
|
|
||||
|
You can also specify it for a single series, like this: |
||||
|
|
||||
|
$.plot( $("#placeholder"), [{ |
||||
|
data: [ ... ], |
||||
|
stack: true |
||||
|
}]) |
||||
|
|
||||
|
The stacking order is determined by the order of the data series in the array |
||||
|
(later series end up on top of the previous). |
||||
|
|
||||
|
Internally, the plugin modifies the datapoints in each series, adding an |
||||
|
offset to the y value. For line series, extra data points are inserted through |
||||
|
interpolation. If there's a second y value, it's also adjusted (e.g for bar |
||||
|
charts or filled areas). |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
(function ($) { |
||||
|
var options = { |
||||
|
series: { stack: null } // or number/string
|
||||
|
}; |
||||
|
|
||||
|
function init(plot) { |
||||
|
function findMatchingSeries(s, allseries) { |
||||
|
var res = null; |
||||
|
for (var i = 0; i < allseries.length; ++i) { |
||||
|
if (s == allseries[i]) |
||||
|
break; |
||||
|
|
||||
|
if (allseries[i].stack == s.stack) |
||||
|
res = allseries[i]; |
||||
|
} |
||||
|
|
||||
|
return res; |
||||
|
} |
||||
|
|
||||
|
function stackData(plot, s, datapoints) { |
||||
|
if (s.stack == null || s.stack === false) |
||||
|
return; |
||||
|
|
||||
|
var other = findMatchingSeries(s, plot.getData()); |
||||
|
if (!other) |
||||
|
return; |
||||
|
|
||||
|
var ps = datapoints.pointsize, |
||||
|
points = datapoints.points, |
||||
|
otherps = other.datapoints.pointsize, |
||||
|
otherpoints = other.datapoints.points, |
||||
|
newpoints = [], |
||||
|
px, py, intery, qx, qy, bottom, |
||||
|
withlines = s.lines.show, |
||||
|
horizontal = s.bars.horizontal, |
||||
|
withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), |
||||
|
withsteps = withlines && s.lines.steps, |
||||
|
fromgap = true, |
||||
|
keyOffset = horizontal ? 1 : 0, |
||||
|
accumulateOffset = horizontal ? 0 : 1, |
||||
|
i = 0, j = 0, l, m; |
||||
|
|
||||
|
while (true) { |
||||
|
if (i >= points.length) |
||||
|
break; |
||||
|
|
||||
|
l = newpoints.length; |
||||
|
|
||||
|
if (points[i] == null) { |
||||
|
// copy gaps
|
||||
|
for (m = 0; m < ps; ++m) |
||||
|
newpoints.push(points[i + m]); |
||||
|
i += ps; |
||||
|
} |
||||
|
else if (j >= otherpoints.length) { |
||||
|
// for lines, we can't use the rest of the points
|
||||
|
if (!withlines) { |
||||
|
for (m = 0; m < ps; ++m) |
||||
|
newpoints.push(points[i + m]); |
||||
|
} |
||||
|
i += ps; |
||||
|
} |
||||
|
else if (otherpoints[j] == null) { |
||||
|
// oops, got a gap
|
||||
|
for (m = 0; m < ps; ++m) |
||||
|
newpoints.push(null); |
||||
|
fromgap = true; |
||||
|
j += otherps; |
||||
|
} |
||||
|
else { |
||||
|
// cases where we actually got two points
|
||||
|
px = points[i + keyOffset]; |
||||
|
py = points[i + accumulateOffset]; |
||||
|
qx = otherpoints[j + keyOffset]; |
||||
|
qy = otherpoints[j + accumulateOffset]; |
||||
|
bottom = 0; |
||||
|
|
||||
|
if (px == qx) { |
||||
|
for (m = 0; m < ps; ++m) |
||||
|
newpoints.push(points[i + m]); |
||||
|
|
||||
|
newpoints[l + accumulateOffset] += qy; |
||||
|
bottom = qy; |
||||
|
|
||||
|
i += ps; |
||||
|
j += otherps; |
||||
|
} |
||||
|
else if (px > qx) { |
||||
|
// we got past point below, might need to
|
||||
|
// insert interpolated extra point
|
||||
|
if (withlines && i > 0 && points[i - ps] != null) { |
||||
|
intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); |
||||
|
newpoints.push(qx); |
||||
|
newpoints.push(intery + qy); |
||||
|
for (m = 2; m < ps; ++m) |
||||
|
newpoints.push(points[i + m]); |
||||
|
bottom = qy; |
||||
|
} |
||||
|
|
||||
|
j += otherps; |
||||
|
} |
||||
|
else { // px < qx
|
||||
|
if (fromgap && withlines) { |
||||
|
// if we come from a gap, we just skip this point
|
||||
|
i += ps; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
for (m = 0; m < ps; ++m) |
||||
|
newpoints.push(points[i + m]); |
||||
|
|
||||
|
// we might be able to interpolate a point below,
|
||||
|
// this can give us a better y
|
||||
|
if (withlines && j > 0 && otherpoints[j - otherps] != null) |
||||
|
bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); |
||||
|
|
||||
|
newpoints[l + accumulateOffset] += bottom; |
||||
|
|
||||
|
i += ps; |
||||
|
} |
||||
|
|
||||
|
fromgap = false; |
||||
|
|
||||
|
if (l != newpoints.length && withbottom) |
||||
|
newpoints[l + 2] += bottom; |
||||
|
} |
||||
|
|
||||
|
// maintain the line steps invariant
|
||||
|
if (withsteps && l != newpoints.length && l > 0 |
||||
|
&& newpoints[l] != null |
||||
|
&& newpoints[l] != newpoints[l - ps] |
||||
|
&& newpoints[l + 1] != newpoints[l - ps + 1]) { |
||||
|
for (m = 0; m < ps; ++m) |
||||
|
newpoints[l + ps + m] = newpoints[l + m]; |
||||
|
newpoints[l + 1] = newpoints[l - ps + 1]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
datapoints.points = newpoints; |
||||
|
} |
||||
|
|
||||
|
plot.hooks.processDatapoints.push(stackData); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: 'stack', |
||||
|
version: '1.2' |
||||
|
}); |
||||
|
})(jQuery); |
||||
@ -0,0 +1,71 @@ |
|||||
|
/* Flot plugin that adds some extra symbols for plotting points. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
The symbols are accessed as strings through the standard symbol options: |
||||
|
|
||||
|
series: { |
||||
|
points: { |
||||
|
symbol: "square" // or "diamond", "triangle", "cross"
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
(function ($) { |
||||
|
function processRawData(plot, series, datapoints) { |
||||
|
// we normalize the area of each symbol so it is approximately the
|
||||
|
// same as a circle of the given radius
|
||||
|
|
||||
|
var handlers = { |
||||
|
square: function (ctx, x, y, radius, shadow) { |
||||
|
// pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2
|
||||
|
var size = radius * Math.sqrt(Math.PI) / 2; |
||||
|
ctx.rect(x - size, y - size, size + size, size + size); |
||||
|
}, |
||||
|
diamond: function (ctx, x, y, radius, shadow) { |
||||
|
// pi * r^2 = 2s^2 => s = r * sqrt(pi/2)
|
||||
|
var size = radius * Math.sqrt(Math.PI / 2); |
||||
|
ctx.moveTo(x - size, y); |
||||
|
ctx.lineTo(x, y - size); |
||||
|
ctx.lineTo(x + size, y); |
||||
|
ctx.lineTo(x, y + size); |
||||
|
ctx.lineTo(x - size, y); |
||||
|
}, |
||||
|
triangle: function (ctx, x, y, radius, shadow) { |
||||
|
// pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3))
|
||||
|
var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); |
||||
|
var height = size * Math.sin(Math.PI / 3); |
||||
|
ctx.moveTo(x - size/2, y + height/2); |
||||
|
ctx.lineTo(x + size/2, y + height/2); |
||||
|
if (!shadow) { |
||||
|
ctx.lineTo(x, y - height/2); |
||||
|
ctx.lineTo(x - size/2, y + height/2); |
||||
|
} |
||||
|
}, |
||||
|
cross: function (ctx, x, y, radius, shadow) { |
||||
|
// pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2
|
||||
|
var size = radius * Math.sqrt(Math.PI) / 2; |
||||
|
ctx.moveTo(x - size, y - size); |
||||
|
ctx.lineTo(x + size, y + size); |
||||
|
ctx.moveTo(x - size, y + size); |
||||
|
ctx.lineTo(x + size, y - size); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var s = series.points.symbol; |
||||
|
if (handlers[s]) |
||||
|
series.points.symbol = handlers[s]; |
||||
|
} |
||||
|
|
||||
|
function init(plot) { |
||||
|
plot.hooks.processDatapoints.push(processRawData); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
name: 'symbols', |
||||
|
version: '1.0' |
||||
|
}); |
||||
|
})(jQuery); |
||||
@ -0,0 +1,142 @@ |
|||||
|
/* Flot plugin for thresholding data. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
The plugin supports these options: |
||||
|
|
||||
|
series: { |
||||
|
threshold: { |
||||
|
below: number |
||||
|
color: colorspec |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
It can also be applied to a single series, like this: |
||||
|
|
||||
|
$.plot( $("#placeholder"), [{ |
||||
|
data: [ ... ], |
||||
|
threshold: { ... } |
||||
|
}]) |
||||
|
|
||||
|
An array can be passed for multiple thresholding, like this: |
||||
|
|
||||
|
threshold: [{ |
||||
|
below: number1 |
||||
|
color: color1 |
||||
|
},{ |
||||
|
below: number2 |
||||
|
color: color2 |
||||
|
}] |
||||
|
|
||||
|
These multiple threshold objects can be passed in any order since they are |
||||
|
sorted by the processing function. |
||||
|
|
||||
|
The data points below "below" are drawn with the specified color. This makes |
||||
|
it easy to mark points below 0, e.g. for budget data. |
||||
|
|
||||
|
Internally, the plugin works by splitting the data into two series, above and |
||||
|
below the threshold. The extra series below the threshold will have its label |
||||
|
cleared and the special "originSeries" attribute set to the original series. |
||||
|
You may need to check for this in hover events. |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
(function ($) { |
||||
|
var options = { |
||||
|
series: { threshold: null } // or { below: number, color: color spec}
|
||||
|
}; |
||||
|
|
||||
|
function init(plot) { |
||||
|
function thresholdData(plot, s, datapoints, below, color) { |
||||
|
var ps = datapoints.pointsize, i, x, y, p, prevp, |
||||
|
thresholded = $.extend({}, s); // note: shallow copy
|
||||
|
|
||||
|
thresholded.datapoints = { points: [], pointsize: ps, format: datapoints.format }; |
||||
|
thresholded.label = null; |
||||
|
thresholded.color = color; |
||||
|
thresholded.threshold = null; |
||||
|
thresholded.originSeries = s; |
||||
|
thresholded.data = []; |
||||
|
|
||||
|
var origpoints = datapoints.points, |
||||
|
addCrossingPoints = s.lines.show; |
||||
|
|
||||
|
var threspoints = []; |
||||
|
var newpoints = []; |
||||
|
var m; |
||||
|
|
||||
|
for (i = 0; i < origpoints.length; i += ps) { |
||||
|
x = origpoints[i]; |
||||
|
y = origpoints[i + 1]; |
||||
|
|
||||
|
prevp = p; |
||||
|
if (y < below) |
||||
|
p = threspoints; |
||||
|
else |
||||
|
p = newpoints; |
||||
|
|
||||
|
if (addCrossingPoints && prevp != p && x != null |
||||
|
&& i > 0 && origpoints[i - ps] != null) { |
||||
|
var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]); |
||||
|
prevp.push(interx); |
||||
|
prevp.push(below); |
||||
|
for (m = 2; m < ps; ++m) |
||||
|
prevp.push(origpoints[i + m]); |
||||
|
|
||||
|
p.push(null); // start new segment
|
||||
|
p.push(null); |
||||
|
for (m = 2; m < ps; ++m) |
||||
|
p.push(origpoints[i + m]); |
||||
|
p.push(interx); |
||||
|
p.push(below); |
||||
|
for (m = 2; m < ps; ++m) |
||||
|
p.push(origpoints[i + m]); |
||||
|
} |
||||
|
|
||||
|
p.push(x); |
||||
|
p.push(y); |
||||
|
for (m = 2; m < ps; ++m) |
||||
|
p.push(origpoints[i + m]); |
||||
|
} |
||||
|
|
||||
|
datapoints.points = newpoints; |
||||
|
thresholded.datapoints.points = threspoints; |
||||
|
|
||||
|
if (thresholded.datapoints.points.length > 0) { |
||||
|
var origIndex = $.inArray(s, plot.getData()); |
||||
|
// Insert newly-generated series right after original one (to prevent it from becoming top-most)
|
||||
|
plot.getData().splice(origIndex + 1, 0, thresholded); |
||||
|
} |
||||
|
|
||||
|
// FIXME: there are probably some edge cases left in bars
|
||||
|
} |
||||
|
|
||||
|
function processThresholds(plot, s, datapoints) { |
||||
|
if (!s.threshold) |
||||
|
return; |
||||
|
|
||||
|
if (s.threshold instanceof Array) { |
||||
|
s.threshold.sort(function(a, b) { |
||||
|
return a.below - b.below; |
||||
|
}); |
||||
|
|
||||
|
$(s.threshold).each(function(i, th) { |
||||
|
thresholdData(plot, s, datapoints, th.below, th.color); |
||||
|
}); |
||||
|
} |
||||
|
else { |
||||
|
thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
plot.hooks.processDatapoints.push(processThresholds); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: 'threshold', |
||||
|
version: '1.2' |
||||
|
}); |
||||
|
})(jQuery); |
||||
@ -0,0 +1,432 @@ |
|||||
|
/* Pretty handling of time axes. |
||||
|
|
||||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen. |
||||
|
Licensed under the MIT license. |
||||
|
|
||||
|
Set axis.mode to "time" to enable. See the section "Time series data" in |
||||
|
API.txt for details. |
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
(function($) { |
||||
|
|
||||
|
var options = { |
||||
|
xaxis: { |
||||
|
timezone: null, // "browser" for local to the client or timezone for timezone-js
|
||||
|
timeformat: null, // format string to use
|
||||
|
twelveHourClock: false, // 12 or 24 time in time mode
|
||||
|
monthNames: null // list of names of months
|
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// round to nearby lower multiple of base
|
||||
|
|
||||
|
function floorInBase(n, base) { |
||||
|
return base * Math.floor(n / base); |
||||
|
} |
||||
|
|
||||
|
// Returns a string with the date d formatted according to fmt.
|
||||
|
// A subset of the Open Group's strftime format is supported.
|
||||
|
|
||||
|
function formatDate(d, fmt, monthNames, dayNames) { |
||||
|
|
||||
|
if (typeof d.strftime == "function") { |
||||
|
return d.strftime(fmt); |
||||
|
} |
||||
|
|
||||
|
var leftPad = function(n, pad) { |
||||
|
n = "" + n; |
||||
|
pad = "" + (pad == null ? "0" : pad); |
||||
|
return n.length == 1 ? pad + n : n; |
||||
|
}; |
||||
|
|
||||
|
var r = []; |
||||
|
var escape = false; |
||||
|
var hours = d.getHours(); |
||||
|
var isAM = hours < 12; |
||||
|
|
||||
|
if (monthNames == null) { |
||||
|
monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; |
||||
|
} |
||||
|
|
||||
|
if (dayNames == null) { |
||||
|
dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; |
||||
|
} |
||||
|
|
||||
|
var hours12; |
||||
|
|
||||
|
if (hours > 12) { |
||||
|
hours12 = hours - 12; |
||||
|
} else if (hours == 0) { |
||||
|
hours12 = 12; |
||||
|
} else { |
||||
|
hours12 = hours; |
||||
|
} |
||||
|
|
||||
|
for (var i = 0; i < fmt.length; ++i) { |
||||
|
|
||||
|
var c = fmt.charAt(i); |
||||
|
|
||||
|
if (escape) { |
||||
|
switch (c) { |
||||
|
case 'a': c = "" + dayNames[d.getDay()]; break; |
||||
|
case 'b': c = "" + monthNames[d.getMonth()]; break; |
||||
|
case 'd': c = leftPad(d.getDate()); break; |
||||
|
case 'e': c = leftPad(d.getDate(), " "); break; |
||||
|
case 'h': // For back-compat with 0.7; remove in 1.0
|
||||
|
case 'H': c = leftPad(hours); break; |
||||
|
case 'I': c = leftPad(hours12); break; |
||||
|
case 'l': c = leftPad(hours12, " "); break; |
||||
|
case 'm': c = leftPad(d.getMonth() + 1); break; |
||||
|
case 'M': c = leftPad(d.getMinutes()); break; |
||||
|
// quarters not in Open Group's strftime specification
|
||||
|
case 'q': |
||||
|
c = "" + (Math.floor(d.getMonth() / 3) + 1); break; |
||||
|
case 'S': c = leftPad(d.getSeconds()); break; |
||||
|
case 'y': c = leftPad(d.getFullYear() % 100); break; |
||||
|
case 'Y': c = "" + d.getFullYear(); break; |
||||
|
case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; |
||||
|
case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; |
||||
|
case 'w': c = "" + d.getDay(); break; |
||||
|
} |
||||
|
r.push(c); |
||||
|
escape = false; |
||||
|
} else { |
||||
|
if (c == "%") { |
||||
|
escape = true; |
||||
|
} else { |
||||
|
r.push(c); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return r.join(""); |
||||
|
} |
||||
|
|
||||
|
// To have a consistent view of time-based data independent of which time
|
||||
|
// zone the client happens to be in we need a date-like object independent
|
||||
|
// of time zones. This is done through a wrapper that only calls the UTC
|
||||
|
// versions of the accessor methods.
|
||||
|
|
||||
|
function makeUtcWrapper(d) { |
||||
|
|
||||
|
function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) { |
||||
|
sourceObj[sourceMethod] = function() { |
||||
|
return targetObj[targetMethod].apply(targetObj, arguments); |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
var utc = { |
||||
|
date: d |
||||
|
}; |
||||
|
|
||||
|
// support strftime, if found
|
||||
|
|
||||
|
if (d.strftime != undefined) { |
||||
|
addProxyMethod(utc, "strftime", d, "strftime"); |
||||
|
} |
||||
|
|
||||
|
addProxyMethod(utc, "getTime", d, "getTime"); |
||||
|
addProxyMethod(utc, "setTime", d, "setTime"); |
||||
|
|
||||
|
var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"]; |
||||
|
|
||||
|
for (var p = 0; p < props.length; p++) { |
||||
|
addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]); |
||||
|
addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]); |
||||
|
} |
||||
|
|
||||
|
return utc; |
||||
|
}; |
||||
|
|
||||
|
// select time zone strategy. This returns a date-like object tied to the
|
||||
|
// desired timezone
|
||||
|
|
||||
|
function dateGenerator(ts, opts) { |
||||
|
if (opts.timezone == "browser") { |
||||
|
return new Date(ts); |
||||
|
} else if (!opts.timezone || opts.timezone == "utc") { |
||||
|
return makeUtcWrapper(new Date(ts)); |
||||
|
} else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") { |
||||
|
var d = new timezoneJS.Date(); |
||||
|
// timezone-js is fickle, so be sure to set the time zone before
|
||||
|
// setting the time.
|
||||
|
d.setTimezone(opts.timezone); |
||||
|
d.setTime(ts); |
||||
|
return d; |
||||
|
} else { |
||||
|
return makeUtcWrapper(new Date(ts)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// map of app. size of time units in milliseconds
|
||||
|
|
||||
|
var timeUnitSize = { |
||||
|
"second": 1000, |
||||
|
"minute": 60 * 1000, |
||||
|
"hour": 60 * 60 * 1000, |
||||
|
"day": 24 * 60 * 60 * 1000, |
||||
|
"month": 30 * 24 * 60 * 60 * 1000, |
||||
|
"quarter": 3 * 30 * 24 * 60 * 60 * 1000, |
||||
|
"year": 365.2425 * 24 * 60 * 60 * 1000 |
||||
|
}; |
||||
|
|
||||
|
// the allowed tick sizes, after 1 year we use
|
||||
|
// an integer algorithm
|
||||
|
|
||||
|
var baseSpec = [ |
||||
|
[1, "second"], [2, "second"], [5, "second"], [10, "second"], |
||||
|
[30, "second"], |
||||
|
[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], |
||||
|
[30, "minute"], |
||||
|
[1, "hour"], [2, "hour"], [4, "hour"], |
||||
|
[8, "hour"], [12, "hour"], |
||||
|
[1, "day"], [2, "day"], [3, "day"], |
||||
|
[0.25, "month"], [0.5, "month"], [1, "month"], |
||||
|
[2, "month"] |
||||
|
]; |
||||
|
|
||||
|
// we don't know which variant(s) we'll need yet, but generating both is
|
||||
|
// cheap
|
||||
|
|
||||
|
var specMonths = baseSpec.concat([[3, "month"], [6, "month"], |
||||
|
[1, "year"]]); |
||||
|
var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"], |
||||
|
[1, "year"]]); |
||||
|
|
||||
|
function init(plot) { |
||||
|
plot.hooks.processOptions.push(function (plot, options) { |
||||
|
$.each(plot.getAxes(), function(axisName, axis) { |
||||
|
|
||||
|
var opts = axis.options; |
||||
|
|
||||
|
if (opts.mode == "time") { |
||||
|
axis.tickGenerator = function(axis) { |
||||
|
|
||||
|
var ticks = []; |
||||
|
var d = dateGenerator(axis.min, opts); |
||||
|
var minSize = 0; |
||||
|
|
||||
|
// make quarter use a possibility if quarters are
|
||||
|
// mentioned in either of these options
|
||||
|
|
||||
|
var spec = (opts.tickSize && opts.tickSize[1] === |
||||
|
"quarter") || |
||||
|
(opts.minTickSize && opts.minTickSize[1] === |
||||
|
"quarter") ? specQuarters : specMonths; |
||||
|
|
||||
|
if (opts.minTickSize != null) { |
||||
|
if (typeof opts.tickSize == "number") { |
||||
|
minSize = opts.tickSize; |
||||
|
} else { |
||||
|
minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (var i = 0; i < spec.length - 1; ++i) { |
||||
|
if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] |
||||
|
+ spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 |
||||
|
&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var size = spec[i][0]; |
||||
|
var unit = spec[i][1]; |
||||
|
|
||||
|
// special-case the possibility of several years
|
||||
|
|
||||
|
if (unit == "year") { |
||||
|
|
||||
|
// if given a minTickSize in years, just use it,
|
||||
|
// ensuring that it's an integer
|
||||
|
|
||||
|
if (opts.minTickSize != null && opts.minTickSize[1] == "year") { |
||||
|
size = Math.floor(opts.minTickSize[0]); |
||||
|
} else { |
||||
|
|
||||
|
var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10)); |
||||
|
var norm = (axis.delta / timeUnitSize.year) / magn; |
||||
|
|
||||
|
if (norm < 1.5) { |
||||
|
size = 1; |
||||
|
} else if (norm < 3) { |
||||
|
size = 2; |
||||
|
} else if (norm < 7.5) { |
||||
|
size = 5; |
||||
|
} else { |
||||
|
size = 10; |
||||
|
} |
||||
|
|
||||
|
size *= magn; |
||||
|
} |
||||
|
|
||||
|
// minimum size for years is 1
|
||||
|
|
||||
|
if (size < 1) { |
||||
|
size = 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
axis.tickSize = opts.tickSize || [size, unit]; |
||||
|
var tickSize = axis.tickSize[0]; |
||||
|
unit = axis.tickSize[1]; |
||||
|
|
||||
|
var step = tickSize * timeUnitSize[unit]; |
||||
|
|
||||
|
if (unit == "second") { |
||||
|
d.setSeconds(floorInBase(d.getSeconds(), tickSize)); |
||||
|
} else if (unit == "minute") { |
||||
|
d.setMinutes(floorInBase(d.getMinutes(), tickSize)); |
||||
|
} else if (unit == "hour") { |
||||
|
d.setHours(floorInBase(d.getHours(), tickSize)); |
||||
|
} else if (unit == "month") { |
||||
|
d.setMonth(floorInBase(d.getMonth(), tickSize)); |
||||
|
} else if (unit == "quarter") { |
||||
|
d.setMonth(3 * floorInBase(d.getMonth() / 3, |
||||
|
tickSize)); |
||||
|
} else if (unit == "year") { |
||||
|
d.setFullYear(floorInBase(d.getFullYear(), tickSize)); |
||||
|
} |
||||
|
|
||||
|
// reset smaller components
|
||||
|
|
||||
|
d.setMilliseconds(0); |
||||
|
|
||||
|
if (step >= timeUnitSize.minute) { |
||||
|
d.setSeconds(0); |
||||
|
} |
||||
|
if (step >= timeUnitSize.hour) { |
||||
|
d.setMinutes(0); |
||||
|
} |
||||
|
if (step >= timeUnitSize.day) { |
||||
|
d.setHours(0); |
||||
|
} |
||||
|
if (step >= timeUnitSize.day * 4) { |
||||
|
d.setDate(1); |
||||
|
} |
||||
|
if (step >= timeUnitSize.month * 2) { |
||||
|
d.setMonth(floorInBase(d.getMonth(), 3)); |
||||
|
} |
||||
|
if (step >= timeUnitSize.quarter * 2) { |
||||
|
d.setMonth(floorInBase(d.getMonth(), 6)); |
||||
|
} |
||||
|
if (step >= timeUnitSize.year) { |
||||
|
d.setMonth(0); |
||||
|
} |
||||
|
|
||||
|
var carry = 0; |
||||
|
var v = Number.NaN; |
||||
|
var prev; |
||||
|
|
||||
|
do { |
||||
|
|
||||
|
prev = v; |
||||
|
v = d.getTime(); |
||||
|
ticks.push(v); |
||||
|
|
||||
|
if (unit == "month" || unit == "quarter") { |
||||
|
if (tickSize < 1) { |
||||
|
|
||||
|
// a bit complicated - we'll divide the
|
||||
|
// month/quarter up but we need to take
|
||||
|
// care of fractions so we don't end up in
|
||||
|
// the middle of a day
|
||||
|
|
||||
|
d.setDate(1); |
||||
|
var start = d.getTime(); |
||||
|
d.setMonth(d.getMonth() + |
||||
|
(unit == "quarter" ? 3 : 1)); |
||||
|
var end = d.getTime(); |
||||
|
d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); |
||||
|
carry = d.getHours(); |
||||
|
d.setHours(0); |
||||
|
} else { |
||||
|
d.setMonth(d.getMonth() + |
||||
|
tickSize * (unit == "quarter" ? 3 : 1)); |
||||
|
} |
||||
|
} else if (unit == "year") { |
||||
|
d.setFullYear(d.getFullYear() + tickSize); |
||||
|
} else { |
||||
|
d.setTime(v + step); |
||||
|
} |
||||
|
} while (v < axis.max && v != prev); |
||||
|
|
||||
|
return ticks; |
||||
|
}; |
||||
|
|
||||
|
axis.tickFormatter = function (v, axis) { |
||||
|
|
||||
|
var d = dateGenerator(v, axis.options); |
||||
|
|
||||
|
// first check global format
|
||||
|
|
||||
|
if (opts.timeformat != null) { |
||||
|
return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); |
||||
|
} |
||||
|
|
||||
|
// possibly use quarters if quarters are mentioned in
|
||||
|
// any of these places
|
||||
|
|
||||
|
var useQuarters = (axis.options.tickSize && |
||||
|
axis.options.tickSize[1] == "quarter") || |
||||
|
(axis.options.minTickSize && |
||||
|
axis.options.minTickSize[1] == "quarter"); |
||||
|
|
||||
|
var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; |
||||
|
var span = axis.max - axis.min; |
||||
|
var suffix = (opts.twelveHourClock) ? " %p" : ""; |
||||
|
var hourCode = (opts.twelveHourClock) ? "%I" : "%H"; |
||||
|
var fmt; |
||||
|
|
||||
|
if (t < timeUnitSize.minute) { |
||||
|
fmt = hourCode + ":%M:%S" + suffix; |
||||
|
} else if (t < timeUnitSize.day) { |
||||
|
if (span < 2 * timeUnitSize.day) { |
||||
|
fmt = hourCode + ":%M" + suffix; |
||||
|
} else { |
||||
|
fmt = "%b %d " + hourCode + ":%M" + suffix; |
||||
|
} |
||||
|
} else if (t < timeUnitSize.month) { |
||||
|
fmt = "%b %d"; |
||||
|
} else if ((useQuarters && t < timeUnitSize.quarter) || |
||||
|
(!useQuarters && t < timeUnitSize.year)) { |
||||
|
if (span < timeUnitSize.year) { |
||||
|
fmt = "%b"; |
||||
|
} else { |
||||
|
fmt = "%b %Y"; |
||||
|
} |
||||
|
} else if (useQuarters && t < timeUnitSize.year) { |
||||
|
if (span < timeUnitSize.year) { |
||||
|
fmt = "Q%q"; |
||||
|
} else { |
||||
|
fmt = "Q%q %Y"; |
||||
|
} |
||||
|
} else { |
||||
|
fmt = "%Y"; |
||||
|
} |
||||
|
|
||||
|
var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames); |
||||
|
|
||||
|
return rt; |
||||
|
}; |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
$.plot.plugins.push({ |
||||
|
init: init, |
||||
|
options: options, |
||||
|
name: 'time', |
||||
|
version: '1.0' |
||||
|
}); |
||||
|
|
||||
|
// Time-axis support used to be in Flot core, which exposed the
|
||||
|
// formatDate function on the plot object. Various plugins depend
|
||||
|
// on the function, so we need to re-expose it here.
|
||||
|
|
||||
|
$.plot.formatDate = formatDate; |
||||
|
$.plot.dateGenerator = dateGenerator; |
||||
|
|
||||
|
})(jQuery); |
||||
@ -0,0 +1,11 @@ |
|||||
|
{ |
||||
|
"name": "Flot", |
||||
|
"version": "0.8.3", |
||||
|
"main": "jquery.flot.js", |
||||
|
"scripts": { |
||||
|
"test": "make test" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"jshint": "0.9.1" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
{ |
||||
|
"name": "font-awesome", |
||||
|
"description": "Font Awesome", |
||||
|
"version": "4.2.0", |
||||
|
"keywords": [], |
||||
|
"homepage": "http://fontawesome.io", |
||||
|
"dependencies": {}, |
||||
|
"devDependencies": {}, |
||||
|
"license": [ |
||||
|
"OFL-1.1", |
||||
|
"MIT", |
||||
|
"CC-BY-3.0" |
||||
|
], |
||||
|
"main": [ |
||||
|
"./css/font-awesome.css", |
||||
|
"./fonts/*" |
||||
|
], |
||||
|
"ignore": [ |
||||
|
"*/.*", |
||||
|
"*.json", |
||||
|
"src", |
||||
|
"*.yml", |
||||
|
"Gemfile", |
||||
|
"Gemfile.lock", |
||||
|
"*.md" |
||||
|
], |
||||
|
"_release": "4.2.0", |
||||
|
"_resolution": { |
||||
|
"type": "version", |
||||
|
"tag": "v4.2.0", |
||||
|
"commit": "0b924144a95a54fa738d0450ff66c1dabd11ae74" |
||||
|
}, |
||||
|
"_source": "git://github.com/FortAwesome/Font-Awesome.git", |
||||
|
"_target": "~4.2.0", |
||||
|
"_originalSource": "font-awesome" |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
*.pyc |
||||
|
*.egg-info |
||||
|
*.db |
||||
|
*.db.old |
||||
|
*.swp |
||||
|
*.db-journal |
||||
|
|
||||
|
.coverage |
||||
|
.DS_Store |
||||
|
.installed.cfg |
||||
|
_gh_pages/* |
||||
|
|
||||
|
.idea/* |
||||
|
.svn/* |
||||
|
src/website/static/* |
||||
|
src/website/media/* |
||||
|
|
||||
|
bin |
||||
|
cfcache |
||||
|
develop-eggs |
||||
|
dist |
||||
|
downloads |
||||
|
eggs |
||||
|
parts |
||||
|
tmp |
||||
|
.sass-cache |
||||
|
node_modules |
||||
|
|
||||
|
src/website/settingslocal.py |
||||
|
stunnel.log |
||||
|
|
||||
|
.ruby-version |
||||
|
|
||||
|
# don't need these in the npm package. |
||||
|
src/ |
||||
|
_config.yml |
||||
|
bower.json |
||||
|
component.json |
||||
|
composer.json |
||||
|
CONTRIBUTING.md |
||||
|
Gemfile |
||||
|
Gemfile.lock |
||||
@ -0,0 +1,23 @@ |
|||||
|
{ |
||||
|
"name": "font-awesome", |
||||
|
"description": "Font Awesome", |
||||
|
"version": "4.2.0", |
||||
|
"keywords": [], |
||||
|
"homepage": "http://fontawesome.io", |
||||
|
"dependencies": {}, |
||||
|
"devDependencies": {}, |
||||
|
"license": ["OFL-1.1", "MIT", "CC-BY-3.0"], |
||||
|
"main": [ |
||||
|
"./css/font-awesome.css", |
||||
|
"./fonts/*" |
||||
|
], |
||||
|
"ignore": [ |
||||
|
"*/.*", |
||||
|
"*.json", |
||||
|
"src", |
||||
|
"*.yml", |
||||
|
"Gemfile", |
||||
|
"Gemfile.lock", |
||||
|
"*.md" |
||||
|
] |
||||
|
} |
||||
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 280 KiB |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,16 @@ |
|||||
|
// Bordered & Pulled |
||||
|
// ------------------------- |
||||
|
|
||||
|
.@{fa-css-prefix}-border { |
||||
|
padding: .2em .25em .15em; |
||||
|
border: solid .08em @fa-border-color; |
||||
|
border-radius: .1em; |
||||
|
} |
||||
|
|
||||
|
.pull-right { float: right; } |
||||
|
.pull-left { float: left; } |
||||
|
|
||||
|
.@{fa-css-prefix} { |
||||
|
&.pull-left { margin-right: .3em; } |
||||
|
&.pull-right { margin-left: .3em; } |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// Base Class Definition |
||||
|
// ------------------------- |
||||
|
|
||||
|
.@{fa-css-prefix} { |
||||
|
display: inline-block; |
||||
|
font: normal normal normal 14px/1 FontAwesome; // shortening font declaration |
||||
|
font-size: inherit; // can't have font-size inherit on line above, so need to override |
||||
|
text-rendering: auto; // optimizelegibility throws things off #1094 |
||||
|
-webkit-font-smoothing: antialiased; |
||||
|
-moz-osx-font-smoothing: grayscale; |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
// Extras |
||||
|
// -------------------------- |
||||
@ -0,0 +1,6 @@ |
|||||
|
// Fixed Width Icons |
||||
|
// ------------------------- |
||||
|
.@{fa-css-prefix}-fw { |
||||
|
width: (18em / 14); |
||||
|
text-align: center; |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
/*! |
||||
|
* Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome |
||||
|
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) |
||||
|
*/ |
||||
|
|
||||
|
@import "variables.less"; |
||||
|
@import "mixins.less"; |
||||
|
@import "path.less"; |
||||
|
@import "core.less"; |
||||
|
@import "larger.less"; |
||||
|
@import "fixed-width.less"; |
||||
|
@import "list.less"; |
||||
|
@import "bordered-pulled.less"; |
||||
|
@import "spinning.less"; |
||||
|
@import "rotated-flipped.less"; |
||||
|
@import "stacked.less"; |
||||
|
@import "icons.less"; |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue