From a67459018cbac767d70ba7ce8d786193b5509617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 21 Aug 2013 12:51:09 +0200 Subject: [PATCH 001/335] [IMP] resource: first refactoring of various methods to calculate timed intervals based on resource.calendar. Added tests. bzr revid: tde@openerp.com-20130821105109-1rtynzx69hmslxey --- addons/resource/resource.py | 343 +++++++++++++++++++++++-- addons/resource/resource_view.xml | 1 + addons/resource/tests/__init__.py | 28 ++ addons/resource/tests/common.py | 123 +++++++++ addons/resource/tests/test_resource.py | 189 ++++++++++++++ 5 files changed, 668 insertions(+), 16 deletions(-) create mode 100644 addons/resource/tests/__init__.py create mode 100644 addons/resource/tests/common.py create mode 100644 addons/resource/tests/test_resource.py diff --git a/addons/resource/resource.py b/addons/resource/resource.py index 449fa422ab4..dd87157aeb0 100644 --- a/addons/resource/resource.py +++ b/addons/resource/resource.py @@ -20,17 +20,19 @@ ############################################################################## import pytz -from datetime import datetime, timedelta +from datetime import date, datetime, timedelta from dateutil import rrule +from dateutil.relativedelta import relativedelta +import itertools import math +from operator import itemgetter + from faces import * +from openerp import tools from openerp.osv import fields, osv from openerp.tools.float_utils import float_compare from openerp.tools.translate import _ -from itertools import groupby -from operator import itemgetter - class resource_calendar(osv.osv): _name = "resource.calendar" @@ -40,25 +42,321 @@ class resource_calendar(osv.osv): 'company_id' : fields.many2one('res.company', 'Company', required=False), 'attendance_ids' : fields.one2many('resource.calendar.attendance', 'calendar_id', 'Working Time'), 'manager' : fields.many2one('res.users', 'Workgroup Manager'), + 'leave_ids': fields.one2many( + 'resource.calendar.leaves', 'calendar_id', 'Leaves', + help='' + ), } _defaults = { 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'resource.calendar', context=context) } - def working_hours_on_day(self, cr, uid, resource_calendar_id, day, context=None): - """Calculates the Working Total Hours based on Resource Calendar and - given working day (datetime object). + # -------------------------------------------------- + # Utility methods + # -------------------------------------------------- - @param resource_calendar_id: resource.calendar browse record - @param day: datetime object + def interval_clean(self, intervals): + """ Utility method that removes overlapping inside datetime intervals. - @return: returns the working hours (as float) men should work on the given day if is in the attendance_ids of the resource_calendar_id (i.e if that day is a working day), returns 0.0 otherwise + :param list intervals: list of datetime intervals. Each interval is a + tuple(datetime_from, datetime_to) + :return list final_list: list of intervals without overlap """ - res = 0.0 - for working_day in resource_calendar_id.attendance_ids: - if (int(working_day.dayofweek) + 1) == day.isoweekday(): - res += working_day.hour_to - working_day.hour_from - return res + intervals = sorted(intervals) # TODO: check sorted method + final_list = [] + working_interval = None + while intervals: + current_interval = intervals.pop(0) + if not working_interval: # init + working_interval = [current_interval[0], current_interval[1]] + elif working_interval[1] < current_interval[0]: # interval is disjoint + final_list.append(tuple(working_interval)) + working_interval = [current_interval[0], current_interval[1]] + elif working_interval[1] < current_interval[1]: # union of greater intervals + working_interval[1] = current_interval[1] + if working_interval: # handle void lists + final_list.append(tuple(working_interval)) + return final_list + + def interval_remove_leaves(self, interval, leave_intervals): + """ Utility method that remove leave intervals from a base interval: + + - clean the leave intrevals, to have an ordered list of not-overlapping + intervals + - initiate the current interval to be the base interval + - for each leave interval: + + - finishing before the current interval: skip, go to next + - beginning after the current interval: skip and get out of the loop + because we are outside range (leaves are ordered) + - beginning within the current interval: close the current interval + and begin a new current interval that begins at the end of the leave + interval + - ending within the current interval: update the current interval begin + to match the leave interval ending + + :param tuple interval: a tuple (beginning datetime, ending datetime) that + is the base interval from which the leave intervals + will be removed + :param list interval: a list of tuples (beginning datetime, ending datetime) + that are intervals to remove from the base interval + :return list final_list: a list of tuples (begin datetime, end datetime) + that are the remaining valid intervals + """ + if not interval: + return interval + if leave_intervals is None: + leave_intervals = [] + final_list = [] + leave_intervals = self.interval_clean(leave_intervals) + current_interval = [interval[0], interval[1]] + # print '\tcurrent_intreval', current_interval + for leave in leave_intervals: + # print '\thandling leave', leave + if leave[1] <= current_interval[0]: + # print '\t\tbefore, skipping' + continue + if leave[0] >= current_interval[1]: + # print '\t\tafter, abort' + break + if current_interval[0] < leave[0] < current_interval[1]: + # print '\t\tbeginning inside' + current_interval[1] = leave[0] + final_list.append((current_interval[0], current_interval[1])) + current_interval = [leave[1], interval[1]] + if current_interval[0] <= leave[1] <= current_interval[1]: + # print '\t\tending inside' + current_interval[0] = leave[1] + if current_interval and current_interval[0] < interval[1]: # remove intervals moved outside base interval due to leaves + final_list.append((current_interval[0], current_interval[1])) + return final_list + + # -------------------------------------------------- + # Date and hours computation + # -------------------------------------------------- + + def get_next_day(self, cr, uid, id, day_date, context=None): + if id is None: + return day_date + relativedelta(days=1) + calendar = self.browse(cr, uid, id, context=None) + weekdays = set() + for attendance in calendar.attendance_ids: + weekdays.add(int(attendance.dayofweek)) + weekdays = list(weekdays) + + if day_date.weekday() in weekdays: + base_index = weekdays.index(day_date.weekday()) + else: + base_index = -1 + for weekday in weekdays: + if weekday > day_date.weekday(): + break + base_index += 1 + + new_index = (base_index + 1) % len(weekdays) + days = (weekdays[new_index] - day_date.weekday()) + + if days < 0: + days = 7 + days + + return day_date + relativedelta(days=days) + + def get_previous_day(self, cr, uid, id, day_date, context=None): + if id is None: + return day_date + relativedelta(days=-1) + calendar = self.browse(cr, uid, id, context=None) + weekdays = set() + for attendance in calendar.attendance_ids: + weekdays.add(int(attendance.dayofweek)) + weekdays = list(weekdays) + weekdays.reverse() + + if day_date.weekday() in weekdays: + base_index = weekdays.index(day_date.weekday()) + else: + base_index = -1 + for weekday in weekdays: + if weekday < day_date.weekday(): + break + base_index += 1 + + new_index = (base_index + 1) % len(weekdays) + days = (weekdays[new_index] - day_date.weekday()) + if days > 0: + days = days - 7 + + return day_date + relativedelta(days=days) + + def _get_leave_intervals(self, cr, uid, id, resource_id=None, start_datetime=None, end_datetime=None, context=None): + """Get the leaves of the calendar. Leaves can be filtered on the resource, + the start datetime or the end datetime. + + :param int resource_id: if set, global + specific leaves will be taken + into account + TODO: COMPLETE ME + """ + resource_calendar = self.browse(cr, uid, id, context=context) + leaves = [] + for leave in resource_calendar.leave_ids: + if resource_id and leave.resource_id and not resource_id == leave.resource_id.id: + continue + date_from = datetime.strptime(leave.date_from, tools.DEFAULT_SERVER_DATETIME_FORMAT) + if start_datetime and date_from < start_datetime: + continue + if end_datetime and date_end > end_datetime: + continue + date_to = datetime.strptime(leave.date_to, tools.DEFAULT_SERVER_DATETIME_FORMAT) + leaves.append((date_from, date_to)) + return leaves + + def get_working_intervals_of_day(self, cr, uid, id, day_date=None, leaves=None, compute_leaves=False, resource_id=None, context=None): + """Get the working intervals of the day based on calendar. This method + handle leaves that come directly from the leaves parameter or can be computed. + + :param int id: resource.calendar id; take the first one if is a list + :param date day_date: date object that is the day for which this method + computes the working intervals; is None, set to today + :param list leaves: a list of tuples(start_datetime, end_datetime) that + represent leaves. + :param boolean compute_leaves: if set and if leaves is None, compute the + leaves based on calendar and resource. + If leaves is None and compute_leaves false + no leaves are taken into account. + :param int resource_id: the id of the resource to take into account when + computing the leaves. If not set, only general + leaves will be computed. + + :returns list intervals: a list of tuples (start_datetime, end_datetime) + that are intervals of work + """ + if id is None: + return [] + if isinstance(id, (list, tuple)): + id = id[0] + if day_date is None: + day_date = date.today() + resource_calendar = self.browse(cr, uid, id, context=context) + intervals = [] + + # find working intervals + date_dict = { + 'Y': day_date.year, + 'm': day_date.month, + 'd': day_date.day, + } + working_intervals = [] + for calendar_working_day in resource_calendar.attendance_ids: + if int(calendar_working_day.dayofweek) == day_date.weekday(): + date_dict.update({ + 'HF': calendar_working_day.hour_from, + 'HT': calendar_working_day.hour_to, + }) + date_from = datetime.strptime('%(Y)04d-%(m)02d-%(d)02d %(HF)02d:00:00' % date_dict, '%Y-%m-%d %H:%M:%S') + date_to = datetime.strptime('%(Y)04d-%(m)02d-%(d)02d %(HT)02d:00:00' % date_dict, '%Y-%m-%d %H:%M:%S') + working_intervals.append((date_from, date_to)) + + # find leave intervals + if leaves is None and compute_leaves: + leaves = self._get_leave_intervals(cr, uid, id, resource_id=resource_id, context=None) + + # filter according to leaves + for interval in working_intervals: + work_intervals = self.interval_remove_leaves(interval, leaves) + intervals += work_intervals + + return intervals + + def get_working_hours_of_date(self, cr, uid, id, day_date=None, leaves=None, compute_leaves=False, resource_id=None, context=None): + """Get the working hours of the day based on calendar. This method uses + get_working_intervals_of_day to have the work intervals of the day. It + then calculates the number of hours contained in those intervals. """ + res = timedelta() + intervals = self.get_working_intervals_of_day(cr, uid, id, day_date, leaves, compute_leaves, resource_id, context) + for interval in intervals: + res += interval[1] - interval[0] + return (res.total_seconds() / 3600.0) + + def schedule_hours(self, cr, uid, id, hours, start_datetime=None, end_datetime=None, compute_leaves=False, resource_id=None, context=None): + """ + """ + if start_datetime is None: + start_datetime = datetime.now() + work_hours = 0 + iterations = 0 + final_intervals = [] + + # compute work days + work_days = set() + resource_calendar = self.browse(cr, uid, id, context=context) + for attendance in resource_calendar.attendance_ids: + work_days.add(int(attendance.dayofweek)) + + # prepare rrule arguments + rrule_args = { + 'byweekday': work_days, + 'dtstart': start_datetime, + } + if end_date: + rrule_args['until'] = end_datetime + else: + rrule_args['count'] = 1024 + + for day in rrule.rrule(rrule.DAILY, **rrule_args): + working_intervals = self.get_working_intervals_of_day(cr, uid, id, day_date=day, compute_leaves=compute_leaves, resource_id=resource_id, context=context) + if not working_intervals: + continue + # Compute worked hours, compare to requested number of hours + res = timedelta() + for interval in working_intervals: + res += interval[1] - interval[0] + work_hours += (res.total_seconds() / 3600.0) + final_intervals += working_intervals + if float_compare(work_hours, hours * 1.0, precision_digits=2) in (0, 1) or (iterations >= 50): + break + iterations += 1 + + return final_intervals + + def _schedule_days(self, cr, uid, id, days, date=None, compute_leaves=False, resource_id=None, context=None): + """Schedule days of work. + + This method can be used backwards, i.e. scheduling days before a deadline. + """ + backwards = False + if days < 0: + backwards = True + days = abs(days) + intervals = [] + planned_days = 0 + iterations = 0 + current_datetime = date + + while planned_days < days and iterations < 1000: + working_intervals = self.get_working_intervals_of_day(cr, uid, id, current_datetime, compute_leaves=compute_leaves, resource_id=resource_id, context=context) + if id is None or working_intervals: # no calendar -> no working hours, but day is considered as worked + planned_days += 1 + intervals += working_intervals + # get next day + if backwards: + current_datetime = self.get_previous_day(cr, uid, id, current_datetime) + else: + current_datetime = self.get_next_day(cr, uid, id, current_datetime) + + return (current_datetime, intervals) + + def schedule_days(self, cr, uid, id, days, date=None, compute_leaves=False, resource_id=None, context=None): + res = self._schedule_days(cr, uid, id, days, date, compute_leaves, resource_id, context) + return res[0] + + # -------------------------------------------------- + # Compaqtibility / to clean / to remove + # -------------------------------------------------- + + def working_hours_on_day(self, cr, uid, resource_calendar_id, day, context=None): + """ Compatibility method - will be removed for OpenERP v8 + TDE TODO: hr_payroll/hr_payroll.py + """ + return self.get_working_hours_of_date(cr, uid, resource_calendar_id.id, day_date=day, context=None) def _get_leaves(self, cr, uid, id, resource): """Private Method to Calculate resource Leaves days @@ -99,6 +397,8 @@ class resource_calendar(osv.osv): schedule. @return : List datetime object of working schedule based on supplies params + + TDE TODO: used in mrp_operations/mrp_operations.py """ if not id: td = int(hours)*3 @@ -138,9 +438,10 @@ class resource_calendar(osv.osv): # def interval_get(self, cr, uid, id, dt_from, hours, resource=False, byday=True): def interval_get_multi(self, cr, uid, date_and_hours_by_cal, resource=False, byday=True): + """ TDE NOTE: used in mrp_operations/mrp_operations.py and in interval_get() """ def group(lst, key): lst.sort(key=itemgetter(key)) - grouped = groupby(lst, itemgetter(key)) + grouped = itertools.groupby(lst, itemgetter(key)) return dict([(k, [v for v in itr]) for k, itr in grouped]) # END group @@ -198,6 +499,8 @@ class resource_calendar(osv.osv): @param byday: boolean flag bit enforce day wise scheduling @return : list of scheduled working timing based on resource calendar. + + TDE NOTE: mrp_operations/mrp_operations.py, crm/crm_lead.py """ res = self.interval_get_multi(cr, uid, [(dt_from.strftime('%Y-%m-%d %H:%M:%S'), hours, id)], resource, byday)[(dt_from.strftime('%Y-%m-%d %H:%M:%S'), hours, id)] return res @@ -233,6 +536,8 @@ class resource_calendar(osv.osv): @param context: current request context @return : Total number of working hours based dt_from and dt_end and resource if supplied. + + TDE NOTE: used in project_issue/project_issue.py """ utc_tz = pytz.timezone('UTC') local_tz = utc_tz @@ -374,6 +679,8 @@ class resource_resource(osv.osv): def generate_resources(self, cr, uid, user_ids, calendar_id, context=None): """ Return a list of Resource Class objects for the resources allocated to the phase. + + TDE NOTE: used in project/project.py """ resource_objs = {} user_pool = self.pool.get('res.users') @@ -401,6 +708,8 @@ class resource_resource(osv.osv): @param calendar_id : working calendar of the project @param resource_id : resource working on phase/task @param resource_calendar : working calendar of the resource + + TDE NOTE: used in project/project.py, and in generate_resources """ resource_calendar_leaves_pool = self.pool.get('resource.calendar.leaves') leave_list = [] @@ -426,6 +735,8 @@ class resource_resource(osv.osv): """ Change the format of working calendar from 'Openerp' format to bring it into 'Faces' format. @param calendar_id : working calendar of the project + + TDE NOTE: used in project/project.py """ if not calendar_id: # Calendar is not specified: working days: 24/7 diff --git a/addons/resource/resource_view.xml b/addons/resource/resource_view.xml index 3c9f83905a9..dcd7e1a67c0 100644 --- a/addons/resource/resource_view.xml +++ b/addons/resource/resource_view.xml @@ -253,5 +253,6 @@ + diff --git a/addons/resource/tests/__init__.py b/addons/resource/tests/__init__.py new file mode 100644 index 00000000000..f22158c04b8 --- /dev/null +++ b/addons/resource/tests/__init__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2013-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.addons.resource.tests import test_resource + +checks = [ + test_resource, +] + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/resource/tests/common.py b/addons/resource/tests/common.py new file mode 100644 index 00000000000..6dffafd8179 --- /dev/null +++ b/addons/resource/tests/common.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2013-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from datetime import datetime + +from openerp.tests import common + + +class TestResourceCommon(common.TransactionCase): + + def setUp(self): + super(TestResourceCommon, self).setUp() + cr, uid = self.cr, self.uid + if not hasattr(self, 'context'): + self.context = {} + + # Usefull models + self.resource_resource = self.registry('resource.resource') + self.resource_calendar = self.registry('resource.calendar') + self.resource_attendance = self.registry('resource.calendar.attendance') + self.resource_leaves = self.registry('resource.calendar.leaves') + + # Some demo data + self.date1 = datetime.strptime('2013-02-12 09:22:13', '%Y-%m-%d %H:%M:%S') # weekday() returns 1, isoweekday() returns 2 + self.date2 = datetime.strptime('2013-02-15 09:22:13', '%Y-%m-%d %H:%M:%S') # weekday() returns 4, isoweekday() returns 5 + # Leave1: 19/02/2013, from 9 to 12, is a day 0 + self.leave1_start = datetime.strptime('2013-02-19 09:00:00', '%Y-%m-%d %H:%M:%S') + self.leave1_end = datetime.strptime('2013-02-19 12:00:00', '%Y-%m-%d %H:%M:%S') + # Leave2: 22/02/2013, from 9 to 15, is a day 3 + self.leave2_start = datetime.strptime('2013-02-22 09:00:00', '%Y-%m-%d %H:%M:%S') + self.leave2_end = datetime.strptime('2013-02-22 15:00:00', '%Y-%m-%d %H:%M:%S') + # Leave3: 25/02/2013 (day6) -> 01/03/2013 (day3) + self.leave3_start = datetime.strptime('2013-02-25 13:00:00', '%Y-%m-%d %H:%M:%S') + self.leave3_end = datetime.strptime('2013-03-01 11:30:00', '%Y-%m-%d %H:%M:%S') + + # Resource data + # Calendar working days: 1 (8-16), 4 (8-13, 21-22) + self.calendar_id = self.resource_calendar.create( + cr, uid, { + 'name': 'TestCalendar', + } + ) + self.att1_id = self.resource_attendance.create( + cr, uid, { + 'name': 'Att1', + 'dayofweek': '1', + 'hour_from': 8, + 'hour_to': 16, + 'calendar_id': self.calendar_id, + } + ) + self.att2_id = self.resource_attendance.create( + cr, uid, { + 'name': 'Att2', + 'dayofweek': '4', + 'hour_from': 8, + 'hour_to': 13, + 'calendar_id': self.calendar_id, + } + ) + self.att3_id = self.resource_attendance.create( + cr, uid, { + 'name': 'Att3', + 'dayofweek': '4', + 'hour_from': 16, + 'hour_to': 23, + 'calendar_id': self.calendar_id, + } + ) + self.resource1_id = self.resource_resource.create( + cr, uid, { + 'name': 'TestResource1', + 'resource_type': 'user', + 'time_efficiency': 150.0, + 'calendar_id': self.calendar_id, + } + ) + self.leave1_id = self.resource_leaves.create( + cr, uid, { + 'name': 'GenericLeave', + 'calendar_id': self.calendar_id, + 'date_from': self.leave1_start, + 'date_to': self.leave1_end, + } + ) + self.leave2_id = self.resource_leaves.create( + cr, uid, { + 'name': 'ResourceLeave', + 'calendar_id': self.calendar_id, + 'resource_id': self.resource1_id, + 'date_from': self.leave2_start, + 'date_to': self.leave2_end, + } + ) + self.leave3_id = self.resource_leaves.create( + cr, uid, { + 'name': 'ResourceLeave2', + 'calendar_id': self.calendar_id, + 'resource_id': self.resource1_id, + 'date_from': self.leave3_start, + 'date_to': self.leave3_end, + } + ) + # Some browse data + self.calendar = self.resource_calendar.browse(cr, uid, self.calendar_id) diff --git a/addons/resource/tests/test_resource.py b/addons/resource/tests/test_resource.py new file mode 100644 index 00000000000..fa29de91b3d --- /dev/null +++ b/addons/resource/tests/test_resource.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2013-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from datetime import datetime +from dateutil.relativedelta import relativedelta + +from openerp.addons.resource.tests.common import TestResourceCommon + + +class TestResource(TestResourceCommon): + + def test_00_intervals(self): + intervals = [ + ( + datetime.strptime('2013-02-04 09:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-04 11:00:00', '%Y-%m-%d %H:%M:%S') + ), ( + datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-04 12:00:00', '%Y-%m-%d %H:%M:%S') + ), ( + datetime.strptime('2013-02-04 11:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S') + ), ( + datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-04 21:00:00', '%Y-%m-%d %H:%M:%S') + ), ( + datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-03 10:00:00', '%Y-%m-%d %H:%M:%S') + ), ( + datetime.strptime('2013-02-04 18:00:00', '%Y-%m-%d %H:%M:%S'), + datetime.strptime('2013-02-04 19:00:00', '%Y-%m-%d %H:%M:%S') + ) + ] + + # Test: interval cleaning + result = self.resource_calendar.interval_clean(intervals) + self.assertEqual(len(result), 3, 'resource_calendar: wrong interval cleaning') + # First interval: 03, unchanged + self.assertEqual(result[0][0], datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(result[0][1], datetime.strptime('2013-02-03 10:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + # Second intreval: 04, 08-14, combining 08-12 and 11-14, 09-11 being inside 08-12 + self.assertEqual(result[1][0], datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(result[1][1], datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + # Third interval: 04, 17-21, 18-19 being inside 17-21 + self.assertEqual(result[2][0], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(result[2][1], datetime.strptime('2013-02-04 21:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + + # Test: disjoint removal + working_interval = (datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), datetime.strptime('2013-02-04 18:00:00', '%Y-%m-%d %H:%M:%S')) + result = self.resource_calendar.interval_remove_leaves(working_interval, intervals) + self.assertEqual(len(result), 1, 'resource_calendar: wrong leave removal from interval') + # First interval: 04, 14-17 + self.assertEqual(result[0][0], datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + self.assertEqual(result[0][1], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + + def test_10_calendar_basics(self): + """ Testing basic method of resource.calendar """ + cr, uid = self.cr, self.uid + + # -------------------------------------------------- + # Test1: get_next_day + # -------------------------------------------------- + + # Test: next day: next day after day1 is day4 + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date1) + self.assertEqual(date, self.date2, 'resource_calendar: wrong next day computing') + + # Test: next day: next day after day4 is (day1+7) + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date2) + self.assertEqual(date, self.date1 + relativedelta(days=7), 'resource_calendar: wrong next day computing') + + # Test: next day: next day after day4+1 is (day1+7) + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date2 + relativedelta(days=1)) + self.assertEqual(date, self.date1 + relativedelta(days=7), 'resource_calendar: wrong next day computing') + + # Test: next day: next day after day1-1 is day1 + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date1 + relativedelta(days=-1)) + self.assertEqual(date, self.date1, 'resource_calendar: wrong next day computing') + + # -------------------------------------------------- + # Test2: get_previous_day + # -------------------------------------------------- + + # Test: previous day: previous day before day1 is (day4-7) + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date1) + self.assertEqual(date, self.date2 + relativedelta(days=-7), 'resource_calendar: wrong previous day computing') + + # Test: previous day: previous day before day4 is day1 + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date2) + self.assertEqual(date, self.date1, 'resource_calendar: wrong previous day computing') + + # Test: previous day: previous day before day4+1 is day4 + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date2 + relativedelta(days=1)) + self.assertEqual(date, self.date2, 'resource_calendar: wrong previous day computing') + + # Test: previous day: previous day before day1-1 is (day4-7) + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date1 + relativedelta(days=-1)) + self.assertEqual(date, self.date2 + relativedelta(days=-7), 'resource_calendar: wrong previous day computing') + + def test_20_calendar_working_intervals(self): + """ Testing working intervals computing method of resource.calendar """ + cr, uid = self.cr, self.uid + + # Test: day0 without leaves: 1 interval + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date1) + self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + + # Test: day3 without leaves: 2 interval + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date2) + self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-15 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-15 13:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][0], datetime.strptime('2013-02-15 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][1], datetime.strptime('2013-02-15 23:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + + # Test: day0 with leaves outside range: 1 interval + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date1, compute_leaves=True) + self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + + # Test: day0 with leaves: 2 intrevals because of leave between 9 ans 12 + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date1 + relativedelta(days=7), compute_leaves=True) + self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 09:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][0], datetime.strptime('2013-02-19 12:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][1], datetime.strptime('2013-02-19 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + + def test_40_calendar_schedule_days(self): + """ Testing calendar days scheduling """ + cr, uid = self.cr, self.uid + + print '---------------' + res = self.resource_calendar.schedule_days(cr, uid, self.calendar_id, 5, date=self.date1) + print res + + # -------------------------------------------------- + # Misc + # -------------------------------------------------- + + # Without calendar, should only count days + print '---------------' + res = self.resource_calendar.schedule_days(cr, uid, None, 5, date=self.date1) + print res + + # @mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm') + # def test_20_calendar(self): + # """ Testing calendar and time computation """ + # cr, uid = self.cr, self.uid + + # wh = self.resource_calendar.get_working_hours_of_date(cr, uid, self.calendar_id, day_date=self.date1) + # self.assertEqual(wh, 8, 'cacamou') + + # wh = self.resource_calendar.get_working_hours_of_date(cr, uid, self.calendar_id, day_date=self.date2+relativedelta(days=7)) + # self.assertEqual(wh, 12, 'cacamou') + + # # print '---------------------' + # # print self.date1 + # # res = self.resource_calendar.interval_min_get(cr, uid, self.calendar_id, self.date1, 40, resource=False) + # # print res + + # print '----------------------' + # res = self.resource_calendar.schedule_hours(cr, uid, self.calendar_id, 40, start_datetime=self.date1) + # print res + # print '----------------------' + # # print self.date1 + # # res = self.resource_calendar.interval_get(cr, uid, self.calendar_id, self.date1, 40, resource=False, byday=True) + # # print res From 49234995346258faa183fa92e660512538a4461e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 21 Aug 2013 12:51:27 +0200 Subject: [PATCH 002/335] [IMP] base_action_rule: when using a timed condition on days, allow to use a working calendar to compute days. bzr revid: tde@openerp.com-20130821105127-182i9f7mzkoo4cf0 --- addons/base_action_rule/__openerp__.py | 2 +- addons/base_action_rule/base_action_rule.py | 14 +++++++++++++- addons/base_action_rule/base_action_rule_view.xml | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/addons/base_action_rule/__openerp__.py b/addons/base_action_rule/__openerp__.py index 973567c613f..5dd4b057514 100644 --- a/addons/base_action_rule/__openerp__.py +++ b/addons/base_action_rule/__openerp__.py @@ -35,7 +35,7 @@ trigger an automatic reminder email. """, 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', - 'depends': ['base', 'mail'], + 'depends': ['base', 'resource', 'mail'], 'data': [ 'base_action_rule_view.xml', 'security/ir.model.access.csv', diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 3497255fa76..fd1d8e8d121 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -74,6 +74,11 @@ class base_action_rule(osv.osv): "trigger date, like sending a reminder 15 minutes before a meeting."), 'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'), ('day', 'Days'), ('month', 'Months')], 'Delay type'), + 'trg_date_calendar_id': fields.many2one( + 'resource.calendar', 'Use Calendar', + help='When calculating a day-based timed condition, it is possible to use a calendar to compute the date based on working days.', + ondelete='set null', + ), 'act_user_id': fields.many2one('res.users', 'Set Responsible'), 'act_followers': fields.many2many("res.partner", string="Add Followers"), 'server_action_ids': fields.many2many('ir.actions.server', string='Server Actions', @@ -268,7 +273,14 @@ class base_action_rule(osv.osv): record_dt = get_record_dt(record) if not record_dt: continue - action_dt = get_datetime(record_dt) + delay + if action.trg_date_calendar_id and action.trg_date_range_type == 'day': + start_dt = get_datetime(record_dt) + action_dt = self.pool['resource.calendar'].schedule_days( + cr, uid, action.trg_date_calendar_id.id, action.trg_date_range, + date=start_dt, compute_leaves=True + ) + else: + action_dt = get_datetime(record_dt) + delay if last_run and (last_run <= action_dt < now) or (action_dt < now): try: self._process(cr, uid, action, [record.id], context=context) diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml index c2262adde39..a4341d403ca 100644 --- a/addons/base_action_rule/base_action_rule_view.xml +++ b/addons/base_action_rule/base_action_rule_view.xml @@ -43,6 +43,8 @@ +

Select when the action must be run, and add filters and/or timing conditions. From 0cb0a5994f1b808239f58de3a3c6a4d17262d5fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 29 Aug 2013 12:27:57 +0200 Subject: [PATCH 003/335] [FIX] crm: removed duplicate calendar menu item bzr revid: tde@openerp.com-20130829102757-3mxgu096ttygnway --- addons/crm/crm_view.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/addons/crm/crm_view.xml b/addons/crm/crm_view.xml index d0dcc07fcb0..5766ea3fb29 100644 --- a/addons/crm/crm_view.xml +++ b/addons/crm/crm_view.xml @@ -311,9 +311,6 @@ groups="base.group_no_one" sequence="15" parent="base.menu_base_config"/> - - - From af79a26a1f5f85b760f8b1cbab169acaa8bd4e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 30 Aug 2013 15:32:50 +0200 Subject: [PATCH 004/335] [IMP] resource: overall fix of hours/days computation + scheduling + added tests bzr revid: tde@openerp.com-20130830133250-ixehp7imac0duygd --- addons/resource/resource.py | 473 ++++++++++++++++--------- addons/resource/test/resource.yml | 2 +- addons/resource/tests/common.py | 10 +- addons/resource/tests/test_resource.py | 346 ++++++++++++++---- 4 files changed, 588 insertions(+), 243 deletions(-) diff --git a/addons/resource/resource.py b/addons/resource/resource.py index dd87157aeb0..c8635a01d0c 100644 --- a/addons/resource/resource.py +++ b/addons/resource/resource.py @@ -19,13 +19,14 @@ # ############################################################################## -import pytz -from datetime import date, datetime, timedelta +import datetime +# from datetime import datetime, timedelta from dateutil import rrule from dateutil.relativedelta import relativedelta import itertools import math from operator import itemgetter +import pytz from faces import * from openerp import tools @@ -38,10 +39,10 @@ class resource_calendar(osv.osv): _name = "resource.calendar" _description = "Resource Calendar" _columns = { - 'name' : fields.char("Name", size=64, required=True), - 'company_id' : fields.many2one('res.company', 'Company', required=False), - 'attendance_ids' : fields.one2many('resource.calendar.attendance', 'calendar_id', 'Working Time'), - 'manager' : fields.many2one('res.users', 'Workgroup Manager'), + 'name': fields.char("Name", size=64, required=True), + 'company_id': fields.many2one('res.company', 'Company', required=False), + 'attendance_ids': fields.one2many('resource.calendar.attendance', 'calendar_id', 'Working Time'), + 'manager': fields.many2one('res.users', 'Workgroup Manager'), 'leave_ids': fields.one2many( 'resource.calendar.leaves', 'calendar_id', 'Leaves', help='' @@ -56,27 +57,29 @@ class resource_calendar(osv.osv): # -------------------------------------------------- def interval_clean(self, intervals): - """ Utility method that removes overlapping inside datetime intervals. + """ Utility method that sorts and removes overlapping inside datetime + intervals. The intervals are sorted based on increasing starting datetime. + Overlapping intervals are merged into a single one. - :param list intervals: list of datetime intervals. Each interval is a - tuple(datetime_from, datetime_to) - :return list final_list: list of intervals without overlap + :param list intervals: list of intervals; each interval is a tuple + (datetime_from, datetime_to) + :return list cleaned: list of sorted intervals without overlap """ - intervals = sorted(intervals) # TODO: check sorted method - final_list = [] + intervals = sorted(intervals, key=itemgetter(0)) # sort on first datetime + cleaned = [] working_interval = None while intervals: current_interval = intervals.pop(0) if not working_interval: # init working_interval = [current_interval[0], current_interval[1]] elif working_interval[1] < current_interval[0]: # interval is disjoint - final_list.append(tuple(working_interval)) + cleaned.append(tuple(working_interval)) working_interval = [current_interval[0], current_interval[1]] elif working_interval[1] < current_interval[1]: # union of greater intervals working_interval[1] = current_interval[1] if working_interval: # handle void lists - final_list.append(tuple(working_interval)) - return final_list + cleaned.append(tuple(working_interval)) + return cleaned def interval_remove_leaves(self, interval, leave_intervals): """ Utility method that remove leave intervals from a base interval: @@ -98,87 +101,120 @@ class resource_calendar(osv.osv): :param tuple interval: a tuple (beginning datetime, ending datetime) that is the base interval from which the leave intervals will be removed - :param list interval: a list of tuples (beginning datetime, ending datetime) - that are intervals to remove from the base interval - :return list final_list: a list of tuples (begin datetime, end datetime) - that are the remaining valid intervals + :param list leave_intervals: a list of tuples (beginning datetime, ending datetime) + that are intervals to remove from the base interval + :return list intervals: a list of tuples (begin datetime, end datetime) + that are the remaining valid intervals """ if not interval: return interval if leave_intervals is None: leave_intervals = [] - final_list = [] + intervals = [] leave_intervals = self.interval_clean(leave_intervals) current_interval = [interval[0], interval[1]] - # print '\tcurrent_intreval', current_interval for leave in leave_intervals: - # print '\thandling leave', leave if leave[1] <= current_interval[0]: - # print '\t\tbefore, skipping' continue if leave[0] >= current_interval[1]: - # print '\t\tafter, abort' break if current_interval[0] < leave[0] < current_interval[1]: - # print '\t\tbeginning inside' current_interval[1] = leave[0] - final_list.append((current_interval[0], current_interval[1])) + intervals.append((current_interval[0], current_interval[1])) current_interval = [leave[1], interval[1]] - if current_interval[0] <= leave[1] <= current_interval[1]: - # print '\t\tending inside' + # if current_interval[0] <= leave[1] <= current_interval[1]: + if current_interval[0] <= leave[1]: current_interval[0] = leave[1] if current_interval and current_interval[0] < interval[1]: # remove intervals moved outside base interval due to leaves - final_list.append((current_interval[0], current_interval[1])) - return final_list + intervals.append((current_interval[0], current_interval[1])) + return intervals + + def interval_schedule_hours(self, intervals, hour, remove_at_end=True): + """ Schedule hours in intervals. The last matching interval is truncated + to match the specified hours. + + It is possible to truncate the last interval at its beginning or ending. + However this does nothing on the given interval order that should be + submitted accordingly. + + :param list intervals: a list of tuples (beginning datetime, ending datetime) + :param int/float hours: number of hours to schedule + :param boolean remove_at_end: remove extra hours at the end of the last + matching interval. Otherwise, do it at the + beginning. + + :return list results: a list of intervals + """ + results = [] + res = datetime.timedelta() + limit = datetime.timedelta(hours=hour) + for interval in intervals: + res += interval[1] - interval[0] + if res > limit and remove_at_end: + interval = (interval[0], interval[1] + relativedelta(seconds=(limit-res).total_seconds())) + elif res > limit: + interval = (interval[0] + relativedelta(seconds=(res-limit).total_seconds()), interval[1]) + results.append(interval) + if res > limit: + break + return results # -------------------------------------------------- # Date and hours computation # -------------------------------------------------- - def get_next_day(self, cr, uid, id, day_date, context=None): - if id is None: - return day_date + relativedelta(days=1) + def get_attendances_for_weekdays(self, cr, uid, id, weekdays, context=None): + """ Given a list of weekdays, return matching resource.calendar.attendance""" + calendar = self.browse(cr, uid, id, context=None) + return [att for att in calendar.attendance_ids if int(att.dayofweek) in weekdays] + + def get_weekdays(self, cr, uid, id, context=None): + """ Return the list of weekdays that contain at least one working interval """ calendar = self.browse(cr, uid, id, context=None) weekdays = set() for attendance in calendar.attendance_ids: weekdays.add(int(attendance.dayofweek)) - weekdays = list(weekdays) + return list(weekdays) - if day_date.weekday() in weekdays: - base_index = weekdays.index(day_date.weekday()) - else: - base_index = -1 - for weekday in weekdays: - if weekday > day_date.weekday(): - break - base_index += 1 + def get_next_day(self, cr, uid, id, day_date, context=None): + """ Get following date of day_date, based on resource.calendar. If no + calendar is provided, just return the next day. + + :param date day_date: current day as a date + """ + if id is None: + return day_date + relativedelta(days=1) + weekdays = self.get_weekdays(cr, uid, id, context) + + base_index = -1 + for weekday in weekdays: + if weekday > day_date.weekday(): + break + base_index += 1 new_index = (base_index + 1) % len(weekdays) days = (weekdays[new_index] - day_date.weekday()) - if days < 0: days = 7 + days return day_date + relativedelta(days=days) def get_previous_day(self, cr, uid, id, day_date, context=None): + """ Get previous date of day_date, based on resource.calendar. If no + calendar is provided, just return the previous day. + + :param date day_date: current day as a date + """ if id is None: return day_date + relativedelta(days=-1) - calendar = self.browse(cr, uid, id, context=None) - weekdays = set() - for attendance in calendar.attendance_ids: - weekdays.add(int(attendance.dayofweek)) - weekdays = list(weekdays) + weekdays = self.get_weekdays(cr, uid, id, context) weekdays.reverse() - if day_date.weekday() in weekdays: - base_index = weekdays.index(day_date.weekday()) - else: - base_index = -1 - for weekday in weekdays: - if weekday < day_date.weekday(): - break - base_index += 1 + base_index = -1 + for weekday in weekdays: + if weekday < day_date.weekday(): + break + base_index += 1 new_index = (base_index + 1) % len(weekdays) days = (weekdays[new_index] - day_date.weekday()) @@ -187,35 +223,55 @@ class resource_calendar(osv.osv): return day_date + relativedelta(days=days) - def _get_leave_intervals(self, cr, uid, id, resource_id=None, start_datetime=None, end_datetime=None, context=None): + def get_leave_intervals(self, cr, uid, id, resource_id=None, + start_datetime=None, end_datetime=None, + context=None): """Get the leaves of the calendar. Leaves can be filtered on the resource, the start datetime or the end datetime. - :param int resource_id: if set, global + specific leaves will be taken - into account - TODO: COMPLETE ME + :param int resource_id: the id of the resource to take into account when + computing the leaves. If not set, only general + leaves are computed. If set, generic and + specific leaves are computed. + :param datetime start_datetime: if provided, do not take into account leaves + ending before this date. + :param datetime end_datetime: if provided, do not take into account leaves + beginning after this date. + + :return list leaves: list of tuples (start_datetime, end_datetime) of + leave intervals """ resource_calendar = self.browse(cr, uid, id, context=context) leaves = [] for leave in resource_calendar.leave_ids: - if resource_id and leave.resource_id and not resource_id == leave.resource_id.id: + if leave.resource_id and not resource_id == leave.resource_id.id: continue - date_from = datetime.strptime(leave.date_from, tools.DEFAULT_SERVER_DATETIME_FORMAT) - if start_datetime and date_from < start_datetime: + date_from = datetime.datetime.strptime(leave.date_from, tools.DEFAULT_SERVER_DATETIME_FORMAT) + if end_datetime and date_from > end_datetime: continue - if end_datetime and date_end > end_datetime: + date_to = datetime.datetime.strptime(leave.date_to, tools.DEFAULT_SERVER_DATETIME_FORMAT) + if start_datetime and date_to < start_datetime: continue - date_to = datetime.strptime(leave.date_to, tools.DEFAULT_SERVER_DATETIME_FORMAT) leaves.append((date_from, date_to)) return leaves - def get_working_intervals_of_day(self, cr, uid, id, day_date=None, leaves=None, compute_leaves=False, resource_id=None, context=None): + def get_working_intervals_of_day(self, cr, uid, id, start_dt=None, end_dt=None, + leaves=None, compute_leaves=False, resource_id=None, + context=None): """Get the working intervals of the day based on calendar. This method handle leaves that come directly from the leaves parameter or can be computed. :param int id: resource.calendar id; take the first one if is a list - :param date day_date: date object that is the day for which this method - computes the working intervals; is None, set to today + :param datetime start_dt: datetime object that is the beginning hours + for the working intervals computation; any + working interval beginning before start_dt + will be truncated. If not set, set to end_dt + or today() if no end_dt at 00.00.00. + :param datetime end_dt: datetime object that is the ending hour + for the working intervals computation; any + working interval ending after end_dt + will be truncated. If not set, set to start_dt() + at 23.59.59. :param list leaves: a list of tuples(start_datetime, end_datetime) that represent leaves. :param boolean compute_leaves: if set and if leaves is None, compute the @@ -224,40 +280,45 @@ class resource_calendar(osv.osv): no leaves are taken into account. :param int resource_id: the id of the resource to take into account when computing the leaves. If not set, only general - leaves will be computed. + leaves are computed. If set, generic and + specific leaves are computed. - :returns list intervals: a list of tuples (start_datetime, end_datetime) - that are intervals of work + :return list intervals: a list of tuples (start_datetime, end_datetime) + of work intervals """ if id is None: return [] if isinstance(id, (list, tuple)): id = id[0] - if day_date is None: - day_date = date.today() - resource_calendar = self.browse(cr, uid, id, context=context) - intervals = [] - # find working intervals - date_dict = { - 'Y': day_date.year, - 'm': day_date.month, - 'd': day_date.day, - } + # Computes start_dt, end_dt (with default values if not set) + off-interval work limits + work_limits = [] + if start_dt is None and end_dt is not None: + start_dt = end_dt.replace(hour=0, minute=0, second=0) + elif start_dt is None: + start_dt = datetime.datetime.now().replace(hour=0, minute=0, second=0) + else: + work_limits.append((start_dt.replace(hour=0, minute=0, second=0), start_dt)) + if end_dt is None: + end_dt = start_dt.replace(hour=23, minute=59, second=59) + else: + work_limits.append((end_dt, end_dt.replace(hour=23, minute=59, second=59))) + assert start_dt.date() == end_dt.date(), 'get_working_intervals_of_day is restricted to one day' + + intervals = [] + work_dt = start_dt.replace(hour=0, minute=0, second=0) + working_intervals = [] - for calendar_working_day in resource_calendar.attendance_ids: - if int(calendar_working_day.dayofweek) == day_date.weekday(): - date_dict.update({ - 'HF': calendar_working_day.hour_from, - 'HT': calendar_working_day.hour_to, - }) - date_from = datetime.strptime('%(Y)04d-%(m)02d-%(d)02d %(HF)02d:00:00' % date_dict, '%Y-%m-%d %H:%M:%S') - date_to = datetime.strptime('%(Y)04d-%(m)02d-%(d)02d %(HT)02d:00:00' % date_dict, '%Y-%m-%d %H:%M:%S') - working_intervals.append((date_from, date_to)) + for calendar_working_day in self.get_attendances_for_weekdays(cr, uid, id, [start_dt.weekday()], context): + working_interval = ( + work_dt.replace(hour=int(calendar_working_day.hour_from)), + work_dt.replace(hour=int(calendar_working_day.hour_to)) + ) + working_intervals += self.interval_remove_leaves(working_interval, work_limits) # find leave intervals if leaves is None and compute_leaves: - leaves = self._get_leave_intervals(cr, uid, id, resource_id=resource_id, context=None) + leaves = self.get_leave_intervals(cr, uid, id, resource_id=resource_id, context=None) # filter according to leaves for interval in working_intervals: @@ -266,70 +327,135 @@ class resource_calendar(osv.osv): return intervals - def get_working_hours_of_date(self, cr, uid, id, day_date=None, leaves=None, compute_leaves=False, resource_id=None, context=None): + def get_working_hours_of_date(self, cr, uid, id, start_dt=None, end_dt=None, + leaves=None, compute_leaves=False, resource_id=None, + context=None): """Get the working hours of the day based on calendar. This method uses get_working_intervals_of_day to have the work intervals of the day. It then calculates the number of hours contained in those intervals. """ - res = timedelta() - intervals = self.get_working_intervals_of_day(cr, uid, id, day_date, leaves, compute_leaves, resource_id, context) + res = datetime.timedelta() + intervals = self.get_working_intervals_of_day(cr, uid, id, start_dt, end_dt, leaves, compute_leaves, resource_id, context) for interval in intervals: res += interval[1] - interval[0] return (res.total_seconds() / 3600.0) - def schedule_hours(self, cr, uid, id, hours, start_datetime=None, end_datetime=None, compute_leaves=False, resource_id=None, context=None): + def _schedule_hours(self, cr, uid, id, hours, day_dt=None, + compute_leaves=False, resource_id=None, + context=None): + """Schedule hours of work, using a calendar and an optional resource to + compute working and leave days. This method can be used backwards, i.e. + scheduling days before a deadline. + + :param timedelta hours: number of hours to schedule + :param datetime day_dt: reference date to compute working days. If days is + > 0 date is the starting date. If days is < 0 + date is the ending date. + :param boolean compute_leaves: if set, compute the leaves based on calendar + and resource. Otherwise no leaves are taken + into account. + :param int resource_id: the id of the resource to take into account when + computing the leaves. If not set, only general + leaves are computed. If set, generic and + specific leaves are computed. + + :return tuple (datetime, intervals): datetime is the beginning/ending date + of the schedulign; intervals are the + working intervals of the scheduling. + + Note: Why not using rrule.rrule ? Because rrule does not seem to allow + getting back in time. """ - """ - if start_datetime is None: - start_datetime = datetime.now() - work_hours = 0 + if day_dt is None: + day_dt = datetime.datetime.now() + backwards = (hours < 0) + hours = abs(hours) + intervals = [] + remaining_hours = hours * 1.0 iterations = 0 - final_intervals = [] + current_datetime = day_dt - # compute work days - work_days = set() - resource_calendar = self.browse(cr, uid, id, context=context) - for attendance in resource_calendar.attendance_ids: - work_days.add(int(attendance.dayofweek)) + call_args = dict(compute_leaves=compute_leaves, resource_id=resource_id, context=context) - # prepare rrule arguments - rrule_args = { - 'byweekday': work_days, - 'dtstart': start_datetime, - } - if end_date: - rrule_args['until'] = end_datetime - else: - rrule_args['count'] = 1024 + while float_compare(remaining_hours, 0.0, precision_digits=2) in (1, 0) and iterations < 1000: + if backwards: + call_args['end_dt'] = current_datetime + else: + call_args['start_dt'] = current_datetime - for day in rrule.rrule(rrule.DAILY, **rrule_args): - working_intervals = self.get_working_intervals_of_day(cr, uid, id, day_date=day, compute_leaves=compute_leaves, resource_id=resource_id, context=context) - if not working_intervals: - continue - # Compute worked hours, compare to requested number of hours - res = timedelta() - for interval in working_intervals: - res += interval[1] - interval[0] - work_hours += (res.total_seconds() / 3600.0) - final_intervals += working_intervals - if float_compare(work_hours, hours * 1.0, precision_digits=2) in (0, 1) or (iterations >= 50): - break + working_intervals = self.get_working_intervals_of_day(cr, uid, id, **call_args) + + if id is None: # no calendar -> consider 8 working hours + remaining_hours -= 8.0 + elif working_intervals: + if backwards: + working_intervals.reverse() + new_working_intervals = self.interval_schedule_hours(working_intervals, remaining_hours, not backwards) + if backwards: + new_working_intervals.reverse() + + res = datetime.timedelta() + for interval in working_intervals: + res += interval[1] - interval[0] + remaining_hours -= (res.total_seconds() / 3600.0) + if backwards: + intervals = new_working_intervals + intervals + else: + intervals = intervals + new_working_intervals + # get next day + if backwards: + current_datetime = datetime.datetime.combine(self.get_previous_day(cr, uid, id, current_datetime, context), datetime.time(23, 59, 59)) + else: + current_datetime = datetime.datetime.combine(self.get_next_day(cr, uid, id, current_datetime, context), datetime.time()) + # avoid infinite loops iterations += 1 - return final_intervals + return intervals - def _schedule_days(self, cr, uid, id, days, date=None, compute_leaves=False, resource_id=None, context=None): - """Schedule days of work. + def schedule_hours_get_date(self, cr, uid, id, hours, day_dt=None, compute_leaves=False, resource_id=None, context=None): + """Wrapper on _schedule_hours: return the beginning/ending datetime of + an hours scheduling. """ + res = self._schedule_hours(cr, uid, id, hours, day_dt, compute_leaves, resource_id, context) + return res[0][0] - This method can be used backwards, i.e. scheduling days before a deadline. + def schedule_hours(self, cr, uid, id, hours, day_dt=None, compute_leaves=False, resource_id=None, context=None): + """Wrapper on _schedule_hours: return the working intervals of an hours + scheduling. """ + return self._schedule_hours(cr, uid, id, hours, day_dt, compute_leaves, resource_id, context) + + def _schedule_days(self, cr, uid, id, days, day_date=None, compute_leaves=False, resource_id=None, context=None): + """Schedule days of work, using a calendar and an optional resource to + compute working and leave days. This method can be used backwards, i.e. + scheduling days before a deadline. + + :param date day_date: reference date to compute working days. If days is > 0 + date is the starting date. If days is < 0 date is the + ending date. + :param boolean compute_leaves: if set, compute the leaves based on calendar + and resource. Otherwise no leaves are taken + into account. + :param int resource_id: the id of the resource to take into account when + computing the leaves. If not set, only general + leaves are computed. If set, generic and + specific leaves are computed. + + :return tuple (datetime, intervals): datetime is the beginning/ending date + of the schedulign; intervals are the + working intervals of the scheduling. + + TDE NOTE: Why not using rrule.rrule ? Because rrule does not seem to + allow getting back in time. """ - backwards = False - if days < 0: - backwards = True - days = abs(days) + if day_date is None: + day_date = datetime.datetime.now() + backwards = (days < 0) + days = abs(days) intervals = [] planned_days = 0 iterations = 0 - current_datetime = date + if backwards: + current_datetime = day_date.replace(hour=23, minute=59, second=59) + else: + current_datetime = day_date.replace(hour=0, minute=0, second=0) while planned_days < days and iterations < 1000: working_intervals = self.get_working_intervals_of_day(cr, uid, id, current_datetime, compute_leaves=compute_leaves, resource_id=resource_id, context=context) @@ -338,25 +464,38 @@ class resource_calendar(osv.osv): intervals += working_intervals # get next day if backwards: - current_datetime = self.get_previous_day(cr, uid, id, current_datetime) + current_datetime = self.get_previous_day(cr, uid, id, current_datetime, context) else: - current_datetime = self.get_next_day(cr, uid, id, current_datetime) + current_datetime = self.get_next_day(cr, uid, id, current_datetime, context) + # avoid infinite loops + iterations += 1 - return (current_datetime, intervals) + return intervals - def schedule_days(self, cr, uid, id, days, date=None, compute_leaves=False, resource_id=None, context=None): - res = self._schedule_days(cr, uid, id, days, date, compute_leaves, resource_id, context) - return res[0] + def schedule_days_get_date(self, cr, uid, id, days, day_date=None, compute_leaves=False, resource_id=None, context=None): + """Wrapper on _schedule_days: return the beginning/ending datetime of + a days scheduling. """ + res = self._schedule_days(cr, uid, id, days, day_date, compute_leaves, resource_id, context) + return res[-1][1].date() + + def schedule_days(self, cr, uid, id, days, day_date=None, compute_leaves=False, resource_id=None, context=None): + """Wrapper on _schedule_days: return the working intervals of a days + scheduling. """ + return self._schedule_days(cr, uid, id, days, day_date, compute_leaves, resource_id, context) # -------------------------------------------------- # Compaqtibility / to clean / to remove # -------------------------------------------------- def working_hours_on_day(self, cr, uid, resource_calendar_id, day, context=None): - """ Compatibility method - will be removed for OpenERP v8 + """ Compatibility method - will be removed for OpenERP v8. Computation + was done for the whole day, therefore setting start_dt at the beginning + of the day. TDE TODO: hr_payroll/hr_payroll.py """ - return self.get_working_hours_of_date(cr, uid, resource_calendar_id.id, day_date=day, context=None) + if isinstance(day, datetime.datetime): + day = day.replace(hour=0, minute=0) + return self.get_working_hours_of_date(cr, uid, resource_calendar_id.id, start_dt=day, context=None) def _get_leaves(self, cr, uid, id, resource): """Private Method to Calculate resource Leaves days @@ -366,6 +505,7 @@ class resource_calendar(osv.osv): @return : returns the list of dates, where resource on leave in resource.calendar.leaves object (e.g.['%Y-%m-%d', '%Y-%m-%d']) + TDE TODO: internal only """ resource_cal_leaves = self.pool.get('resource.calendar.leaves') dt_leave = [] @@ -374,10 +514,10 @@ class resource_calendar(osv.osv): res_leaves = resource_cal_leaves.browse(cr, uid, resource_leave_ids) for leave in res_leaves: - dtf = datetime.strptime(leave.date_from, '%Y-%m-%d %H:%M:%S') - dtt = datetime.strptime(leave.date_to, '%Y-%m-%d %H:%M:%S') + dtf = datetime.datetime.strptime(leave.date_from, '%Y-%m-%d %H:%M:%S') + dtt = datetime.datetime.strptime(leave.date_to, '%Y-%m-%d %H:%M:%S') no = dtt - dtf - [dt_leave.append((dtf + timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))] + [dt_leave.append((dtf + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))] dt_leave.sort() return dt_leave @@ -399,10 +539,11 @@ class resource_calendar(osv.osv): params TDE TODO: used in mrp_operations/mrp_operations.py + TDE NOTE: do not count leave hours, a leave is considered all-day """ if not id: td = int(hours)*3 - return [(dt_from - timedelta(hours=td), dt_from)] + return [(dt_from - datetime.timedelta(hours=td), dt_from)] dt_leave = self._get_leaves(cr, uid, id, resource) dt_leave.reverse() todo = hours @@ -420,17 +561,17 @@ class resource_calendar(osv.osv): dt_check = dt_from.strftime('%Y-%m-%d') for leave in dt_leave: if dt_check == leave: - dt_check = datetime.strptime(dt_check, '%Y-%m-%d') + timedelta(days=1) + dt_check = datetime.datetime.strptime(dt_check, '%Y-%m-%d') + datetime.timedelta(days=1) leave_flag = True if leave_flag: break else: - d1 = datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(hour_from)), int((hour_from%1) * 60)) - d2 = datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(m)), int((m%1) * 60)) + d1 = datetime.datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(hour_from)), int((hour_from%1) * 60)) + d2 = datetime.datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(m)), int((m%1) * 60)) result.append((d1, d2)) current_hour = hour_from todo -= (m-hour_from) - dt_from -= timedelta(days=1) + dt_from -= datetime.timedelta(days=1) current_hour = 24 maxrecur -= 1 result.reverse() @@ -438,7 +579,10 @@ class resource_calendar(osv.osv): # def interval_get(self, cr, uid, id, dt_from, hours, resource=False, byday=True): def interval_get_multi(self, cr, uid, date_and_hours_by_cal, resource=False, byday=True): - """ TDE NOTE: used in mrp_operations/mrp_operations.py and in interval_get() """ + """ TDE NOTE: used in mrp_operations/mrp_operations.py (default parameters) and in interval_get() + TDE NOTE: byday is not used in this method... + TDE NOTE: do not count leave hours, a leave is considered all-day + """ def group(lst, key): lst.sort(key=itemgetter(key)) grouped = itertools.groupby(lst, itemgetter(key)) @@ -452,10 +596,10 @@ class resource_calendar(osv.osv): results = {} for d, hours, id in date_and_hours_by_cal: - dt_from = datetime.strptime(d, '%Y-%m-%d %H:%M:%S') + dt_from = datetime.datetime.strptime(d, '%Y-%m-%d %H:%M:%S') if not id: td = int(hours)*3 - results[(d, hours, id)] = [(dt_from, dt_from + timedelta(hours=td))] + results[(d, hours, id)] = [(dt_from, dt_from + datetime.timedelta(hours=td))] continue dt_leave = self._get_leaves(cr, uid, id, resource) @@ -473,17 +617,17 @@ class resource_calendar(osv.osv): dt_check = dt_from.strftime('%Y-%m-%d') for leave in dt_leave: if dt_check == leave: - dt_check = datetime.strptime(dt_check, '%Y-%m-%d') + timedelta(days=1) + dt_check = datetime.datetime.strptime(dt_check, '%Y-%m-%d') + datetime.timedelta(days=1) leave_flag = True if leave_flag: break else: - d1 = datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(m)), int((m%1) * 60)) - d2 = datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(hour_to)), int((hour_to%1) * 60)) + d1 = datetime.datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(m)), int((m%1) * 60)) + d2 = datetime.datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(hour_to)), int((hour_to%1) * 60)) result.append((d1, d2)) current_hour = hour_to todo -= (hour_to - m) - dt_from += timedelta(days=1) + dt_from += datetime.timedelta(days=1) current_hour = 0 maxrecur -= 1 results[(d, hours, id)] = result @@ -500,7 +644,7 @@ class resource_calendar(osv.osv): @return : list of scheduled working timing based on resource calendar. - TDE NOTE: mrp_operations/mrp_operations.py, crm/crm_lead.py + TDE NOTE: mrp_operations/mrp_operations.py, crm/crm_lead.py (res given) """ res = self.interval_get_multi(cr, uid, [(dt_from.strftime('%Y-%m-%d %H:%M:%S'), hours, id)], resource, byday)[(dt_from.strftime('%Y-%m-%d %H:%M:%S'), hours, id)] return res @@ -538,6 +682,7 @@ class resource_calendar(osv.osv): resource if supplied. TDE NOTE: used in project_issue/project_issue.py + TDE NOTE: day-long leaves """ utc_tz = pytz.timezone('UTC') local_tz = utc_tz @@ -591,7 +736,7 @@ class resource_calendar(osv.osv): interval_start = utc_to_local_zone(dt_from) interval_end = utc_to_local_zone(dt_to) - hours_timedelta = timedelta() + hours_timedelta = datetime.timedelta() # Get leaves for requested resource dt_leaves = set([]) @@ -599,7 +744,7 @@ class resource_calendar(osv.osv): dt_leaves = set(self._get_leaves(cr, uid, id, resource=resource_id)) for day in rrule.rrule(rrule.DAILY, dtstart=interval_start, - until=interval_end+timedelta(days=1), + until=interval_end+datetime.timedelta(days=1), byweekday=hours_range_per_weekday.keys()): if exclude_leaves and day.strftime('%Y-%m-%d') in dt_leaves: # XXX: futher improve leave management to allow for partial day leave @@ -724,10 +869,10 @@ class resource_resource(osv.osv): ], context=context) leaves = resource_calendar_leaves_pool.read(cr, uid, leave_ids, ['date_from', 'date_to'], context=context) for i in range(len(leaves)): - dt_start = datetime.strptime(leaves[i]['date_from'], '%Y-%m-%d %H:%M:%S') - dt_end = datetime.strptime(leaves[i]['date_to'], '%Y-%m-%d %H:%M:%S') + dt_start = datetime.datetime.strptime(leaves[i]['date_from'], '%Y-%m-%d %H:%M:%S') + dt_end = datetime.datetime.strptime(leaves[i]['date_to'], '%Y-%m-%d %H:%M:%S') no = dt_end - dt_start - [leave_list.append((dt_start + timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))] + [leave_list.append((dt_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))] leave_list.sort() return leave_list diff --git a/addons/resource/test/resource.yml b/addons/resource/test/resource.yml index 28604518b7b..b085c0734c4 100644 --- a/addons/resource/test/resource.yml +++ b/addons/resource/test/resource.yml @@ -26,7 +26,7 @@ dt = now - timedelta(days=now.weekday()) for resource in resources: result = calendar_pool.working_hours_on_day(cr, uid, resource.calendar_id, dt, context) - assert result == 9.0, 'Wrong calculation of day work hour availability of the Resource.' + assert result == 9.0, 'Wrong calculation of day work hour availability of the Resource (found %d).' % result - Now, resource "Developer" drafted leave on Thursday in this week. - diff --git a/addons/resource/tests/common.py b/addons/resource/tests/common.py index 6dffafd8179..e5d17bc7455 100644 --- a/addons/resource/tests/common.py +++ b/addons/resource/tests/common.py @@ -39,15 +39,15 @@ class TestResourceCommon(common.TransactionCase): self.resource_leaves = self.registry('resource.calendar.leaves') # Some demo data - self.date1 = datetime.strptime('2013-02-12 09:22:13', '%Y-%m-%d %H:%M:%S') # weekday() returns 1, isoweekday() returns 2 - self.date2 = datetime.strptime('2013-02-15 09:22:13', '%Y-%m-%d %H:%M:%S') # weekday() returns 4, isoweekday() returns 5 - # Leave1: 19/02/2013, from 9 to 12, is a day 0 + self.date1 = datetime.strptime('2013-02-12 09:08:07', '%Y-%m-%d %H:%M:%S') # weekday() returns 1, isoweekday() returns 2 + self.date2 = datetime.strptime('2013-02-15 10:11:12', '%Y-%m-%d %H:%M:%S') # weekday() returns 4, isoweekday() returns 5 + # Leave1: 19/02/2013, from 9 to 12, is a day 1 self.leave1_start = datetime.strptime('2013-02-19 09:00:00', '%Y-%m-%d %H:%M:%S') self.leave1_end = datetime.strptime('2013-02-19 12:00:00', '%Y-%m-%d %H:%M:%S') - # Leave2: 22/02/2013, from 9 to 15, is a day 3 + # Leave2: 22/02/2013, from 9 to 15, is a day 4 self.leave2_start = datetime.strptime('2013-02-22 09:00:00', '%Y-%m-%d %H:%M:%S') self.leave2_end = datetime.strptime('2013-02-22 15:00:00', '%Y-%m-%d %H:%M:%S') - # Leave3: 25/02/2013 (day6) -> 01/03/2013 (day3) + # Leave3: 25/02/2013 (day0) -> 01/03/2013 (day4) self.leave3_start = datetime.strptime('2013-02-25 13:00:00', '%Y-%m-%d %H:%M:%S') self.leave3_end = datetime.strptime('2013-03-01 11:30:00', '%Y-%m-%d %H:%M:%S') diff --git a/addons/resource/tests/test_resource.py b/addons/resource/tests/test_resource.py index fa29de91b3d..7ae4343a21f 100644 --- a/addons/resource/tests/test_resource.py +++ b/addons/resource/tests/test_resource.py @@ -19,7 +19,7 @@ # ############################################################################## -from datetime import datetime +from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta from openerp.addons.resource.tests.common import TestResourceCommon @@ -51,17 +51,17 @@ class TestResource(TestResourceCommon): ] # Test: interval cleaning - result = self.resource_calendar.interval_clean(intervals) - self.assertEqual(len(result), 3, 'resource_calendar: wrong interval cleaning') + cleaned_intervals = self.resource_calendar.interval_clean(intervals) + self.assertEqual(len(cleaned_intervals), 3, 'resource_calendar: wrong interval cleaning') # First interval: 03, unchanged - self.assertEqual(result[0][0], datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') - self.assertEqual(result[0][1], datetime.strptime('2013-02-03 10:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(cleaned_intervals[0][0], datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(cleaned_intervals[0][1], datetime.strptime('2013-02-03 10:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') # Second intreval: 04, 08-14, combining 08-12 and 11-14, 09-11 being inside 08-12 - self.assertEqual(result[1][0], datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') - self.assertEqual(result[1][1], datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(cleaned_intervals[1][0], datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(cleaned_intervals[1][1], datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') # Third interval: 04, 17-21, 18-19 being inside 17-21 - self.assertEqual(result[2][0], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') - self.assertEqual(result[2][1], datetime.strptime('2013-02-04 21:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(cleaned_intervals[2][0], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') + self.assertEqual(cleaned_intervals[2][1], datetime.strptime('2013-02-04 21:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong interval cleaning') # Test: disjoint removal working_interval = (datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), datetime.strptime('2013-02-04 18:00:00', '%Y-%m-%d %H:%M:%S')) @@ -71,6 +71,27 @@ class TestResource(TestResourceCommon): self.assertEqual(result[0][0], datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') self.assertEqual(result[0][1], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + # Test: schedule hours on intervals + result = self.resource_calendar.interval_schedule_hours(cleaned_intervals, 5.5) + self.assertEqual(len(result), 2, 'resource_calendar: wrong hours scheduling in interval') + # First interval: 03, 8-10 untouches + self.assertEqual(result[0][0], datetime.strptime('2013-02-03 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + self.assertEqual(result[0][1], datetime.strptime('2013-02-03 10:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + # First interval: 04, 08-11:30 + self.assertEqual(result[1][0], datetime.strptime('2013-02-04 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + self.assertEqual(result[1][1], datetime.strptime('2013-02-04 11:30:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + + # Test: schedule hours on intervals, backwards + cleaned_intervals.reverse() + result = self.resource_calendar.interval_schedule_hours(cleaned_intervals, 5.5, remove_at_end=False) + self.assertEqual(len(result), 2, 'resource_calendar: wrong hours scheduling in interval') + # First interval: 03, 8-10 untouches + self.assertEqual(result[0][0], datetime.strptime('2013-02-04 17:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + self.assertEqual(result[0][1], datetime.strptime('2013-02-04 21:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + # First interval: 04, 08-11:30 + self.assertEqual(result[1][0], datetime.strptime('2013-02-04 12:30:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + self.assertEqual(result[1][1], datetime.strptime('2013-02-04 14:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong leave removal from interval') + def test_10_calendar_basics(self): """ Testing basic method of resource.calendar """ cr, uid = self.cr, self.uid @@ -80,110 +101,289 @@ class TestResource(TestResourceCommon): # -------------------------------------------------- # Test: next day: next day after day1 is day4 - date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date1) - self.assertEqual(date, self.date2, 'resource_calendar: wrong next day computing') + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date1.date()) + self.assertEqual(date, self.date2.date(), 'resource_calendar: wrong next day computing') # Test: next day: next day after day4 is (day1+7) - date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date2) - self.assertEqual(date, self.date1 + relativedelta(days=7), 'resource_calendar: wrong next day computing') + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date2.date()) + self.assertEqual(date, self.date1.date() + relativedelta(days=7), 'resource_calendar: wrong next day computing') # Test: next day: next day after day4+1 is (day1+7) - date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date2 + relativedelta(days=1)) - self.assertEqual(date, self.date1 + relativedelta(days=7), 'resource_calendar: wrong next day computing') + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date2.date() + relativedelta(days=1)) + self.assertEqual(date, self.date1.date() + relativedelta(days=7), 'resource_calendar: wrong next day computing') # Test: next day: next day after day1-1 is day1 - date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date1 + relativedelta(days=-1)) - self.assertEqual(date, self.date1, 'resource_calendar: wrong next day computing') + date = self.resource_calendar.get_next_day(cr, uid, self.calendar_id, day_date=self.date1.date() + relativedelta(days=-1)) + self.assertEqual(date, self.date1.date(), 'resource_calendar: wrong next day computing') # -------------------------------------------------- # Test2: get_previous_day # -------------------------------------------------- # Test: previous day: previous day before day1 is (day4-7) - date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date1) - self.assertEqual(date, self.date2 + relativedelta(days=-7), 'resource_calendar: wrong previous day computing') + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date1.date()) + self.assertEqual(date, self.date2.date() + relativedelta(days=-7), 'resource_calendar: wrong previous day computing') # Test: previous day: previous day before day4 is day1 - date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date2) - self.assertEqual(date, self.date1, 'resource_calendar: wrong previous day computing') + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date2.date()) + self.assertEqual(date, self.date1.date(), 'resource_calendar: wrong previous day computing') # Test: previous day: previous day before day4+1 is day4 - date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date2 + relativedelta(days=1)) - self.assertEqual(date, self.date2, 'resource_calendar: wrong previous day computing') + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date2.date() + relativedelta(days=1)) + self.assertEqual(date, self.date2.date(), 'resource_calendar: wrong previous day computing') # Test: previous day: previous day before day1-1 is (day4-7) - date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date1 + relativedelta(days=-1)) - self.assertEqual(date, self.date2 + relativedelta(days=-7), 'resource_calendar: wrong previous day computing') + date = self.resource_calendar.get_previous_day(cr, uid, self.calendar_id, day_date=self.date1.date() + relativedelta(days=-1)) + self.assertEqual(date, self.date2.date() + relativedelta(days=-7), 'resource_calendar: wrong previous day computing') + + # -------------------------------------------------- + # Test3: misc + # -------------------------------------------------- + + weekdays = self.resource_calendar.get_weekdays(cr, uid, self.calendar_id) + self.assertEqual(weekdays, [1, 4], 'resource_calendar: wrong weekdays computing') + + attendances = self.resource_calendar.get_attendances_for_weekdays(cr, uid, self.calendar_id, [2, 3, 4, 5]) + self.assertEqual(set([att.id for att in attendances]), set([self.att2_id, self.att3_id]), + 'resource_calendar: wrong attendances filtering by weekdays computing') def test_20_calendar_working_intervals(self): """ Testing working intervals computing method of resource.calendar """ cr, uid = self.cr, self.uid + _format = '%Y-%m-%d %H:%M:%S' # Test: day0 without leaves: 1 interval - intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date1) + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1) self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 09:08:07', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong working intervals') # Test: day3 without leaves: 2 interval - intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date2) + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date2) self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[0][0], datetime.strptime('2013-02-15 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[0][1], datetime.strptime('2013-02-15 13:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[1][0], datetime.strptime('2013-02-15 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[1][1], datetime.strptime('2013-02-15 23:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-15 10:11:12', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-15 13:00:00', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][0], datetime.strptime('2013-02-15 16:00:00', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][1], datetime.strptime('2013-02-15 23:00:00', _format), 'resource_calendar: wrong working intervals') # Test: day0 with leaves outside range: 1 interval - intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date1, compute_leaves=True) + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=0), compute_leaves=True) self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 08:00:00', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong working intervals') - # Test: day0 with leaves: 2 intrevals because of leave between 9 ans 12 - intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, day_date=self.date1 + relativedelta(days=7), compute_leaves=True) + # Test: day0 with leaves: 2 intervals because of leave between 9 ans 12, ending at 15:45:30 + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, + start_dt=self.date1.replace(hour=8) + relativedelta(days=7), + end_dt=self.date1.replace(hour=15, minute=45, second=30) + relativedelta(days=7), + compute_leaves=True) self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 09:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[1][0], datetime.strptime('2013-02-19 12:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') - self.assertEqual(intervals[1][1], datetime.strptime('2013-02-19 16:00:00', '%Y-%m-%d %H:%M:%S'), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:08:07', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 09:00:00', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][0], datetime.strptime('2013-02-19 12:00:00', _format), 'resource_calendar: wrong working intervals') + self.assertEqual(intervals[1][1], datetime.strptime('2013-02-19 15:45:30', _format), 'resource_calendar: wrong working intervals') + + def test_30_calendar_working_days(self): + """ Testing calendar hours computation on a working day """ + cr, uid = self.cr, self.uid + _format = '%Y-%m-%d %H:%M:%S' + + # Test: day1, beginning at 10:30 -> work from 10:30 (arrival) until 16:00 + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=10, minute=30, second=0)) + self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 10:30:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong working interval / day computing') + # Test: hour computation for same interval, should give 5.5 + wh = self.resource_calendar.get_working_hours_of_date(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=10, minute=30, second=0)) + self.assertEqual(wh, 5.5, 'resource_calendar: wrong working interval / day time computing') + + # Test: day1+7 on leave, without leave computation + intervals = self.resource_calendar.get_working_intervals_of_day( + cr, uid, self.calendar_id, + start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=7) + ) + # Result: day1 (08->16) + self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval/day computing') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong working interval / day computing') + + # Test: day1+7 on leave, with generic leave computation + intervals = self.resource_calendar.get_working_intervals_of_day( + cr, uid, self.calendar_id, + start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=7), + compute_leaves=True + ) + # Result: day1 (08->09 + 12->16) + self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working interval/day computing') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 09:00:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[1][0], datetime.strptime('2013-02-19 12:00:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[1][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong working interval / day computing') + + # Test: day1+14 on leave, with generic leave computation + intervals = self.resource_calendar.get_working_intervals_of_day( + cr, uid, self.calendar_id, + start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=14), + compute_leaves=True + ) + # Result: day1 (08->16) + self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval/day computing') + self.assertEqual(intervals[0][0], datetime.strptime('2013-02-26 08:00:00', _format), 'resource_calendar: wrong working interval / day computing') + self.assertEqual(intervals[0][1], datetime.strptime('2013-02-26 16:00:00', _format), 'resource_calendar: wrong working interval / day computing') + + # Test: day1+14 on leave, with resource leave computation + intervals = self.resource_calendar.get_working_intervals_of_day( + cr, uid, self.calendar_id, + start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=14), + compute_leaves=True, + resource_id=self.resource1_id + ) + # Result: nothing, because on leave + self.assertEqual(len(intervals), 0, 'resource_calendar: wrong working interval/day computing') + + def test_40_calendar_hours_scheduling(self): + """ Testing calendar hours scheduling """ + cr, uid = self.cr, self.uid + _format = '%Y-%m-%d %H:%M:%S' + + # -------------------------------------------------- + # Test0: schedule hours backwards (old interval_min_get) + # Done without calendar + # -------------------------------------------------- + + # Done without calendar + # res = self.resource_calendar.interval_min_get(cr, uid, None, self.date1, 40, resource=False) + # res: (datetime.datetime(2013, 2, 7, 9, 8, 7), datetime.datetime(2013, 2, 12, 9, 8, 7)) + + # -------------------------------------------------- + # Test1: schedule hours backwards (old interval_min_get) + # -------------------------------------------------- + + # res = self.resource_calendar.interval_min_get(cr, uid, self.calendar_id, self.date1, 40, resource=False) + # (datetime.datetime(2013, 1, 29, 9, 0), datetime.datetime(2013, 1, 29, 16, 0)) + # (datetime.datetime(2013, 2, 1, 8, 0), datetime.datetime(2013, 2, 1, 13, 0)) + # (datetime.datetime(2013, 2, 1, 16, 0), datetime.datetime(2013, 2, 1, 23, 0)) + # (datetime.datetime(2013, 2, 5, 8, 0), datetime.datetime(2013, 2, 5, 16, 0)) + # (datetime.datetime(2013, 2, 8, 8, 0), datetime.datetime(2013, 2, 8, 13, 0)) + # (datetime.datetime(2013, 2, 8, 16, 0), datetime.datetime(2013, 2, 8, 23, 0)) + # (datetime.datetime(2013, 2, 12, 8, 0), datetime.datetime(2013, 2, 12, 9, 0)) + + res = self.resource_calendar.schedule_hours(cr, uid, self.calendar_id, -40, day_dt=self.date1.replace(minute=0, second=0)) + # current day, limited at 09:00 because of day_dt specified -> 1 hour + self.assertEqual(res[-1][0], datetime.strptime('2013-02-12 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-1][1], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + # previous days: 5+7 hours / 8 hours / 5+7 hours -> 32 hours + self.assertEqual(res[-2][0], datetime.strptime('2013-02-08 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-2][1], datetime.strptime('2013-02-08 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-3][0], datetime.strptime('2013-02-08 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-3][1], datetime.strptime('2013-02-08 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-4][0], datetime.strptime('2013-02-05 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-4][1], datetime.strptime('2013-02-05 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-5][0], datetime.strptime('2013-02-01 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-5][1], datetime.strptime('2013-02-01 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-6][0], datetime.strptime('2013-02-01 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-6][1], datetime.strptime('2013-02-01 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + # 7 hours remaining + self.assertEqual(res[-7][0], datetime.strptime('2013-01-29 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[-7][1], datetime.strptime('2013-01-29 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + # Compute scheduled hours + td = timedelta() + for item in res: + td += item[1] - item[0] + self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling') + + # -------------------------------------------------- + # Test1: schedule hours forward (old interval_get) + # -------------------------------------------------- + + # res = self.resource_calendar.interval_get(cr, uid, self.calendar_id, self.date1, 40, resource=False, byday=True) + # (datetime.datetime(2013, 2, 12, 9, 0), datetime.datetime(2013, 2, 12, 16, 0)) + # (datetime.datetime(2013, 2, 15, 8, 0), datetime.datetime(2013, 2, 15, 13, 0)) + # (datetime.datetime(2013, 2, 15, 16, 0), datetime.datetime(2013, 2, 15, 23, 0)) + # (datetime.datetime(2013, 2, 22, 8, 0), datetime.datetime(2013, 2, 22, 13, 0)) + # (datetime.datetime(2013, 2, 22, 16, 0), datetime.datetime(2013, 2, 22, 23, 0)) + # (datetime.datetime(2013, 2, 26, 8, 0), datetime.datetime(2013, 2, 26, 16, 0)) + # (datetime.datetime(2013, 3, 1, 8, 0), datetime.datetime(2013, 3, 1, 9, 0)) + + res = self.resource_calendar.schedule_hours( + cr, uid, self.calendar_id, 40, + day_dt=self.date1.replace(minute=0, second=0) + ) + self.assertEqual(res[0][0], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[1][0], datetime.strptime('2013-02-15 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[1][1], datetime.strptime('2013-02-15 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[2][0], datetime.strptime('2013-02-15 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[2][1], datetime.strptime('2013-02-15 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[3][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[3][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[4][0], datetime.strptime('2013-02-22 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[4][1], datetime.strptime('2013-02-22 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[5][0], datetime.strptime('2013-02-22 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[5][1], datetime.strptime('2013-02-22 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[6][0], datetime.strptime('2013-02-26 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[6][1], datetime.strptime('2013-02-26 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + td = timedelta() + for item in res: + td += item[1] - item[0] + self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling') + + # res = self.resource_calendar.interval_get(cr, uid, self.calendar_id, self.date1, 40, resource=self.resource1_id, byday=True) + # (datetime.datetime(2013, 2, 12, 9, 0), datetime.datetime(2013, 2, 12, 16, 0)) + # (datetime.datetime(2013, 2, 15, 8, 0), datetime.datetime(2013, 2, 15, 13, 0)) + # (datetime.datetime(2013, 2, 15, 16, 0), datetime.datetime(2013, 2, 15, 23, 0)) + # (datetime.datetime(2013, 3, 1, 8, 0), datetime.datetime(2013, 3, 1, 13, 0)) + # (datetime.datetime(2013, 3, 1, 16, 0), datetime.datetime(2013, 3, 1, 23, 0)) + # (datetime.datetime(2013, 3, 5, 8, 0), datetime.datetime(2013, 3, 5, 16, 0)) + # (datetime.datetime(2013, 3, 8, 8, 0), datetime.datetime(2013, 3, 8, 9, 0)) + + res = self.resource_calendar.schedule_hours( + cr, uid, self.calendar_id, 40, + day_dt=self.date1.replace(minute=0, second=0), + compute_leaves=True, + resource_id=self.resource1_id + ) + self.assertEqual(res[0][0], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[1][0], datetime.strptime('2013-02-15 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[1][1], datetime.strptime('2013-02-15 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[2][0], datetime.strptime('2013-02-15 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[2][1], datetime.strptime('2013-02-15 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[3][0], datetime.strptime('2013-02-19 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[3][1], datetime.strptime('2013-02-19 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[4][0], datetime.strptime('2013-02-19 12:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[4][1], datetime.strptime('2013-02-19 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[5][0], datetime.strptime('2013-02-22 08:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[5][1], datetime.strptime('2013-02-22 09:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[6][0], datetime.strptime('2013-02-22 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[6][1], datetime.strptime('2013-02-22 23:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[7][0], datetime.strptime('2013-03-01 11:30:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[7][1], datetime.strptime('2013-03-01 13:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[8][0], datetime.strptime('2013-03-01 16:00:00', _format), 'resource_calendar: wrong hours scheduling') + self.assertEqual(res[8][1], datetime.strptime('2013-03-01 22:30:00', _format), 'resource_calendar: wrong hours scheduling') + td = timedelta() + for item in res: + td += item[1] - item[0] + self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling') def test_40_calendar_schedule_days(self): """ Testing calendar days scheduling """ cr, uid = self.cr, self.uid + _format = '%Y-%m-%d %H:%M:%S' - print '---------------' - res = self.resource_calendar.schedule_days(cr, uid, self.calendar_id, 5, date=self.date1) - print res + res = self.resource_calendar.schedule_days_get_date(cr, uid, self.calendar_id, 5, day_date=self.date1) + self.assertEqual(res, datetime.strptime('2013-02-26 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') + + res = self.resource_calendar.schedule_days_get_date( + cr, uid, self.calendar_id, 5, day_date=self.date1, + compute_leaves=True, resource_id=self.resource1_id) + self.assertEqual(res, datetime.strptime('2013-03-01 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') # -------------------------------------------------- # Misc # -------------------------------------------------- - # Without calendar, should only count days - print '---------------' - res = self.resource_calendar.schedule_days(cr, uid, None, 5, date=self.date1) - print res - - # @mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm') - # def test_20_calendar(self): - # """ Testing calendar and time computation """ - # cr, uid = self.cr, self.uid - - # wh = self.resource_calendar.get_working_hours_of_date(cr, uid, self.calendar_id, day_date=self.date1) - # self.assertEqual(wh, 8, 'cacamou') - - # wh = self.resource_calendar.get_working_hours_of_date(cr, uid, self.calendar_id, day_date=self.date2+relativedelta(days=7)) - # self.assertEqual(wh, 12, 'cacamou') - - # # print '---------------------' - # # print self.date1 - # # res = self.resource_calendar.interval_min_get(cr, uid, self.calendar_id, self.date1, 40, resource=False) - # # print res - - # print '----------------------' - # res = self.resource_calendar.schedule_hours(cr, uid, self.calendar_id, 40, start_datetime=self.date1) - # print res - # print '----------------------' - # # print self.date1 - # # res = self.resource_calendar.interval_get(cr, uid, self.calendar_id, self.date1, 40, resource=False, byday=True) - # # print res + # # Without calendar, should only count days + # print '---------------' + # res = self.resource_calendar.schedule_days_get_date(cr, uid, None, 5, day_date=self.date1) + # print res From 53fe285259a613f152f7cd8b57c3026324da9b49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 30 Aug 2013 16:16:10 +0200 Subject: [PATCH 005/335] [IMP] resource: old API replacement + first implementation of compute_hours, to compute the number of working hours between two dates. bzr revid: tde@openerp.com-20130830141610-uu1t88atcvpmkjjb --- addons/resource/resource.py | 234 +++---------------------- addons/resource/tests/test_resource.py | 14 +- 2 files changed, 41 insertions(+), 207 deletions(-) diff --git a/addons/resource/resource.py b/addons/resource/resource.py index c8635a01d0c..f43908f98e8 100644 --- a/addons/resource/resource.py +++ b/addons/resource/resource.py @@ -422,6 +422,14 @@ class resource_calendar(osv.osv): scheduling. """ return self._schedule_hours(cr, uid, id, hours, day_dt, compute_leaves, resource_id, context) + def get_working_hours(self, cr, uid, id, start_dt, end_dt, compute_leaves=False, resource_id=None, context=None): + hours = 0.0 + for day in rrule.rrule(rrule.DAILY, dtstart=start_dt, + until=end_dt + datetime.timedelta(days=1), + byweekday=self.get_weekdays(cr, uid, id, context=context)): + hours += self.get_working_hours_of_date(cr, uid, id, start_dt=day, compute_leaves=compute_leaves, resource_id=resource_id) + return hours + def _schedule_days(self, cr, uid, id, days, day_date=None, compute_leaves=False, resource_id=None, context=None): """Schedule days of work, using a calendar and an optional resource to compute working and leave days. This method can be used backwards, i.e. @@ -484,7 +492,7 @@ class resource_calendar(osv.osv): return self._schedule_days(cr, uid, id, days, day_date, compute_leaves, resource_id, context) # -------------------------------------------------- - # Compaqtibility / to clean / to remove + # Compatibility / to clean / to remove # -------------------------------------------------- def working_hours_on_day(self, cr, uid, resource_calendar_id, day, context=None): @@ -497,31 +505,6 @@ class resource_calendar(osv.osv): day = day.replace(hour=0, minute=0) return self.get_working_hours_of_date(cr, uid, resource_calendar_id.id, start_dt=day, context=None) - def _get_leaves(self, cr, uid, id, resource): - """Private Method to Calculate resource Leaves days - - @param id: resource calendar id - @param resource: resource id for which leaves will ew calculated - - @return : returns the list of dates, where resource on leave in - resource.calendar.leaves object (e.g.['%Y-%m-%d', '%Y-%m-%d']) - TDE TODO: internal only - """ - resource_cal_leaves = self.pool.get('resource.calendar.leaves') - dt_leave = [] - resource_leave_ids = resource_cal_leaves.search(cr, uid, [('calendar_id','=',id), '|', ('resource_id','=',False), ('resource_id','=',resource)]) - #res_leaves = resource_cal_leaves.read(cr, uid, resource_leave_ids, ['date_from', 'date_to']) - res_leaves = resource_cal_leaves.browse(cr, uid, resource_leave_ids) - - for leave in res_leaves: - dtf = datetime.datetime.strptime(leave.date_from, '%Y-%m-%d %H:%M:%S') - dtt = datetime.datetime.strptime(leave.date_to, '%Y-%m-%d %H:%M:%S') - no = dtt - dtf - [dt_leave.append((dtf + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))] - dt_leave.sort() - - return dt_leave - def interval_min_get(self, cr, uid, id, dt_from, hours, resource=False): """ Calculates the working Schedule from supplied from date to till hours @@ -540,98 +523,29 @@ class resource_calendar(osv.osv): TDE TODO: used in mrp_operations/mrp_operations.py TDE NOTE: do not count leave hours, a leave is considered all-day + TDE UPDATE: now count leave hours """ - if not id: - td = int(hours)*3 - return [(dt_from - datetime.timedelta(hours=td), dt_from)] - dt_leave = self._get_leaves(cr, uid, id, resource) - dt_leave.reverse() - todo = hours - result = [] - maxrecur = 100 - current_hour = dt_from.hour - while float_compare(todo, 0, 4) and maxrecur: - cr.execute("select hour_from,hour_to from resource_calendar_attendance where dayofweek='%s' and calendar_id=%s order by hour_from desc", (dt_from.weekday(),id)) - for (hour_from,hour_to) in cr.fetchall(): - leave_flag = False - if (hour_fromtodo: - hour_from = m-todo - dt_check = dt_from.strftime('%Y-%m-%d') - for leave in dt_leave: - if dt_check == leave: - dt_check = datetime.datetime.strptime(dt_check, '%Y-%m-%d') + datetime.timedelta(days=1) - leave_flag = True - if leave_flag: - break - else: - d1 = datetime.datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(hour_from)), int((hour_from%1) * 60)) - d2 = datetime.datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(m)), int((m%1) * 60)) - result.append((d1, d2)) - current_hour = hour_from - todo -= (m-hour_from) - dt_from -= datetime.timedelta(days=1) - current_hour = 24 - maxrecur -= 1 - result.reverse() - return result + return self.schedule_hours( + cr, uid, id, hours * -1.0, + day_dt=dt_from.replace(minute=0, second=0), + compute_leaves=True, resource_id=resource + ) - # def interval_get(self, cr, uid, id, dt_from, hours, resource=False, byday=True): def interval_get_multi(self, cr, uid, date_and_hours_by_cal, resource=False, byday=True): """ TDE NOTE: used in mrp_operations/mrp_operations.py (default parameters) and in interval_get() TDE NOTE: byday is not used in this method... TDE NOTE: do not count leave hours, a leave is considered all-day + TDE UPDATE: now count leave hours """ - def group(lst, key): - lst.sort(key=itemgetter(key)) - grouped = itertools.groupby(lst, itemgetter(key)) - return dict([(k, [v for v in itr]) for k, itr in grouped]) - # END group - - cr.execute("select calendar_id, dayofweek, hour_from, hour_to from resource_calendar_attendance order by hour_from") - hour_res = cr.dictfetchall() - hours_by_cal = group(hour_res, 'calendar_id') - - results = {} - - for d, hours, id in date_and_hours_by_cal: - dt_from = datetime.datetime.strptime(d, '%Y-%m-%d %H:%M:%S') - if not id: - td = int(hours)*3 - results[(d, hours, id)] = [(dt_from, dt_from + datetime.timedelta(hours=td))] - continue - - dt_leave = self._get_leaves(cr, uid, id, resource) - todo = hours - result = [] - maxrecur = 100 - current_hour = dt_from.hour - while float_compare(todo, 0, 4) and maxrecur: - for (hour_from,hour_to) in [(item['hour_from'], item['hour_to']) for item in hours_by_cal[id] if item['dayofweek'] == str(dt_from.weekday())]: - leave_flag = False - if (hour_to>current_hour) and float_compare(todo, 0, 4): - m = max(hour_from, current_hour) - if (hour_to-m)>todo: - hour_to = m+todo - dt_check = dt_from.strftime('%Y-%m-%d') - for leave in dt_leave: - if dt_check == leave: - dt_check = datetime.datetime.strptime(dt_check, '%Y-%m-%d') + datetime.timedelta(days=1) - leave_flag = True - if leave_flag: - break - else: - d1 = datetime.datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(m)), int((m%1) * 60)) - d2 = datetime.datetime(dt_from.year, dt_from.month, dt_from.day, int(math.floor(hour_to)), int((hour_to%1) * 60)) - result.append((d1, d2)) - current_hour = hour_to - todo -= (hour_to - m) - dt_from += datetime.timedelta(days=1) - current_hour = 0 - maxrecur -= 1 - results[(d, hours, id)] = result - return results + res = {} + for dt_str, hours, calendar_id in date_and_hours_by_cal: + result = self.schedule_hours( + cr, uid, calendar_id, hours, + day_dt=datetime.datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S').replace(minute=0, second=0), + compute_leaves=True, resource_id=resource + ) + res[(dt_str, hours, calendar_id)] = result + return res def interval_get(self, cr, uid, id, dt_from, hours, resource=False, byday=True): """Calculates Resource Working Internal Timing Based on Resource Calendar. @@ -650,18 +564,7 @@ class resource_calendar(osv.osv): return res def interval_hours_get(self, cr, uid, id, dt_from, dt_to, resource=False): - """ Calculates the Total Working hours based on given start_date to - end_date, If resource id is supplied that it will consider the source - leaves also in calculating the hours. - - @param dt_from : date start to calculate hours - @param dt_end : date end to calculate hours - @param resource: optional resource id, If given resource leave will be - considered. - - @return : Total number of working hours based dt_from and dt_end and - resource if supplied. - """ + """ Unused wrapper """ return self._interval_hours_get(cr, uid, id, dt_from, dt_to, resource_id=resource) def _interval_hours_get(self, cr, uid, id, dt_from, dt_to, resource_id=False, timezone_from_uid=None, exclude_leaves=True, context=None): @@ -683,90 +586,9 @@ class resource_calendar(osv.osv): TDE NOTE: used in project_issue/project_issue.py TDE NOTE: day-long leaves + TDE UPDATE: timezone_from_uid not yet supported """ - utc_tz = pytz.timezone('UTC') - local_tz = utc_tz - - if timezone_from_uid: - users_obj = self.pool.get('res.users') - user_timezone = users_obj.browse(cr, uid, timezone_from_uid, context=context).partner_id.tz - if user_timezone: - try: - local_tz = pytz.timezone(user_timezone) - except pytz.UnknownTimeZoneError: - pass # fallback to UTC as local timezone - - def utc_to_local_zone(naive_datetime): - utc_dt = utc_tz.localize(naive_datetime, is_dst=False) - return utc_dt.astimezone(local_tz) - - def float_time_convert(float_val): - factor = float_val < 0 and -1 or 1 - val = abs(float_val) - return (factor * int(math.floor(val)), int(round((val % 1) * 60))) - - # Get slots hours per day - # {day_of_week: [(8, 12), (13, 17), ...], ...} - hours_range_per_weekday = {} - if id: - cr.execute("select dayofweek, hour_from,hour_to from resource_calendar_attendance where calendar_id=%s order by hour_from", (id,)) - for weekday, hour_from, hour_to in cr.fetchall(): - weekday = int(weekday) - hours_range_per_weekday.setdefault(weekday, []) - hours_range_per_weekday[weekday].append((hour_from, hour_to)) - else: - # considering default working hours (Monday -> Friday, 8 -> 12, 13 -> 17) - for weekday in range(5): - hours_range_per_weekday[weekday] = [(8, 12), (13, 17)] - - ## Interval between dt_from - dt_to - ## - ## dt_from dt_to - ## =============|==================|============ - ## [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] - ## - ## [ : start of range - ## ] : end of range - ## - ## case 1: range end before interval start (skip) - ## case 2: range overlap interval start (fit start to internal) - ## case 3: range within interval - ## case 4: range overlap interval end (fit end to interval) - ## case 5: range start after interval end (skip) - - interval_start = utc_to_local_zone(dt_from) - interval_end = utc_to_local_zone(dt_to) - hours_timedelta = datetime.timedelta() - - # Get leaves for requested resource - dt_leaves = set([]) - if exclude_leaves and id: - dt_leaves = set(self._get_leaves(cr, uid, id, resource=resource_id)) - - for day in rrule.rrule(rrule.DAILY, dtstart=interval_start, - until=interval_end+datetime.timedelta(days=1), - byweekday=hours_range_per_weekday.keys()): - if exclude_leaves and day.strftime('%Y-%m-%d') in dt_leaves: - # XXX: futher improve leave management to allow for partial day leave - continue - for (range_from, range_to) in hours_range_per_weekday.get(day.weekday(), []): - range_from_hour, range_from_min = float_time_convert(range_from) - range_to_hour, range_to_min = float_time_convert(range_to) - daytime_start = local_tz.localize(day.replace(hour=range_from_hour, minute=range_from_min, second=0, tzinfo=None)) - daytime_end = local_tz.localize(day.replace(hour=range_to_hour, minute=range_to_min, second=0, tzinfo=None)) - - # case 1 & 5: time range out of interval - if daytime_end < interval_start or daytime_start > interval_end: - continue - # case 2 & 4: adjust start, end to fit within interval - daytime_start = max(daytime_start, interval_start) - daytime_end = min(daytime_end, interval_end) - - # case 2+, 4+, 3 - hours_timedelta += (daytime_end - daytime_start) - - # return timedelta converted to hours - return (hours_timedelta.days * 24.0 + hours_timedelta.seconds / 3600.0) + return self.get_working_hours(cr, uid, id, dt_from, dt_to, compute_leaves=exclude_leaves, resource_id=resource_id, context=context) class resource_calendar_attendance(osv.osv): diff --git a/addons/resource/tests/test_resource.py b/addons/resource/tests/test_resource.py index 7ae4343a21f..fdb4982af90 100644 --- a/addons/resource/tests/test_resource.py +++ b/addons/resource/tests/test_resource.py @@ -366,7 +366,19 @@ class TestResource(TestResourceCommon): td += item[1] - item[0] self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling') - def test_40_calendar_schedule_days(self): + res = self.resource_calendar._interval_hours_get(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), resource_id=self.resource1_id, exclude_leaves=False) + print res + + res = self.resource_calendar.get_working_hours(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), compute_leaves=False, resource_id=self.resource1_id) + print res + + res = self.resource_calendar._interval_hours_get(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), resource_id=self.resource1_id, exclude_leaves=True) + print res + + res = self.resource_calendar.get_working_hours(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), compute_leaves=True, resource_id=self.resource1_id) + print res + + def test_50_calendar_schedule_days(self): """ Testing calendar days scheduling """ cr, uid = self.cr, self.uid _format = '%Y-%m-%d %H:%M:%S' From 83e0e842c23558162bd26d02b7eb9504a0274ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 30 Aug 2013 16:27:36 +0200 Subject: [PATCH 006/335] [CLEAN] resource: cleaned old API bzr revid: tde@openerp.com-20130830142736-pd1o12fe40h2vgi2 --- addons/resource/resource.py | 77 ++++++++----------------------------- 1 file changed, 15 insertions(+), 62 deletions(-) diff --git a/addons/resource/resource.py b/addons/resource/resource.py index f43908f98e8..b2c3f54101e 100644 --- a/addons/resource/resource.py +++ b/addons/resource/resource.py @@ -496,35 +496,17 @@ class resource_calendar(osv.osv): # -------------------------------------------------- def working_hours_on_day(self, cr, uid, resource_calendar_id, day, context=None): - """ Compatibility method - will be removed for OpenERP v8. Computation - was done for the whole day, therefore setting start_dt at the beginning - of the day. - TDE TODO: hr_payroll/hr_payroll.py - """ + """ Compatibility method - will be removed for OpenERP v8. + Computation was done for the whole day, ignoring hour/minutes. + Used in hr_payroll/hr_payroll.py """ if isinstance(day, datetime.datetime): day = day.replace(hour=0, minute=0) return self.get_working_hours_of_date(cr, uid, resource_calendar_id.id, start_dt=day, context=None) def interval_min_get(self, cr, uid, id, dt_from, hours, resource=False): - """ - Calculates the working Schedule from supplied from date to till hours - will be satisfied based or resource calendar id. If resource is also - given then it will consider the resource leave also and than will - calculates resource working schedule - - @param dt_from: datetime object, start of working scheduled - @param hours: float, total number working hours needed scheduled from - start date - @param resource : Optional Resource id, if supplied than resource leaves - will also taken into consideration for calculating working - schedule. - @return : List datetime object of working schedule based on supplies - params - - TDE TODO: used in mrp_operations/mrp_operations.py - TDE NOTE: do not count leave hours, a leave is considered all-day - TDE UPDATE: now count leave hours - """ + """ Compatibility method - will be removed for OpenERP v8. + Schedule hours backwards. Note: now count leave hours instead of all-day leaves. + Used in mrp_operations/mrp_operations.py. """ return self.schedule_hours( cr, uid, id, hours * -1.0, day_dt=dt_from.replace(minute=0, second=0), @@ -532,11 +514,9 @@ class resource_calendar(osv.osv): ) def interval_get_multi(self, cr, uid, date_and_hours_by_cal, resource=False, byday=True): - """ TDE NOTE: used in mrp_operations/mrp_operations.py (default parameters) and in interval_get() - TDE NOTE: byday is not used in this method... - TDE NOTE: do not count leave hours, a leave is considered all-day - TDE UPDATE: now count leave hours - """ + """ Compatibility method - will be removed for OpenERP v8. + Note: used in mrp_operations/mrp_operations.py (default parameters) and in interval_get() + Byday was not used. Now counts Leave hours instead of all-day leaves. """ res = {} for dt_str, hours, calendar_id in date_and_hours_by_cal: result = self.schedule_hours( @@ -548,46 +528,19 @@ class resource_calendar(osv.osv): return res def interval_get(self, cr, uid, id, dt_from, hours, resource=False, byday=True): - """Calculates Resource Working Internal Timing Based on Resource Calendar. - - @param dt_from: start resource schedule calculation. - @param hours : total number of working hours to be scheduled. - @param resource: optional resource id, If supplied it will take care of - resource leave while scheduling. - @param byday: boolean flag bit enforce day wise scheduling - - @return : list of scheduled working timing based on resource calendar. - - TDE NOTE: mrp_operations/mrp_operations.py, crm/crm_lead.py (res given) - """ + """ Compatibility method - will be removed for OpenERP v8. Unifier of interval_get_multi. + Used in: mrp_operations/mrp_operations.py, crm/crm_lead.py (res given) """ res = self.interval_get_multi(cr, uid, [(dt_from.strftime('%Y-%m-%d %H:%M:%S'), hours, id)], resource, byday)[(dt_from.strftime('%Y-%m-%d %H:%M:%S'), hours, id)] return res def interval_hours_get(self, cr, uid, id, dt_from, dt_to, resource=False): - """ Unused wrapper """ + """ Compatibility method - will be removed for OpenERP v8. Unused wrapper """ return self._interval_hours_get(cr, uid, id, dt_from, dt_to, resource_id=resource) def _interval_hours_get(self, cr, uid, id, dt_from, dt_to, resource_id=False, timezone_from_uid=None, exclude_leaves=True, context=None): - """ Calculates the Total Working hours based on given start_date to - end_date, If resource id is supplied that it will consider the source - leaves also in calculating the hours. - - @param dt_from : date start to calculate hours - @param dt_end : date end to calculate hours - @param resource_id: optional resource id, If given resource leave will be - considered. - @param timezone_from_uid: optional uid, if given we will considerer - working hours in that user timezone - @param exclude_leaves: optionnal, if set to True (default) we will exclude - resource leaves from working hours - @param context: current request context - @return : Total number of working hours based dt_from and dt_end and - resource if supplied. - - TDE NOTE: used in project_issue/project_issue.py - TDE NOTE: day-long leaves - TDE UPDATE: timezone_from_uid not yet supported - """ + """ Compatibility method - will be removed for OpenERP v8. + Computes working hours between two dates, taking always same hour/minuts. + Note: now resets hour/minuts. Now counts leave hours instead of all-day leaves. """ return self.get_working_hours(cr, uid, id, dt_from, dt_to, compute_leaves=exclude_leaves, resource_id=resource_id, context=context) From 2cdefa52ef508d1583231ebbfa517cb205200440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 30 Aug 2013 16:43:34 +0200 Subject: [PATCH 007/335] [FIX] resource: fixed computation with a False calendar id (spotted in stock_no_autopicking.py yml tests) bzr revid: tde@openerp.com-20130830144334-ong666lutkbibtrd --- addons/resource/resource.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/addons/resource/resource.py b/addons/resource/resource.py index b2c3f54101e..859e31e82a0 100644 --- a/addons/resource/resource.py +++ b/addons/resource/resource.py @@ -20,13 +20,10 @@ ############################################################################## import datetime -# from datetime import datetime, timedelta from dateutil import rrule from dateutil.relativedelta import relativedelta -import itertools -import math from operator import itemgetter -import pytz +# import pytz from faces import * from openerp import tools @@ -182,7 +179,7 @@ class resource_calendar(osv.osv): :param date day_date: current day as a date """ - if id is None: + if not id: return day_date + relativedelta(days=1) weekdays = self.get_weekdays(cr, uid, id, context) @@ -205,7 +202,7 @@ class resource_calendar(osv.osv): :param date day_date: current day as a date """ - if id is None: + if not id: return day_date + relativedelta(days=-1) weekdays = self.get_weekdays(cr, uid, id, context) weekdays.reverse() From 6e1cb5226b8eedc2db15a495dfa31dbd725fb1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 2 Sep 2013 15:42:00 +0200 Subject: [PATCH 008/335] [IMP] hr_contract: added override of base_action_rule (+ dependency) to add the possibility to use a user-related working scheduling. bzr revid: tde@openerp.com-20130902134200-xvy5hb7lqv2wv0um --- addons/base_action_rule/base_action_rule.py | 23 +++++---- addons/hr_contract/__init__.py | 3 +- addons/hr_contract/__openerp__.py | 5 +- addons/hr_contract/base_action_rule.py | 52 ++++++++++++++++++++ addons/hr_contract/base_action_rule_view.xml | 19 +++++++ addons/resource/resource.py | 2 +- addons/resource/resource_view.xml | 20 +++++--- addons/resource/tests/test_resource.py | 12 ++--- 8 files changed, 108 insertions(+), 28 deletions(-) create mode 100644 addons/hr_contract/base_action_rule.py create mode 100644 addons/hr_contract/base_action_rule_view.xml diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index fd1d8e8d121..20be9cd4dec 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -240,6 +240,18 @@ class base_action_rule(osv.osv): data.update({'model': model.model}) return {'value': data} + def _check_delay(self, cr, uid, action, record, record_dt, context=None): + if action.trg_date_calendar_id and action.trg_date_range_type == 'day': + start_dt = get_datetime(record_dt) + action_dt = self.pool['resource.calendar'].schedule_days_get_date( + cr, uid, action.trg_date_calendar_id.id, action.trg_date_range, + day_date=start_dt, compute_leaves=True, context=context + ) + else: + delay = DATE_RANGE_FUNCTION[action.trg_date_range_type](action.trg_date_range) + action_dt = get_datetime(record_dt) + delay + return action_dt + def _check(self, cr, uid, automatic=False, use_new_cursor=False, context=None): """ This Function is called by scheduler. """ context = context or {} @@ -266,21 +278,12 @@ class base_action_rule(osv.osv): else: get_record_dt = lambda record: record[date_field] - delay = DATE_RANGE_FUNCTION[action.trg_date_range_type](action.trg_date_range) - # process action on the records that should be executed for record in model.browse(cr, uid, record_ids, context=context): record_dt = get_record_dt(record) if not record_dt: continue - if action.trg_date_calendar_id and action.trg_date_range_type == 'day': - start_dt = get_datetime(record_dt) - action_dt = self.pool['resource.calendar'].schedule_days( - cr, uid, action.trg_date_calendar_id.id, action.trg_date_range, - date=start_dt, compute_leaves=True - ) - else: - action_dt = get_datetime(record_dt) + delay + action_dt = self._check_delay(cr, uid, action, record, record_dt, context=context) if last_run and (last_run <= action_dt < now) or (action_dt < now): try: self._process(cr, uid, action, [record.id], context=context) diff --git a/addons/hr_contract/__init__.py b/addons/hr_contract/__init__.py index 36375675bda..9bec9d6fffe 100644 --- a/addons/hr_contract/__init__.py +++ b/addons/hr_contract/__init__.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). +# Copyright (C) 2004-Today OpenERP SA ( +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.addons.base_action_rule.base_action_rule import get_datetime +from openerp.osv import fields, osv + + +class base_action_rule(osv.Model): + """ Add resource and calendar for time-based conditions """ + _name = 'base.action.rule' + _inherit = ['base.action.rule'] + + _columns = { + 'trg_date_resource_field_id': fields.many2one( + 'ir.model.fields', 'User related field', + help='Use the user\'s working schedule.', + ), + } + + def _check_delay(self, cr, uid, action, record, record_dt, context=None): + """ Override the check of delay to try to use a user-related calendar. + If no calendar is found, fallback on the default behavior. """ + if action.trg_date_calendar_id and action.trg_date_range_type == 'day' and action.trg_date_resource_field_id: + user = record[action.trg_date_resource_field_id.name] + if user.employee_ids and user.employee_ids[0].contract_id \ + and user.employee_ids[0].contract_id.working_hours: + calendar = user.employee_ids[0].contract_id.working_hours + start_dt = get_datetime(record_dt) + action_dt = self.pool['resource.calendar'].schedule_days_get_date( + cr, uid, calendar.id, action.trg_date_range, + day_date=start_dt, compute_leaves=True, context=context + ) + return action_dt + return super(base_action_rule, self)._check_delay(cr, uid, action, record, record_dt, context=context) diff --git a/addons/hr_contract/base_action_rule_view.xml b/addons/hr_contract/base_action_rule_view.xml new file mode 100644 index 00000000000..c37628b8c37 --- /dev/null +++ b/addons/hr_contract/base_action_rule_view.xml @@ -0,0 +1,19 @@ + + + + + + base.action.rule.form + base.action.rule + + + + + + + + + + diff --git a/addons/resource/resource.py b/addons/resource/resource.py index 859e31e82a0..3824f154681 100644 --- a/addons/resource/resource.py +++ b/addons/resource/resource.py @@ -481,7 +481,7 @@ class resource_calendar(osv.osv): """Wrapper on _schedule_days: return the beginning/ending datetime of a days scheduling. """ res = self._schedule_days(cr, uid, id, days, day_date, compute_leaves, resource_id, context) - return res[-1][1].date() + return res[-1][1] def schedule_days(self, cr, uid, id, days, day_date=None, compute_leaves=False, resource_id=None, context=None): """Wrapper on _schedule_days: return the working intervals of a days diff --git a/addons/resource/resource_view.xml b/addons/resource/resource_view.xml index dcd7e1a67c0..6f5bc71863a 100644 --- a/addons/resource/resource_view.xml +++ b/addons/resource/resource_view.xml @@ -60,12 +60,13 @@

- - - - - - + + + + + + +
@@ -245,14 +246,17 @@
+ Resource Leaves resource.calendar.leaves tree,form,calendar + - - + + + diff --git a/addons/resource/tests/test_resource.py b/addons/resource/tests/test_resource.py index fdb4982af90..1413c6732c6 100644 --- a/addons/resource/tests/test_resource.py +++ b/addons/resource/tests/test_resource.py @@ -367,16 +367,16 @@ class TestResource(TestResourceCommon): self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling') res = self.resource_calendar._interval_hours_get(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), resource_id=self.resource1_id, exclude_leaves=False) - print res + # print res res = self.resource_calendar.get_working_hours(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), compute_leaves=False, resource_id=self.resource1_id) - print res + # print res res = self.resource_calendar._interval_hours_get(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), resource_id=self.resource1_id, exclude_leaves=True) - print res + # print res res = self.resource_calendar.get_working_hours(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), compute_leaves=True, resource_id=self.resource1_id) - print res + # print res def test_50_calendar_schedule_days(self): """ Testing calendar days scheduling """ @@ -384,12 +384,12 @@ class TestResource(TestResourceCommon): _format = '%Y-%m-%d %H:%M:%S' res = self.resource_calendar.schedule_days_get_date(cr, uid, self.calendar_id, 5, day_date=self.date1) - self.assertEqual(res, datetime.strptime('2013-02-26 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') + self.assertEqual(res.date(), datetime.strptime('2013-02-26 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') res = self.resource_calendar.schedule_days_get_date( cr, uid, self.calendar_id, 5, day_date=self.date1, compute_leaves=True, resource_id=self.resource1_id) - self.assertEqual(res, datetime.strptime('2013-03-01 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') + self.assertEqual(res.date(), datetime.strptime('2013-03-01 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') # -------------------------------------------------- # Misc From 3e04e5daff76be0566f5d0b2da2992a54145013d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 2 Sep 2013 15:44:18 +0200 Subject: [PATCH 009/335] [IMP] hr_contract: base_action_rule: better string bzr revid: tde@openerp.com-20130902134418-ds0exspb1kkhhw6x --- addons/hr_contract/base_action_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hr_contract/base_action_rule.py b/addons/hr_contract/base_action_rule.py index 5788aa50caf..d06d5e47e89 100644 --- a/addons/hr_contract/base_action_rule.py +++ b/addons/hr_contract/base_action_rule.py @@ -30,7 +30,7 @@ class base_action_rule(osv.Model): _columns = { 'trg_date_resource_field_id': fields.many2one( - 'ir.model.fields', 'User related field', + 'ir.model.fields', 'Use employee work schedule', help='Use the user\'s working schedule.', ), } From 6ee6b211b61bb2032c897c92eb67a6c894684864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 2 Sep 2013 15:49:44 +0200 Subject: [PATCH 010/335] [IMP] hr_contract: base_action_rule: add resource for leave computation bzr revid: tde@openerp.com-20130902134944-4b4ex8e7il32uvy5 --- addons/hr_contract/base_action_rule.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/hr_contract/base_action_rule.py b/addons/hr_contract/base_action_rule.py index d06d5e47e89..c5a29743497 100644 --- a/addons/hr_contract/base_action_rule.py +++ b/addons/hr_contract/base_action_rule.py @@ -44,9 +44,11 @@ class base_action_rule(osv.Model): and user.employee_ids[0].contract_id.working_hours: calendar = user.employee_ids[0].contract_id.working_hours start_dt = get_datetime(record_dt) + resource_id = user.employee_ids[0].resource_id.id action_dt = self.pool['resource.calendar'].schedule_days_get_date( cr, uid, calendar.id, action.trg_date_range, - day_date=start_dt, compute_leaves=True, context=context + day_date=start_dt, compute_leaves=True, resource_id=resource_id, + context=context ) return action_dt return super(base_action_rule, self)._check_delay(cr, uid, action, record, record_dt, context=context) From 69257d5d6f9c757cfba4447f63336fd14fb4be49 Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Fri, 8 Nov 2013 12:32:10 +0100 Subject: [PATCH 011/335] [REF] work in progress. reset graph view to minimal state in addon web_graph bzr revid: ged@openerp.com-20131108113210-aptz2pne05iwca82 --- addons/web_graph/__openerp__.py | 31 - addons/web_graph/static/lib/dropdown.js | 92 - addons/web_graph/static/lib/flotr2/LICENSE | 19 - addons/web_graph/static/lib/flotr2/Makefile | 40 - addons/web_graph/static/lib/flotr2/README.md | 89 - .../web_graph/static/lib/flotr2/dev/notes.txt | 86 - .../static/lib/flotr2/flotr2.examples.min.js | 2 - .../lib/flotr2/flotr2.examples.types.js | 1425 ---- .../static/lib/flotr2/flotr2.ie.min.js | 33 - addons/web_graph/static/lib/flotr2/flotr2.js | 6865 ----------------- .../web_graph/static/lib/flotr2/flotr2.min.js | 27 - addons/web_graph/static/lib/flotr2/js/Axis.js | 303 - .../web_graph/static/lib/flotr2/js/Color.js | 163 - addons/web_graph/static/lib/flotr2/js/DOM.js | 88 - addons/web_graph/static/lib/flotr2/js/Date.js | 207 - .../static/lib/flotr2/js/DefaultOptions.js | 98 - .../static/lib/flotr2/js/EventAdapter.js | 52 - .../web_graph/static/lib/flotr2/js/Flotr.js | 250 - .../web_graph/static/lib/flotr2/js/Graph.js | 745 -- .../web_graph/static/lib/flotr2/js/Series.js | 74 - addons/web_graph/static/lib/flotr2/js/Text.js | 88 - .../static/lib/flotr2/js/plugins/crosshair.js | 84 - .../static/lib/flotr2/js/plugins/download.js | 51 - .../static/lib/flotr2/js/plugins/grid.js | 208 - .../static/lib/flotr2/js/plugins/handles.js | 199 - .../static/lib/flotr2/js/plugins/hit.js | 337 - .../static/lib/flotr2/js/plugins/labels.js | 227 - .../static/lib/flotr2/js/plugins/legend.js | 179 - .../static/lib/flotr2/js/plugins/selection.js | 278 - .../lib/flotr2/js/plugins/spreadsheet.js | 296 - .../static/lib/flotr2/js/plugins/titles.js | 177 - .../static/lib/flotr2/js/types/bars.js | 274 - .../static/lib/flotr2/js/types/bubbles.js | 119 - .../static/lib/flotr2/js/types/candles.js | 127 - .../static/lib/flotr2/js/types/gantt.js | 229 - .../static/lib/flotr2/js/types/lines.js | 275 - .../static/lib/flotr2/js/types/markers.js | 140 - .../static/lib/flotr2/js/types/pie.js | 210 - .../static/lib/flotr2/js/types/points.js | 66 - .../static/lib/flotr2/js/types/radar.js | 60 - .../static/lib/flotr2/js/types/timeline.js | 90 - .../web_graph/static/lib/flotr2/lib/base64.js | 113 - .../static/lib/flotr2/lib/bean-min.js | 10 - .../web_graph/static/lib/flotr2/lib/bean.js | 501 -- .../static/lib/flotr2/lib/canvas2image.js | 198 - .../static/lib/flotr2/lib/canvastext.js | 429 - .../static/lib/flotr2/lib/excanvas.js | 1425 ---- .../static/lib/flotr2/lib/imagediff.js | 343 - .../static/lib/flotr2/lib/jasmine/MIT.LICENSE | 20 - .../lib/flotr2/lib/jasmine/jasmine-html.js | 190 - .../static/lib/flotr2/lib/jasmine/jasmine.css | 166 - .../static/lib/flotr2/lib/jasmine/jasmine.js | 2476 ------ .../flotr2/lib/jasmine/jasmine_favicon.png | Bin 700 -> 0 bytes .../static/lib/flotr2/lib/prototype.js | 4320 ----------- .../static/lib/flotr2/lib/underscore-min.js | 27 - .../static/lib/flotr2/lib/underscore.js | 839 -- .../static/lib/flotr2/lib/yepnope.js | 1 - addons/web_graph/static/src/css/flotr2.css | 69 - addons/web_graph/static/src/css/graph.css | 80 - addons/web_graph/static/src/js/graph.js | 412 +- addons/web_graph/static/src/xml/web_graph.xml | 39 +- 61 files changed, 5 insertions(+), 26056 deletions(-) delete mode 100644 addons/web_graph/static/lib/dropdown.js delete mode 100644 addons/web_graph/static/lib/flotr2/LICENSE delete mode 100644 addons/web_graph/static/lib/flotr2/Makefile delete mode 100644 addons/web_graph/static/lib/flotr2/README.md delete mode 100644 addons/web_graph/static/lib/flotr2/dev/notes.txt delete mode 100644 addons/web_graph/static/lib/flotr2/flotr2.examples.min.js delete mode 100644 addons/web_graph/static/lib/flotr2/flotr2.examples.types.js delete mode 100644 addons/web_graph/static/lib/flotr2/flotr2.ie.min.js delete mode 100644 addons/web_graph/static/lib/flotr2/flotr2.js delete mode 100644 addons/web_graph/static/lib/flotr2/flotr2.min.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/Axis.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/Color.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/DOM.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/Date.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/DefaultOptions.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/EventAdapter.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/Flotr.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/Graph.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/Series.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/Text.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/crosshair.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/download.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/grid.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/handles.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/hit.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/labels.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/legend.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/selection.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/spreadsheet.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/titles.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/types/bars.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/types/bubbles.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/types/candles.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/types/gantt.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/types/lines.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/types/markers.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/types/pie.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/types/points.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/types/radar.js delete mode 100644 addons/web_graph/static/lib/flotr2/js/types/timeline.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/base64.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/bean-min.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/bean.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/canvas2image.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/canvastext.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/excanvas.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/imagediff.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/jasmine/MIT.LICENSE delete mode 100644 addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine-html.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.css delete mode 100644 addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine_favicon.png delete mode 100644 addons/web_graph/static/lib/flotr2/lib/prototype.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/underscore-min.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/underscore.js delete mode 100644 addons/web_graph/static/lib/flotr2/lib/yepnope.js delete mode 100644 addons/web_graph/static/src/css/flotr2.css diff --git a/addons/web_graph/__openerp__.py b/addons/web_graph/__openerp__.py index c62a1877ee0..8a7baa2bc0a 100644 --- a/addons/web_graph/__openerp__.py +++ b/addons/web_graph/__openerp__.py @@ -15,37 +15,6 @@ Graph Views for Web Client. 'version': '3.0', 'depends': ['web'], 'js': [ - 'static/lib/dropdown.js', - 'static/lib/flotr2/lib/bean.js', - 'static/lib/flotr2/js/Flotr.js', - 'static/lib/flotr2/js/DefaultOptions.js', - 'static/lib/flotr2/js/Color.js', - 'static/lib/flotr2/js/Date.js', - 'static/lib/flotr2/js/DOM.js', - 'static/lib/flotr2/js/EventAdapter.js', - 'static/lib/flotr2/js/Text.js', - 'static/lib/flotr2/js/Graph.js', - 'static/lib/flotr2/js/Axis.js', - 'static/lib/flotr2/js/Series.js', - 'static/lib/flotr2/js/types/lines.js', - 'static/lib/flotr2/js/types/bars.js', - 'static/lib/flotr2/js/types/bubbles.js', - 'static/lib/flotr2/js/types/candles.js', - 'static/lib/flotr2/js/types/gantt.js', - 'static/lib/flotr2/js/types/markers.js', - 'static/lib/flotr2/js/types/pie.js', - 'static/lib/flotr2/js/types/points.js', - 'static/lib/flotr2/js/types/radar.js', - 'static/lib/flotr2/js/types/timeline.js', - 'static/lib/flotr2/js/plugins/crosshair.js', - 'static/lib/flotr2/js/plugins/download.js', - 'static/lib/flotr2/js/plugins/grid.js', - 'static/lib/flotr2/js/plugins/hit.js', - 'static/lib/flotr2/js/plugins/selection.js', - 'static/lib/flotr2/js/plugins/labels.js', - 'static/lib/flotr2/js/plugins/legend.js', - 'static/lib/flotr2/js/plugins/spreadsheet.js', - 'static/lib/flotr2/js/plugins/titles.js', 'static/src/js/graph.js' ], 'css': [ diff --git a/addons/web_graph/static/lib/dropdown.js b/addons/web_graph/static/lib/dropdown.js deleted file mode 100644 index 54b61c5e9d0..00000000000 --- a/addons/web_graph/static/lib/dropdown.js +++ /dev/null @@ -1,92 +0,0 @@ -/* ============================================================ - * bootstrap-dropdown.js v2.0.2 - * http://twitter.github.com/bootstrap/javascript.html#dropdowns - * ============================================================ - * Copyright 2012 Twitter, Inc. - * - * 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. - * ============================================================ */ - - -!function( $ ){ - - "use strict" - - /* DROPDOWN CLASS DEFINITION - * ========================= */ - - var toggle = '[data-toggle="dropdown"]' - , Dropdown = function ( element ) { - var $el = $(element).on('click.dropdown.data-api', this.toggle) - $('html').on('click.dropdown.data-api', function () { - $el.parent().removeClass('open') - }) - } - - Dropdown.prototype = { - - constructor: Dropdown - - , toggle: function ( e ) { - var $this = $(this) - , selector = $this.attr('data-target') - , $parent - , isActive - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 - } - - $parent = $(selector) - $parent.length || ($parent = $this.parent()) - - isActive = $parent.hasClass('open') - - clearMenus() - !isActive && $parent.toggleClass('open') - - return false - } - - } - - function clearMenus() { - $(toggle).parent().removeClass('open') - } - - - /* DROPDOWN PLUGIN DEFINITION - * ========================== */ - - $.fn.dropdown = function ( option ) { - return this.each(function () { - var $this = $(this) - , data = $this.data('dropdown') - if (!data) $this.data('dropdown', (data = new Dropdown(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - $.fn.dropdown.Constructor = Dropdown - - - /* APPLY TO STANDARD DROPDOWN ELEMENTS - * =================================== */ - - $(function () { - $('html').on('click.dropdown.data-api', clearMenus) - $('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) - }) - -}( window.jQuery ); \ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/LICENSE b/addons/web_graph/static/lib/flotr2/LICENSE deleted file mode 100644 index eeb8ce52b3b..00000000000 --- a/addons/web_graph/static/lib/flotr2/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2012 Carl Sutherland - -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. diff --git a/addons/web_graph/static/lib/flotr2/Makefile b/addons/web_graph/static/lib/flotr2/Makefile deleted file mode 100644 index 70157b0f3fa..00000000000 --- a/addons/web_graph/static/lib/flotr2/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -all: test flotr2 - -test: - cd spec; jasmine-headless-webkit -j jasmine.yml -c - -libraries: - smoosh make/lib.json - cat ./build/bean.js > build/lib.js - cat ./build/underscore.js >> build/lib.js - cat ./build/bean.min.js > build/lib.min.js - echo ";" >> build/lib.min.js - cat ./build/underscore.min.js >> build/lib.min.js - echo ";" >> build/lib.min.js - -ie: - smoosh make/ie.json - -flotr2: libraries ie - smoosh make/flotr2.json - cat build/lib.js build/flotr2.js > flotr2.js - cat build/lib.min.js > flotr2.min.js - cat build/flotr2.min.js >> flotr2.min.js - echo ';' >> flotr2.min.js - cp build/ie.min.js flotr2.ie.min.js - -flotr2-basic: libraries ie - smoosh make/basic.json - cat build/lib.min.js > flotr2-basic.min.js - cat build/flotr2-basic.min.js >> flotr2-basic.min.js - -flotr2-standalone: ie - smoosh make/flotr2.json - cat build/flotr2.js > flotr2.js - cp build/ie.min.js flotr2.ie.min.js - -flotr-examples: - smoosh make/examples.json - cp build/examples.min.js flotr2.examples.min.js - cp build/examples-types.js flotr2.examples.types.js - diff --git a/addons/web_graph/static/lib/flotr2/README.md b/addons/web_graph/static/lib/flotr2/README.md deleted file mode 100644 index 46f180c0fc6..00000000000 --- a/addons/web_graph/static/lib/flotr2/README.md +++ /dev/null @@ -1,89 +0,0 @@ -Flotr2 -====== - -The Canvas graphing library. - -![Google Groups](http://groups.google.com/intl/en/images/logos/groups_logo_sm.gif) - -http://groups.google.com/group/flotr2/ - -Please fork http://jsfiddle.net/cesutherland/ZFBj5/ with your question or bug reproduction case. - - -API ---- - -The API consists of a primary draw method which accepts a configuration object, helper methods, and several microlibs. - -### Example - -```javascript - var - // Container div: - container = document.getElementById("flotr-example-graph"), - // First data series: - d1 = [[0, 3], [4, 8], [8, 5], [9, 13]], - // Second data series: - d2 = [], - // A couple flotr configuration options: - options = { - xaxis: { - minorTickFreq: 4 - }, - grid: { - minorVerticalLines: true - } - }, - i, graph; - - // Generated second data set: - for (i = 0; i < 14; i += 0.5) { - d2.push([i, Math.sin(i)]); - } - - // Draw the graph: - graph = Flotr.draw( - container, // Container element - [ d1, d2 ], // Array of data series - options // Configuration options - ); -``` - -### Microlibs - -* [underscore.js](http://documentcloud.github.com/underscore/) -* [bean.js](https://github.com/fat/bean) - -Extending ---------- - -Flotr may be extended by adding new plugins and graph types. - -### Graph Types - -Graph types define how a particular chart is rendered. Examples include line, bar, pie. - -Existing graph types are found in `js/types/`. - -### Plugins - -Plugins extend the core of flotr with new functionality. They can add interactions, new decorations, etc. Examples -include titles, labels and selection. - -The plugins included are found in `js/plugins/`. - -Development ------------ - -This project uses [smoosh](https://github.com/fat/smoosh) to build and [jasmine](http://pivotal.github.com/jasmine/) -with [js-imagediff](https://github.com/HumbleSoftware/js-imagediff) to test. Tests may be executed by -[jasmine-headless-webkit](http://johnbintz.github.com/jasmine-headless-webkit/) with -`cd spec; jasmine-headless-webkit -j jasmine.yml -c` or by a browser by navigating to -`flotr2/spec/SpecRunner.html`. - -Shoutouts ---------- - -Thanks to Bas Wenneker, Fabien Ménager and others for all the work on the original Flotr. -Thanks to Jochen Berger and Jordan Santell for their contributions to Flotr2. - diff --git a/addons/web_graph/static/lib/flotr2/dev/notes.txt b/addons/web_graph/static/lib/flotr2/dev/notes.txt deleted file mode 100644 index 982474e579f..00000000000 --- a/addons/web_graph/static/lib/flotr2/dev/notes.txt +++ /dev/null @@ -1,86 +0,0 @@ -Flotr 2 Architecture Notes - - -Global: -====== - -Flotr.js - - versioning information - browser detection - extension (plugins, graph types) - draw - clone / merge - tick size - tick formatter - engineering notation - magnitude - rad, pixel, floor - drawText - measureText - getBestTextAlign - align map - compatibility - - -Graph Architecture: -=================== - -Axis - - all series - orientation - ticks (major, minor) - scale (d2p, p2d, logarithmic) - notion of stacks - -Series - - per 'data' - notion of range (x, y, min, max) - -Graph - - DOM constructon - event attachment - options initialization - data range calculations - canvas spacing calculations - event normalization - draw methods - DOM cleanup - event cleanup - - -Utilities: -========== - -Color - build colors - parse textual color data - convert colors - clone colors - -Text - calculate text size - canvas size - html size - -Date - formatting - constants - - -Spacing Calculation -=================== - -Flotr - calculate data - calculate margins - -Chart - calculate Data Ranges - Explicit or auto data minimum, maximums - calculate Data Range Extensions - By chart type, extend data range with needs of chart type (ie. stacked bars, stacked lines) - add Chart Padding - By chart type - -Text - use explicit margins - calculate label margins - calculate title margins - diff --git a/addons/web_graph/static/lib/flotr2/flotr2.examples.min.js b/addons/web_graph/static/lib/flotr2/flotr2.examples.min.js deleted file mode 100644 index 7bbf25592a3..00000000000 --- a/addons/web_graph/static/lib/flotr2/flotr2.examples.min.js +++ /dev/null @@ -1,2 +0,0 @@ - -(function(){var a=Flotr.EventAdapter,b=Flotr._,c="click",d="example",e="mouseenter",f="mouseleave",g=".",h="flotr-examples",i="flotr-examples-container",j="flotr-examples-reset",k="flotr-examples-thumbs",l="flotr-examples-thumb",m="flotr-examples-collapsed",n="flotr-examples-highlight",o="flotr-examples-large",p="flotr-examples-medium",q="flotr-examples-small",r="flotr-examples-mobile",s='
',t='
'+'
View All
'+'
'+'
'+"
";Examples=function(a){if(b.isUndefined(Flotr.ExampleList))throw"Flotr.ExampleList not defined.";this.options=a,this.list=Flotr.ExampleList,this.current=null,this.single=!1,this._initNodes(),this._example=new Flotr.Examples.Example({node:this._exampleNode}),this._initExamples()},Examples.prototype={examples:function(){function f(b){var c=$(b.currentTarget),e=c.data("example"),f=b.data.orientation;f^c.hasClass(n)&&(c.toggleClass(n).css(a),d._example.executeCallback(e,c))}var a={cursor:"pointer"},b=this._thumbsNode,c=this.list.get(),d=this,e=["basic","basic-axis","basic-bars","basic-bars-horizontal","basic-bar-stacked","basic-stacked-horizontal","basic-pie","basic-radar","basic-bubble","basic-candle","basic-legend","mouse-tracking","mouse-zoom","mouse-drag","basic-time","negative-values","click-example","download-image","download-data","advanced-titles","color-gradients","basic-timeline","advanced-markers"];(function h(){var a=e.shift(),f=c[a];if(f.type==="profile"||f.type==="test")return;var g=$(s);g.data("example",f),b.append(g),d._example.executeCallback(f,g),g.click(function(){d._loadExample(f)}),e.length&&setTimeout(h,20)})(),b.delegate(g+l,"mouseenter",{orientation:!0},f),b.delegate(g+l,"mouseleave",{orientation:!1},f)},_loadExample:function(a){a&&(window.location.hash="!"+(this.single?"single/":"")+a.key,u||(this._thumbsNode.css({position:"absolute",height:"0px",overflow:"hidden",width:"0px"}),this._resetNode.css({top:"16px"})),this._examplesNode.addClass(m),this._exampleNode.show(),this._example.setExample(a),this._resize(),$(document).scrollTop(0))},_reset:function(){window.location.hash="",u||this._thumbsNode.css({position:"",height:"",overflow:"",width:""}),this._examplesNode.removeClass(m),this._thumbsNode.height(""),this._exampleNode.hide()},_initNodes:function(){var a=$(this.options.node),b=this,c=$(t);b._resetNode=c.find(g+j),b._exampleNode=c.find(g+i),b._thumbsNode=c.find(g+k),b._examplesNode=c,b._resetNode.click(function(){b._reset()}),a.append(c),this._initResizer()},_initResizer:function(){function e(){var b=c.height()-(a.options.thumbPadding||0),e=c.width(),f;e>1760?(f=o,a._thumbsNode.height(b)):e>1140?(f=p,a._thumbsNode.height(b)):(f=q,a._thumbsNode.height("")),d!==f&&(d&&a._examplesNode.removeClass(d),a._examplesNode.addClass(f),d=f)}var a=this,b=a._examplesNode,c=$(window),d;$(window).resize(e),e(),this._resize=e},_initExamples:function(){var a=window.location.hash,b,c;a?(a=a.substring(2),c=a.split("/"),c.length==1?(b=this.list.get(a),this.examples()):c[0]=="single"&&(this.single=!0,b=this.list.get(c[1])),this._loadExample(b)):this.examples()}};var u=function(){var a=!!(navigator.userAgent.match(/Android/i)||navigator.userAgent.match(/webOS/i)||navigator.userAgent.match(/iPhone/i)||navigator.userAgent.match(/iPod/i)),b=!!$.browser.mozilla;return!a||b}();Flotr.Examples=Examples})(),function(){var a=Flotr._,b=".",c="flotr-example",d="flotr-example-label",e="flotr-example-title",f="flotr-example-description",g="flotr-example-editor",h="flotr-example-graph",i='
'+'
'+'
'+'
'+"
",j=function(a){this.options=a,this.example=null,this._initNodes()};j.prototype={setExample:function(a){var b=this.getSource(a),c=this._editorNode;this.example=a,Math.seedrandom(a.key),this._exampleNode.css({display:"block"}),this._titleNode.html(a.name||""),this._markupNode.html(a.description||""),this._editor?this._editor.setExample(b):this._editor=new Flotr.Examples.Editor(c,{example:b,teardown:function(){Flotr.EventAdapter.stopObserving($(c).find(".render")[0]),$(c).find("canvas").each(function(a,b){Flotr.EventAdapter.stopObserving(b)})}})},getSource:function(a){var b=a.callback.toString();return navigator.userAgent.search(/firefox/i)!==-1&&(b=js_beautify(b)),b},executeCallback:function(b,c){a.isElement(c)||(c=c[0]);var d=b.args?[c].concat(b.args):[c];return Math.seedrandom(b.key),b.callback.apply(this,d)},_initNodes:function(){var a=this.options.node,c=$(i);this._titleNode=c.find(b+e),this._markupNode=c.find(b+f),this._editorNode=c.find(b+g),this._exampleNode=c,a.append(c)}},Flotr.Examples.Example=j}(),function(){function Editor(a,b){function o(){i.hide(),f&&f.call(),m.render({example:d,render:h})}function p(a,b,c){var d=!1,e='Error: ',f,g;e+=''+a+"",typeof c!="undefined"&&(e+='',e+='Line '+c+"",console.log(b),b&&(e+=" of ",b==window.location?(e+='script',!d):e+=''+b+""),e+="."),i.show(),i.html(e)}var c=b.type||"javascript",d=b.example||"",e=b.noRun||!1,f=b.teardown||!1,g=$(T_CONTROLS),h=$(T_RENDER),i=$(T_ERRORS),j=$(T_SOURCE),k=$(T_EDITOR),l="editor-render-"+COUNT,m,h,n;m=new TYPES[c]({onerror:p});if(!m)throw"Invalid type: API not found for type `"+c+"`.";h.attr("id",l),i.hide(),k.append(h).append(g).append(j).addClass(c).addClass(e?"no-run":""),a=$(a),a.append(k),j.append(i),d=m.example({example:d,render:h}),n=CodeMirror(j[0],{value:d,readOnly:e,lineNumbers:!0,mode:m.codeMirrorType}),e||(g.delegate(".run","click",function(){d=n.getValue(),o()}),o()),window.onerror=function(a,b,c){return p(a,b,c),console.log(a),ONERROR&&$.isFunction(ONERROR)?ONERROR(a,b,c):!1},COUNT++,this.setExample=function(a){d=m.example({example:a,render:h}),n.setValue(d),n.refresh(),o()}}var ONERROR=window.onerror,COUNT=0,TYPES={},T_CONTROLS='
',T_EDITOR='
',T_SOURCE='
',T_ERRORS='
',T_RENDER='
',T_IFRAME="";TYPES.javascript=function(b){this.onerror=b.onerror},TYPES.javascript.prototype={codeMirrorType:"javascript",example:function(a){var b=a.example,c=a.render,d=$(c).attr("id");return"("+b+')(document.getElementById("'+d+'"));'},render:function(o){eval(o.example)}},TYPES.html=function(b){this.onerror=b.onerror},TYPES.html.prototype={codeMirrorType:"htmlmixed",example:function(a){return $.trim(a.example)},render:function(a){var b=a.example,c=a.render,d=$(T_IFRAME),e=this,f,g;c.html(d),f=d[0].contentWindow,g=f.document,g.open(),f.onerror=d.onerror=function(){e.onerror.apply(null,arguments)},g.write(b),g.close()}},typeof Flotr.Examples=="undefined"&&(Flotr.Examples={}),Flotr.Examples.Editor=Editor}(),function(){var a=Flotr.DOM,b=Flotr.EventAdapter,c=Flotr._,d="click",e="example-profile",f="examples",g=function(a){if(c.isUndefined(Flotr.ExampleList))throw"Flotr.ExampleList not defined.";this.editMode="off",this.list=Flotr.ExampleList,this.current=null,this.single=!1,this.init()};g.prototype=c.extend({},Flotr.Examples.prototype,{examples:function(){var e=document.getElementById(f),g=a.node("
    "),h;c.each(this.list.getType("profile"),function(e){h=a.node("
  • "+e.name+"
  • "),a.insert(g,h),b.observe(h,d,c.bind(function(){this.example(e)},this))},this),a.insert(e,g)},example:function(a){this._renderSource(a),this.profileStart(a),setTimeout(c.bind(function(){this._renderGraph(a),this.profileEnd()},this),50)},profileStart:function(a){var b=document.getElementById(e);this._startTime=new Date,b.innerHTML='
    Profile started for "'+a.name+'"...
    '},profileEnd:function(a){var b=document.getElementById(e);profileTime=new Date-this._startTime,this._startTime=null,b.innerHTML+="
    Profile complete: "+profileTime+"ms
    "}}),Flotr.Profile=g}() \ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/flotr2.examples.types.js b/addons/web_graph/static/lib/flotr2/flotr2.examples.types.js deleted file mode 100644 index f22bc9596f9..00000000000 --- a/addons/web_graph/static/lib/flotr2/flotr2.examples.types.js +++ /dev/null @@ -1,1425 +0,0 @@ -(function () { - -var ExampleList = function () { - - // Map of examples. - this.examples = {}; - -}; - -ExampleList.prototype = { - - add : function (example) { - this.examples[example.key] = example; - }, - - get : function (key) { - return key ? (this.examples[key] || null) : this.examples; - }, - - getType : function (type) { - return Flotr._.select(this.examples, function (example) { - return (example.type === type); - }); - } -} - -Flotr.ExampleList = new ExampleList(); - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic', - name : 'Basic', - callback : basic -}); - -function basic (container) { - - var - d1 = [[0, 3], [4, 8], [8, 5], [9, 13]], // First data series - d2 = [], // Second data series - i, graph; - - // Generate first data set - for (i = 0; i < 14; i += 0.5) { - d2.push([i, Math.sin(i)]); - } - - // Draw Graph - graph = Flotr.draw(container, [ d1, d2 ], { - xaxis: { - minorTickFreq: 4 - }, - grid: { - minorVerticalLines: true - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-stacked', - name : 'Basic Stacked', - callback : basic_stacked, - type : 'test' -}); - -function basic_stacked (container) { - - var - d1 = [[0, 3], [4, 8], [8, 2], [9, 3]], // First data series - d2 = [[0, 2], [4, 3], [8, 8], [9, 4]], // Second data series - i, graph; - - // Draw Graph - graph = Flotr.draw(container, [ d1, d2 ], { - lines: { - show : true, - stacked: true - }, - xaxis: { - minorTickFreq: 4 - }, - grid: { - minorVerticalLines: true - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-stepped', - name : 'Basic Stepped', - callback : basic_stepped, - type : 'test' -}); - -function basic_stepped (container) { - - var - d1 = [[0, 3], [4, 8], [8, 5], [9, 13]], // First data series - d2 = [], // Second data series - i, graph; - - // Generate first data set - for (i = 0; i < 14; i += 0.5) { - d2.push([i, Math.sin(i)]); - } - - // Draw Graph - graph = Flotr.draw(container, [ d1, d2 ], { - lines: { - steps : true, - show : true - }, - xaxis: { - minorTickFreq: 4 - }, - yaxis: { - autoscale: true - }, - grid: { - minorVerticalLines: true - }, - mouse : { - track : true, - relative : true - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-axis', - name : 'Basic Axis', - callback : basic_axis -}); - -function basic_axis (container) { - - var - d1 = [], - d2 = [], - d3 = [], - d4 = [], - d5 = [], // Data - ticks = [[ 0, "Lower"], 10, 20, 30, [40, "Upper"]], // Ticks for the Y-Axis - graph; - - for(var i = 0; i <= 10; i += 0.1){ - d1.push([i, 4 + Math.pow(i,1.5)]); - d2.push([i, Math.pow(i,3)]); - d3.push([i, i*5+3*Math.sin(i*4)]); - d4.push([i, i]); - if( i.toFixed(1)%1 == 0 ){ - d5.push([i, 2*i]); - } - } - - d3[30][1] = null; - d3[31][1] = null; - - function ticksFn (n) { return '('+n+')'; } - - graph = Flotr.draw(container, [ - { data : d1, label : 'y = 4 + x^(1.5)', lines : { fill : true } }, - { data : d2, label : 'y = x^3'}, - { data : d3, label : 'y = 5x + 3sin(4x)'}, - { data : d4, label : 'y = x'}, - { data : d5, label : 'y = 2x', lines : { show : true }, points : { show : true } } - ], { - xaxis : { - noTicks : 7, // Display 7 ticks. - tickFormatter : ticksFn, // Displays tick values between brackets. - min : 1, // Part of the series is not displayed. - max : 7.5 // Part of the series is not displayed. - }, - yaxis : { - ticks : ticks, // Set Y-Axis ticks - max : 40 // Maximum value along Y-Axis - }, - grid : { - verticalLines : false, - backgroundColor : { - colors : [[0,'#fff'], [1,'#ccc']], - start : 'top', - end : 'bottom' - } - }, - legend : { - position : 'nw' - }, - title : 'Basic Axis example', - subtitle : 'This is a subtitle' - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-bars', - name : 'Basic Bars', - callback : basic_bars -}); - -Flotr.ExampleList.add({ - key : 'basic-bars-horizontal', - name : 'Horizontal Bars', - args : [true], - callback : basic_bars, - tolerance : 5 -}); - -function basic_bars (container, horizontal) { - - var - horizontal = (horizontal ? true : false), // Show horizontal bars - d1 = [], // First data series - d2 = [], // Second data series - point, // Data point variable declaration - i; - - for (i = 0; i < 4; i++) { - - if (horizontal) { - point = [Math.ceil(Math.random()*10), i]; - } else { - point = [i, Math.ceil(Math.random()*10)]; - } - - d1.push(point); - - if (horizontal) { - point = [Math.ceil(Math.random()*10), i+0.5]; - } else { - point = [i+0.5, Math.ceil(Math.random()*10)]; - } - - d2.push(point); - }; - - // Draw the graph - Flotr.draw( - container, - [d1, d2], - { - bars : { - show : true, - horizontal : horizontal, - shadowSize : 0, - barWidth : 0.5 - }, - mouse : { - track : true, - relative : true - }, - yaxis : { - min : 0, - autoscaleMargin : 1 - } - } - ); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-bar-stacked', - name : 'Stacked Bars', - callback : bars_stacked -}); - -Flotr.ExampleList.add({ - key : 'basic-stacked-horizontal', - name : 'Stacked Horizontal Bars', - args : [true], - callback : bars_stacked, - tolerance : 5 -}); - -function bars_stacked (container, horizontal) { - - var - d1 = [], - d2 = [], - d3 = [], - graph, i; - - for (i = -10; i < 10; i++) { - if (horizontal) { - d1.push([Math.random(), i]); - d2.push([Math.random(), i]); - d3.push([Math.random(), i]); - } else { - d1.push([i, Math.random()]); - d2.push([i, Math.random()]); - d3.push([i, Math.random()]); - } - } - - graph = Flotr.draw(container,[ - { data : d1, label : 'Serie 1' }, - { data : d2, label : 'Serie 2' }, - { data : d3, label : 'Serie 3' } - ], { - legend : { - backgroundColor : '#D2E8FF' // Light blue - }, - bars : { - show : true, - stacked : true, - horizontal : horizontal, - barWidth : 0.6, - lineWidth : 1, - shadowSize : 0 - }, - grid : { - verticalLines : horizontal, - horizontalLines : !horizontal - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-pie', - name : 'Basic Pie', - callback : basic_pie -}); - -function basic_pie (container) { - - var - d1 = [[0, 4]], - d2 = [[0, 3]], - d3 = [[0, 1.03]], - d4 = [[0, 3.5]], - graph; - - graph = Flotr.draw(container, [ - { data : d1, label : 'Comedy' }, - { data : d2, label : 'Action' }, - { data : d3, label : 'Romance', - pie : { - explode : 50 - } - }, - { data : d4, label : 'Drama' } - ], { - HtmlText : false, - grid : { - verticalLines : false, - horizontalLines : false - }, - xaxis : { showLabels : false }, - yaxis : { showLabels : false }, - pie : { - show : true, - explode : 6 - }, - mouse : { track : true }, - legend : { - position : 'se', - backgroundColor : '#D2E8FF' - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-radar', - name : 'Basic Radar', - callback : basic_radar -}); - -function basic_radar (container) { - - // Fill series s1 and s2. - var - s1 = { label : 'Actual', data : [[0, 3], [1, 8], [2, 5], [3, 5], [4, 3], [5, 9]] }, - s2 = { label : 'Target', data : [[0, 8], [1, 7], [2, 8], [3, 2], [4, 4], [5, 7]] }, - graph, ticks; - - // Radar Labels - ticks = [ - [0, "Statutory"], - [1, "External"], - [2, "Videos"], - [3, "Yippy"], - [4, "Management"], - [5, "oops"] - ]; - - // Draw the graph. - graph = Flotr.draw(container, [ s1, s2 ], { - radar : { show : true}, - grid : { circular : true, minorHorizontalLines : true}, - yaxis : { min : 0, max : 10, minorTickFreq : 2}, - xaxis : { ticks : ticks} - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-bubble', - name : 'Basic Bubble', - callback : basic_bubble -}); - -function basic_bubble (container) { - - var - d1 = [], - d2 = [], - point, graph, i; - - for (i = 0; i < 10; i++ ){ - point = [i, Math.ceil(Math.random()*10), Math.ceil(Math.random()*10)]; - d1.push(point); - - point = [i, Math.ceil(Math.random()*10), Math.ceil(Math.random()*10)]; - d2.push(point); - } - - // Draw the graph - graph = Flotr.draw(container, [d1, d2], { - bubbles : { show : true, baseRadius : 5 }, - xaxis : { min : -4, max : 14 }, - yaxis : { min : -4, max : 14 } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-candle', - name : 'Basic Candle', - callback : basic_candle -}); - -function basic_candle (container) { - - var - d1 = [], - price = 3.206, - graph, - i, a, b, c; - - for (i = 0; i < 50; i++) { - a = Math.random(); - b = Math.random(); - c = (Math.random() * (a + b)) - b; - d1.push([i, price, price + a, price - b, price + c]); - price = price + c; - } - - // Graph - graph = Flotr.draw(container, [ d1 ], { - candles : { show : true, candleWidth : 0.6 }, - xaxis : { noTicks : 10 } - }); -} - -})(); - - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-legend', - name : 'Basic Legend', - callback : basic_legend -}); - -function basic_legend (container) { - - var - d1 = [], - d2 = [], - d3 = [], - data, - graph, i; - - // Data Generation - for (i = 0; i < 15; i += 0.5) { - d1.push([i, i + Math.sin(i+Math.PI)]); - d2.push([i, i]); - d3.push([i, 15-Math.cos(i)]); - } - - data = [ - { data : d1, label :'x + sin(x+π)' }, - { data : d2, label :'x' }, - { data : d3, label :'15 - cos(x)' } - ]; - - - // This function prepend each label with 'y = ' - function labelFn (label) { - return 'y = ' + label; - } - - // Draw graph - graph = Flotr.draw(container, data, { - legend : { - position : 'se', // Position the legend 'south-east'. - labelFormatter : labelFn, // Format the labels. - backgroundColor : '#D2E8FF' // A light blue background color. - }, - HtmlText : false - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'mouse-tracking', - name : 'Mouse Tracking', - callback : mouse_tracking -}); - -function mouse_tracking (container) { - - var - d1 = [], - d2 = [], - d3 = [], - graph, i; - - for (i = 0; i < 20; i += 0.5) { - d1.push([i, 2*i]); - d2.push([i, i*1.5+1.5*Math.sin(i)]); - d3.push([i, 3*Math.cos(i)+10]); - } - - graph = Flotr.draw( - container, - [ - { - data : d1, - mouse : { track : false } // Disable mouse tracking for d1 - }, - d2, - d3 - ], - { - mouse : { - track : true, // Enable mouse tracking - lineColor : 'purple', - relative : true, - position : 'ne', - sensibility : 1, - trackDecimals : 2, - trackFormatter : function (o) { return 'x = ' + o.x +', y = ' + o.y; } - }, - crosshair : { - mode : 'xy' - } - } - ); - -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'mouse-zoom', - name : 'Mouse Zoom', - callback : mouse_zoom, - description : "

    Select an area of the graph to zoom. Click to reset the chart.

    " -}); - -function mouse_zoom (container) { - - var - d1 = [], - d2 = [], - d3 = [], - options, - graph, - i; - - for (i = 0; i < 40; i += 0.5) { - d1.push([i, Math.sin(i)+3*Math.cos(i)]); - d2.push([i, Math.pow(1.1, i)]); - d3.push([i, 40 - i+Math.random()*10]); - } - - options = { - selection : { mode : 'x', fps : 30 }, - title : 'Mouse Zoom' - }; - - // Draw graph with default options, overwriting with passed options - function drawGraph (opts) { - - // Clone the options, so the 'options' variable always keeps intact. - var o = Flotr._.extend(Flotr._.clone(options), opts || {}); - - // Return a new graph. - return Flotr.draw( - container, - [ d1, d2, d3 ], - o - ); - } - - // Actually draw the graph. - graph = drawGraph(); - - // Hook into the 'flotr:select' event. - Flotr.EventAdapter.observe(container, 'flotr:select', function (area) { - - // Draw graph with new area - f = drawGraph({ - xaxis: {min:area.x1, max:area.x2}, - yaxis: {min:area.y1, max:area.y2} - }); - - }); - - // When graph is clicked, draw the graph with default area. - Flotr.EventAdapter.observe(container, 'flotr:click', function () { drawGraph(); }); -}; - -})(); - - -(function () { - -Flotr.ExampleList.add({ - key : 'mouse-drag', - name : 'Mouse Drag', - callback : mouse_drag -}); - -function mouse_drag (container) { - - var - d1 = [], - d2 = [], - d3 = [], - options, - graph, - start, - i; - - for (i = -40; i < 40; i += 0.5) { - d1.push([i, Math.sin(i)+3*Math.cos(i)]); - d2.push([i, Math.pow(1.1, i)]); - d3.push([i, 40 - i+Math.random()*10]); - } - - options = { - xaxis: {min: 0, max: 20}, - title : 'Mouse Drag' - }; - - // Draw graph with default options, overwriting with passed options - function drawGraph (opts) { - - // Clone the options, so the 'options' variable always keeps intact. - var o = Flotr._.extend(Flotr._.clone(options), opts || {}); - - // Return a new graph. - return Flotr.draw( - container, - [ d1, d2, d3 ], - o - ); - } - - graph = drawGraph(); - - function initializeDrag (e) { - start = graph.getEventPosition(e); - Flotr.EventAdapter.observe(document, 'mousemove', move); - Flotr.EventAdapter.observe(document, 'mouseup', stopDrag); - } - - function move (e) { - var - end = graph.getEventPosition(e), - xaxis = graph.axes.x, - offset = start.x - end.x; - - graph = drawGraph({ - xaxis : { - min : xaxis.min + offset, - max : xaxis.max + offset - } - }); - // @todo: refector initEvents in order not to remove other observed events - Flotr.EventAdapter.observe(graph.overlay, 'mousedown', initializeDrag); - } - - function stopDrag () { - Flotr.EventAdapter.stopObserving(document, 'mousemove', move); - } - - Flotr.EventAdapter.observe(graph.overlay, 'mousedown', initializeDrag); - -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-time', - name : 'Basic Time', - callback : basic_time, - description : "

    Select an area of the graph to zoom. Click to reset the chart.

    " -}); - -function basic_time (container) { - - var - d1 = [], - start = new Date("2009/01/01 01:00").getTime(), - options, - graph, - i, x, o; - - for (i = 0; i < 100; i++) { - x = start+(i*1000*3600*24*36.5); - d1.push([x, i+Math.random()*30+Math.sin(i/20+Math.random()*2)*20+Math.sin(i/10+Math.random())*10]); - } - - options = { - xaxis : { - mode : 'time', - labelsAngle : 45 - }, - selection : { - mode : 'x' - }, - HtmlText : false, - title : 'Time' - }; - - // Draw graph with default options, overwriting with passed options - function drawGraph (opts) { - - // Clone the options, so the 'options' variable always keeps intact. - o = Flotr._.extend(Flotr._.clone(options), opts || {}); - - // Return a new graph. - return Flotr.draw( - container, - [ d1 ], - o - ); - } - - graph = drawGraph(); - - Flotr.EventAdapter.observe(container, 'flotr:select', function(area){ - // Draw selected area - graph = drawGraph({ - xaxis : { min : area.x1, max : area.x2, mode : 'time', labelsAngle : 45 }, - yaxis : { min : area.y1, max : area.y2 } - }); - }); - - // When graph is clicked, draw the graph with default area. - Flotr.EventAdapter.observe(container, 'flotr:click', function () { graph = drawGraph(); }); -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'negative-values', - name : 'Negative Values', - callback : negative_values -}); - -function negative_values (container) { - - var - d0 = [], // Line through y = 0 - d1 = [], // Random data presented as a scatter plot. - d2 = [], // A regression line for the scatter. - sx = 0, - sy = 0, - sxy = 0, - sxsq = 0, - xmean, - ymean, - alpha, - beta, - n, x, y; - - for (n = 0; n < 20; n++){ - - x = n; - y = x + Math.random()*8 - 15; - - d0.push([x, 0]); - d1.push([x, y]); - - // Computations used for regression line - sx += x; - sy += y; - sxy += x*y; - sxsq += Math.pow(x,2); - } - - xmean = sx/n; - ymean = sy/n; - beta = ((n*sxy) - (sx*sy))/((n*sxsq)-(Math.pow(sx,2))); - alpha = ymean - (beta * xmean); - - // Compute the regression line. - for (n = 0; n < 20; n++){ - d2.push([n, alpha + beta*n]) - } - - // Draw the graph - graph = Flotr.draw( - container, [ - { data : d0, shadowSize : 0, color : '#545454' }, // Horizontal - { data : d1, label : 'y = x + (Math.random() * 8) - 15', points : { show : true } }, // Scatter - { data : d2, label : 'y = ' + alpha.toFixed(2) + ' + ' + beta.toFixed(2) + '*x' } // Regression - ], - { - legend : { position : 'se', backgroundColor : '#D2E8FF' }, - title : 'Negative Values' - } - ); -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'click-example', - name : 'Click Example', - callback : click_example -}); - -function click_example (container) { - - var - d1 = [[0,0]], // Point at origin - options, - graph; - - options = { - xaxis: {min: 0, max: 15}, - yaxis: {min: 0, max: 15}, - lines: {show: true}, - points: {show: true}, - mouse: {track:true}, - title: 'Click Example' - }; - - graph = Flotr.draw(container, [d1], options); - - // Add a point to the series and redraw the graph - Flotr.EventAdapter.observe(container, 'flotr:click', function(position){ - - // Add a point to the series at the location of the click - d1.push([position.x, position.y]); - - // Sort the series. - d1 = d1.sort(function (a, b) { return a[0] - b[0]; }); - - // Redraw the graph, with the new series. - graph = Flotr.draw(container, [d1], options); - }); -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'download-image', - name : 'Download Image', - callback : download_image, - description : '' + - '
    ' + - '' + - '' + - - '' + - '' + - '' + - '
    ' -}); - -function download_image (container) { - - var - d1 = [], - d2 = [], - d3 = [], - d4 = [], - d5 = [], - graph, - i; - - for (i = 0; i <= 10; i += 0.1) { - d1.push([i, 4 + Math.pow(i,1.5)]); - d2.push([i, Math.pow(i,3)]); - d3.push([i, i*5+3*Math.sin(i*4)]); - d4.push([i, i]); - if( i.toFixed(1)%1 == 0 ){ - d5.push([i, 2*i]); - } - } - - // Draw the graph - graph = Flotr.draw( - container,[ - {data:d1, label:'y = 4 + x^(1.5)', lines:{fill:true}}, - {data:d2, label:'y = x^3', yaxis:2}, - {data:d3, label:'y = 5x + 3sin(4x)'}, - {data:d4, label:'y = x'}, - {data:d5, label:'y = 2x', lines: {show: true}, points: {show: true}} - ],{ - title: 'Download Image Example', - subtitle: 'You can save me as an image', - xaxis:{ - noTicks: 7, // Display 7 ticks. - tickFormatter: function(n){ return '('+n+')'; }, // => displays tick values between brackets. - min: 1, // => part of the series is not displayed. - max: 7.5, // => part of the series is not displayed. - labelsAngle: 45, - title: 'x Axis' - }, - yaxis:{ - ticks: [[0, "Lower"], 10, 20, 30, [40, "Upper"]], - max: 40, - title: 'y = f(x)' - }, - y2axis:{color:'#FF0000', max: 500, title: 'y = x^3'}, - grid:{ - verticalLines: false, - backgroundColor: 'white' - }, - HtmlText: false, - legend: { - position: 'nw' - } - }); - - this.CurrentExample = function (operation) { - - var - format = $('#image-download input:radio[name=format]:checked').val(); - if (Flotr.isIE && Flotr.isIE < 9) { - alert( - "Your browser doesn't allow you to get a bitmap image from the plot, " + - "you can only get a VML image that you can use in Microsoft Office.
    " - ); - } - - if (operation == 'to-image') { - graph.download.saveImage(format, null, null, true) - } else if (operation == 'download') { - graph.download.saveImage(format); - } else if (operation == 'reset') { - graph.download.restoreCanvas(); - } - }; - - return graph; -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'download-data', - name : 'Download Data', - callback : download_data -}); - -function download_data (container) { - - var - d1 = [], - d2 = [], - d3 = [], - d4 = [], - d5 = [], - graph, - i,x; - - for (i = 0; i <= 100; i += 1) { - x = i / 10; - d1.push([x, 4 + Math.pow(x,1.5)]); - d2.push([x, Math.pow(x,3)]); - d3.push([x, i*5+3*Math.sin(x*4)]); - d4.push([x, x]); - if(x%1 === 0 ){ - d5.push([x, 2*x]); - } - } - - // Draw the graph. - graph = Flotr.draw( - container, [ - { data : d1, label : 'y = 4 + x^(1.5)', lines : { fill : true } }, - { data : d2, label : 'y = x^3' }, - { data : d3, label : 'y = 5x + 3sin(4x)' }, - { data : d4, label : 'y = x' }, - { data : d5, label : 'y = 2x', lines : { show : true }, points : { show : true } } - ],{ - xaxis : { - noTicks : 7, - tickFormatter : function (n) { return '('+n+')'; }, - min: 1, // Part of the series is not displayed. - max: 7.5 - }, - yaxis : { - ticks : [[ 0, "Lower"], 10, 20, 30, [40, "Upper"]], - max : 40 - }, - grid : { - verticalLines : false, - backgroundColor : 'white' - }, - legend : { - position : 'nw' - }, - spreadsheet : { - show : true, - tickFormatter : function (e) { return e+''; } - } - }); -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'advanced-titles', - name : 'Advanced Titles', - callback : advanced_titles -}); - -function advanced_titles (container) { - - var - d1 = [], - d2 = [], - d3 = [], - d4 = [], - d5 = [], - graph, - i; - - for (i = 0; i <= 10; i += 0.1) { - d1.push([i, 4 + Math.pow(i,1.5)]); - d2.push([i, Math.pow(i,3)]); - d3.push([i, i*5+3*Math.sin(i*4)]); - d4.push([i, i]); - if (i.toFixed(1)%1 == 0) { - d5.push([i, 2*i]); - } - } - - // Draw the graph. - graph = Flotr.draw( - container,[ - { data : d1, label : 'y = 4 + x^(1.5)', lines : { fill : true } }, - { data : d2, label : 'y = x^3', yaxis : 2 }, - { data : d3, label : 'y = 5x + 3sin(4x)' }, - { data : d4, label : 'y = x' }, - { data : d5, label : 'y = 2x', lines : { show : true }, points : { show : true } } - ], { - title : 'Advanced Titles Example', - subtitle : 'You can save me as an image', - xaxis : { - noTicks : 7, - tickFormatter : function (n) { return '('+n+')'; }, - min : 1, - max : 7.5, - labelsAngle : 45, - title : 'x Axis' - }, - yaxis : { - ticks : [[0, "Lower"], 10, 20, 30, [40, "Upper"]], - max : 40, - title : 'y = f(x)' - }, - y2axis : { color : '#FF0000', max : 500, title : 'y = x^3' }, - grid : { - verticalLines : false, - backgroundColor : 'white' - }, - HtmlText : false, - legend : { - position : 'nw' - } - }); -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'color-gradients', - name : 'Color Gradients', - callback : color_gradients -}); - -function color_gradients (container) { - - var - bars = { - data: [], - bars: { - show: true, - barWidth: 0.8, - lineWidth: 0, - fillColor: { - colors: ['#CB4B4B', '#fff'], - start: 'top', - end: 'bottom' - }, - fillOpacity: 0.8 - } - }, markers = { - data: [], - markers: { - show: true, - position: 'ct' - } - }, lines = { - data: [], - lines: { - show: true, - fillColor: ['#00A8F0', '#fff'], - fill: true, - fillOpacity: 1 - } - }, - point, - graph, - i; - - for (i = 0; i < 8; i++) { - point = [i, Math.ceil(Math.random() * 10)]; - bars.data.push(point); - markers.data.push(point); - } - - for (i = -1; i < 9; i += 0.01){ - lines.data.push([i, i*i/8+2]); - } - - graph = Flotr.draw( - container, - [lines, bars, markers], { - yaxis: { - min: 0, - max: 11 - }, - xaxis: { - min: -0.5, - max: 7.5 - }, - grid: { - verticalLines: false, - backgroundColor: ['#fff', '#ccc'] - } - } - ); -}; - -})(); - - -(function () { - -Flotr.ExampleList.add({ - key : 'profile-bars', - name : 'Profile Bars', - type : 'profile', - callback : profile_bars -}); - -/* -Flotr.ExampleList.add({ - key : 'basic-bars-horizontal', - name : 'Horizontal Bars', - args : [true], - callback : basic_bars -}); -*/ - -function profile_bars (container, horizontal) { - - var - horizontal = (horizontal ? true : false), // Show horizontal bars - d1 = [], // First data series - d2 = [], // Second data series - point, // Data point variable declaration - i; - - for (i = 0; i < 5000; i++) { - - if (horizontal) { - point = [Math.ceil(Math.random()*10), i]; - } else { - point = [i, Math.ceil(Math.random()*10)]; - } - - d1.push(point); - - if (horizontal) { - point = [Math.ceil(Math.random()*10), i+0.5]; - } else { - point = [i+0.5, Math.ceil(Math.random()*10)]; - } - - d2.push(point); - }; - - // Draw the graph - Flotr.draw( - container, - [d1, d2], - { - bars : { - show : true, - horizontal : horizontal, - barWidth : 0.5 - }, - mouse : { - track : true, - relative : true - }, - yaxis : { - min : 0, - autoscaleMargin : 1 - } - } - ); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-timeline', - name : 'Basic Timeline', - callback : basic_timeline -}); - -function basic_timeline (container) { - - var - d1 = [[1, 4, 5]], - d2 = [[3.2, 3, 4]], - d3 = [[1.9, 2, 2], [5, 2, 3.3]], - d4 = [[1.55, 1, 9]], - d5 = [[5, 0, 2.3]], - data = [], - timeline = { show : true, barWidth : .5 }, - markers = [], - labels = ['Obama', 'Bush', 'Clinton', 'Palin', 'McCain'], - i, graph, point; - - // Timeline - Flotr._.each([d1, d2, d3, d4, d5], function (d) { - data.push({ - data : d, - timeline : Flotr._.clone(timeline) - }); - }); - - // Markers - Flotr._.each([d1, d2, d3, d4, d5], function (d) { - point = d[0]; - markers.push([point[0], point[1]]); - }); - data.push({ - data: markers, - markers: { - show: true, - position: 'rm', - fontSize: 11, - labelFormatter : function (o) { return labels[o.index]; } - } - }); - - // Draw Graph - graph = Flotr.draw(container, data, { - xaxis: { - noTicks: 3, - tickFormatter: function (x) { - var - x = parseInt(x), - months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - return months[(x-1)%12]; - } - }, - yaxis: { - showLabels : false - }, - grid: { - horizontalLines : false - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'advanced-markers', - name : 'Advanced Markers', - callback : advanced_markers, - timeout : 150 -}); - -function advanced_markers (container) { - - var - xmark = new Image(), - checkmark = new Image(), - bars = { - data: [], - bars: { - show: true, - barWidth: 0.6, - lineWidth: 0, - fillOpacity: 0.8 - } - }, markers = { - data: [], - markers: { - show: true, - position: 'ct', - labelFormatter: function (o) { - return (o.y >= 5) ? checkmark : xmark; - } - } - }, - flotr = Flotr, - point, - graph, - i; - - - for (i = 0; i < 8; i++) { - point = [i, Math.ceil(Math.random() * 10)]; - bars.data.push(point); - markers.data.push(point); - } - - var runner = function () { - if (!xmark.complete || !checkmark.complete) { - setTimeout(runner, 50); - return; - } - - graph = flotr.draw( - container, - [bars, markers], { - yaxis: { - min: 0, - max: 11 - }, - xaxis: { - min: -0.5, - max: 7.5 - }, - grid: { - verticalLines: false - } - } - ); - } - - xmark.onload = runner; - xmark.src = 'images/xmark.png'; - checkmark.src = 'images/checkmark.png'; -}; - -})(); - diff --git a/addons/web_graph/static/lib/flotr2/flotr2.ie.min.js b/addons/web_graph/static/lib/flotr2/flotr2.ie.min.js deleted file mode 100644 index 0c64520fcb5..00000000000 --- a/addons/web_graph/static/lib/flotr2/flotr2.ie.min.js +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2006 Google Inc. -// -// 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. -// Known Issues: -// -// * Patterns only support repeat. -// * Radial gradient are not implemented. The VML version of these look very -// different from the canvas one. -// * Clipping paths are not implemented. -// * Coordsize. The width and height attribute have higher priority than the -// width and height style values which isn't correct. -// * Painting mode isn't implemented. -// * Canvas width/height should is using content-box by default. IE in -// Quirks mode will draw the canvas using border-box. Either change your -// doctype to HTML5 -// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) -// or use Box Sizing Behavior from WebFX -// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) -// * Non uniform scaling does not correctly scale strokes. -// * Optimize. There is always room for speed improvements. -// Only add this code if we do not already have a canvas implementation - -document.createElement("canvas").getContext||function(){function j(){return this.context_||(this.context_=new N(this))}function l(a,b,c){var d=k.call(arguments,2);return function(){return a.apply(b,d.concat(k.call(arguments)))}}function m(a){return String(a).replace(/&/g,"&").replace(/"/g,""")}function n(a,b,c){a.namespaces[b]||a.namespaces.add(b,c,"#default#VML")}function o(a){n(a,"g_vml_","urn:schemas-microsoft-com:vml"),n(a,"g_o_","urn:schemas-microsoft-com:office:office");if(!a.styleSheets.ex_canvas_){var b=a.createStyleSheet();b.owningElement.id="ex_canvas_",b.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}function q(a){var b=a.srcElement;switch(a.propertyName){case"width":b.getContext().clearRect(),b.style.width=b.attributes.width.nodeValue+"px",b.firstChild&&(b.firstChild.style.width=b.clientWidth+"px");break;case"height":b.getContext().clearRect(),b.style.height=b.attributes.height.nodeValue+"px",b.firstChild&&(b.firstChild.style.height=b.clientHeight+"px")}}function r(a){var b=a.srcElement;b.firstChild&&(b.firstChild.style.width=b.clientWidth+"px",b.firstChild.style.height=b.clientHeight+"px")}function v(){return[[1,0,0],[0,1,0],[0,0,1]]}function w(a,b){var c=v();for(var d=0;d<3;d++)for(var e=0;e<3;e++){var f=0;for(var g=0;g<3;g++)f+=a[d][g]*b[g][e];c[d][e]=f}return c}function x(a,b){b.fillStyle=a.fillStyle,b.lineCap=a.lineCap,b.lineJoin=a.lineJoin,b.lineWidth=a.lineWidth,b.miterLimit=a.miterLimit,b.shadowBlur=a.shadowBlur,b.shadowColor=a.shadowColor,b.shadowOffsetX=a.shadowOffsetX,b.shadowOffsetY=a.shadowOffsetY,b.strokeStyle=a.strokeStyle,b.globalAlpha=a.globalAlpha,b.font=a.font,b.textAlign=a.textAlign,b.textBaseline=a.textBaseline,b.arcScaleX_=a.arcScaleX_,b.arcScaleY_=a.arcScaleY_,b.lineScale_=a.lineScale_}function z(a){var b=a.indexOf("(",3),c=a.indexOf(")",b+1),d=a.substring(b+1,c).split(",");if(d.length!=4||a.charAt(3)!="a")d[3]=1;return d}function A(a){return parseFloat(a)/100}function B(a,b,c){return Math.min(c,Math.max(b,a))}function C(a){var b,c,d,e,f,g;e=parseFloat(a[0])/360%360,e<0&&e++,f=B(A(a[1]),0,1),g=B(A(a[2]),0,1);if(f==0)b=c=d=g;else{var h=g<.5?g*(1+f):g+f-g*f,i=2*g-h;b=D(i,h,e+1/3),c=D(i,h,e),d=D(i,h,e-1/3)}return"#"+s[Math.floor(b*255)]+s[Math.floor(c*255)]+s[Math.floor(d*255)]}function D(a,b,c){return c<0&&c++,c>1&&c--,6*c<1?a+(b-a)*6*c:2*c<1?b:3*c<2?a+(b-a)*(2/3-c)*6:a}function F(a){if(a in E)return E[a];var b,c=1;a=String(a);if(a.charAt(0)=="#")b=a;else if(/^rgb/.test(a)){var d=z(a),b="#",e;for(var f=0;f<3;f++)d[f].indexOf("%")!=-1?e=Math.floor(A(d[f])*255):e=+d[f],b+=s[B(e,0,255)];c=+d[3]}else if(/^hsl/.test(a)){var d=z(a);b=C(d),c=d[3]}else b=y[a]||a;return E[a]={color:b,alpha:c}}function I(a){if(H[a])return H[a];var b=document.createElement("div"),c=b.style;try{c.font=a}catch(d){}return H[a]={style:c.fontStyle||G.style,variant:c.fontVariant||G.variant,weight:c.fontWeight||G.weight,size:c.fontSize||G.size,family:c.fontFamily||G.family}}function J(a,b){var c={};for(var d in a)c[d]=a[d];var e=parseFloat(b.currentStyle.fontSize),f=parseFloat(a.size);return typeof a.size=="number"?c.size=a.size:a.size.indexOf("px")!=-1?c.size=f:a.size.indexOf("em")!=-1?c.size=e*f:a.size.indexOf("%")!=-1?c.size=e/100*f:a.size.indexOf("pt")!=-1?c.size=f/.75:c.size=e,c}function K(a){return a.style+" "+a.variant+" "+a.weight+" "+a.size+"px "+a.family}function M(a){return L[a]||"square"}function N(a){this.m_=v(),this.mStack_=[],this.aStack_=[],this.currentPath_=[],this.strokeStyle="#000",this.fillStyle="#000",this.lineWidth=1,this.lineJoin="miter",this.lineCap="butt",this.miterLimit=g*1,this.globalAlpha=1,this.font="10px sans-serif",this.textAlign="left",this.textBaseline="alphabetic",this.canvas=a;var b="width:"+a.clientWidth+"px;height:"+a.clientHeight+"px;overflow:hidden;position:absolute",c=a.ownerDocument.createElement("div");c.style.cssText=b,a.appendChild(c);var d=c.cloneNode(!1);d.style.backgroundColor="red",d.style.filter="alpha(opacity=0)",a.appendChild(d),this.element_=c,this.arcScaleX_=1,this.arcScaleY_=1,this.lineScale_=1}function P(a,b,c,d){a.currentPath_.push({type:"bezierCurveTo",cp1x:b.x,cp1y:b.y,cp2x:c.x,cp2y:c.y,x:d.x,y:d.y}),a.currentX_=d.x,a.currentY_=d.y}function Q(a,b){var c=F(a.strokeStyle),d=c.color,e=c.alpha*a.globalAlpha,f=a.lineScale_*a.lineWidth;f<1&&(e*=f),b.push("')}function R(b,c,d,e){var f=b.fillStyle,h=b.arcScaleX_,i=b.arcScaleY_,j=e.x-d.x,k=e.y-d.y;if(f instanceof V){var l=0,m={x:0,y:0},n=0,o=1;if(f.type_=="gradient"){var p=f.x0_/h,q=f.y0_/i,r=f.x1_/h,s=f.y1_/i,t=S(b,p,q),u=S(b,r,s),v=u.x-t.x,w=u.y-t.y;l=Math.atan2(v,w)*180/Math.PI,l<0&&(l+=360),l<1e-6&&(l=0)}else{var t=S(b,f.x0_,f.y0_);m={x:(t.x-d.x)/j,y:(t.y-d.y)/k},j/=h*g,k/=i*g;var x=a.max(j,k);n=2*f.r0_/x,o=2*f.r1_/x-n}var y=f.colors_;y.sort(function(a,b){return a.offset-b.offset});var z=y.length,A=y[0].color,B=y[z-1].color,C=y[0].alpha*b.globalAlpha,D=y[z-1].alpha*b.globalAlpha,E=[];for(var G=0;G')}else if(f instanceof W){if(j&&k){var I=-d.x,J=-d.y;c.push("')}}else{var K=F(b.fillStyle),L=K.color,M=K.alpha*b.globalAlpha;c.push('')}}function S(a,b,c){var d=a.m_;return{x:g*(b*d[0][0]+c*d[1][0]+d[2][0])-h,y:g*(b*d[0][1]+c*d[1][1]+d[2][1])-h}}function T(a){return isFinite(a[0][0])&&isFinite(a[0][1])&&isFinite(a[1][0])&&isFinite(a[1][1])&&isFinite(a[2][0])&&isFinite(a[2][1])}function U(a,b,c){if(!T(b))return;a.m_=b;if(c){var d=b[0][0]*b[1][1]-b[0][1]*b[1][0];a.lineScale_=f(e(d))}}function V(a){this.type_=a,this.x0_=0,this.y0_=0,this.r0_=0,this.x1_=0,this.y1_=0,this.r1_=0,this.colors_=[]}function W(a,b){Y(a);switch(b){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=b;break;default:X("SYNTAX_ERR")}this.src_=a.src,this.width_=a.width,this.height_=a.height}function X(a){throw new Z(a)}function Y(a){(!a||a.nodeType!=1||a.tagName!="IMG")&&X("TYPE_MISMATCH_ERR"),a.readyState!="complete"&&X("INVALID_STATE_ERR")}function Z(a){this.code=this[a],this.message=a+": DOM Exception "+this.code}var a=Math,b=a.round,c=a.sin,d=a.cos,e=a.abs,f=a.sqrt,g=10,h=g/2,i=+navigator.userAgent.match(/MSIE ([\d.]+)?/)[1],k=Array.prototype.slice;o(document);var p={init:function(a){var b=a||document;b.createElement("canvas"),b.attachEvent("onreadystatechange",l(this.init_,this,b))},init_:function(a){var b=a.getElementsByTagName("canvas");for(var c=0;c','",""),this.element_.insertAdjacentHTML("BeforeEnd",u.join(""))},O.stroke=function(a){var c=[],d=!1,e=10,f=10;c.push("j.x)j.x=l.x;if(i.y==null||l.yj.y)j.y=l.y}}c.push(' ">'),a?R(this,c,i,j):Q(this,c),c.push(""),this.element_.insertAdjacentHTML("beforeEnd",c.join(""))},O.fill=function(){this.stroke(!0)},O.closePath=function(){this.currentPath_.push({type:"close"})},O.save=function(){var a={};x(this,a),this.aStack_.push(a),this.mStack_.push(this.m_),this.m_=w(v(),this.m_)},O.restore=function(){this.aStack_.length&&(x(this.aStack_.pop(),this),this.m_=this.mStack_.pop())},O.translate=function(a,b){var c=[[1,0,0],[0,1,0],[a,b,1]];U(this,w(c,this.m_),!1)},O.rotate=function(a){var b=d(a),e=c(a),f=[[b,e,0],[-e,b,0],[0,0,1]];U(this,w(f,this.m_),!1)},O.scale=function(a,b){this.arcScaleX_*=a,this.arcScaleY_*=b;var c=[[a,0,0],[0,b,0],[0,0,1]];U(this,w(c,this.m_),!0)},O.transform=function(a,b,c,d,e,f){var g=[[a,b,0],[c,d,0],[e,f,1]];U(this,w(g,this.m_),!0)},O.setTransform=function(a,b,c,d,e,f){var g=[[a,b,0],[c,d,0],[e,f,1]];U(this,g,!0)},O.drawText_=function(a,c,d,e,f){var h=this.m_,i=1e3,j=0,k=i,l={x:0,y:0},n=[],o=J(I(this.font),this.element_),p=K(o),q=this.element_.currentStyle,r=this.textAlign.toLowerCase();switch(r){case"left":case"center":case"right":break;case"end":r=q.direction=="ltr"?"right":"left";break;case"start":r=q.direction=="rtl"?"right":"left";break;default:r="left"}switch(this.textBaseline){case"hanging":case"top":l.y=o.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":l.y=-o.size/2.25}switch(r){case"right":j=i,k=.05;break;case"center":j=k=i/2}var s=S(this,c+l.x,d+l.y);n.push(''),f?Q(this,n):R(this,n,{x:-j,y:0},{x:k,y:o.size});var t=h[0][0].toFixed(3)+","+h[1][0].toFixed(3)+","+h[0][1].toFixed(3)+","+h[1][1].toFixed(3)+",0,0",u=b(s.x/g)+","+b(s.y/g);n.push('','',''),this.element_.insertAdjacentHTML("beforeEnd",n.join(""))},O.fillText=function(a,b,c,d){this.drawText_(a,b,c,d,!1)},O.strokeText=function(a,b,c,d){this.drawText_(a,b,c,d,!0)},O.measureText=function(a){if(!this.textMeasureEl_){var b='';this.element_.insertAdjacentHTML("beforeEnd",b),this.textMeasureEl_=this.element_.lastChild}var c=this.element_.ownerDocument;return this.textMeasureEl_.innerHTML="",this.textMeasureEl_.style.font=this.font,this.textMeasureEl_.appendChild(c.createTextNode(a)),{width:this.textMeasureEl_.offsetWidth}},O.clip=function(){},O.arcTo=function(){},O.createPattern=function(a,b){return new W(a,b)},V.prototype.addColorStop=function(a,b){b=F(b),this.colors_.push({offset:a,color:b.color,alpha:b.alpha})};var $=Z.prototype=new Error;$.INDEX_SIZE_ERR=1,$.DOMSTRING_SIZE_ERR=2,$.HIERARCHY_REQUEST_ERR=3,$.WRONG_DOCUMENT_ERR=4,$.INVALID_CHARACTER_ERR=5,$.NO_DATA_ALLOWED_ERR=6,$.NO_MODIFICATION_ALLOWED_ERR=7,$.NOT_FOUND_ERR=8,$.NOT_SUPPORTED_ERR=9,$.INUSE_ATTRIBUTE_ERR=10,$.INVALID_STATE_ERR=11,$.SYNTAX_ERR=12,$.INVALID_MODIFICATION_ERR=13,$.NAMESPACE_ERR=14,$.INVALID_ACCESS_ERR=15,$.VALIDATION_ERR=16,$.TYPE_MISMATCH_ERR=17,G_vmlCanvasManager=p,CanvasRenderingContext2D=N,CanvasGradient=V,CanvasPattern=W,DOMException=Z}(),function(){function c(b){var c,d,e,f,g,h;e=b.length,d=0,c="";while(d>2),c+=a.charAt((f&3)<<4),c+="==";break}g=b.charCodeAt(d++);if(d==e){c+=a.charAt(f>>2),c+=a.charAt((f&3)<<4|(g&240)>>4),c+=a.charAt((g&15)<<2),c+="=";break}h=b.charCodeAt(d++),c+=a.charAt(f>>2),c+=a.charAt((f&3)<<4|(g&240)>>4),c+=a.charAt((g&15)<<2|(h&192)>>6),c+=a.charAt(h&63)}return c}function d(a){var c,d,e,f,g,h,i;h=a.length,g=0,i="";while(g>4);do{e=a.charCodeAt(g++)&255;if(e==61)return i;e=b[e]}while(g>2);do{f=a.charCodeAt(g++)&255;if(f==61)return i;f=b[f]}while(g":{width:24,points:[[4,18],[20,9],[4,0]]},"?":{width:18,points:[[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],null,[9,2],[8,1],[9,0],[10,1],[9,2]]},"@":{width:27,points:[[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],null,[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],null,[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],null,[19,16],[18,8],[18,6],[19,5]]},A:{width:18,points:[[9,21],[1,0],null,[9,21],[17,0],null,[4,7],[14,7]]},B:{width:21,points:[[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],null,[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]]},C:{width:21,points:[[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]]},D:{width:21,points:[[4,21],[4,0],null,[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]]},E:{width:19,points:[[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11],null,[4,0],[17,0]]},F:{width:18,points:[[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11]]},G:{width:21,points:[[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],null,[13,8],[18,8]]},H:{width:22,points:[[4,21],[4,0],null,[18,21],[18,0],null,[4,11],[18,11]]},I:{width:8,points:[[4,21],[4,0]]},J:{width:16,points:[[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]]},K:{width:21,points:[[4,21],[4,0],null,[18,21],[4,7],null,[9,12],[18,0]]},L:{width:17,points:[[4,21],[4,0],null,[4,0],[16,0]]},M:{width:24,points:[[4,21],[4,0],null,[4,21],[12,0],null,[20,21],[12,0],null,[20,21],[20,0]]},N:{width:22,points:[[4,21],[4,0],null,[4,21],[18,0],null,[18,21],[18,0]]},O:{width:22,points:[[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]]},P:{width:21,points:[[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]]},Q:{width:22,points:[[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],null,[12,4],[18,-2]]},R:{width:21,points:[[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],null,[11,11],[18,0]]},S:{width:20,points:[[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]]},T:{width:16,points:[[8,21],[8,0],null,[1,21],[15,21]]},U:{width:22,points:[[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]]},V:{width:18,points:[[1,21],[9,0],null,[17,21],[9,0]]},W:{width:24,points:[[2,21],[7,0],null,[12,21],[7,0],null,[12,21],[17,0],null,[22,21],[17,0]]},X:{width:20,points:[[3,21],[17,0],null,[17,21],[3,0]]},Y:{width:18,points:[[1,21],[9,11],[9,0],null,[17,21],[9,11]]},Z:{width:20,points:[[17,21],[3,0],null,[3,21],[17,21],null,[3,0],[17,0]]},"[":{width:14,points:[[4,25],[4,-7],null,[5,25],[5,-7],null,[4,25],[11,25],null,[4,-7],[11,-7]]},"\\":{width:14,points:[[0,21],[14,-3]]},"]":{width:14,points:[[9,25],[9,-7],null,[10,25],[10,-7],null,[3,25],[10,25],null,[3,-7],[10,-7]]},"^":{width:14,points:[[3,10],[8,18],[13,10]]},_:{width:16,points:[[0,-2],[16,-2]]},"`":{width:10,points:[[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]]},a:{width:19,points:[[15,14],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},b:{width:19,points:[[4,21],[4,0],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]]},c:{width:18,points:[[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},d:{width:19,points:[[15,21],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},e:{width:18,points:[[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},f:{width:12,points:[[10,21],[8,21],[6,20],[5,17],[5,0],null,[2,14],[9,14]]},g:{width:19,points:[[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},h:{width:19,points:[[4,21],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]]},i:{width:8,points:[[3,21],[4,20],[5,21],[4,22],[3,21],null,[4,14],[4,0]]},j:{width:10,points:[[5,21],[6,20],[7,21],[6,22],[5,21],null,[6,14],[6,-3],[5,-6],[3,-7],[1,-7]]},k:{width:17,points:[[4,21],[4,0],null,[14,14],[4,4],null,[8,8],[15,0]]},l:{width:8,points:[[4,21],[4,0]]},m:{width:30,points:[[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],null,[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]]},n:{width:19,points:[[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]]},o:{width:19,points:[[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]]},p:{width:19,points:[[4,14],[4,-7],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]]},q:{width:19,points:[[15,14],[15,-7],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},r:{width:13,points:[[4,14],[4,0],null,[4,8],[5,11],[7,13],[9,14],[12,14]]},s:{width:17,points:[[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]]},t:{width:12,points:[[5,21],[5,4],[6,1],[8,0],[10,0],null,[2,14],[9,14]]},u:{width:19,points:[[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],null,[15,14],[15,0]]},v:{width:16,points:[[2,14],[8,0],null,[14,14],[8,0]]},w:{width:22,points:[[3,14],[7,0],null,[11,14],[7,0],null,[11,14],[15,0],null,[19,14],[15,0]]},x:{width:17,points:[[3,14],[14,0],null,[14,14],[3,0]]},y:{width:16,points:[[2,14],[8,0],null,[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]]},z:{width:17,points:[[14,14],[3,0],null,[3,14],[14,14],null,[3,0],[14,0]]},"{":{width:14,points:[[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],null,[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],null,[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]]},"|":{width:8,points:[[4,25],[4,-7]]},"}":{width:14,points:[[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],null,[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],null,[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]]},"~":{width:24,points:[[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],null,[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]]},"�":{diacritic:"`",letter:"a"},"�":{diacritic:"�",letter:"a"},"�":{diacritic:"^",letter:"a"},"�":{diacritic:"�",letter:"a"},"�":{diacritic:"~",letter:"a"},"�":{diacritic:"`",letter:"e"},"�":{diacritic:"�",letter:"e"},"�":{diacritic:"^",letter:"e"},"�":{diacritic:"�",letter:"e"},"�":{diacritic:"`",letter:"i"},"�":{diacritic:"�",letter:"i"},"�":{diacritic:"^",letter:"i"},"�":{diacritic:"�",letter:"i"},"�":{diacritic:"`",letter:"o"},"�":{diacritic:"�",letter:"o"},"�":{diacritic:"^",letter:"o"},"�":{diacritic:"�",letter:"o"},"�":{diacritic:"~",letter:"o"},"�":{diacritic:"`",letter:"u"},"�":{diacritic:"�",letter:"u"},"�":{diacritic:"^",letter:"u"},"�":{diacritic:"�",letter:"u"},"�":{diacritic:"�",letter:"y"},"�":{diacritic:"�",letter:"y"},"�":{diacritic:"�",letter:"c"},"�":{diacritic:"~",letter:"n"},"�":{diacritic:"`",letter:"A"},"�":{diacritic:"�",letter:"A"},"�":{diacritic:"^",letter:"A"},"�":{diacritic:"�",letter:"A"},"�":{diacritic:"~",letter:"A"},"�":{diacritic:"`",letter:"E"},"�":{diacritic:"�",letter:"E"},"�":{diacritic:"^",letter:"E"},"�":{diacritic:"�",letter:"E"},"�":{diacritic:"`",letter:"I"},"�":{diacritic:"�",letter:"I"},"�":{diacritic:"^",letter:"I"},"�":{diacritic:"�",letter:"I"},"�":{diacritic:"`",letter:"O"},"�":{diacritic:"�",letter:"O"},"�":{diacritic:"^",letter:"O"},"�":{diacritic:"�",letter:"O"},"�":{diacritic:"~",letter:"O"},"�":{diacritic:"`",letter:"U"},"�":{diacritic:"�",letter:"U"},"�":{diacritic:"^",letter:"U"},"�":{diacritic:"�",letter:"U"},"�":{diacritic:"�",letter:"Y"},"�":{diacritic:"�",letter:"C"},"�":{diacritic:"~",letter:"N"}},specialchars:{pi:{width:19,points:[[6,14],[6,0],null,[14,14],[14,0],null,[2,13],[6,16],[13,13],[17,16]]}},diacritics:{"�":{entity:"cedil",points:[[6,-4],[4,-6],[2,-7],[1,-7]]},"�":{entity:"acute",points:[[8,19],[13,22]]},"`":{entity:"grave",points:[[7,22],[12,19]]},"^":{entity:"circ",points:[[5.5,19],[9.5,23],[12.5,19]]},"�":{entity:"trema",points:[[5,21],[6,20],[7,21],[6,22],[5,21],null,[12,21],[13,20],[14,21],[13,22],[12,21]]},"~":{entity:"tilde",points:[[4,18],[7,22],[10,18],[13,22]]}},style:{size:8,font:null,color:"#000000",weight:1,textAlign:"left",textBaseline:"bottom",adjustAlign:!1,angle:0,tracking:1,boundingBoxColor:"#ff0000",originPointColor:"#000000"},debug:!1,_bufferLexemes:{},extend:function(a,b){for(var c in b){if(c in a)continue;a[c]=b[c]}return a},letter:function(a){return CanvasText.letters[a]},parseLexemes:function(a){if(CanvasText._bufferLexemes[a])return CanvasText._bufferLexemes[a];var b,c,d=a.match(/&[A-Za-z]{2,5};|\s|./g),e=[],f=[];for(b=0;b-1;--d)c=f[d],e=c.diacritic?CanvasText.letter(c.letter).width:c.width,g+=e*(b.tracking||CanvasText.style.tracking)*(b.size||CanvasText.style.size)/25;return g},getDimensions:function(a,b){b=b||CanvasText.style;var c=CanvasText.measure(a,b),d=b.size||CanvasText.style.size,e=b.angle||CanvasText.style.angle;return b.angle==0?{width:c,height:d}:{width:Math.abs(Math.cos(e)*c)+Math.abs(Math.sin(e)*d),height:Math.abs(Math.sin(e)*c)+Math.abs(Math.cos(e)*d)}},drawPoints:function(a,b,c,d,e,f){var g,h,i=!0,j=0;f=f||{x:0,y:0},a.beginPath();for(g=0;g 0) { - // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3') - typeSpec = typeSpec.split(' ') - for (i = typeSpec.length; i--;) - remove(element, typeSpec[i], fn) - return element - } - type = isString && typeSpec.replace(nameRegex, '') - if (type && customEvents[type]) - type = customEvents[type].type - if (!typeSpec || isString) { - // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3) - if (namespaces = isString && typeSpec.replace(namespaceRegex, '')) - namespaces = namespaces.split('.') - rm(element, type, fn, namespaces) - } else if (typeof typeSpec === 'function') { - // remove(el, fn) - rm(element, null, typeSpec) - } else { - // remove(el, { t1: fn1, t2, fn2 }) - for (k in typeSpec) { - if (typeSpec.hasOwnProperty(k)) - remove(element, k, typeSpec[k]) - } - } - return element - } - - , add = function (element, events, fn, delfn, $) { - var type, types, i, args - , originalFn = fn - , isDel = fn && typeof fn === 'string' - - if (events && !fn && typeof events === 'object') { - for (type in events) { - if (events.hasOwnProperty(type)) - add.apply(this, [ element, type, events[type] ]) - } - } else { - args = arguments.length > 3 ? slice.call(arguments, 3) : [] - types = (isDel ? fn : events).split(' ') - isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1)) - // special case for one() - this === ONE && (fn = once(remove, element, events, fn, originalFn)) - for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args) - } - return element - } - - , one = function () { - return add.apply(ONE, arguments) - } - - , fireListener = W3C_MODEL ? function (isNative, type, element) { - var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents') - evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1) - element.dispatchEvent(evt) - } : function (isNative, type, element) { - element = targetElement(element, isNative) - // if not-native then we're using onpropertychange so we just increment a custom property - isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++ - } - - , fire = function (element, type, args) { - var i, j, l, names, handlers - , types = type.split(' ') - - for (i = types.length; i--;) { - type = types[i].replace(nameRegex, '') - if (names = types[i].replace(namespaceRegex, '')) - names = names.split('.') - if (!names && !args && element[eventSupport]) { - fireListener(nativeEvents[type], type, element) - } else { - // non-native event, either because of a namespace, arguments or a non DOM element - // iterate over all listeners and manually 'fire' - handlers = registry.get(element, type) - args = [false].concat(args) - for (j = 0, l = handlers.length; j < l; j++) { - if (handlers[j].inNamespaces(names)) - handlers[j].handler.apply(element, args) - } - } - } - return element - } - - , clone = function (element, from, type) { - var i = 0 - , handlers = registry.get(from, type) - , l = handlers.length - - for (;i < l; i++) - handlers[i].original && add(element, handlers[i].type, handlers[i].original) - return element - } - - , bean = { - add: add - , one: one - , remove: remove - , clone: clone - , fire: fire - , noConflict: function () { - context[name] = old - return this - } - } - - if (win[attachEvent]) { - // for IE, clean up on unload to avoid leaks - var cleanup = function () { - var i, entries = registry.entries() - for (i in entries) { - if (entries[i].type && entries[i].type !== 'unload') - remove(entries[i].element, entries[i].type) - } - win[detachEvent]('onunload', cleanup) - win.CollectGarbage && win.CollectGarbage() - } - win[attachEvent]('onunload', cleanup) - } - - return bean -}); -// Underscore.js 1.1.7 -// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the MIT license. -// Portions of Underscore are inspired or borrowed from Prototype, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore - -(function() { - - // Baseline setup - // -------------- - - // Establish the root object, `window` in the browser, or `global` on the server. - var root = this; - - // Save the previous value of the `_` variable. - var previousUnderscore = root._; - - // Establish the object that gets returned to break out of a loop iteration. - var breaker = {}; - - // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; - - // Create quick reference variables for speed access to core prototypes. - var slice = ArrayProto.slice, - unshift = ArrayProto.unshift, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty; - - // All **ECMAScript 5** native function implementations that we hope to use - // are declared here. - var - nativeForEach = ArrayProto.forEach, - nativeMap = ArrayProto.map, - nativeReduce = ArrayProto.reduce, - nativeReduceRight = ArrayProto.reduceRight, - nativeFilter = ArrayProto.filter, - nativeEvery = ArrayProto.every, - nativeSome = ArrayProto.some, - nativeIndexOf = ArrayProto.indexOf, - nativeLastIndexOf = ArrayProto.lastIndexOf, - nativeIsArray = Array.isArray, - nativeKeys = Object.keys, - nativeBind = FuncProto.bind; - - // Create a safe reference to the Underscore object for use below. - var _ = function(obj) { return new wrapper(obj); }; - - // Export the Underscore object for **CommonJS**, with backwards-compatibility - // for the old `require()` API. If we're not in CommonJS, add `_` to the - // global object. - if (typeof module !== 'undefined' && module.exports) { - module.exports = _; - _._ = _; - } else { - // Exported as a string, for Closure Compiler "advanced" mode. - root['_'] = _; - } - - // Current version. - _.VERSION = '1.1.7'; - - // Collection Functions - // -------------------- - - // The cornerstone, an `each` implementation, aka `forEach`. - // Handles objects with the built-in `forEach`, arrays, and raw objects. - // Delegates to **ECMAScript 5**'s native `forEach` if available. - var each = _.each = _.forEach = function(obj, iterator, context) { - if (obj == null) return; - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (obj.length === +obj.length) { - for (var i = 0, l = obj.length; i < l; i++) { - if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; - } - } else { - for (var key in obj) { - if (hasOwnProperty.call(obj, key)) { - if (iterator.call(context, obj[key], key, obj) === breaker) return; - } - } - } - }; - - // Return the results of applying the iterator to each element. - // Delegates to **ECMAScript 5**'s native `map` if available. - _.map = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - each(obj, function(value, index, list) { - results[results.length] = iterator.call(context, value, index, list); - }); - return results; - }; - - // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. - _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = memo !== void 0; - if (obj == null) obj = []; - if (nativeReduce && obj.reduce === nativeReduce) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); - } - each(obj, function(value, index, list) { - if (!initial) { - memo = value; - initial = true; - } else { - memo = iterator.call(context, memo, value, index, list); - } - }); - if (!initial) throw new TypeError("Reduce of empty array with no initial value"); - return memo; - }; - - // The right-associative version of reduce, also known as `foldr`. - // Delegates to **ECMAScript 5**'s native `reduceRight` if available. - _.reduceRight = _.foldr = function(obj, iterator, memo, context) { - if (obj == null) obj = []; - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { - if (context) iterator = _.bind(iterator, context); - return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); - } - var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); - return _.reduce(reversed, iterator, memo, context); - }; - - // Return the first value which passes a truth test. Aliased as `detect`. - _.find = _.detect = function(obj, iterator, context) { - var result; - any(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) { - result = value; - return true; - } - }); - return result; - }; - - // Return all the elements that pass a truth test. - // Delegates to **ECMAScript 5**'s native `filter` if available. - // Aliased as `select`. - _.filter = _.select = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); - each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Return all the elements for which a truth test fails. - _.reject = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - each(obj, function(value, index, list) { - if (!iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Determine whether all of the elements match a truth test. - // Delegates to **ECMAScript 5**'s native `every` if available. - // Aliased as `all`. - _.every = _.all = function(obj, iterator, context) { - var result = true; - if (obj == null) return result; - if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); - each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) return breaker; - }); - return result; - }; - - // Determine if at least one element in the object matches a truth test. - // Delegates to **ECMAScript 5**'s native `some` if available. - // Aliased as `any`. - var any = _.some = _.any = function(obj, iterator, context) { - iterator = iterator || _.identity; - var result = false; - if (obj == null) return result; - if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); - each(obj, function(value, index, list) { - if (result |= iterator.call(context, value, index, list)) return breaker; - }); - return !!result; - }; - - // Determine if a given value is included in the array or object using `===`. - // Aliased as `contains`. - _.include = _.contains = function(obj, target) { - var found = false; - if (obj == null) return found; - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - any(obj, function(value) { - if (found = value === target) return true; - }); - return found; - }; - - // Invoke a method (with arguments) on every item in a collection. - _.invoke = function(obj, method) { - var args = slice.call(arguments, 2); - return _.map(obj, function(value) { - return (method.call ? method || value : value[method]).apply(value, args); - }); - }; - - // Convenience version of a common use case of `map`: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, function(value){ return value[key]; }); - }; - - // Return the maximum element or (element-based computation). - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); - var result = {computed : -Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed >= result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); - var result = {computed : Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed < result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, iterator, context) { - return _.pluck(_.map(obj, function(value, index, list) { - return { - value : value, - criteria : iterator.call(context, value, index, list) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }), 'value'); - }; - - // Groups the object's values by a criterion produced by an iterator - _.groupBy = function(obj, iterator) { - var result = {}; - each(obj, function(value, index) { - var key = iterator(value, index); - (result[key] || (result[key] = [])).push(value); - }); - return result; - }; - - // Use a comparator function to figure out at what index an object should - // be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator) { - iterator || (iterator = _.identity); - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >> 1; - iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; - } - return low; - }; - - // Safely convert anything iterable into a real, live array. - _.toArray = function(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - if (_.isArray(iterable)) return slice.call(iterable); - if (_.isArguments(iterable)) return slice.call(iterable); - return _.values(iterable); - }; - - // Return the number of elements in an object. - _.size = function(obj) { - return _.toArray(obj).length; - }; - - // Array Functions - // --------------- - - // Get the first element of an array. Passing **n** will return the first N - // values in the array. Aliased as `head`. The **guard** check allows it to work - // with `_.map`. - _.first = _.head = function(array, n, guard) { - return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; - }; - - // Returns everything but the first entry of the array. Aliased as `tail`. - // Especially useful on the arguments object. Passing an **index** will return - // the rest of the values in the array from that index onward. The **guard** - // check allows it to work with `_.map`. - _.rest = _.tail = function(array, index, guard) { - return slice.call(array, (index == null) || guard ? 1 : index); - }; - - // Get the last element of an array. - _.last = function(array) { - return array[array.length - 1]; - }; - - // Trim out all falsy values from an array. - _.compact = function(array) { - return _.filter(array, function(value){ return !!value; }); - }; - - // Return a completely flattened version of an array. - _.flatten = function(array) { - return _.reduce(array, function(memo, value) { - if (_.isArray(value)) return memo.concat(_.flatten(value)); - memo[memo.length] = value; - return memo; - }, []); - }; - - // Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - return _.difference(array, slice.call(arguments, 1)); - }; - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted) { - return _.reduce(array, function(memo, el, i) { - if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el; - return memo; - }, []); - }; - - // Produce an array that contains the union: each distinct element from all of - // the passed-in arrays. - _.union = function() { - return _.uniq(_.flatten(arguments)); - }; - - // Produce an array that contains every item shared between all the - // passed-in arrays. (Aliased as "intersect" for back-compat.) - _.intersection = _.intersect = function(array) { - var rest = slice.call(arguments, 1); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.indexOf(other, item) >= 0; - }); - }); - }; - - // Take the difference between one array and another. - // Only the elements present in just the first array will remain. - _.difference = function(array, other) { - return _.filter(array, function(value){ return !_.include(other, value); }); - }; - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - _.zip = function() { - var args = slice.call(arguments); - var length = _.max(_.pluck(args, 'length')); - var results = new Array(length); - for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); - return results; - }; - - // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), - // we need this function. Return the position of the first occurrence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to **ECMAScript 5**'s native `indexOf` if available. - // If the array is large and already in sort order, pass `true` - // for **isSorted** to use binary search. - _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i, l; - if (isSorted) { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); - for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; - return -1; - }; - - - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. - _.lastIndexOf = function(array, item) { - if (array == null) return -1; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); - var i = array.length; - while (i--) if (array[i] === item) return i; - return -1; - }; - - // Generate an integer Array containing an arithmetic progression. A port of - // the native Python `range()` function. See - // [the Python documentation](http://docs.python.org/library/functions.html#range). - _.range = function(start, stop, step) { - if (arguments.length <= 1) { - stop = start || 0; - start = 0; - } - step = arguments[2] || 1; - - var len = Math.max(Math.ceil((stop - start) / step), 0); - var idx = 0; - var range = new Array(len); - - while(idx < len) { - range[idx++] = start; - start += step; - } - - return range; - }; - - // Function (ahem) Functions - // ------------------ - - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). Binding with arguments is also known as `curry`. - // Delegates to **ECMAScript 5**'s native `Function.bind` if available. - // We check for `func.bind` first, to fail fast when `func` is undefined. - _.bind = function(func, obj) { - if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - var args = slice.call(arguments, 2); - return function() { - return func.apply(obj, args.concat(slice.call(arguments))); - }; - }; - - // Bind all of an object's methods to that object. Useful for ensuring that - // all callbacks defined on an object belong to it. - _.bindAll = function(obj) { - var funcs = slice.call(arguments, 1); - if (funcs.length == 0) funcs = _.functions(obj); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); - return obj; - }; - - // Memoize an expensive function by storing its results. - _.memoize = function(func, hasher) { - var memo = {}; - hasher || (hasher = _.identity); - return function() { - var key = hasher.apply(this, arguments); - return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); - }; - }; - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - _.delay = function(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(func, args); }, wait); - }; - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; - - // Internal function used to implement `_.throttle` and `_.debounce`. - var limit = function(func, wait, debounce) { - var timeout; - return function() { - var context = this, args = arguments; - var throttler = function() { - timeout = null; - func.apply(context, args); - }; - if (debounce) clearTimeout(timeout); - if (debounce || !timeout) timeout = setTimeout(throttler, wait); - }; - }; - - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. - _.throttle = function(func, wait) { - return limit(func, wait, false); - }; - - // Returns a function, that, as long as it continues to be invoked, will not - // be triggered. The function will be called after it stops being called for - // N milliseconds. - _.debounce = function(func, wait) { - return limit(func, wait, true); - }; - - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = function(func) { - var ran = false, memo; - return function() { - if (ran) return memo; - ran = true; - return memo = func.apply(this, arguments); - }; - }; - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - _.wrap = function(func, wrapper) { - return function() { - var args = [func].concat(slice.call(arguments)); - return wrapper.apply(this, args); - }; - }; - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = slice.call(arguments); - return function() { - var args = slice.call(arguments); - for (var i = funcs.length - 1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; - }; - }; - - // Returns a function that will only be executed after being called N times. - _.after = function(times, func) { - return function() { - if (--times < 1) { return func.apply(this, arguments); } - }; - }; - - - // Object Functions - // ---------------- - - // Retrieve the names of an object's properties. - // Delegates to **ECMAScript 5**'s native `Object.keys` - _.keys = nativeKeys || function(obj) { - if (obj !== Object(obj)) throw new TypeError('Invalid object'); - var keys = []; - for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; - return keys; - }; - - // Retrieve the values of an object's properties. - _.values = function(obj) { - return _.map(obj, _.identity); - }; - - // Return a sorted list of the function names available on the object. - // Aliased as `methods` - _.functions = _.methods = function(obj) { - var names = []; - for (var key in obj) { - if (_.isFunction(obj[key])) names.push(key); - } - return names.sort(); - }; - - // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - if (source[prop] !== void 0) obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Fill in a given object with default properties. - _.defaults = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - if (obj[prop] == null) obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - return _.isArray(obj) ? obj.slice() : _.extend({}, obj); - }; - - // Invokes interceptor with the obj, and then returns obj. - // The primary purpose of this method is to "tap into" a method chain, in - // order to perform operations on intermediate results within the chain. - _.tap = function(obj, interceptor) { - interceptor(obj); - return obj; - }; - - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - // Check object identity. - if (a === b) return true; - // Different types? - var atype = typeof(a), btype = typeof(b); - if (atype != btype) return false; - // Basic equality test (watch out for coercions). - if (a == b) return true; - // One is falsy and the other truthy. - if ((!a && b) || (a && !b)) return false; - // Unwrap any wrapped objects. - if (a._chain) a = a._wrapped; - if (b._chain) b = b._wrapped; - // One of them implements an isEqual()? - if (a.isEqual) return a.isEqual(b); - if (b.isEqual) return b.isEqual(a); - // Check dates' integer values. - if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); - // Both are NaN? - if (_.isNaN(a) && _.isNaN(b)) return false; - // Compare regular expressions. - if (_.isRegExp(a) && _.isRegExp(b)) - return a.source === b.source && - a.global === b.global && - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; - // If a is not an object by this point, we can't handle it. - if (atype !== 'object') return false; - // Check for different array lengths before comparing contents. - if (a.length && (a.length !== b.length)) return false; - // Nothing else worked, deep compare the contents. - var aKeys = _.keys(a), bKeys = _.keys(b); - // Different object sizes? - if (aKeys.length != bKeys.length) return false; - // Recursive comparison of contents. - for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; - return true; - }; - - // Is a given array or object empty? - _.isEmpty = function(obj) { - if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; - for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; - return true; - }; - - // Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType == 1); - }; - - // Is a given value an array? - // Delegates to ECMA5's native Array.isArray - _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) === '[object Array]'; - }; - - // Is a given variable an object? - _.isObject = function(obj) { - return obj === Object(obj); - }; - - // Is a given variable an arguments object? - _.isArguments = function(obj) { - return !!(obj && hasOwnProperty.call(obj, 'callee')); - }; - - // Is a given value a function? - _.isFunction = function(obj) { - return !!(obj && obj.constructor && obj.call && obj.apply); - }; - - // Is a given value a string? - _.isString = function(obj) { - return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); - }; - - // Is a given value a number? - _.isNumber = function(obj) { - return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); - }; - - // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript - // that does not equal itself. - _.isNaN = function(obj) { - return obj !== obj; - }; - - // Is a given value a boolean? - _.isBoolean = function(obj) { - return obj === true || obj === false; - }; - - // Is a given value a date? - _.isDate = function(obj) { - return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); - }; - - // Is the given value a regular expression? - _.isRegExp = function(obj) { - return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); - }; - - // Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - // Is a given variable undefined? - _.isUndefined = function(obj) { - return obj === void 0; - }; - - // Utility Functions - // ----------------- - - // Run Underscore.js in *noConflict* mode, returning the `_` variable to its - // previous owner. Returns a reference to the Underscore object. - _.noConflict = function() { - root._ = previousUnderscore; - return this; - }; - - // Keep the identity function around for default iterators. - _.identity = function(value) { - return value; - }; - - // Run a function **n** times. - _.times = function (n, iterator, context) { - for (var i = 0; i < n; i++) iterator.call(context, i); - }; - - // Add your own custom functions to the Underscore object, ensuring that - // they're correctly added to the OOP wrapper as well. - _.mixin = function(obj) { - each(_.functions(obj), function(name){ - addToWrapper(name, _[name] = obj[name]); - }); - }; - - // Generate a unique integer id (unique within the entire client session). - // Useful for temporary DOM ids. - var idCounter = 0; - _.uniqueId = function(prefix) { - var id = idCounter++; - return prefix ? prefix + id : id; - }; - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g - }; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - _.template = function(str, data) { - var c = _.templateSettings; - var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + - 'with(obj||{}){__p.push(\'' + - str.replace(/\\/g, '\\\\') - .replace(/'/g, "\\'") - .replace(c.interpolate, function(match, code) { - return "'," + code.replace(/\\'/g, "'") + ",'"; - }) - .replace(c.evaluate || null, function(match, code) { - return "');" + code.replace(/\\'/g, "'") - .replace(/[\r\n\t]/g, ' ') + "__p.push('"; - }) - .replace(/\r/g, '\\r') - .replace(/\n/g, '\\n') - .replace(/\t/g, '\\t') - + "');}return __p.join('');"; - var func = new Function('obj', tmpl); - return data ? func(data) : func; - }; - - // The OOP Wrapper - // --------------- - - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - var wrapper = function(obj) { this._wrapped = obj; }; - - // Expose `wrapper.prototype` as `_.prototype` - _.prototype = wrapper.prototype; - - // Helper function to continue chaining intermediate results. - var result = function(obj, chain) { - return chain ? _(obj).chain() : obj; - }; - - // A method to easily add functions to the OOP wrapper. - var addToWrapper = function(name, func) { - wrapper.prototype[name] = function() { - var args = slice.call(arguments); - unshift.call(args, this._wrapped); - return result(func.apply(_, args), this._chain); - }; - }; - - // Add all of the Underscore functions to the wrapper object. - _.mixin(_); - - // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - method.apply(this._wrapped, arguments); - return result(this._wrapped, this._chain); - }; - }); - - // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - return result(method.apply(this._wrapped, arguments), this._chain); - }; - }); - - // Start chaining a wrapped Underscore object. - wrapper.prototype.chain = function() { - this._chain = true; - return this; - }; - - // Extracts the result from a wrapped and chained object. - wrapper.prototype.value = function() { - return this._wrapped; - }; - -})(); -/** - * Flotr2 (c) 2012 Carl Sutherland - * MIT License - * Special thanks to: - * Flotr: http://code.google.com/p/flotr/ (fork) - * Flot: https://github.com/flot/flot (original fork) - */ -(function () { - -var - global = this, - previousFlotr = this.Flotr, - Flotr; - -Flotr = { - _: _, - bean: bean, - isIphone: /iphone/i.test(navigator.userAgent), - isIE: (navigator.appVersion.indexOf("MSIE") != -1 ? parseFloat(navigator.appVersion.split("MSIE")[1]) : false), - - /** - * An object of the registered graph types. Use Flotr.addType(type, object) - * to add your own type. - */ - graphTypes: {}, - - /** - * The list of the registered plugins - */ - plugins: {}, - - /** - * Can be used to add your own chart type. - * @param {String} name - Type of chart, like 'pies', 'bars' etc. - * @param {String} graphType - The object containing the basic drawing functions (draw, etc) - */ - addType: function(name, graphType){ - Flotr.graphTypes[name] = graphType; - Flotr.defaultOptions[name] = graphType.options || {}; - Flotr.defaultOptions.defaultType = Flotr.defaultOptions.defaultType || name; - }, - - /** - * Can be used to add a plugin - * @param {String} name - The name of the plugin - * @param {String} plugin - The object containing the plugin's data (callbacks, options, function1, function2, ...) - */ - addPlugin: function(name, plugin){ - Flotr.plugins[name] = plugin; - Flotr.defaultOptions[name] = plugin.options || {}; - }, - - /** - * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha. - * You could also draw graphs by directly calling Flotr.Graph(element, data, options). - * @param {Element} el - element to insert the graph into - * @param {Object} data - an array or object of dataseries - * @param {Object} options - an object containing options - * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph - * @return {Object} returns a new graph object and of course draws the graph. - */ - draw: function(el, data, options, GraphKlass){ - GraphKlass = GraphKlass || Flotr.Graph; - return new GraphKlass(el, data, options); - }, - - /** - * Recursively merges two objects. - * @param {Object} src - source object (likely the object with the least properties) - * @param {Object} dest - destination object (optional, object with the most properties) - * @return {Object} recursively merged Object - * @TODO See if we can't remove this. - */ - merge: function(src, dest){ - var i, v, result = dest || {}; - - for (i in src) { - v = src[i]; - if (v && typeof(v) === 'object') { - if (v.constructor === Array) { - result[i] = this._.clone(v); - } else if (v.constructor !== RegExp && !this._.isElement(v)) { - result[i] = Flotr.merge(v, (dest ? dest[i] : undefined)); - } else { - result[i] = v; - } - } else { - result[i] = v; - } - } - - return result; - }, - - /** - * Recursively clones an object. - * @param {Object} object - The object to clone - * @return {Object} the clone - * @TODO See if we can't remove this. - */ - clone: function(object){ - return Flotr.merge(object, {}); - }, - - /** - * Function calculates the ticksize and returns it. - * @param {Integer} noTicks - number of ticks - * @param {Integer} min - lower bound integer value for the current axis - * @param {Integer} max - upper bound integer value for the current axis - * @param {Integer} decimals - number of decimals for the ticks - * @return {Integer} returns the ticksize in pixels - */ - getTickSize: function(noTicks, min, max, decimals){ - var delta = (max - min) / noTicks, - magn = Flotr.getMagnitude(delta), - tickSize = 10, - norm = delta / magn; // Norm is between 1.0 and 10.0. - - if(norm < 1.5) tickSize = 1; - else if(norm < 2.25) tickSize = 2; - else if(norm < 3) tickSize = ((decimals === 0) ? 2 : 2.5); - else if(norm < 7.5) tickSize = 5; - - return tickSize * magn; - }, - - /** - * Default tick formatter. - * @param {String, Integer} val - tick value integer - * @param {Object} axisOpts - the axis' options - * @return {String} formatted tick string - */ - defaultTickFormatter: function(val, axisOpts){ - return val+''; - }, - - /** - * Formats the mouse tracker values. - * @param {Object} obj - Track value Object {x:..,y:..} - * @return {String} Formatted track string - */ - defaultTrackFormatter: function(obj){ - return '('+obj.x+', '+obj.y+')'; - }, - - /** - * Utility function to convert file size values in bytes to kB, MB, ... - * @param value {Number} - The value to convert - * @param precision {Number} - The number of digits after the comma (default: 2) - * @param base {Number} - The base (default: 1000) - */ - engineeringNotation: function(value, precision, base){ - var sizes = ['Y','Z','E','P','T','G','M','k',''], - fractionSizes = ['y','z','a','f','p','n','µ','m',''], - total = sizes.length; - - base = base || 1000; - precision = Math.pow(10, precision || 2); - - if (value === 0) return 0; - - if (value > 1) { - while (total-- && (value >= base)) value /= base; - } - else { - sizes = fractionSizes; - total = sizes.length; - while (total-- && (value < 1)) value *= base; - } - - return (Math.round(value * precision) / precision) + sizes[total]; - }, - - /** - * Returns the magnitude of the input value. - * @param {Integer, Float} x - integer or float value - * @return {Integer, Float} returns the magnitude of the input value - */ - getMagnitude: function(x){ - return Math.pow(10, Math.floor(Math.log(x) / Math.LN10)); - }, - toPixel: function(val){ - return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val); - }, - toRad: function(angle){ - return -angle * (Math.PI/180); - }, - floorInBase: function(n, base) { - return base * Math.floor(n / base); - }, - drawText: function(ctx, text, x, y, style) { - if (!ctx.fillText) { - ctx.drawText(text, x, y, style); - return; - } - - style = this._.extend({ - size: Flotr.defaultOptions.fontSize, - color: '#000000', - textAlign: 'left', - textBaseline: 'bottom', - weight: 1, - angle: 0 - }, style); - - ctx.save(); - ctx.translate(x, y); - ctx.rotate(style.angle); - ctx.fillStyle = style.color; - ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif"; - ctx.textAlign = style.textAlign; - ctx.textBaseline = style.textBaseline; - ctx.fillText(text, 0, 0); - ctx.restore(); - }, - getBestTextAlign: function(angle, style) { - style = style || {textAlign: 'center', textBaseline: 'middle'}; - angle += Flotr.getTextAngleFromAlign(style); - - if (Math.abs(Math.cos(angle)) > 10e-3) - style.textAlign = (Math.cos(angle) > 0 ? 'right' : 'left'); - - if (Math.abs(Math.sin(angle)) > 10e-3) - style.textBaseline = (Math.sin(angle) > 0 ? 'top' : 'bottom'); - - return style; - }, - alignTable: { - 'right middle' : 0, - 'right top' : Math.PI/4, - 'center top' : Math.PI/2, - 'left top' : 3*(Math.PI/4), - 'left middle' : Math.PI, - 'left bottom' : -3*(Math.PI/4), - 'center bottom': -Math.PI/2, - 'right bottom' : -Math.PI/4, - 'center middle': 0 - }, - getTextAngleFromAlign: function(style) { - return Flotr.alignTable[style.textAlign+' '+style.textBaseline] || 0; - }, - noConflict : function () { - global.Flotr = previousFlotr; - return this; - } -}; - -global.Flotr = Flotr; - -})(); - -/** - * Flotr Defaults - */ -Flotr.defaultOptions = { - colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated. - ieBackgroundColor: '#FFFFFF', // Background color for excanvas clipping - title: null, // => The graph's title - subtitle: null, // => The graph's subtitle - shadowSize: 4, // => size of the 'fake' shadow - defaultType: null, // => default series type - HtmlText: true, // => wether to draw the text using HTML or on the canvas - fontColor: '#545454', // => default font color - fontSize: 7.5, // => canvas' text font size - resolution: 1, // => resolution of the graph, to have printer-friendly graphs ! - parseFloat: true, // => whether to preprocess data for floats (ie. if input is string) - xaxis: { - ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] - minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3] - showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise - showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide - labelsAngle: 0, // => labels' angle, in degrees - title: null, // => axis title - titleAngle: 0, // => axis title's angle, in degrees - noTicks: 5, // => number of ticks for automagically generated ticks - minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks - tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string - tickDecimals: null, // => no. of decimals, null means auto - min: null, // => min. value to show, null means set automatically - max: null, // => max. value to show, null means set automatically - autoscale: false, // => Turns autoscaling on with true - autoscaleMargin: 0, // => margin in % to add if auto-setting min/max - color: null, // => color of the ticks - mode: 'normal', // => can be 'time' or 'normal' - timeFormat: null, - timeMode:'UTC', // => For UTC time ('local' for local time). - timeUnit:'millisecond',// => Unit for time (millisecond, second, minute, hour, day, month, year) - scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic' - base: Math.E, - titleAlign: 'center', - margin: true // => Turn off margins with false - }, - x2axis: {}, - yaxis: { - ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] - minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3] - showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise - showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide - labelsAngle: 0, // => labels' angle, in degrees - title: null, // => axis title - titleAngle: 90, // => axis title's angle, in degrees - noTicks: 5, // => number of ticks for automagically generated ticks - minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks - tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string - tickDecimals: null, // => no. of decimals, null means auto - min: null, // => min. value to show, null means set automatically - max: null, // => max. value to show, null means set automatically - autoscale: false, // => Turns autoscaling on with true - autoscaleMargin: 0, // => margin in % to add if auto-setting min/max - color: null, // => The color of the ticks - scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic' - base: Math.E, - titleAlign: 'center', - margin: true // => Turn off margins with false - }, - y2axis: { - titleAngle: 270 - }, - grid: { - color: '#545454', // => primary color used for outline and labels - backgroundColor: null, // => null for transparent, else color - backgroundImage: null, // => background image. String or object with src, left and top - watermarkAlpha: 0.4, // => - tickColor: '#DDDDDD', // => color used for the ticks - labelMargin: 3, // => margin in pixels - verticalLines: true, // => whether to show gridlines in vertical direction - minorVerticalLines: null, // => whether to show gridlines for minor ticks in vertical dir. - horizontalLines: true, // => whether to show gridlines in horizontal direction - minorHorizontalLines: null, // => whether to show gridlines for minor ticks in horizontal dir. - outlineWidth: 1, // => width of the grid outline/border in pixels - outline : 'nsew', // => walls of the outline to display - circular: false // => if set to true, the grid will be circular, must be used when radars are drawn - }, - mouse: { - track: false, // => true to track the mouse, no tracking otherwise - trackAll: false, - position: 'se', // => position of the value box (default south-east) - relative: false, // => next to the mouse cursor - trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box - margin: 5, // => margin in pixels of the valuebox - lineColor: '#FF3F19', // => line color of points that are drawn when mouse comes near a value of a series - trackDecimals: 1, // => decimals for the track values - sensibility: 2, // => the lower this number, the more precise you have to aim to show a value - trackY: true, // => whether or not to track the mouse in the y axis - radius: 3, // => radius of the track point - fillColor: null, // => color to fill our select bar with only applies to bar and similar graphs (only bars for now) - fillOpacity: 0.4 // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - } -}; - -/** - * Flotr Color - */ - -(function () { - -var - _ = Flotr._; - -// Constructor -function Color (r, g, b, a) { - this.rgba = ['r','g','b','a']; - var x = 4; - while(-1<--x){ - this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0); - } - this.normalize(); -} - -// Constants -var COLOR_NAMES = { - 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] -}; - -Color.prototype = { - scale: function(rf, gf, bf, af){ - var x = 4; - while (-1 < --x) { - if (!_.isUndefined(arguments[x])) this[this.rgba[x]] *= arguments[x]; - } - return this.normalize(); - }, - alpha: function(alpha) { - if (!_.isUndefined(alpha) && !_.isNull(alpha)) { - this.a = alpha; - } - return this.normalize(); - }, - clone: function(){ - return new Color(this.r, this.b, this.g, this.a); - }, - limit: function(val,minVal,maxVal){ - return Math.max(Math.min(val, maxVal), minVal); - }, - normalize: function(){ - var limit = this.limit; - this.r = limit(parseInt(this.r, 10), 0, 255); - this.g = limit(parseInt(this.g, 10), 0, 255); - this.b = limit(parseInt(this.b, 10), 0, 255); - this.a = limit(this.a, 0, 1); - return this; - }, - distance: function(color){ - if (!color) return; - color = new Color.parse(color); - var dist = 0, x = 3; - while(-1<--x){ - dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]); - } - return dist; - }, - toString: function(){ - return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')'; - }, - contrast: function () { - var - test = 1 - ( 0.299 * this.r + 0.587 * this.g + 0.114 * this.b) / 255; - return (test < 0.5 ? '#000000' : '#ffffff'); - } -}; - -_.extend(Color, { - /** - * Parses a color string and returns a corresponding Color. - * The different tests are in order of probability to improve speed. - * @param {String, Color} str - string thats representing a color - * @return {Color} returns a Color object or false - */ - parse: function(color){ - if (color instanceof Color) return color; - - var result; - - // #a0b1c2 - if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))) - return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)); - - // rgb(num,num,num) - if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))) - return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10)); - - // #fff - if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))) - return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)); - - // rgba(num,num,num,num) - if((result = /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(color))) - return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4])); - - // rgb(num%,num%,num%) - if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))) - return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55); - - // rgba(num%,num%,num%,num) - if((result = /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(color))) - return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4])); - - // Otherwise, we're most likely dealing with a named color. - var name = (color+'').replace(/^\s*([\S\s]*?)\s*$/, '$1').toLowerCase(); - if(name == 'transparent'){ - return new Color(255, 255, 255, 0); - } - return (result = COLOR_NAMES[name]) ? new Color(result[0], result[1], result[2]) : new Color(0, 0, 0, 0); - }, - - /** - * Process color and options into color style. - */ - processColor: function(color, options) { - - var opacity = options.opacity; - if (!color) return 'rgba(0, 0, 0, 0)'; - if (color instanceof Color) return color.alpha(opacity).toString(); - if (_.isString(color)) return Color.parse(color).alpha(opacity).toString(); - - var grad = color.colors ? color : {colors: color}; - - if (!options.ctx) { - if (!_.isArray(grad.colors)) return 'rgba(0, 0, 0, 0)'; - return Color.parse(_.isArray(grad.colors[0]) ? grad.colors[0][1] : grad.colors[0]).alpha(opacity).toString(); - } - grad = _.extend({start: 'top', end: 'bottom'}, grad); - - if (/top/i.test(grad.start)) options.x1 = 0; - if (/left/i.test(grad.start)) options.y1 = 0; - if (/bottom/i.test(grad.end)) options.x2 = 0; - if (/right/i.test(grad.end)) options.y2 = 0; - - var i, c, stop, gradient = options.ctx.createLinearGradient(options.x1, options.y1, options.x2, options.y2); - for (i = 0; i < grad.colors.length; i++) { - c = grad.colors[i]; - if (_.isArray(c)) { - stop = c[0]; - c = c[1]; - } - else stop = i / (grad.colors.length-1); - gradient.addColorStop(stop, Color.parse(c).alpha(opacity)); - } - return gradient; - } -}); - -Flotr.Color = Color; - -})(); - -/** - * Flotr Date - */ -Flotr.Date = { - - set : function (date, name, mode, value) { - mode = mode || 'UTC'; - name = 'set' + (mode === 'UTC' ? 'UTC' : '') + name; - date[name](value); - }, - - get : function (date, name, mode) { - mode = mode || 'UTC'; - name = 'get' + (mode === 'UTC' ? 'UTC' : '') + name; - return date[name](); - }, - - format: function(d, format, mode) { - if (!d) return; - - // We should maybe use an "official" date format spec, like PHP date() or ColdFusion - // http://fr.php.net/manual/en/function.date.php - // http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_c-d_29.html - var - get = this.get, - tokens = { - h: get(d, 'Hours', mode).toString(), - H: leftPad(get(d, 'Hours', mode)), - M: leftPad(get(d, 'Minutes', mode)), - S: leftPad(get(d, 'Seconds', mode)), - s: get(d, 'Milliseconds', mode), - d: get(d, 'Date', mode).toString(), - m: (get(d, 'Month') + 1).toString(), - y: get(d, 'FullYear').toString(), - b: Flotr.Date.monthNames[get(d, 'Month', mode)] - }; - - function leftPad(n){ - n += ''; - return n.length == 1 ? "0" + n : n; - } - - var r = [], c, - escape = false; - - for (var i = 0; i < format.length; ++i) { - c = format.charAt(i); - - if (escape) { - r.push(tokens[c] || c); - escape = false; - } - else if (c == "%") - escape = true; - else - r.push(c); - } - return r.join(''); - }, - getFormat: function(time, span) { - var tu = Flotr.Date.timeUnits; - if (time < tu.second) return "%h:%M:%S.%s"; - else if (time < tu.minute) return "%h:%M:%S"; - else if (time < tu.day) return (span < 2 * tu.day) ? "%h:%M" : "%b %d %h:%M"; - else if (time < tu.month) return "%b %d"; - else if (time < tu.year) return (span < tu.year) ? "%b" : "%b %y"; - else return "%y"; - }, - formatter: function (v, axis) { - var - options = axis.options, - scale = Flotr.Date.timeUnits[options.timeUnit], - d = new Date(v * scale); - - // first check global format - if (axis.options.timeFormat) - return Flotr.Date.format(d, options.timeFormat, options.timeMode); - - var span = (axis.max - axis.min) * scale, - t = axis.tickSize * Flotr.Date.timeUnits[axis.tickUnit]; - - return Flotr.Date.format(d, Flotr.Date.getFormat(t, span), options.timeMode); - }, - generator: function(axis) { - - var - set = this.set, - get = this.get, - timeUnits = this.timeUnits, - spec = this.spec, - options = axis.options, - mode = options.timeMode, - scale = timeUnits[options.timeUnit], - min = axis.min * scale, - max = axis.max * scale, - delta = (max - min) / options.noTicks, - ticks = [], - tickSize = axis.tickSize, - tickUnit, - formatter, i; - - // Use custom formatter or time tick formatter - formatter = (options.tickFormatter === Flotr.defaultTickFormatter ? - this.formatter : options.tickFormatter - ); - - for (i = 0; i < spec.length - 1; ++i) { - var d = spec[i][0] * timeUnits[spec[i][1]]; - if (delta < (d + spec[i+1][0] * timeUnits[spec[i+1][1]]) / 2 && d >= tickSize) - break; - } - tickSize = spec[i][0]; - tickUnit = spec[i][1]; - - // special-case the possibility of several years - if (tickUnit == "year") { - tickSize = Flotr.getTickSize(options.noTicks*timeUnits.year, min, max, 0); - - // Fix for 0.5 year case - if (tickSize == 0.5) { - tickUnit = "month"; - tickSize = 6; - } - } - - axis.tickUnit = tickUnit; - axis.tickSize = tickSize; - - var - d = new Date(min); - - var step = tickSize * timeUnits[tickUnit]; - - function setTick (name) { - set(d, name, mode, Flotr.floorInBase( - get(d, name, mode), tickSize - )); - } - - switch (tickUnit) { - case "millisecond": setTick('Milliseconds'); break; - case "second": setTick('Seconds'); break; - case "minute": setTick('Minutes'); break; - case "hour": setTick('Hours'); break; - case "month": setTick('Month'); break; - case "year": setTick('FullYear'); break; - } - - // reset smaller components - if (step >= timeUnits.second) set(d, 'Milliseconds', mode, 0); - if (step >= timeUnits.minute) set(d, 'Seconds', mode, 0); - if (step >= timeUnits.hour) set(d, 'Minutes', mode, 0); - if (step >= timeUnits.day) set(d, 'Hours', mode, 0); - if (step >= timeUnits.day * 4) set(d, 'Date', mode, 1); - if (step >= timeUnits.year) set(d, 'Month', mode, 0); - - var carry = 0, v = NaN, prev; - do { - prev = v; - v = d.getTime(); - ticks.push({ v: v / scale, label: formatter(v / scale, axis) }); - if (tickUnit == "month") { - if (tickSize < 1) { - /* a bit complicated - we'll divide the month up but we need to take care of fractions - so we don't end up in the middle of a day */ - set(d, 'Date', mode, 1); - var start = d.getTime(); - set(d, 'Month', mode, get(d, 'Month', mode) + 1) - var end = d.getTime(); - d.setTime(v + carry * timeUnits.hour + (end - start) * tickSize); - carry = get(d, 'Hours', mode) - set(d, 'Hours', mode, 0); - } - else - set(d, 'Month', mode, get(d, 'Month', mode) + tickSize); - } - else if (tickUnit == "year") { - set(d, 'FullYear', mode, get(d, 'FullYear', mode) + tickSize); - } - else - d.setTime(v + step); - - } while (v < max && v != prev); - - return ticks; - }, - timeUnits: { - millisecond: 1, - second: 1000, - minute: 1000 * 60, - hour: 1000 * 60 * 60, - day: 1000 * 60 * 60 * 24, - month: 1000 * 60 * 60 * 24 * 30, - year: 1000 * 60 * 60 * 24 * 365.2425 - }, - // the allowed tick sizes, after 1 year we use an integer algorithm - spec: [ - [1, "millisecond"], [20, "millisecond"], [50, "millisecond"], [100, "millisecond"], [200, "millisecond"], [500, "millisecond"], - [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"], [3, "month"], [6, "month"], - [1, "year"] - ], - monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] -}; - -(function () { - -var _ = Flotr._; - -Flotr.DOM = { - addClass: function(element, name){ - var classList = (element.className ? element.className : ''); - if (_.include(classList.split(/\s+/g), name)) return; - element.className = (classList ? classList + ' ' : '') + name; - }, - /** - * Create an element. - */ - create: function(tag){ - return document.createElement(tag); - }, - node: function(html) { - var div = Flotr.DOM.create('div'), n; - div.innerHTML = html; - n = div.children[0]; - div.innerHTML = ''; - return n; - }, - /** - * Remove all children. - */ - empty: function(element){ - element.innerHTML = ''; - /* - if (!element) return; - _.each(element.childNodes, function (e) { - Flotr.DOM.empty(e); - element.removeChild(e); - }); - */ - }, - hide: function(element){ - Flotr.DOM.setStyles(element, {display:'none'}); - }, - /** - * Insert a child. - * @param {Element} element - * @param {Element|String} Element or string to be appended. - */ - insert: function(element, child){ - if(_.isString(child)) - element.innerHTML += child; - else if (_.isElement(child)) - element.appendChild(child); - }, - // @TODO find xbrowser implementation - opacity: function(element, opacity) { - element.style.opacity = opacity; - }, - position: function(element, p){ - if (!element.offsetParent) - return {left: (element.offsetLeft || 0), top: (element.offsetTop || 0)}; - - p = this.position(element.offsetParent); - p.left += element.offsetLeft; - p.top += element.offsetTop; - return p; - }, - removeClass: function(element, name) { - var classList = (element.className ? element.className : ''); - element.className = _.filter(classList.split(/\s+/g), function (c) { - if (c != name) return true; } - ).join(' '); - }, - setStyles: function(element, o) { - _.each(o, function (value, key) { - element.style[key] = value; - }); - }, - show: function(element){ - Flotr.DOM.setStyles(element, {display:''}); - }, - /** - * Return element size. - */ - size: function(element){ - return { - height : element.offsetHeight, - width : element.offsetWidth }; - } -}; - -})(); - -/** - * Flotr Event Adapter - */ -(function () { -var - F = Flotr, - bean = F.bean; -F.EventAdapter = { - observe: function(object, name, callback) { - bean.add(object, name, callback); - return this; - }, - fire: function(object, name, args) { - bean.fire(object, name, args); - if (typeof(Prototype) != 'undefined') - Event.fire(object, name, args); - // @TODO Someone who uses mootools, add mootools adapter for existing applciations. - return this; - }, - stopObserving: function(object, name, callback) { - bean.remove(object, name, callback); - return this; - }, - eventPointer: function(e) { - if (!F._.isUndefined(e.touches) && e.touches.length > 0) { - return { - x : e.touches[0].pageX, - y : e.touches[0].pageY - }; - } else if (!F._.isUndefined(e.changedTouches) && e.changedTouches.length > 0) { - return { - x : e.changedTouches[0].pageX, - y : e.changedTouches[0].pageY - }; - } else if (e.pageX || e.pageY) { - return { - x : e.pageX, - y : e.pageY - }; - } else if (e.clientX || e.clientY) { - var - d = document, - b = d.body, - de = d.documentElement; - return { - x: e.clientX + b.scrollLeft + de.scrollLeft, - y: e.clientY + b.scrollTop + de.scrollTop - }; - } - } -}; -})(); - -/** - * Text Utilities - */ -(function () { - -var - F = Flotr, - D = F.DOM, - _ = F._, - -Text = function (o) { - this.o = o; -}; - -Text.prototype = { - - dimensions : function (text, canvasStyle, htmlStyle, className) { - - if (!text) return { width : 0, height : 0 }; - - return (this.o.html) ? - this.html(text, this.o.element, htmlStyle, className) : - this.canvas(text, canvasStyle); - }, - - canvas : function (text, style) { - - if (!this.o.textEnabled) return; - style = style || {}; - - var - metrics = this.measureText(text, style), - width = metrics.width, - height = style.size || F.defaultOptions.fontSize, - angle = style.angle || 0, - cosAngle = Math.cos(angle), - sinAngle = Math.sin(angle), - widthPadding = 2, - heightPadding = 6, - bounds; - - bounds = { - width: Math.abs(cosAngle * width) + Math.abs(sinAngle * height) + widthPadding, - height: Math.abs(sinAngle * width) + Math.abs(cosAngle * height) + heightPadding - }; - - return bounds; - }, - - html : function (text, element, style, className) { - - var div = D.create('div'); - - D.setStyles(div, { 'position' : 'absolute', 'top' : '-10000px' }); - D.insert(div, '
    ' + text + '
    '); - D.insert(this.o.element, div); - - return D.size(div); - }, - - measureText : function (text, style) { - - var - context = this.o.ctx, - metrics; - - if (!context.fillText || (F.isIphone && context.measure)) { - return { width : context.measure(text, style)}; - } - - style = _.extend({ - size: F.defaultOptions.fontSize, - weight: 1, - angle: 0 - }, style); - - context.save(); - context.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif"; - metrics = context.measureText(text); - context.restore(); - - return metrics; - } -}; - -Flotr.Text = Text; - -})(); - -/** - * Flotr Graph class that plots a graph on creation. - */ -(function () { - -var - D = Flotr.DOM, - E = Flotr.EventAdapter, - _ = Flotr._, - flotr = Flotr; -/** - * Flotr Graph constructor. - * @param {Element} el - element to insert the graph into - * @param {Object} data - an array or object of dataseries - * @param {Object} options - an object containing options - */ -Graph = function(el, data, options){ -// Let's see if we can get away with out this [JS] -// try { - this._setEl(el); - this._initMembers(); - this._initPlugins(); - - E.fire(this.el, 'flotr:beforeinit', [this]); - - this.data = data; - this.series = flotr.Series.getSeries(data); - this._initOptions(options); - this._initGraphTypes(); - this._initCanvas(); - this._text = new flotr.Text({ - element : this.el, - ctx : this.ctx, - html : this.options.HtmlText, - textEnabled : this.textEnabled - }); - E.fire(this.el, 'flotr:afterconstruct', [this]); - this._initEvents(); - - this.findDataRanges(); - this.calculateSpacing(); - - this.draw(_.bind(function() { - E.fire(this.el, 'flotr:afterinit', [this]); - }, this)); -/* - try { - } catch (e) { - try { - console.error(e); - } catch (e2) {} - }*/ -}; - -function observe (object, name, callback) { - E.observe.apply(this, arguments); - this._handles.push(arguments); - return this; -} - -Graph.prototype = { - - destroy: function () { - E.fire(this.el, 'flotr:destroy'); - _.each(this._handles, function (handle) { - E.stopObserving.apply(this, handle); - }); - this._handles = []; - this.el.graph = null; - }, - - observe : observe, - - /** - * @deprecated - */ - _observe : observe, - - processColor: function(color, options){ - var o = { x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight, opacity: 1, ctx: this.ctx }; - _.extend(o, options); - return flotr.Color.processColor(color, o); - }, - /** - * Function determines the min and max values for the xaxis and yaxis. - * - * TODO logarithmic range validation (consideration of 0) - */ - findDataRanges: function(){ - var a = this.axes, - xaxis, yaxis, range; - - _.each(this.series, function (series) { - range = series.getRange(); - if (range) { - xaxis = series.xaxis; - yaxis = series.yaxis; - xaxis.datamin = Math.min(range.xmin, xaxis.datamin); - xaxis.datamax = Math.max(range.xmax, xaxis.datamax); - yaxis.datamin = Math.min(range.ymin, yaxis.datamin); - yaxis.datamax = Math.max(range.ymax, yaxis.datamax); - xaxis.used = (xaxis.used || range.xused); - yaxis.used = (yaxis.used || range.yused); - } - }, this); - - // Check for empty data, no data case (none used) - if (!a.x.used && !a.x2.used) a.x.used = true; - if (!a.y.used && !a.y2.used) a.y.used = true; - - _.each(a, function (axis) { - axis.calculateRange(); - }); - - var - types = _.keys(flotr.graphTypes), - drawn = false; - - _.each(this.series, function (series) { - if (series.hide) return; - _.each(types, function (type) { - if (series[type] && series[type].show) { - this.extendRange(type, series); - drawn = true; - } - }, this); - if (!drawn) { - this.extendRange(this.options.defaultType, series); - } - }, this); - }, - - extendRange : function (type, series) { - if (this[type].extendRange) this[type].extendRange(series, series.data, series[type], this[type]); - if (this[type].extendYRange) this[type].extendYRange(series.yaxis, series.data, series[type], this[type]); - if (this[type].extendXRange) this[type].extendXRange(series.xaxis, series.data, series[type], this[type]); - }, - - /** - * Calculates axis label sizes. - */ - calculateSpacing: function(){ - - var a = this.axes, - options = this.options, - series = this.series, - margin = options.grid.labelMargin, - T = this._text, - x = a.x, - x2 = a.x2, - y = a.y, - y2 = a.y2, - maxOutset = options.grid.outlineWidth, - i, j, l, dim; - - // TODO post refactor, fix this - _.each(a, function (axis) { - axis.calculateTicks(); - axis.calculateTextDimensions(T, options); - }); - - // Title height - dim = T.dimensions( - options.title, - {size: options.fontSize*1.5}, - 'font-size:1em;font-weight:bold;', - 'flotr-title' - ); - this.titleHeight = dim.height; - - // Subtitle height - dim = T.dimensions( - options.subtitle, - {size: options.fontSize}, - 'font-size:smaller;', - 'flotr-subtitle' - ); - this.subtitleHeight = dim.height; - - for(j = 0; j < options.length; ++j){ - if (series[j].points.show){ - maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2); - } - } - - var p = this.plotOffset; - if (x.options.margin === false) { - p.bottom = 0; - p.top = 0; - } else { - p.bottom += (options.grid.circular ? 0 : (x.used && x.options.showLabels ? (x.maxLabel.height + margin) : 0)) + - (x.used && x.options.title ? (x.titleSize.height + margin) : 0) + maxOutset; - - p.top += (options.grid.circular ? 0 : (x2.used && x2.options.showLabels ? (x2.maxLabel.height + margin) : 0)) + - (x2.used && x2.options.title ? (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + maxOutset; - } - if (y.options.margin === false) { - p.left = 0; - p.right = 0; - } else { - p.left += (options.grid.circular ? 0 : (y.used && y.options.showLabels ? (y.maxLabel.width + margin) : 0)) + - (y.used && y.options.title ? (y.titleSize.width + margin) : 0) + maxOutset; - - p.right += (options.grid.circular ? 0 : (y2.used && y2.options.showLabels ? (y2.maxLabel.width + margin) : 0)) + - (y2.used && y2.options.title ? (y2.titleSize.width + margin) : 0) + maxOutset; - } - - p.top = Math.floor(p.top); // In order the outline not to be blured - - this.plotWidth = this.canvasWidth - p.left - p.right; - this.plotHeight = this.canvasHeight - p.bottom - p.top; - - // TODO post refactor, fix this - x.length = x2.length = this.plotWidth; - y.length = y2.length = this.plotHeight; - y.offset = y2.offset = this.plotHeight; - x.setScale(); - x2.setScale(); - y.setScale(); - y2.setScale(); - }, - /** - * Draws grid, labels, series and outline. - */ - draw: function(after) { - - var - context = this.ctx, - i; - - E.fire(this.el, 'flotr:beforedraw', [this.series, this]); - - if (this.series.length) { - - context.save(); - context.translate(this.plotOffset.left, this.plotOffset.top); - - for (i = 0; i < this.series.length; i++) { - if (!this.series[i].hide) this.drawSeries(this.series[i]); - } - - context.restore(); - this.clip(); - } - - E.fire(this.el, 'flotr:afterdraw', [this.series, this]); - if (after) after(); - }, - /** - * Actually draws the graph. - * @param {Object} series - series to draw - */ - drawSeries: function(series){ - - function drawChart (series, typeKey) { - var options = this.getOptions(series, typeKey); - this[typeKey].draw(options); - } - - var drawn = false; - series = series || this.series; - - _.each(flotr.graphTypes, function (type, typeKey) { - if (series[typeKey] && series[typeKey].show && this[typeKey]) { - drawn = true; - drawChart.call(this, series, typeKey); - } - }, this); - - if (!drawn) drawChart.call(this, series, this.options.defaultType); - }, - - getOptions : function (series, typeKey) { - var - type = series[typeKey], - graphType = this[typeKey], - options = { - context : this.ctx, - width : this.plotWidth, - height : this.plotHeight, - fontSize : this.options.fontSize, - fontColor : this.options.fontColor, - textEnabled : this.textEnabled, - htmlText : this.options.HtmlText, - text : this._text, // TODO Is this necessary? - element : this.el, - data : series.data, - color : series.color, - shadowSize : series.shadowSize, - xScale : _.bind(series.xaxis.d2p, series.xaxis), - yScale : _.bind(series.yaxis.d2p, series.yaxis) - }; - - options = flotr.merge(type, options); - - // Fill - options.fillStyle = this.processColor( - type.fillColor || series.color, - {opacity: type.fillOpacity} - ); - - return options; - }, - /** - * Calculates the coordinates from a mouse event object. - * @param {Event} event - Mouse Event object. - * @return {Object} Object with coordinates of the mouse. - */ - getEventPosition: function (e){ - - var - d = document, - b = d.body, - de = d.documentElement, - axes = this.axes, - plotOffset = this.plotOffset, - lastMousePos = this.lastMousePos, - pointer = E.eventPointer(e), - dx = pointer.x - lastMousePos.pageX, - dy = pointer.y - lastMousePos.pageY, - r, rx, ry; - - if ('ontouchstart' in this.el) { - r = D.position(this.overlay); - rx = pointer.x - r.left - plotOffset.left; - ry = pointer.y - r.top - plotOffset.top; - } else { - r = this.overlay.getBoundingClientRect(); - rx = e.clientX - r.left - plotOffset.left - b.scrollLeft - de.scrollLeft; - ry = e.clientY - r.top - plotOffset.top - b.scrollTop - de.scrollTop; - } - - return { - x: axes.x.p2d(rx), - x2: axes.x2.p2d(rx), - y: axes.y.p2d(ry), - y2: axes.y2.p2d(ry), - relX: rx, - relY: ry, - dX: dx, - dY: dy, - absX: pointer.x, - absY: pointer.y, - pageX: pointer.x, - pageY: pointer.y - }; - }, - /** - * Observes the 'click' event and fires the 'flotr:click' event. - * @param {Event} event - 'click' Event object. - */ - clickHandler: function(event){ - if(this.ignoreClick){ - this.ignoreClick = false; - return this.ignoreClick; - } - E.fire(this.el, 'flotr:click', [this.getEventPosition(event), this]); - }, - /** - * Observes mouse movement over the graph area. Fires the 'flotr:mousemove' event. - * @param {Event} event - 'mousemove' Event object. - */ - mouseMoveHandler: function(event){ - if (this.mouseDownMoveHandler) return; - var pos = this.getEventPosition(event); - E.fire(this.el, 'flotr:mousemove', [event, pos, this]); - this.lastMousePos = pos; - }, - /** - * Observes the 'mousedown' event. - * @param {Event} event - 'mousedown' Event object. - */ - mouseDownHandler: function (event){ - - /* - // @TODO Context menu? - if(event.isRightClick()) { - event.stop(); - - var overlay = this.overlay; - overlay.hide(); - - function cancelContextMenu () { - overlay.show(); - E.stopObserving(document, 'mousemove', cancelContextMenu); - } - E.observe(document, 'mousemove', cancelContextMenu); - return; - } - */ - - if (this.mouseUpHandler) return; - this.mouseUpHandler = _.bind(function (e) { - E.stopObserving(document, 'mouseup', this.mouseUpHandler); - E.stopObserving(document, 'mousemove', this.mouseDownMoveHandler); - this.mouseDownMoveHandler = null; - this.mouseUpHandler = null; - // @TODO why? - //e.stop(); - E.fire(this.el, 'flotr:mouseup', [e, this]); - }, this); - this.mouseDownMoveHandler = _.bind(function (e) { - var pos = this.getEventPosition(e); - E.fire(this.el, 'flotr:mousemove', [event, pos, this]); - this.lastMousePos = pos; - }, this); - E.observe(document, 'mouseup', this.mouseUpHandler); - E.observe(document, 'mousemove', this.mouseDownMoveHandler); - E.fire(this.el, 'flotr:mousedown', [event, this]); - this.ignoreClick = false; - }, - drawTooltip: function(content, x, y, options) { - var mt = this.getMouseTrack(), - style = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;', - p = options.position, - m = options.margin, - plotOffset = this.plotOffset; - - if(x !== null && y !== null){ - if (!options.relative) { // absolute to the canvas - if(p.charAt(0) == 'n') style += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;'; - else if(p.charAt(0) == 's') style += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;'; - if(p.charAt(1) == 'e') style += 'right:' + (m + plotOffset.right) + 'px;left:auto;'; - else if(p.charAt(1) == 'w') style += 'left:' + (m + plotOffset.left) + 'px;right:auto;'; - } - else { // relative to the mouse - if(p.charAt(0) == 'n') style += 'bottom:' + (m - plotOffset.top - y + this.canvasHeight) + 'px;top:auto;'; - else if(p.charAt(0) == 's') style += 'top:' + (m + plotOffset.top + y) + 'px;bottom:auto;'; - if(p.charAt(1) == 'e') style += 'left:' + (m + plotOffset.left + x) + 'px;right:auto;'; - else if(p.charAt(1) == 'w') style += 'right:' + (m - plotOffset.left - x + this.canvasWidth) + 'px;left:auto;'; - } - - mt.style.cssText = style; - D.empty(mt); - D.insert(mt, content); - D.show(mt); - } - else { - D.hide(mt); - } - }, - - clip: function () { - - var - ctx = this.ctx, - o = this.plotOffset, - w = this.canvasWidth, - h = this.canvasHeight; - - if (flotr.isIE && flotr.isIE < 9) { - // Clipping for excanvas :-( - ctx.save(); - ctx.fillStyle = this.processColor(this.options.ieBackgroundColor); - ctx.fillRect(0, 0, w, o.top); - ctx.fillRect(0, 0, o.left, h); - ctx.fillRect(0, h - o.bottom, w, o.bottom); - ctx.fillRect(w - o.right, 0, o.right,h); - ctx.restore(); - } else { - ctx.clearRect(0, 0, w, o.top); - ctx.clearRect(0, 0, o.left, h); - ctx.clearRect(0, h - o.bottom, w, o.bottom); - ctx.clearRect(w - o.right, 0, o.right,h); - } - }, - - _initMembers: function() { - this._handles = []; - this.lastMousePos = {pageX: null, pageY: null }; - this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0}; - this.ignoreClick = true; - this.prevHit = null; - }, - - _initGraphTypes: function() { - _.each(flotr.graphTypes, function(handler, graphType){ - this[graphType] = flotr.clone(handler); - }, this); - }, - - _initEvents: function () { - - var - el = this.el, - touchendHandler, movement, touchend; - - if ('ontouchstart' in el) { - - touchendHandler = _.bind(function (e) { - touchend = true; - E.stopObserving(document, 'touchend', touchendHandler); - E.fire(el, 'flotr:mouseup', [event, this]); - this.multitouches = null; - - if (!movement) { - this.clickHandler(e); - } - }, this); - - this.observe(this.overlay, 'touchstart', _.bind(function (e) { - movement = false; - touchend = false; - this.ignoreClick = false; - - if (e.touches && e.touches.length > 1) { - this.multitouches = e.touches; - } - - E.fire(el, 'flotr:mousedown', [event, this]); - this.observe(document, 'touchend', touchendHandler); - }, this)); - - this.observe(this.overlay, 'touchmove', _.bind(function (e) { - - var pos = this.getEventPosition(e); - - e.preventDefault(); - - movement = true; - - if (this.multitouches || (e.touches && e.touches.length > 1)) { - this.multitouches = e.touches; - } else { - if (!touchend) { - E.fire(el, 'flotr:mousemove', [event, pos, this]); - } - } - this.lastMousePos = pos; - }, this)); - - } else { - this. - observe(this.overlay, 'mousedown', _.bind(this.mouseDownHandler, this)). - observe(el, 'mousemove', _.bind(this.mouseMoveHandler, this)). - observe(this.overlay, 'click', _.bind(this.clickHandler, this)). - observe(el, 'mouseout', function () { - E.fire(el, 'flotr:mouseout'); - }); - } - }, - - /** - * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use - * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements - * are created, the elements are inserted into the container element. - */ - _initCanvas: function(){ - var el = this.el, - o = this.options, - children = el.children, - removedChildren = [], - child, i, - size, style; - - // Empty the el - for (i = children.length; i--;) { - child = children[i]; - if (!this.canvas && child.className === 'flotr-canvas') { - this.canvas = child; - } else if (!this.overlay && child.className === 'flotr-overlay') { - this.overlay = child; - } else { - removedChildren.push(child); - } - } - for (i = removedChildren.length; i--;) { - el.removeChild(removedChildren[i]); - } - - D.setStyles(el, {position: 'relative'}); // For positioning labels and overlay. - size = {}; - size.width = el.clientWidth; - size.height = el.clientHeight; - - if(size.width <= 0 || size.height <= 0 || o.resolution <= 0){ - throw 'Invalid dimensions for plot, width = ' + size.width + ', height = ' + size.height + ', resolution = ' + o.resolution; - } - - // Main canvas for drawing graph types - this.canvas = getCanvas(this.canvas, 'canvas'); - // Overlay canvas for interactive features - this.overlay = getCanvas(this.overlay, 'overlay'); - this.ctx = getContext(this.canvas); - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.octx = getContext(this.overlay); - this.octx.clearRect(0, 0, this.overlay.width, this.overlay.height); - this.canvasHeight = size.height; - this.canvasWidth = size.width; - this.textEnabled = !!this.ctx.drawText || !!this.ctx.fillText; // Enable text functions - - function getCanvas(canvas, name){ - if(!canvas){ - canvas = D.create('canvas'); - if (typeof FlashCanvas != "undefined" && typeof canvas.getContext === 'function') { - FlashCanvas.initElement(canvas); - } - canvas.className = 'flotr-'+name; - canvas.style.cssText = 'position:absolute;left:0px;top:0px;'; - D.insert(el, canvas); - } - _.each(size, function(size, attribute){ - D.show(canvas); - if (name == 'canvas' && canvas.getAttribute(attribute) === size) { - return; - } - canvas.setAttribute(attribute, size * o.resolution); - canvas.style[attribute] = size + 'px'; - }); - canvas.context_ = null; // Reset the ExCanvas context - return canvas; - } - - function getContext(canvas){ - if(window.G_vmlCanvasManager) window.G_vmlCanvasManager.initElement(canvas); // For ExCanvas - var context = canvas.getContext('2d'); - if(!window.G_vmlCanvasManager) context.scale(o.resolution, o.resolution); - return context; - } - }, - - _initPlugins: function(){ - // TODO Should be moved to flotr and mixed in. - _.each(flotr.plugins, function(plugin, name){ - _.each(plugin.callbacks, function(fn, c){ - this.observe(this.el, c, _.bind(fn, this)); - }, this); - this[name] = flotr.clone(plugin); - _.each(this[name], function(fn, p){ - if (_.isFunction(fn)) - this[name][p] = _.bind(fn, this); - }, this); - }, this); - }, - - /** - * Sets options and initializes some variables and color specific values, used by the constructor. - * @param {Object} opts - options object - */ - _initOptions: function(opts){ - var options = flotr.clone(flotr.defaultOptions); - options.x2axis = _.extend(_.clone(options.xaxis), options.x2axis); - options.y2axis = _.extend(_.clone(options.yaxis), options.y2axis); - this.options = flotr.merge(opts || {}, options); - - if (this.options.grid.minorVerticalLines === null && - this.options.xaxis.scaling === 'logarithmic') { - this.options.grid.minorVerticalLines = true; - } - if (this.options.grid.minorHorizontalLines === null && - this.options.yaxis.scaling === 'logarithmic') { - this.options.grid.minorHorizontalLines = true; - } - - E.fire(this.el, 'flotr:afterinitoptions', [this]); - - this.axes = flotr.Axis.getAxes(this.options); - - // Initialize some variables used throughout this function. - var assignedColors = [], - colors = [], - ln = this.series.length, - neededColors = this.series.length, - oc = this.options.colors, - usedColors = [], - variation = 0, - c, i, j, s; - - // Collect user-defined colors from series. - for(i = neededColors - 1; i > -1; --i){ - c = this.series[i].color; - if(c){ - --neededColors; - if(_.isNumber(c)) assignedColors.push(c); - else usedColors.push(flotr.Color.parse(c)); - } - } - - // Calculate the number of colors that need to be generated. - for(i = assignedColors.length - 1; i > -1; --i) - neededColors = Math.max(neededColors, assignedColors[i] + 1); - - // Generate needed number of colors. - for(i = 0; colors.length < neededColors;){ - c = (oc.length == i) ? new flotr.Color(100, 100, 100) : flotr.Color.parse(oc[i]); - - // Make sure each serie gets a different color. - var sign = variation % 2 == 1 ? -1 : 1, - factor = 1 + sign * Math.ceil(variation / 2) * 0.2; - c.scale(factor, factor, factor); - - /** - * @todo if we're getting too close to something else, we should probably skip this one - */ - colors.push(c); - - if(++i >= oc.length){ - i = 0; - ++variation; - } - } - - // Fill the options with the generated colors. - for(i = 0, j = 0; i < ln; ++i){ - s = this.series[i]; - - // Assign the color. - if (!s.color){ - s.color = colors[j++].toString(); - }else if(_.isNumber(s.color)){ - s.color = colors[s.color].toString(); - } - - // Every series needs an axis - if (!s.xaxis) s.xaxis = this.axes.x; - if (s.xaxis == 1) s.xaxis = this.axes.x; - else if (s.xaxis == 2) s.xaxis = this.axes.x2; - - if (!s.yaxis) s.yaxis = this.axes.y; - if (s.yaxis == 1) s.yaxis = this.axes.y; - else if (s.yaxis == 2) s.yaxis = this.axes.y2; - - // Apply missing options to the series. - for (var t in flotr.graphTypes){ - s[t] = _.extend(_.clone(this.options[t]), s[t]); - } - s.mouse = _.extend(_.clone(this.options.mouse), s.mouse); - - if (_.isUndefined(s.shadowSize)) s.shadowSize = this.options.shadowSize; - } - }, - - _setEl: function(el) { - if (!el) throw 'The target container doesn\'t exist'; - else if (el.graph instanceof Graph) el.graph.destroy(); - else if (!el.clientWidth) throw 'The target container must be visible'; - - el.graph = this; - this.el = el; - } -}; - -Flotr.Graph = Graph; - -})(); - -/** - * Flotr Axis Library - */ - -(function () { - -var - _ = Flotr._, - LOGARITHMIC = 'logarithmic'; - -function Axis (o) { - - this.orientation = 1; - this.offset = 0; - this.datamin = Number.MAX_VALUE; - this.datamax = -Number.MAX_VALUE; - - _.extend(this, o); - - this._setTranslations(); -} - - -// Prototype -Axis.prototype = { - - setScale : function () { - var length = this.length; - if (this.options.scaling == LOGARITHMIC) { - this.scale = length / (log(this.max, this.options.base) - log(this.min, this.options.base)); - } else { - this.scale = length / (this.max - this.min); - } - }, - - calculateTicks : function () { - var options = this.options; - - this.ticks = []; - this.minorTicks = []; - - // User Ticks - if(options.ticks){ - this._cleanUserTicks(options.ticks, this.ticks); - this._cleanUserTicks(options.minorTicks || [], this.minorTicks); - } - else { - if (options.mode == 'time') { - this._calculateTimeTicks(); - } else if (options.scaling === 'logarithmic') { - this._calculateLogTicks(); - } else { - this._calculateTicks(); - } - } - }, - - /** - * Calculates the range of an axis to apply autoscaling. - */ - calculateRange: function () { - - if (!this.used) return; - - var axis = this, - o = axis.options, - min = o.min !== null ? o.min : axis.datamin, - max = o.max !== null ? o.max : axis.datamax, - margin = o.autoscaleMargin; - - if (o.scaling == 'logarithmic') { - if (min <= 0) min = axis.datamin; - - // Let it widen later on - if (max <= 0) max = min; - } - - if (max == min) { - var widen = max ? 0.01 : 1.00; - if (o.min === null) min -= widen; - if (o.max === null) max += widen; - } - - if (o.scaling === 'logarithmic') { - if (min < 0) min = max / o.base; // Could be the result of widening - - var maxexp = Math.log(max); - if (o.base != Math.E) maxexp /= Math.log(o.base); - maxexp = Math.ceil(maxexp); - - var minexp = Math.log(min); - if (o.base != Math.E) minexp /= Math.log(o.base); - minexp = Math.ceil(minexp); - - axis.tickSize = Flotr.getTickSize(o.noTicks, minexp, maxexp, o.tickDecimals === null ? 0 : o.tickDecimals); - - // Try to determine a suitable amount of miniticks based on the length of a decade - if (o.minorTickFreq === null) { - if (maxexp - minexp > 10) - o.minorTickFreq = 0; - else if (maxexp - minexp > 5) - o.minorTickFreq = 2; - else - o.minorTickFreq = 5; - } - } else { - axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals); - } - - axis.min = min; - axis.max = max; //extendRange may use axis.min or axis.max, so it should be set before it is caled - - // Autoscaling. @todo This probably fails with log scale. Find a testcase and fix it - if(o.min === null && o.autoscale){ - axis.min -= axis.tickSize * margin; - // Make sure we don't go below zero if all values are positive. - if(axis.min < 0 && axis.datamin >= 0) axis.min = 0; - axis.min = axis.tickSize * Math.floor(axis.min / axis.tickSize); - } - - if(o.max === null && o.autoscale){ - axis.max += axis.tickSize * margin; - if(axis.max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) axis.max = 0; - axis.max = axis.tickSize * Math.ceil(axis.max / axis.tickSize); - } - - if (axis.min == axis.max) axis.max = axis.min + 1; - }, - - calculateTextDimensions : function (T, options) { - - var maxLabel = '', - length, - i; - - if (this.options.showLabels) { - for (i = 0; i < this.ticks.length; ++i) { - length = this.ticks[i].label.length; - if (length > maxLabel.length){ - maxLabel = this.ticks[i].label; - } - } - } - - this.maxLabel = T.dimensions( - maxLabel, - {size:options.fontSize, angle: Flotr.toRad(this.options.labelsAngle)}, - 'font-size:smaller;', - 'flotr-grid-label' - ); - - this.titleSize = T.dimensions( - this.options.title, - {size:options.fontSize*1.2, angle: Flotr.toRad(this.options.titleAngle)}, - 'font-weight:bold;', - 'flotr-axis-title' - ); - }, - - _cleanUserTicks : function (ticks, axisTicks) { - - var axis = this, options = this.options, - v, i, label, tick; - - if(_.isFunction(ticks)) ticks = ticks({min : axis.min, max : axis.max}); - - for(i = 0; i < ticks.length; ++i){ - tick = ticks[i]; - if(typeof(tick) === 'object'){ - v = tick[0]; - label = (tick.length > 1) ? tick[1] : options.tickFormatter(v, {min : axis.min, max : axis.max}); - } else { - v = tick; - label = options.tickFormatter(v, {min : this.min, max : this.max}); - } - axisTicks[i] = { v: v, label: label }; - } - }, - - _calculateTimeTicks : function () { - this.ticks = Flotr.Date.generator(this); - }, - - _calculateLogTicks : function () { - - var axis = this, - o = axis.options, - v, - decadeStart; - - var max = Math.log(axis.max); - if (o.base != Math.E) max /= Math.log(o.base); - max = Math.ceil(max); - - var min = Math.log(axis.min); - if (o.base != Math.E) min /= Math.log(o.base); - min = Math.ceil(min); - - for (i = min; i < max; i += axis.tickSize) { - decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i); - // Next decade begins here: - var decadeEnd = decadeStart * ((o.base == Math.E) ? Math.exp(axis.tickSize) : Math.pow(o.base, axis.tickSize)); - var stepSize = (decadeEnd - decadeStart) / o.minorTickFreq; - - axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})}); - for (v = decadeStart + stepSize; v < decadeEnd; v += stepSize) - axis.minorTicks.push({v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max})}); - } - - // Always show the value at the would-be start of next decade (end of this decade) - decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i); - axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})}); - }, - - _calculateTicks : function () { - - var axis = this, - o = axis.options, - tickSize = axis.tickSize, - min = axis.min, - max = axis.max, - start = tickSize * Math.ceil(min / tickSize), // Round to nearest multiple of tick size. - decimals, - minorTickSize, - v, v2, - i, j; - - if (o.minorTickFreq) - minorTickSize = tickSize / o.minorTickFreq; - - // Then store all possible ticks. - for (i = 0; (v = v2 = start + i * tickSize) <= max; ++i){ - - // Round (this is always needed to fix numerical instability). - decimals = o.tickDecimals; - if (decimals === null) decimals = 1 - Math.floor(Math.log(tickSize) / Math.LN10); - if (decimals < 0) decimals = 0; - - v = v.toFixed(decimals); - axis.ticks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) }); - - if (o.minorTickFreq) { - for (j = 0; j < o.minorTickFreq && (i * tickSize + j * minorTickSize) < max; ++j) { - v = v2 + j * minorTickSize; - axis.minorTicks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) }); - } - } - } - - }, - - _setTranslations : function (logarithmic) { - this.d2p = (logarithmic ? d2pLog : d2p); - this.p2d = (logarithmic ? p2dLog : p2d); - } -}; - - -// Static Methods -_.extend(Axis, { - getAxes : function (options) { - return { - x: new Axis({options: options.xaxis, n: 1, length: this.plotWidth}), - x2: new Axis({options: options.x2axis, n: 2, length: this.plotWidth}), - y: new Axis({options: options.yaxis, n: 1, length: this.plotHeight, offset: this.plotHeight, orientation: -1}), - y2: new Axis({options: options.y2axis, n: 2, length: this.plotHeight, offset: this.plotHeight, orientation: -1}) - }; - } -}); - - -// Helper Methods - -function d2p (dataValue) { - return this.offset + this.orientation * (dataValue - this.min) * this.scale; -} - -function p2d (pointValue) { - return (this.offset + this.orientation * pointValue) / this.scale + this.min; -} - -function d2pLog (dataValue) { - return this.offset + this.orientation * (log(dataValue, this.options.base) - log(this.min, this.options.base)) * this.scale; -} - -function p2dLog (pointValue) { - return exp((this.offset + this.orientation * pointValue) / this.scale + log(this.min, this.options.base), this.options.base); -} - -function log (value, base) { - value = Math.log(Math.max(value, Number.MIN_VALUE)); - if (base !== Math.E) - value /= Math.log(base); - return value; -} - -function exp (value, base) { - return (base === Math.E) ? Math.exp(value) : Math.pow(base, value); -} - -Flotr.Axis = Axis; - -})(); - -/** - * Flotr Series Library - */ - -(function () { - -var - _ = Flotr._; - -function Series (o) { - _.extend(this, o); -} - -Series.prototype = { - - getRange: function () { - - var - data = this.data, - length = data.length, - xmin = Number.MAX_VALUE, - ymin = Number.MAX_VALUE, - xmax = -Number.MAX_VALUE, - ymax = -Number.MAX_VALUE, - xused = false, - yused = false, - x, y, i; - - if (length < 0 || this.hide) return false; - - for (i = 0; i < length; i++) { - x = data[i][0]; - y = data[i][1]; - if (x < xmin) { xmin = x; xused = true; } - if (x > xmax) { xmax = x; xused = true; } - if (y < ymin) { ymin = y; yused = true; } - if (y > ymax) { ymax = y; yused = true; } - } - - return { - xmin : xmin, - xmax : xmax, - ymin : ymin, - ymax : ymax, - xused : xused, - yused : yused - }; - } -}; - -_.extend(Series, { - /** - * Collects dataseries from input and parses the series into the right format. It returns an Array - * of Objects each having at least the 'data' key set. - * @param {Array, Object} data - Object or array of dataseries - * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)}) - */ - getSeries: function(data){ - return _.map(data, function(s){ - var series; - if (s.data) { - series = new Series(); - _.extend(series, s); - } else { - series = new Series({data:s}); - } - return series; - }); - } -}); - -Flotr.Series = Series; - -})(); - -/** Lines **/ -Flotr.addType('lines', { - options: { - show: false, // => setting to true will show lines, false will hide - lineWidth: 2, // => line width in pixels - fill: false, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillBorder: false, // => draw a border around the fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - steps: false, // => draw steps - stacked: false // => setting to true will show stacked lines, false will show normal lines - }, - - stack : { - values : [] - }, - - /** - * Draws lines series in the canvas element. - * @param {Object} options - */ - draw : function (options) { - - var - context = options.context, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize, - offset; - - context.save(); - context.lineJoin = 'round'; - - if (shadowSize) { - - context.lineWidth = shadowSize / 2; - offset = lineWidth / 2 + context.lineWidth / 2; - - // @TODO do this instead with a linear gradient - context.strokeStyle = "rgba(0,0,0,0.1)"; - this.plot(options, offset + shadowSize / 2, false); - - context.strokeStyle = "rgba(0,0,0,0.2)"; - this.plot(options, offset, false); - } - - context.lineWidth = lineWidth; - context.strokeStyle = options.color; - - this.plot(options, 0, true); - - context.restore(); - }, - - plot : function (options, shadowOffset, incStack) { - - var - context = options.context, - width = options.width, - height = options.height, - xScale = options.xScale, - yScale = options.yScale, - data = options.data, - stack = options.stacked ? this.stack : false, - length = data.length - 1, - prevx = null, - prevy = null, - zero = yScale(0), - x1, x2, y1, y2, stack1, stack2, i; - - if (length < 1) return; - - context.beginPath(); - - for (i = 0; i < length; ++i) { - - // To allow empty values - if (data[i][1] === null || data[i+1][1] === null) continue; - - // Zero is infinity for log scales - // TODO handle zero for logarithmic - // if (xa.options.scaling === 'logarithmic' && (data[i][0] <= 0 || data[i+1][0] <= 0)) continue; - // if (ya.options.scaling === 'logarithmic' && (data[i][1] <= 0 || data[i+1][1] <= 0)) continue; - - x1 = xScale(data[i][0]); - x2 = xScale(data[i+1][0]); - - if (stack) { - - stack1 = stack.values[data[i][0]] || 0; - stack2 = stack.values[data[i+1][0]] || stack.values[data[i][0]] || 0; - - y1 = yScale(data[i][1] + stack1); - y2 = yScale(data[i+1][1] + stack2); - - if(incStack){ - stack.values[data[i][0]] = data[i][1]+stack1; - - if(i == length-1) - stack.values[data[i+1][0]] = data[i+1][1]+stack2; - } - } - else{ - y1 = yScale(data[i][1]); - y2 = yScale(data[i+1][1]); - } - - if ( - (y1 > height && y2 > height) || - (y1 < 0 && y2 < 0) || - (x1 < 0 && x2 < 0) || - (x1 > width && x2 > width) - ) continue; - - if((prevx != x1) || (prevy != y1 + shadowOffset)) - context.moveTo(x1, y1 + shadowOffset); - - prevx = x2; - prevy = y2 + shadowOffset; - if (options.steps) { - context.lineTo(prevx + shadowOffset / 2, y1 + shadowOffset); - context.lineTo(prevx + shadowOffset / 2, prevy); - } else { - context.lineTo(prevx, prevy); - } - } - - if (!options.fill || options.fill && !options.fillBorder) context.stroke(); - - // TODO stacked lines - if(!shadowOffset && options.fill){ - x1 = xScale(data[0][0]); - context.fillStyle = options.fillStyle; - context.lineTo(x2, zero); - context.lineTo(x1, zero); - context.lineTo(x1, yScale(data[0][1])); - context.fill(); - if (options.fillBorder) { - context.stroke(); - } - } - - context.closePath(); - }, - - // Perform any pre-render precalculations (this should be run on data first) - // - Pie chart total for calculating measures - // - Stacks for lines and bars - // precalculate : function () { - // } - // - // - // Get any bounds after pre calculation (axis can fetch this if does not have explicit min/max) - // getBounds : function () { - // } - // getMin : function () { - // } - // getMax : function () { - // } - // - // - // Padding around rendered elements - // getPadding : function () { - // } - - extendYRange : function (axis, data, options, lines) { - - var o = axis.options; - - // If stacked and auto-min - if (options.stacked && ((!o.max && o.max !== 0) || (!o.min && o.min !== 0))) { - - var - newmax = axis.max, - newmin = axis.min, - positiveSums = lines.positiveSums || {}, - negativeSums = lines.negativeSums || {}, - x, j; - - for (j = 0; j < data.length; j++) { - - x = data[j][0] + ''; - - // Positive - if (data[j][1] > 0) { - positiveSums[x] = (positiveSums[x] || 0) + data[j][1]; - newmax = Math.max(newmax, positiveSums[x]); - } - - // Negative - else { - negativeSums[x] = (negativeSums[x] || 0) + data[j][1]; - newmin = Math.min(newmin, negativeSums[x]); - } - } - - lines.negativeSums = negativeSums; - lines.positiveSums = positiveSums; - - axis.max = newmax; - axis.min = newmin; - } - - if (options.steps) { - - this.hit = function (options) { - var - data = options.data, - args = options.args, - yScale = options.yScale, - mouse = args[0], - length = data.length, - n = args[1], - x = mouse.x, - relY = mouse.relY, - i; - - for (i = 0; i < length - 1; i++) { - if (x >= data[i][0] && x <= data[i+1][0]) { - if (Math.abs(yScale(data[i][1]) - relY) < 8) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - break; - } - } - }; - - this.drawHit = function (options) { - var - context = options.context, - args = options.args, - data = options.data, - xScale = options.xScale, - index = args.index, - x = xScale(args.x), - y = options.yScale(args.y), - x2; - - if (data.length - 1 > index) { - x2 = options.xScale(data[index + 1][0]); - context.save(); - context.strokeStyle = options.color; - context.lineWidth = options.lineWidth; - context.beginPath(); - context.moveTo(x, y); - context.lineTo(x2, y); - context.stroke(); - context.closePath(); - context.restore(); - } - }; - - this.clearHit = function (options) { - var - context = options.context, - args = options.args, - data = options.data, - xScale = options.xScale, - width = options.lineWidth, - index = args.index, - x = xScale(args.x), - y = options.yScale(args.y), - x2; - - if (data.length - 1 > index) { - x2 = options.xScale(data[index + 1][0]); - context.clearRect(x - width, y - width, x2 - x + 2 * width, 2 * width); - } - }; - } - } - -}); - -/** Bars **/ -Flotr.addType('bars', { - - options: { - show: false, // => setting to true will show bars, false will hide - lineWidth: 2, // => in pixels - barWidth: 1, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - horizontal: false, // => horizontal bars (x and y inverted) - stacked: false, // => stacked bar charts - centered: true, // => center the bars to their x axis value - topPadding: 0.1 // => top padding in percent - }, - - stack : { - positive : [], - negative : [], - _positive : [], // Shadow - _negative : [] // Shadow - }, - - draw : function (options) { - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - // @TODO linewidth not interpreted the right way. - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - if (options.fill) context.fillStyle = options.fillStyle; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - shadowSize = options.shadowSize, - i, geometry, left, top, width, height; - - if (data.length < 1) return; - - this.translate(context, options.horizontal); - - for (i = 0; i < data.length; i++) { - - geometry = this.getBarGeometry(data[i][0], data[i][1], options); - if (geometry === null) continue; - - left = geometry.left; - top = geometry.top; - width = geometry.width; - height = geometry.height; - - if (options.fill) context.fillRect(left, top, width, height); - if (shadowSize) { - context.save(); - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.fillRect(left + shadowSize, top + shadowSize, width, height); - context.restore(); - } - if (options.lineWidth) { - context.strokeRect(left, top, width, height); - } - } - }, - - translate : function (context, horizontal) { - if (horizontal) { - context.rotate(-Math.PI / 2); - context.scale(-1, 1); - } - }, - - getBarGeometry : function (x, y, options) { - - var - horizontal = options.horizontal, - barWidth = options.barWidth, - centered = options.centered, - stack = options.stacked ? this.stack : false, - lineWidth = options.lineWidth, - bisection = centered ? barWidth / 2 : 0, - xScale = horizontal ? options.yScale : options.xScale, - yScale = horizontal ? options.xScale : options.yScale, - xValue = horizontal ? y : x, - yValue = horizontal ? x : y, - stackOffset = 0, - stackValue, left, right, top, bottom; - - // Stacked bars - if (stack) { - stackValue = yValue > 0 ? stack.positive : stack.negative; - stackOffset = stackValue[xValue] || stackOffset; - stackValue[xValue] = stackOffset + yValue; - } - - left = xScale(xValue - bisection); - right = xScale(xValue + barWidth - bisection); - top = yScale(yValue + stackOffset); - bottom = yScale(stackOffset); - - // TODO for test passing... probably looks better without this - if (bottom < 0) bottom = 0; - - // TODO Skipping... - // if (right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; - - return (x === null || y === null) ? null : { - x : xValue, - y : yValue, - xScale : xScale, - yScale : yScale, - top : top, - left : Math.min(left, right) - lineWidth / 2, - width : Math.abs(right - left) - lineWidth, - height : bottom - top - }; - }, - - hit : function (options) { - var - data = options.data, - args = options.args, - mouse = args[0], - n = args[1], - x = mouse.x, - y = mouse.y, - hitGeometry = this.getBarGeometry(x, y, options), - width = hitGeometry.width / 2, - left = hitGeometry.left, - geometry, i; - - for (i = data.length; i--;) { - geometry = this.getBarGeometry(data[i][0], data[i][1], options); - if (geometry.y > hitGeometry.y && Math.abs(left - geometry.left) < width) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - } - }, - - drawHit : function (options) { - // TODO hits for stacked bars; implement using calculateStack option? - var - context = options.context, - args = options.args, - geometry = this.getBarGeometry(args.x, args.y, options), - left = geometry.left, - top = geometry.top, - width = geometry.width, - height = geometry.height; - - context.save(); - context.strokeStyle = options.color; - context.lineWidth = options.lineWidth; - this.translate(context, options.horizontal); - - // Draw highlight - context.beginPath(); - context.moveTo(left, top + height); - context.lineTo(left, top); - context.lineTo(left + width, top); - context.lineTo(left + width, top + height); - if (options.fill) { - context.fillStyle = options.fillStyle; - context.fill(); - } - context.stroke(); - context.closePath(); - - context.restore(); - }, - - clearHit: function (options) { - var - context = options.context, - args = options.args, - geometry = this.getBarGeometry(args.x, args.y, options), - left = geometry.left, - width = geometry.width, - top = geometry.top, - height = geometry.height, - lineWidth = 2 * options.lineWidth; - - context.save(); - this.translate(context, options.horizontal); - context.clearRect( - left - lineWidth, - Math.min(top, top + height) - lineWidth, - width + 2 * lineWidth, - Math.abs(height) + 2 * lineWidth - ); - context.restore(); - }, - - extendXRange : function (axis, data, options, bars) { - this._extendRange(axis, data, options, bars); - }, - - extendYRange : function (axis, data, options, bars) { - this._extendRange(axis, data, options, bars); - }, - _extendRange: function (axis, data, options, bars) { - - var - max = axis.options.max; - - if (_.isNumber(max) || _.isString(max)) return; - - var - newmin = axis.min, - newmax = axis.max, - horizontal = options.horizontal, - orientation = axis.orientation, - positiveSums = this.positiveSums || {}, - negativeSums = this.negativeSums || {}, - value, datum, index, j; - - // Sides of bars - if ((orientation == 1 && !horizontal) || (orientation == -1 && horizontal)) { - if (options.centered) { - newmax = Math.max(axis.datamax + options.barWidth, newmax); - newmin = Math.min(axis.datamin - options.barWidth, newmin); - } - } - - if (options.stacked && - ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal))){ - - for (j = data.length; j--;) { - value = data[j][(orientation == 1 ? 1 : 0)]+''; - datum = data[j][(orientation == 1 ? 0 : 1)]; - - // Positive - if (datum > 0) { - positiveSums[value] = (positiveSums[value] || 0) + datum; - newmax = Math.max(newmax, positiveSums[value]); - } - - // Negative - else { - negativeSums[value] = (negativeSums[value] || 0) + datum; - newmin = Math.min(newmin, negativeSums[value]); - } - } - } - - // End of bars - if ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal)) { - if (options.topPadding && (axis.max === axis.datamax || (options.stacked && this.stackMax !== newmax))) { - newmax += options.topPadding * (newmax - newmin); - } - } - - this.stackMin = newmin; - this.stackMax = newmax; - this.negativeSums = negativeSums; - this.positiveSums = positiveSums; - - axis.max = newmax; - axis.min = newmin; - } - -}); - -/** Bubbles **/ -Flotr.addType('bubbles', { - options: { - show: false, // => setting to true will show radar chart, false will hide - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - baseRadius: 2 // => ratio of the radar, against the plot size - }, - draw : function (options) { - var - context = options.context, - shadowSize = options.shadowSize; - - context.save(); - context.lineWidth = options.lineWidth; - - // Shadows - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.strokeStyle = 'rgba(0,0,0,0.05)'; - this.plot(options, shadowSize / 2); - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 4); - - // Chart - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - this.plot(options); - - context.restore(); - }, - plot : function (options, offset) { - - var - data = options.data, - context = options.context, - geometry, - i, x, y, z; - - offset = offset || 0; - - for (i = 0; i < data.length; ++i){ - - geometry = this.getGeometry(data[i], options); - - context.beginPath(); - context.arc(geometry.x + offset, geometry.y + offset, geometry.z, 0, 2 * Math.PI, true); - context.stroke(); - if (options.fill) context.fill(); - context.closePath(); - } - }, - getGeometry : function (point, options) { - return { - x : options.xScale(point[0]), - y : options.yScale(point[1]), - z : point[2] * options.baseRadius - }; - }, - hit : function (options) { - var - data = options.data, - args = options.args, - mouse = args[0], - n = args[1], - x = mouse.x, - y = mouse.y, - geometry, - dx, dy; - - for (i = data.length; i--;) { - geometry = this.getGeometry(data[i], options); - - dx = geometry.x - options.xScale(x); - dy = geometry.y - options.yScale(y); - - if (Math.sqrt(dx * dx + dy * dy) < geometry.z) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - } - }, - drawHit : function (options) { - - var - context = options.context, - geometry = this.getGeometry(options.data[options.args.index], options); - - context.save(); - context.lineWidth = options.lineWidth; - context.fillStyle = options.fillStyle; - context.strokeStyle = options.color; - context.beginPath(); - context.arc(geometry.x, geometry.y, geometry.z, 0, 2 * Math.PI, true); - context.fill(); - context.stroke(); - context.closePath(); - context.restore(); - }, - clearHit : function (options) { - - var - context = options.context, - geometry = this.getGeometry(options.data[options.args.index], options), - offset = geometry.z + options.lineWidth; - - context.save(); - context.clearRect( - geometry.x - offset, - geometry.y - offset, - 2 * offset, - 2 * offset - ); - context.restore(); - } - // TODO Add a hit calculation method (like pie) -}); - -/** Candles **/ -Flotr.addType('candles', { - options: { - show: false, // => setting to true will show candle sticks, false will hide - lineWidth: 1, // => in pixels - wickLineWidth: 1, // => in pixels - candleWidth: 0.6, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - upFillColor: '#00A8F0',// => up sticks fill color - downFillColor: '#CB4B4B',// => down sticks fill color - fillOpacity: 0.5, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - // TODO Test this barcharts option. - barcharts: false // => draw as barcharts (not standard bars but financial barcharts) - }, - - draw : function (options) { - - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - context.lineCap = 'butt'; - // @TODO linewidth not interpreted the right way. - context.lineWidth = options.wickLineWidth || options.lineWidth; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - width = options.candleWidth / 2, - shadowSize = options.shadowSize, - lineWidth = options.lineWidth, - wickLineWidth = options.wickLineWidth, - pixelOffset = (wickLineWidth % 2) / 2, - color, - datum, x, y, - open, high, low, close, - left, right, bottom, top, bottom2, top2, - i; - - if (data.length < 1) return; - - for (i = 0; i < data.length; i++) { - datum = data[i]; - x = datum[0]; - open = datum[1]; - high = datum[2]; - low = datum[3]; - close = datum[4]; - left = xScale(x - width); - right = xScale(x + width); - bottom = yScale(low); - top = yScale(high); - bottom2 = yScale(Math.min(open, close)); - top2 = yScale(Math.max(open, close)); - - /* - // TODO skipping - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - */ - - color = options[open > close ? 'downFillColor' : 'upFillColor']; - - // Fill the candle. - // TODO Test the barcharts option - if (options.fill && !options.barcharts) { - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.fillRect(left + shadowSize, top2 + shadowSize, right - left, bottom2 - top2); - context.save(); - context.globalAlpha = options.fillOpacity; - context.fillStyle = color; - context.fillRect(left, top2 + lineWidth, right - left, bottom2 - top2); - context.restore(); - } - - // Draw candle outline/border, high, low. - if (lineWidth || wickLineWidth) { - - x = Math.floor((left + right) / 2) + pixelOffset; - - context.strokeStyle = color; - context.beginPath(); - - // TODO Again with the bartcharts - if (options.barcharts) { - - context.moveTo(x, Math.floor(top + width)); - context.lineTo(x, Math.floor(bottom + width)); - - y = Math.floor(open + width) + 0.5; - context.moveTo(Math.floor(left) + pixelOffset, y); - context.lineTo(x, y); - - y = Math.floor(close + width) + 0.5; - context.moveTo(Math.floor(right) + pixelOffset, y); - context.lineTo(x, y); - } else { - context.strokeRect(left, top2 + lineWidth, right - left, bottom2 - top2); - - context.moveTo(x, Math.floor(top2 + lineWidth)); - context.lineTo(x, Math.floor(top + lineWidth)); - context.moveTo(x, Math.floor(bottom2 + lineWidth)); - context.lineTo(x, Math.floor(bottom + lineWidth)); - } - - context.closePath(); - context.stroke(); - } - } - }, - extendXRange: function (axis, data, options) { - if (axis.options.max === null) { - axis.max = Math.max(axis.datamax + 0.5, axis.max); - axis.min = Math.min(axis.datamin - 0.5, axis.min); - } - } -}); - -/** Gantt - * Base on data in form [s,y,d] where: - * y - executor or simply y value - * s - task start value - * d - task duration - * **/ -Flotr.addType('gantt', { - options: { - show: false, // => setting to true will show gantt, false will hide - lineWidth: 2, // => in pixels - barWidth: 1, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - centered: true // => center the bars to their x axis value - }, - /** - * Draws gantt series in the canvas element. - * @param {Object} series - Series with options.gantt.show = true. - */ - draw: function(series) { - var ctx = this.ctx, - bw = series.gantt.barWidth, - lw = Math.min(series.gantt.lineWidth, bw); - - ctx.save(); - ctx.translate(this.plotOffset.left, this.plotOffset.top); - ctx.lineJoin = 'miter'; - - /** - * @todo linewidth not interpreted the right way. - */ - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - - ctx.save(); - this.gantt.plotShadows(series, bw, 0, series.gantt.fill); - ctx.restore(); - - if(series.gantt.fill){ - var color = series.gantt.fillColor || series.color; - ctx.fillStyle = this.processColor(color, {opacity: series.gantt.fillOpacity}); - } - - this.gantt.plot(series, bw, 0, series.gantt.fill); - ctx.restore(); - }, - plot: function(series, barWidth, offset, fill){ - var data = series.data; - if(data.length < 1) return; - - var xa = series.xaxis, - ya = series.yaxis, - ctx = this.ctx, i; - - for(i = 0; i < data.length; i++){ - var y = data[i][0], - s = data[i][1], - d = data[i][2], - drawLeft = true, drawTop = true, drawRight = true; - - if (s === null || d === null) continue; - - var left = s, - right = s + d, - bottom = y - (series.gantt.centered ? barWidth/2 : 0), - top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0); - - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - - if(left < xa.min){ - left = xa.min; - drawLeft = false; - } - - if(right > xa.max){ - right = xa.max; - if (xa.lastSerie != series) - drawTop = false; - } - - if(bottom < ya.min) - bottom = ya.min; - - if(top > ya.max){ - top = ya.max; - if (ya.lastSerie != series) - drawTop = false; - } - - /** - * Fill the bar. - */ - if(fill){ - ctx.beginPath(); - ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset); - ctx.lineTo(xa.d2p(left), ya.d2p(top) + offset); - ctx.lineTo(xa.d2p(right), ya.d2p(top) + offset); - ctx.lineTo(xa.d2p(right), ya.d2p(bottom) + offset); - ctx.fill(); - ctx.closePath(); - } - - /** - * Draw bar outline/border. - */ - if(series.gantt.lineWidth && (drawLeft || drawRight || drawTop)){ - ctx.beginPath(); - ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset); - - ctx[drawLeft ?'lineTo':'moveTo'](xa.d2p(left), ya.d2p(top) + offset); - ctx[drawTop ?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(top) + offset); - ctx[drawRight?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(bottom) + offset); - - ctx.stroke(); - ctx.closePath(); - } - } - }, - plotShadows: function(series, barWidth, offset){ - var data = series.data; - if(data.length < 1) return; - - var i, y, s, d, - xa = series.xaxis, - ya = series.yaxis, - ctx = this.ctx, - sw = this.options.shadowSize; - - for(i = 0; i < data.length; i++){ - y = data[i][0]; - s = data[i][1]; - d = data[i][2]; - - if (s === null || d === null) continue; - - var left = s, - right = s + d, - bottom = y - (series.gantt.centered ? barWidth/2 : 0), - top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0); - - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - - if(left < xa.min) left = xa.min; - if(right > xa.max) right = xa.max; - if(bottom < ya.min) bottom = ya.min; - if(top > ya.max) top = ya.max; - - var width = xa.d2p(right)-xa.d2p(left)-((xa.d2p(right)+sw <= this.plotWidth) ? 0 : sw); - var height = ya.d2p(bottom)-ya.d2p(top)-((ya.d2p(bottom)+sw <= this.plotHeight) ? 0 : sw ); - - ctx.fillStyle = 'rgba(0,0,0,0.05)'; - ctx.fillRect(Math.min(xa.d2p(left)+sw, this.plotWidth), Math.min(ya.d2p(top)+sw, this.plotHeight), width, height); - } - }, - extendXRange: function(axis) { - if(axis.options.max === null){ - var newmin = axis.min, - newmax = axis.max, - i, j, x, s, g, - stackedSumsPos = {}, - stackedSumsNeg = {}, - lastSerie = null; - - for(i = 0; i < this.series.length; ++i){ - s = this.series[i]; - g = s.gantt; - - if(g.show && s.xaxis == axis) { - for (j = 0; j < s.data.length; j++) { - if (g.show) { - y = s.data[j][0]+''; - stackedSumsPos[y] = Math.max((stackedSumsPos[y] || 0), s.data[j][1]+s.data[j][2]); - lastSerie = s; - } - } - for (j in stackedSumsPos) { - newmax = Math.max(stackedSumsPos[j], newmax); - } - } - } - axis.lastSerie = lastSerie; - axis.max = newmax; - axis.min = newmin; - } - }, - extendYRange: function(axis){ - if(axis.options.max === null){ - var newmax = Number.MIN_VALUE, - newmin = Number.MAX_VALUE, - i, j, s, g, - stackedSumsPos = {}, - stackedSumsNeg = {}, - lastSerie = null; - - for(i = 0; i < this.series.length; ++i){ - s = this.series[i]; - g = s.gantt; - - if (g.show && !s.hide && s.yaxis == axis) { - var datamax = Number.MIN_VALUE, datamin = Number.MAX_VALUE; - for(j=0; j < s.data.length; j++){ - datamax = Math.max(datamax,s.data[j][0]); - datamin = Math.min(datamin,s.data[j][0]); - } - - if (g.centered) { - newmax = Math.max(datamax + 0.5, newmax); - newmin = Math.min(datamin - 0.5, newmin); - } - else { - newmax = Math.max(datamax + 1, newmax); - newmin = Math.min(datamin, newmin); - } - // For normal horizontal bars - if (g.barWidth + datamax > newmax){ - newmax = axis.max + g.barWidth; - } - } - } - axis.lastSerie = lastSerie; - axis.max = newmax; - axis.min = newmin; - axis.tickSize = Flotr.getTickSize(axis.options.noTicks, newmin, newmax, axis.options.tickDecimals); - } - } -}); - -/** Markers **/ -/** - * Formats the marker labels. - * @param {Object} obj - Marker value Object {x:..,y:..} - * @return {String} Formatted marker string - */ -(function () { - -Flotr.defaultMarkerFormatter = function(obj){ - return (Math.round(obj.y*100)/100)+''; -}; - -Flotr.addType('markers', { - options: { - show: false, // => setting to true will show markers, false will hide - lineWidth: 1, // => line width of the rectangle around the marker - color: '#000000', // => text color - fill: false, // => fill or not the marekers' rectangles - fillColor: "#FFFFFF", // => fill color - fillOpacity: 0.4, // => fill opacity - stroke: false, // => draw the rectangle around the markers - position: 'ct', // => the markers position (vertical align: b, m, t, horizontal align: l, c, r) - verticalMargin: 0, // => the margin between the point and the text. - labelFormatter: Flotr.defaultMarkerFormatter, - fontSize: Flotr.defaultOptions.fontSize, - stacked: false, // => true if markers should be stacked - stackingType: 'b', // => define staching behavior, (b- bars like, a - area like) (see Issue 125 for details) - horizontal: false // => true if markers should be horizontal (For now only in a case on horizontal stacked bars, stacks should be calculated horizontaly) - }, - - // TODO test stacked markers. - stack : { - positive : [], - negative : [], - values : [] - }, - - draw : function (options) { - - var - data = options.data, - context = options.context, - stack = options.stacked ? options.stack : false, - stackType = options.stackingType, - stackOffsetNeg, - stackOffsetPos, - stackOffset, - i, x, y, label; - - context.save(); - context.lineJoin = 'round'; - context.lineWidth = options.lineWidth; - context.strokeStyle = 'rgba(0,0,0,0.5)'; - context.fillStyle = options.fillStyle; - - function stackPos (a, b) { - stackOffsetPos = stack.negative[a] || 0; - stackOffsetNeg = stack.positive[a] || 0; - if (b > 0) { - stack.positive[a] = stackOffsetPos + b; - return stackOffsetPos + b; - } else { - stack.negative[a] = stackOffsetNeg + b; - return stackOffsetNeg + b; - } - } - - for (i = 0; i < data.length; ++i) { - - x = data[i][0]; - y = data[i][1]; - - if (stack) { - if (stackType == 'b') { - if (options.horizontal) y = stackPos(y, x); - else x = stackPos(x, y); - } else if (stackType == 'a') { - stackOffset = stack.values[x] || 0; - stack.values[x] = stackOffset + y; - y = stackOffset + y; - } - } - - label = options.labelFormatter({x: x, y: y, index: i, data : data}); - this.plot(options.xScale(x), options.yScale(y), label, options); - } - context.restore(); - }, - plot: function(x, y, label, options) { - var context = options.context; - if (isImage(label) && !label.complete) { - throw 'Marker image not loaded.'; - } else { - this._plot(x, y, label, options); - } - }, - - _plot: function(x, y, label, options) { - var context = options.context, - margin = 2, - left = x, - top = y, - dim; - - if (isImage(label)) - dim = {height : label.height, width: label.width}; - else - dim = options.text.canvas(label); - - dim.width = Math.floor(dim.width+margin*2); - dim.height = Math.floor(dim.height+margin*2); - - if (options.position.indexOf('c') != -1) left -= dim.width/2 + margin; - else if (options.position.indexOf('l') != -1) left -= dim.width; - - if (options.position.indexOf('m') != -1) top -= dim.height/2 + margin; - else if (options.position.indexOf('t') != -1) top -= dim.height + options.verticalMargin; - else top += options.verticalMargin; - - left = Math.floor(left)+0.5; - top = Math.floor(top)+0.5; - - if(options.fill) - context.fillRect(left, top, dim.width, dim.height); - - if(options.stroke) - context.strokeRect(left, top, dim.width, dim.height); - - if (isImage(label)) - context.drawImage(label, left+margin, top+margin); - else - Flotr.drawText(context, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left', size: options.fontSize, color: options.color}); - } -}); - -function isImage (i) { - return typeof i === 'object' && i.constructor && (Image ? true : i.constructor === Image); -} - -})(); - -/** Pie **/ -/** - * Formats the pies labels. - * @param {Object} slice - Slice object - * @return {String} Formatted pie label string - */ -(function () { - -var - _ = Flotr._; - -Flotr.defaultPieLabelFormatter = function (total, value) { - return (100 * value / total).toFixed(2)+'%'; -}; - -Flotr.addType('pie', { - options: { - show: false, // => setting to true will show bars, false will hide - lineWidth: 1, // => in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.6, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - explode: 6, // => the number of pixels the splices will be far from the center - sizeRatio: 0.6, // => the size ratio of the pie relative to the plot - startAngle: Math.PI/4, // => the first slice start angle - labelFormatter: Flotr.defaultPieLabelFormatter, - pie3D: false, // => whether to draw the pie in 3 dimenstions or not (ineffective) - pie3DviewAngle: (Math.PI/2 * 0.8), - pie3DspliceThickness: 20 - }, - - draw : function (options) { - - // TODO 3D charts what? - - var - data = options.data, - context = options.context, - canvas = context.canvas, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize, - sizeRatio = options.sizeRatio, - height = options.height, - width = options.width, - explode = options.explode, - color = options.color, - fill = options.fill, - fillStyle = options.fillStyle, - radius = Math.min(canvas.width, canvas.height) * sizeRatio / 2, - value = data[0][1], - html = [], - vScale = 1,//Math.cos(series.pie.viewAngle); - measure = Math.PI * 2 * value / this.total, - startAngle = this.startAngle || (2 * Math.PI * options.startAngle), // TODO: this initial startAngle is already in radians (fixing will be test-unstable) - endAngle = startAngle + measure, - bisection = startAngle + measure / 2, - label = options.labelFormatter(this.total, value), - //plotTickness = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale; - explodeCoeff = explode + radius + 4, - distX = Math.cos(bisection) * explodeCoeff, - distY = Math.sin(bisection) * explodeCoeff, - textAlign = distX < 0 ? 'right' : 'left', - textBaseline = distY > 0 ? 'top' : 'bottom', - style, - x, y, - distX, distY; - - context.save(); - context.translate(width / 2, height / 2); - context.scale(1, vScale); - - x = Math.cos(bisection) * explode; - y = Math.sin(bisection) * explode; - - // Shadows - if (shadowSize > 0) { - this.plotSlice(x + shadowSize, y + shadowSize, radius, startAngle, endAngle, context); - if (fill) { - context.fillStyle = 'rgba(0,0,0,0.1)'; - context.fill(); - } - } - - this.plotSlice(x, y, radius, startAngle, endAngle, context); - if (fill) { - context.fillStyle = fillStyle; - context.fill(); - } - context.lineWidth = lineWidth; - context.strokeStyle = color; - context.stroke(); - - style = { - size : options.fontSize * 1.2, - color : options.fontColor, - weight : 1.5 - }; - - if (label) { - if (options.htmlText || !options.textEnabled) { - divStyle = 'position:absolute;' + textBaseline + ':' + (height / 2 + (textBaseline === 'top' ? distY : -distY)) + 'px;'; - divStyle += textAlign + ':' + (width / 2 + (textAlign === 'right' ? -distX : distX)) + 'px;'; - html.push('
    ', label, '
    '); - } - else { - style.textAlign = textAlign; - style.textBaseline = textBaseline; - Flotr.drawText(context, label, distX, distY, style); - } - } - - if (options.htmlText || !options.textEnabled) { - var div = Flotr.DOM.node('
    '); - Flotr.DOM.insert(div, html.join('')); - Flotr.DOM.insert(options.element, div); - } - - context.restore(); - - // New start angle - this.startAngle = endAngle; - this.slices = this.slices || []; - this.slices.push({ - radius : Math.min(canvas.width, canvas.height) * sizeRatio / 2, - x : x, - y : y, - explode : explode, - start : startAngle, - end : endAngle - }); - }, - plotSlice : function (x, y, radius, startAngle, endAngle, context) { - context.beginPath(); - context.moveTo(x, y); - context.arc(x, y, radius, startAngle, endAngle, false); - context.lineTo(x, y); - context.closePath(); - }, - hit : function (options) { - - var - data = options.data[0], - args = options.args, - index = options.index, - mouse = args[0], - n = args[1], - slice = this.slices[index], - x = mouse.relX - options.width / 2, - y = mouse.relY - options.height / 2, - r = Math.sqrt(x * x + y * y), - theta = Math.atan(y / x), - circle = Math.PI * 2, - explode = slice.explode || options.explode, - start = slice.start % circle, - end = slice.end % circle; - - if (x < 0) { - theta += Math.PI; - } else if (x > 0 && y < 0) { - theta += circle; - } - - if (r < slice.radius + explode && r > explode) { - if ((start > end && (theta < end || theta > start)) || - (theta > start && theta < end)) { - - // TODO Decouple this from hit plugin (chart shouldn't know what n means) - n.x = data[0]; - n.y = data[1]; - n.sAngle = start; - n.eAngle = end; - n.index = 0; - n.seriesIndex = index; - n.fraction = data[1] / this.total; - } - } - }, - drawHit: function (options) { - var - context = options.context, - slice = this.slices[options.args.seriesIndex]; - - context.save(); - context.translate(options.width / 2, options.height / 2); - this.plotSlice(slice.x, slice.y, slice.radius, slice.start, slice.end, context); - context.stroke(); - context.restore(); - }, - clearHit : function (options) { - var - context = options.context, - slice = this.slices[options.args.seriesIndex], - padding = 2 * options.lineWidth, - radius = slice.radius + padding; - - context.save(); - context.translate(options.width / 2, options.height / 2); - context.clearRect( - slice.x - radius, - slice.y - radius, - 2 * radius + padding, - 2 * radius + padding - ); - context.restore(); - }, - extendYRange : function (axis, data) { - this.total = (this.total || 0) + data[0][1]; - } -}); -})(); - -/** Points **/ -Flotr.addType('points', { - options: { - show: false, // => setting to true will show points, false will hide - radius: 3, // => point radius (pixels) - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the points with a color, false for (transparent) no fill - fillColor: '#FFFFFF', // => fill color - fillOpacity: 0.4 // => opacity of color inside the points - }, - - draw : function (options) { - var - context = options.context, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize; - - context.save(); - - if (shadowSize > 0) { - context.lineWidth = shadowSize / 2; - - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 2 + context.lineWidth / 2); - - context.strokeStyle = 'rgba(0,0,0,0.2)'; - this.plot(options, context.lineWidth / 2); - } - - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - context.fillStyle = options.fillColor || options.color; - - this.plot(options); - context.restore(); - }, - - plot : function (options, offset) { - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - i, x, y; - - for (i = data.length - 1; i > -1; --i) { - y = data[i][1]; - if (y === null) continue; - - x = xScale(data[i][0]); - y = yScale(y); - - if (x < 0 || x > options.width || y < 0 || y > options.height) continue; - - context.beginPath(); - if (offset) { - context.arc(x, y + offset, options.radius, 0, Math.PI, false); - } else { - context.arc(x, y, options.radius, 0, 2 * Math.PI, true); - if (options.fill) context.fill(); - } - context.stroke(); - context.closePath(); - } - } -}); - -/** Radar **/ -Flotr.addType('radar', { - options: { - show: false, // => setting to true will show radar chart, false will hide - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - radiusRatio: 0.90 // => ratio of the radar, against the plot size - }, - draw : function (options) { - var - context = options.context, - shadowSize = options.shadowSize; - - context.save(); - context.translate(options.width / 2, options.height / 2); - context.lineWidth = options.lineWidth; - - // Shadow - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.strokeStyle = 'rgba(0,0,0,0.05)'; - this.plot(options, shadowSize / 2); - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 4); - - // Chart - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - this.plot(options); - - context.restore(); - }, - plot : function (options, offset) { - var - data = options.data, - context = options.context, - radius = Math.min(options.height, options.width) * options.radiusRatio / 2, - step = 2 * Math.PI / data.length, - angle = -Math.PI / 2, - i, ratio; - - offset = offset || 0; - - context.beginPath(); - for (i = 0; i < data.length; ++i) { - ratio = data[i][1] / this.max; - - context[i === 0 ? 'moveTo' : 'lineTo']( - Math.cos(i * step + angle) * radius * ratio + offset, - Math.sin(i * step + angle) * radius * ratio + offset - ); - } - context.closePath(); - if (options.fill) context.fill(); - context.stroke(); - }, - extendYRange : function (axis, data) { - this.max = Math.max(axis.max, this.max || -Number.MAX_VALUE); - } -}); - -Flotr.addType('timeline', { - options: { - show: false, - lineWidth: 1, - barWidth: 0.2, - fill: true, - fillColor: null, - fillOpacity: 0.4, - centered: true - }, - - draw : function (options) { - - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - barWidth = options.barWidth, - lineWidth = options.lineWidth, - i; - - Flotr._.each(data, function (timeline) { - - var - x = timeline[0], - y = timeline[1], - w = timeline[2], - h = barWidth, - - xt = Math.ceil(xScale(x)), - wt = Math.ceil(xScale(x + w)) - xt, - yt = Math.round(yScale(y)), - ht = Math.round(yScale(y - h)) - yt, - - x0 = xt - lineWidth / 2, - y0 = Math.round(yt - ht / 2) - lineWidth / 2; - - context.strokeRect(x0, y0, wt, ht); - context.fillRect(x0, y0, wt, ht); - - }); - }, - - extendRange : function (series) { - - var - data = series.data, - xa = series.xaxis, - ya = series.yaxis, - w = series.timeline.barWidth; - - if (xa.options.min === null) - xa.min = xa.datamin - w / 2; - - if (xa.options.max === null) { - - var - max = xa.max; - - Flotr._.each(data, function (timeline) { - max = Math.max(max, timeline[0] + timeline[2]); - }, this); - - xa.max = max + w / 2; - } - - if (ya.options.min === null) - ya.min = ya.datamin - w; - if (ya.options.min === null) - ya.max = ya.datamax + w; - } - -}); - -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('crosshair', { - options: { - mode: null, // => one of null, 'x', 'y' or 'xy' - color: '#FF0000', // => crosshair color - hideCursor: true // => hide the cursor when the crosshair is shown - }, - callbacks: { - 'flotr:mousemove': function(e, pos) { - if (this.options.crosshair.mode) { - this.crosshair.clearCrosshair(); - this.crosshair.drawCrosshair(pos); - } - } - }, - /** - * Draws the selection box. - */ - drawCrosshair: function(pos) { - var octx = this.octx, - options = this.options.crosshair, - plotOffset = this.plotOffset, - x = plotOffset.left + pos.relX + 0.5, - y = plotOffset.top + pos.relY + 0.5; - - if (pos.relX < 0 || pos.relY < 0 || pos.relX > this.plotWidth || pos.relY > this.plotHeight) { - this.el.style.cursor = null; - D.removeClass(this.el, 'flotr-crosshair'); - return; - } - - if (options.hideCursor) { - this.el.style.cursor = 'none'; - D.addClass(this.el, 'flotr-crosshair'); - } - - octx.save(); - octx.strokeStyle = options.color; - octx.lineWidth = 1; - octx.beginPath(); - - if (options.mode.indexOf('x') != -1) { - octx.moveTo(x, plotOffset.top); - octx.lineTo(x, plotOffset.top + this.plotHeight); - } - - if (options.mode.indexOf('y') != -1) { - octx.moveTo(plotOffset.left, y); - octx.lineTo(plotOffset.left + this.plotWidth, y); - } - - octx.stroke(); - octx.restore(); - }, - /** - * Removes the selection box from the overlay canvas. - */ - clearCrosshair: function() { - - var - plotOffset = this.plotOffset, - position = this.lastMousePos, - context = this.octx; - - if (position) { - context.clearRect( - position.relX + plotOffset.left, - plotOffset.top, - 1, - this.plotHeight + 1 - ); - context.clearRect( - plotOffset.left, - position.relY + plotOffset.top, - this.plotWidth + 1, - 1 - ); - } - } -}); -})(); - -(function() { - -var - D = Flotr.DOM, - _ = Flotr._; - -function getImage (type, canvas, width, height) { - - // TODO add scaling for w / h - var - mime = 'image/'+type, - data = canvas.toDataURL(mime), - image = new Image(); - image.src = data; - return image; -} - -Flotr.addPlugin('download', { - - saveImage: function (type, width, height, replaceCanvas) { - var image = null; - if (Flotr.isIE && Flotr.isIE < 9) { - image = ''+this.canvas.firstChild.innerHTML+''; - return window.open().document.write(image); - } - - if (type !== 'jpeg' && type !== 'png') return; - - image = getImage(type, this.canvas, width, height); - - if (_.isElement(image) && replaceCanvas) { - this.download.restoreCanvas(); - D.hide(this.canvas); - D.hide(this.overlay); - D.setStyles({position: 'absolute'}); - D.insert(this.el, image); - this.saveImageElement = image; - } else { - return window.open(image.src); - } - }, - - restoreCanvas: function() { - D.show(this.canvas); - D.show(this.overlay); - if (this.saveImageElement) this.el.removeChild(this.saveImageElement); - this.saveImageElement = null; - } -}); - -})(); - -(function () { - -var E = Flotr.EventAdapter, - _ = Flotr._; - -Flotr.addPlugin('graphGrid', { - - callbacks: { - 'flotr:beforedraw' : function () { - this.graphGrid.drawGrid(); - }, - 'flotr:afterdraw' : function () { - this.graphGrid.drawOutline(); - } - }, - - drawGrid: function(){ - - var - ctx = this.ctx, - options = this.options, - grid = options.grid, - verticalLines = grid.verticalLines, - horizontalLines = grid.horizontalLines, - minorVerticalLines = grid.minorVerticalLines, - minorHorizontalLines = grid.minorHorizontalLines, - plotHeight = this.plotHeight, - plotWidth = this.plotWidth, - a, v, i, j; - - if(verticalLines || minorVerticalLines || - horizontalLines || minorHorizontalLines){ - E.fire(this.el, 'flotr:beforegrid', [this.axes.x, this.axes.y, options, this]); - } - ctx.save(); - ctx.lineWidth = 1; - ctx.strokeStyle = grid.tickColor; - - function circularHorizontalTicks (ticks) { - for(i = 0; i < ticks.length; ++i){ - var ratio = ticks[i].v / a.max; - for(j = 0; j <= sides; ++j){ - ctx[j === 0 ? 'moveTo' : 'lineTo']( - Math.cos(j*coeff+angle)*radius*ratio, - Math.sin(j*coeff+angle)*radius*ratio - ); - } - } - } - function drawGridLines (ticks, callback) { - _.each(_.pluck(ticks, 'v'), function(v){ - // Don't show lines on upper and lower bounds. - if ((v <= a.min || v >= a.max) || - (v == a.min || v == a.max) && grid.outlineWidth) - return; - callback(Math.floor(a.d2p(v)) + ctx.lineWidth/2); - }); - } - function drawVerticalLines (x) { - ctx.moveTo(x, 0); - ctx.lineTo(x, plotHeight); - } - function drawHorizontalLines (y) { - ctx.moveTo(0, y); - ctx.lineTo(plotWidth, y); - } - - if (grid.circular) { - ctx.translate(this.plotOffset.left+plotWidth/2, this.plotOffset.top+plotHeight/2); - var radius = Math.min(plotHeight, plotWidth)*options.radar.radiusRatio/2, - sides = this.axes.x.ticks.length, - coeff = 2*(Math.PI/sides), - angle = -Math.PI/2; - - // Draw grid lines in vertical direction. - ctx.beginPath(); - - a = this.axes.y; - - if(horizontalLines){ - circularHorizontalTicks(a.ticks); - } - if(minorHorizontalLines){ - circularHorizontalTicks(a.minorTicks); - } - - if(verticalLines){ - _.times(sides, function(i){ - ctx.moveTo(0, 0); - ctx.lineTo(Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius); - }); - } - ctx.stroke(); - } - else { - ctx.translate(this.plotOffset.left, this.plotOffset.top); - - // Draw grid background, if present in options. - if(grid.backgroundColor){ - ctx.fillStyle = this.processColor(grid.backgroundColor, {x1: 0, y1: 0, x2: plotWidth, y2: plotHeight}); - ctx.fillRect(0, 0, plotWidth, plotHeight); - } - - ctx.beginPath(); - - a = this.axes.x; - if (verticalLines) drawGridLines(a.ticks, drawVerticalLines); - if (minorVerticalLines) drawGridLines(a.minorTicks, drawVerticalLines); - - a = this.axes.y; - if (horizontalLines) drawGridLines(a.ticks, drawHorizontalLines); - if (minorHorizontalLines) drawGridLines(a.minorTicks, drawHorizontalLines); - - ctx.stroke(); - } - - ctx.restore(); - if(verticalLines || minorVerticalLines || - horizontalLines || minorHorizontalLines){ - E.fire(this.el, 'flotr:aftergrid', [this.axes.x, this.axes.y, options, this]); - } - }, - - drawOutline: function(){ - var - that = this, - options = that.options, - grid = options.grid, - outline = grid.outline, - ctx = that.ctx, - backgroundImage = grid.backgroundImage, - plotOffset = that.plotOffset, - leftOffset = plotOffset.left, - topOffset = plotOffset.top, - plotWidth = that.plotWidth, - plotHeight = that.plotHeight, - v, img, src, left, top, globalAlpha; - - if (!grid.outlineWidth) return; - - ctx.save(); - - if (grid.circular) { - ctx.translate(leftOffset + plotWidth / 2, topOffset + plotHeight / 2); - var radius = Math.min(plotHeight, plotWidth) * options.radar.radiusRatio / 2, - sides = this.axes.x.ticks.length, - coeff = 2*(Math.PI/sides), - angle = -Math.PI/2; - - // Draw axis/grid border. - ctx.beginPath(); - ctx.lineWidth = grid.outlineWidth; - ctx.strokeStyle = grid.color; - ctx.lineJoin = 'round'; - - for(i = 0; i <= sides; ++i){ - ctx[i === 0 ? 'moveTo' : 'lineTo'](Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius); - } - //ctx.arc(0, 0, radius, 0, Math.PI*2, true); - - ctx.stroke(); - } - else { - ctx.translate(leftOffset, topOffset); - - // Draw axis/grid border. - var lw = grid.outlineWidth, - orig = 0.5-lw+((lw+1)%2/2), - lineTo = 'lineTo', - moveTo = 'moveTo'; - ctx.lineWidth = lw; - ctx.strokeStyle = grid.color; - ctx.lineJoin = 'miter'; - ctx.beginPath(); - ctx.moveTo(orig, orig); - plotWidth = plotWidth - (lw / 2) % 1; - plotHeight = plotHeight + lw / 2; - ctx[outline.indexOf('n') !== -1 ? lineTo : moveTo](plotWidth, orig); - ctx[outline.indexOf('e') !== -1 ? lineTo : moveTo](plotWidth, plotHeight); - ctx[outline.indexOf('s') !== -1 ? lineTo : moveTo](orig, plotHeight); - ctx[outline.indexOf('w') !== -1 ? lineTo : moveTo](orig, orig); - ctx.stroke(); - ctx.closePath(); - } - - ctx.restore(); - - if (backgroundImage) { - - src = backgroundImage.src || backgroundImage; - left = (parseInt(backgroundImage.left, 10) || 0) + plotOffset.left; - top = (parseInt(backgroundImage.top, 10) || 0) + plotOffset.top; - img = new Image(); - - img.onload = function() { - ctx.save(); - if (backgroundImage.alpha) ctx.globalAlpha = backgroundImage.alpha; - ctx.globalCompositeOperation = 'destination-over'; - ctx.drawImage(img, 0, 0, img.width, img.height, left, top, plotWidth, plotHeight); - ctx.restore(); - }; - - img.src = src; - } - } -}); - -})(); - -(function () { - -var - D = Flotr.DOM, - _ = Flotr._, - flotr = Flotr, - S_MOUSETRACK = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;'; - -Flotr.addPlugin('hit', { - callbacks: { - 'flotr:mousemove': function(e, pos) { - this.hit.track(pos); - }, - 'flotr:click': function(pos) { - this.hit.track(pos); - }, - 'flotr:mouseout': function() { - this.hit.clearHit(); - } - }, - track : function (pos) { - if (this.options.mouse.track || _.any(this.series, function(s){return s.mouse && s.mouse.track;})) { - this.hit.hit(pos); - } - }, - /** - * Try a method on a graph type. If the method exists, execute it. - * @param {Object} series - * @param {String} method Method name. - * @param {Array} args Arguments applied to method. - * @return executed successfully or failed. - */ - executeOnType: function(s, method, args){ - var - success = false, - options; - - if (!_.isArray(s)) s = [s]; - - function e(s, index) { - _.each(_.keys(flotr.graphTypes), function (type) { - if (s[type] && s[type].show && this[type][method]) { - options = this.getOptions(s, type); - - options.fill = !!s.mouse.fillColor; - options.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity}); - options.color = s.mouse.lineColor; - options.context = this.octx; - options.index = index; - - if (args) options.args = args; - this[type][method].call(this[type], options); - success = true; - } - }, this); - } - _.each(s, e, this); - - return success; - }, - /** - * Updates the mouse tracking point on the overlay. - */ - drawHit: function(n){ - var octx = this.octx, - s = n.series; - - if (s.mouse.lineColor) { - octx.save(); - octx.lineWidth = (s.points ? s.points.lineWidth : 1); - octx.strokeStyle = s.mouse.lineColor; - octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity}); - octx.translate(this.plotOffset.left, this.plotOffset.top); - - if (!this.hit.executeOnType(s, 'drawHit', n)) { - var xa = n.xaxis, - ya = n.yaxis; - - octx.beginPath(); - // TODO fix this (points) should move to general testable graph mixin - octx.arc(xa.d2p(n.x), ya.d2p(n.y), s.points.radius || s.mouse.radius, 0, 2 * Math.PI, true); - octx.fill(); - octx.stroke(); - octx.closePath(); - } - octx.restore(); - } - this.prevHit = n; - }, - /** - * Removes the mouse tracking point from the overlay. - */ - clearHit: function(){ - var prev = this.prevHit, - octx = this.octx, - plotOffset = this.plotOffset; - octx.save(); - octx.translate(plotOffset.left, plotOffset.top); - if (prev) { - if (!this.hit.executeOnType(prev.series, 'clearHit', this.prevHit)) { - // TODO fix this (points) should move to general testable graph mixin - var - s = prev.series, - lw = (s.points ? s.points.lineWidth : 1); - offset = (s.points.radius || s.mouse.radius) + lw; - octx.clearRect( - prev.xaxis.d2p(prev.x) - offset, - prev.yaxis.d2p(prev.y) - offset, - offset*2, - offset*2 - ); - } - D.hide(this.mouseTrack); - this.prevHit = null; - } - octx.restore(); - }, - /** - * Retrieves the nearest data point from the mouse cursor. If it's within - * a certain range, draw a point on the overlay canvas and display the x and y - * value of the data. - * @param {Object} mouse - Object that holds the relative x and y coordinates of the cursor. - */ - hit: function(mouse){ - - var - options = this.options, - prevHit = this.prevHit, - closest, sensibility, dataIndex, seriesIndex, series, value, xaxis, yaxis; - - if (this.series.length === 0) return; - - // Nearest data element. - // dist, x, y, relX, relY, absX, absY, sAngle, eAngle, fraction, mouse, - // xaxis, yaxis, series, index, seriesIndex - n = { - relX : mouse.relX, - relY : mouse.relY, - absX : mouse.absX, - absY : mouse.absY - }; - - if (options.mouse.trackY && - !options.mouse.trackAll && - this.hit.executeOnType(this.series, 'hit', [mouse, n])) - { - - if (!_.isUndefined(n.seriesIndex)) { - series = this.series[n.seriesIndex]; - n.series = series; - n.mouse = series.mouse; - n.xaxis = series.xaxis; - n.yaxis = series.yaxis; - } - } else { - - closest = this.hit.closest(mouse); - - if (closest) { - - closest = options.mouse.trackY ? closest.point : closest.x; - seriesIndex = closest.seriesIndex; - series = this.series[seriesIndex]; - xaxis = series.xaxis; - yaxis = series.yaxis; - sensibility = 2 * series.mouse.sensibility; - - if - (options.mouse.trackAll || - (closest.distanceX < sensibility / xaxis.scale && - (!options.mouse.trackY || closest.distanceY < sensibility / yaxis.scale))) - { - n.series = series; - n.xaxis = series.xaxis; - n.yaxis = series.yaxis; - n.mouse = series.mouse; - n.x = closest.x; - n.y = closest.y; - n.dist = closest.distance; - n.index = closest.dataIndex; - n.seriesIndex = seriesIndex; - } - } - } - - if (!prevHit || (prevHit.index !== n.index || prevHit.seriesIndex !== n.seriesIndex)) { - this.hit.clearHit(); - if (n.series && n.mouse && n.mouse.track) { - this.hit.drawMouseTrack(n); - this.hit.drawHit(n); - Flotr.EventAdapter.fire(this.el, 'flotr:hit', [n, this]); - } - } - }, - - closest : function (mouse) { - - var - series = this.series, - options = this.options, - mouseX = mouse.x, - mouseY = mouse.y, - compare = Number.MAX_VALUE, - compareX = Number.MAX_VALUE, - closest = {}, - closestX = {}, - check = false, - serie, data, - distance, distanceX, distanceY, - x, y, i, j; - - function setClosest (o) { - o.distance = distance; - o.distanceX = distanceX; - o.distanceY = distanceY; - o.seriesIndex = i; - o.dataIndex = j; - o.x = x; - o.y = y; - } - - for (i = 0; i < series.length; i++) { - - serie = series[i]; - data = serie.data; - - if (data.length) check = true; - - for (j = data.length; j--;) { - - x = data[j][0]; - y = data[j][1]; - - if (x === null || y === null) continue; - - // don't check if the point isn't visible in the current range - if (x < serie.xaxis.min || x > serie.xaxis.max) continue; - - distanceX = Math.abs(x - mouseX); - distanceY = Math.abs(y - mouseY); - - // Skip square root for speed - distance = distanceX * distanceX + distanceY * distanceY; - - if (distance < compare) { - compare = distance; - setClosest(closest); - } - - if (distanceX < compareX) { - compareX = distanceX; - setClosest(closestX); - } - } - } - - return check ? { - point : closest, - x : closestX - } : false; - }, - - drawMouseTrack : function (n) { - - var - pos = '', - s = n.series, - p = n.mouse.position, - m = n.mouse.margin, - elStyle = S_MOUSETRACK, - mouseTrack = this.mouseTrack, - plotOffset = this.plotOffset, - left = plotOffset.left, - right = plotOffset.right, - bottom = plotOffset.bottom, - top = plotOffset.top, - decimals = n.mouse.trackDecimals, - options = this.options; - - // Create - if (!mouseTrack) { - mouseTrack = D.node('
    '); - this.mouseTrack = mouseTrack; - D.insert(this.el, mouseTrack); - } - - if (!n.mouse.relative) { // absolute to the canvas - - if (p.charAt(0) == 'n') pos += 'top:' + (m + top) + 'px;bottom:auto;'; - else if (p.charAt(0) == 's') pos += 'bottom:' + (m + bottom) + 'px;top:auto;'; - if (p.charAt(1) == 'e') pos += 'right:' + (m + right) + 'px;left:auto;'; - else if (p.charAt(1) == 'w') pos += 'left:' + (m + left) + 'px;right:auto;'; - - // Bars - } else if (s.bars.show) { - pos += 'bottom:' + (m - top - n.yaxis.d2p(n.y/2) + this.canvasHeight) + 'px;top:auto;'; - pos += 'left:' + (m + left + n.xaxis.d2p(n.x - options.bars.barWidth/2)) + 'px;right:auto;'; - - // Pie - } else if (s.pie.show) { - var center = { - x: (this.plotWidth)/2, - y: (this.plotHeight)/2 - }, - radius = (Math.min(this.canvasWidth, this.canvasHeight) * s.pie.sizeRatio) / 2, - bisection = n.sAngle one of null, 'x', 'y' or 'xy' - color: '#B6D9FF', // => selection box color - fps: 20 // => frames-per-second - }, - - callbacks: { - 'flotr:mouseup' : function (event) { - - var - options = this.options.selection, - selection = this.selection, - pointer = this.getEventPosition(event); - - if (!options || !options.mode) return; - if (selection.interval) clearInterval(selection.interval); - - if (this.multitouches) { - selection.updateSelection(); - } else - if (!options.pinchOnly) { - selection.setSelectionPos(selection.selection.second, pointer); - } - selection.clearSelection(); - - if(selection.selecting && selection.selectionIsSane()){ - selection.drawSelection(); - selection.fireSelectEvent(); - this.ignoreClick = true; - } - }, - 'flotr:mousedown' : function (event) { - - var - options = this.options.selection, - selection = this.selection, - pointer = this.getEventPosition(event); - - if (!options || !options.mode) return; - if (!options.mode || (!isLeftClick(event) && _.isUndefined(event.touches))) return; - if (!options.pinchOnly) selection.setSelectionPos(selection.selection.first, pointer); - if (selection.interval) clearInterval(selection.interval); - - this.lastMousePos.pageX = null; - selection.selecting = false; - selection.interval = setInterval( - _.bind(selection.updateSelection, this), - 1000 / options.fps - ); - }, - 'flotr:destroy' : function (event) { - clearInterval(this.selection.interval); - } - }, - - // TODO This isn't used. Maybe it belongs in the draw area and fire select event methods? - getArea: function() { - - var s = this.selection.selection, - first = s.first, - second = s.second; - - return { - x1: Math.min(first.x, second.x), - x2: Math.max(first.x, second.x), - y1: Math.min(first.y, second.y), - y2: Math.max(first.y, second.y) - }; - }, - - selection: {first: {x: -1, y: -1}, second: {x: -1, y: -1}}, - prevSelection: null, - interval: null, - - /** - * Fires the 'flotr:select' event when the user made a selection. - */ - fireSelectEvent: function(name){ - var a = this.axes, - s = this.selection.selection, - x1, x2, y1, y2; - - name = name || 'select'; - - x1 = a.x.p2d(s.first.x); - x2 = a.x.p2d(s.second.x); - y1 = a.y.p2d(s.first.y); - y2 = a.y.p2d(s.second.y); - - E.fire(this.el, 'flotr:'+name, [{ - x1:Math.min(x1, x2), - y1:Math.min(y1, y2), - x2:Math.max(x1, x2), - y2:Math.max(y1, y2), - xfirst:x1, xsecond:x2, yfirst:y1, ysecond:y2 - }, this]); - }, - - /** - * Allows the user the manually select an area. - * @param {Object} area - Object with coordinates to select. - */ - setSelection: function(area, preventEvent){ - var options = this.options, - xa = this.axes.x, - ya = this.axes.y, - vertScale = ya.scale, - hozScale = xa.scale, - selX = options.selection.mode.indexOf('x') != -1, - selY = options.selection.mode.indexOf('y') != -1, - s = this.selection.selection; - - this.selection.clearSelection(); - - s.first.y = boundY((selX && !selY) ? 0 : (ya.max - area.y1) * vertScale, this); - s.second.y = boundY((selX && !selY) ? this.plotHeight - 1: (ya.max - area.y2) * vertScale, this); - s.first.x = boundX((selY && !selX) ? 0 : area.x1, this); - s.second.x = boundX((selY && !selX) ? this.plotWidth : area.x2, this); - - this.selection.drawSelection(); - if (!preventEvent) - this.selection.fireSelectEvent(); - }, - - /** - * Calculates the position of the selection. - * @param {Object} pos - Position object. - * @param {Event} event - Event object. - */ - setSelectionPos: function(pos, pointer) { - var mode = this.options.selection.mode, - selection = this.selection.selection; - - if(mode.indexOf('x') == -1) { - pos.x = (pos == selection.first) ? 0 : this.plotWidth; - }else{ - pos.x = boundX(pointer.relX, this); - } - - if (mode.indexOf('y') == -1) { - pos.y = (pos == selection.first) ? 0 : this.plotHeight - 1; - }else{ - pos.y = boundY(pointer.relY, this); - } - }, - /** - * Draws the selection box. - */ - drawSelection: function() { - - this.selection.fireSelectEvent('selecting'); - - var s = this.selection.selection, - octx = this.octx, - options = this.options, - plotOffset = this.plotOffset, - prevSelection = this.selection.prevSelection; - - if (prevSelection && - s.first.x == prevSelection.first.x && - s.first.y == prevSelection.first.y && - s.second.x == prevSelection.second.x && - s.second.y == prevSelection.second.y) { - return; - } - - octx.save(); - octx.strokeStyle = this.processColor(options.selection.color, {opacity: 0.8}); - octx.lineWidth = 1; - octx.lineJoin = 'miter'; - octx.fillStyle = this.processColor(options.selection.color, {opacity: 0.4}); - - this.selection.prevSelection = { - first: { x: s.first.x, y: s.first.y }, - second: { x: s.second.x, y: s.second.y } - }; - - var x = Math.min(s.first.x, s.second.x), - y = Math.min(s.first.y, s.second.y), - w = Math.abs(s.second.x - s.first.x), - h = Math.abs(s.second.y - s.first.y); - - octx.fillRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); - octx.strokeRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); - octx.restore(); - }, - - /** - * Updates (draws) the selection box. - */ - updateSelection: function(){ - if (!this.lastMousePos.pageX) return; - - this.selection.selecting = true; - - if (this.multitouches) { - this.selection.setSelectionPos(this.selection.selection.first, this.getEventPosition(this.multitouches[0])); - this.selection.setSelectionPos(this.selection.selection.second, this.getEventPosition(this.multitouches[1])); - } else - if (this.options.selection.pinchOnly) { - return; - } else { - this.selection.setSelectionPos(this.selection.selection.second, this.lastMousePos); - } - - this.selection.clearSelection(); - - if(this.selection.selectionIsSane()) { - this.selection.drawSelection(); - } - }, - - /** - * Removes the selection box from the overlay canvas. - */ - clearSelection: function() { - if (!this.selection.prevSelection) return; - - var prevSelection = this.selection.prevSelection, - lw = 1, - plotOffset = this.plotOffset, - x = Math.min(prevSelection.first.x, prevSelection.second.x), - y = Math.min(prevSelection.first.y, prevSelection.second.y), - w = Math.abs(prevSelection.second.x - prevSelection.first.x), - h = Math.abs(prevSelection.second.y - prevSelection.first.y); - - this.octx.clearRect(x + plotOffset.left - lw + 0.5, - y + plotOffset.top - lw, - w + 2 * lw + 0.5, - h + 2 * lw + 0.5); - - this.selection.prevSelection = null; - }, - /** - * Determines whether or not the selection is sane and should be drawn. - * @return {Boolean} - True when sane, false otherwise. - */ - selectionIsSane: function(){ - var s = this.selection.selection; - return Math.abs(s.second.x - s.first.x) >= 5 || - Math.abs(s.second.y - s.first.y) >= 5; - } - -}); - -})(); - -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('labels', { - - callbacks : { - 'flotr:afterdraw' : function () { - this.labels.draw(); - } - }, - - draw: function(){ - // Construct fixed width label boxes, which can be styled easily. - var - axis, tick, left, top, xBoxWidth, - radius, sides, coeff, angle, - div, i, html = '', - noLabels = 0, - options = this.options, - ctx = this.ctx, - a = this.axes, - style = { size: options.fontSize }; - - for (i = 0; i < a.x.ticks.length; ++i){ - if (a.x.ticks[i].label) { ++noLabels; } - } - xBoxWidth = this.plotWidth / noLabels; - - if (options.grid.circular) { - ctx.save(); - ctx.translate(this.plotOffset.left + this.plotWidth / 2, - this.plotOffset.top + this.plotHeight / 2); - - radius = this.plotHeight * options.radar.radiusRatio / 2 + options.fontSize; - sides = this.axes.x.ticks.length; - coeff = 2 * (Math.PI / sides); - angle = -Math.PI / 2; - - drawLabelCircular(this, a.x, false); - drawLabelCircular(this, a.x, true); - drawLabelCircular(this, a.y, false); - drawLabelCircular(this, a.y, true); - ctx.restore(); - } - - if (!options.HtmlText && this.textEnabled) { - drawLabelNoHtmlText(this, a.x, 'center', 'top'); - drawLabelNoHtmlText(this, a.x2, 'center', 'bottom'); - drawLabelNoHtmlText(this, a.y, 'right', 'middle'); - drawLabelNoHtmlText(this, a.y2, 'left', 'middle'); - - } else if (( - a.x.options.showLabels || - a.x2.options.showLabels || - a.y.options.showLabels || - a.y2.options.showLabels) && - !options.grid.circular - ) { - - html = ''; - - drawLabelHtml(this, a.x); - drawLabelHtml(this, a.x2); - drawLabelHtml(this, a.y); - drawLabelHtml(this, a.y2); - - ctx.stroke(); - ctx.restore(); - div = D.create('div'); - D.setStyles(div, { - fontSize: 'smaller', - color: options.grid.color - }); - div.className = 'flotr-labels'; - D.insert(this.el, div); - D.insert(div, html); - } - - function drawLabelCircular (graph, axis, minorTicks) { - var - ticks = minorTicks ? axis.minorTicks : axis.ticks, - isX = axis.orientation === 1, - isFirst = axis.n === 1, - style, offset; - - style = { - color : axis.options.color || options.grid.color, - angle : Flotr.toRad(axis.options.labelsAngle), - textBaseline : 'middle' - }; - - for (i = 0; i < ticks.length && - (minorTicks ? axis.options.showMinorLabels : axis.options.showLabels); ++i){ - tick = ticks[i]; - tick.label += ''; - if (!tick.label || !tick.label.length) { continue; } - - x = Math.cos(i * coeff + angle) * radius; - y = Math.sin(i * coeff + angle) * radius; - - style.textAlign = isX ? (Math.abs(x) < 0.1 ? 'center' : (x < 0 ? 'right' : 'left')) : 'left'; - - Flotr.drawText( - ctx, tick.label, - isX ? x : 3, - isX ? y : -(axis.ticks[i].v / axis.max) * (radius - options.fontSize), - style - ); - } - } - - function drawLabelNoHtmlText (graph, axis, textAlign, textBaseline) { - var - isX = axis.orientation === 1, - isFirst = axis.n === 1, - style, offset; - - style = { - color : axis.options.color || options.grid.color, - textAlign : textAlign, - textBaseline : textBaseline, - angle : Flotr.toRad(axis.options.labelsAngle) - }; - style = Flotr.getBestTextAlign(style.angle, style); - - for (i = 0; i < axis.ticks.length && continueShowingLabels(axis); ++i) { - - tick = axis.ticks[i]; - if (!tick.label || !tick.label.length) { continue; } - - offset = axis.d2p(tick.v); - if (offset < 0 || - offset > (isX ? graph.plotWidth : graph.plotHeight)) { continue; } - - Flotr.drawText( - ctx, tick.label, - leftOffset(graph, isX, isFirst, offset), - topOffset(graph, isX, isFirst, offset), - style - ); - - // Only draw on axis y2 - if (!isX && !isFirst) { - ctx.save(); - ctx.strokeStyle = style.color; - ctx.beginPath(); - ctx.moveTo(graph.plotOffset.left + graph.plotWidth - 8, graph.plotOffset.top + axis.d2p(tick.v)); - ctx.lineTo(graph.plotOffset.left + graph.plotWidth, graph.plotOffset.top + axis.d2p(tick.v)); - ctx.stroke(); - ctx.restore(); - } - } - - function continueShowingLabels (axis) { - return axis.options.showLabels && axis.used; - } - function leftOffset (graph, isX, isFirst, offset) { - return graph.plotOffset.left + - (isX ? offset : - (isFirst ? - -options.grid.labelMargin : - options.grid.labelMargin + graph.plotWidth)); - } - function topOffset (graph, isX, isFirst, offset) { - return graph.plotOffset.top + - (isX ? options.grid.labelMargin : offset) + - ((isX && isFirst) ? graph.plotHeight : 0); - } - } - - function drawLabelHtml (graph, axis) { - var - isX = axis.orientation === 1, - isFirst = axis.n === 1, - name = '', - left, style, top, - offset = graph.plotOffset; - - if (!isX && !isFirst) { - ctx.save(); - ctx.strokeStyle = axis.options.color || options.grid.color; - ctx.beginPath(); - } - - if (axis.options.showLabels && (isFirst ? true : axis.used)) { - for (i = 0; i < axis.ticks.length; ++i) { - tick = axis.ticks[i]; - if (!tick.label || !tick.label.length || - ((isX ? offset.left : offset.top) + axis.d2p(tick.v) < 0) || - ((isX ? offset.left : offset.top) + axis.d2p(tick.v) > (isX ? graph.canvasWidth : graph.canvasHeight))) { - continue; - } - top = offset.top + - (isX ? - ((isFirst ? 1 : -1 ) * (graph.plotHeight + options.grid.labelMargin)) : - axis.d2p(tick.v) - axis.maxLabel.height / 2); - left = isX ? (offset.left + axis.d2p(tick.v) - xBoxWidth / 2) : 0; - - name = ''; - if (i === 0) { - name = ' first'; - } else if (i === axis.ticks.length - 1) { - name = ' last'; - } - name += isX ? ' flotr-grid-label-x' : ' flotr-grid-label-y'; - - html += [ - '
    ' + tick.label + '
    ' - ].join(' '); - - if (!isX && !isFirst) { - ctx.moveTo(offset.left + graph.plotWidth - 8, offset.top + axis.d2p(tick.v)); - ctx.lineTo(offset.left + graph.plotWidth, offset.top + axis.d2p(tick.v)); - } - } - } - } - } - -}); -})(); - -(function () { - -var - D = Flotr.DOM, - _ = Flotr._; - -Flotr.addPlugin('legend', { - options: { - show: true, // => setting to true will show the legend, hide otherwise - noColumns: 1, // => number of colums in legend table // @todo: doesn't work for HtmlText = false - labelFormatter: function(v){return v;}, // => fn: string -> string - labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes - labelBoxWidth: 14, - labelBoxHeight: 10, - labelBoxMargin: 5, - labelBoxOpacity: 0.4, - container: null, // => container (as jQuery object) to put legend in, null means default on top of graph - position: 'nw', // => position of default legend container within plot - margin: 5, // => distance from grid edge to default legend container within plot - backgroundColor: null, // => null means auto-detect - backgroundOpacity: 0.85// => set to 0 to avoid background, set to 1 for a solid background - }, - callbacks: { - 'flotr:afterinit': function() { - this.legend.insertLegend(); - } - }, - /** - * Adds a legend div to the canvas container or draws it on the canvas. - */ - insertLegend: function(){ - - if(!this.options.legend.show) - return; - - var series = this.series, - plotOffset = this.plotOffset, - options = this.options, - legend = options.legend, - fragments = [], - rowStarted = false, - ctx = this.ctx, - itemCount = _.filter(series, function(s) {return (s.label && !s.hide);}).length, - p = legend.position, - m = legend.margin, - i, label, color; - - if (itemCount) { - if (!options.HtmlText && this.textEnabled && !legend.container) { - var style = { - size: options.fontSize*1.1, - color: options.grid.color - }; - - var lbw = legend.labelBoxWidth, - lbh = legend.labelBoxHeight, - lbm = legend.labelBoxMargin, - offsetX = plotOffset.left + m, - offsetY = plotOffset.top + m; - - // We calculate the labels' max width - var labelMaxWidth = 0; - for(i = series.length - 1; i > -1; --i){ - if(!series[i].label || series[i].hide) continue; - label = legend.labelFormatter(series[i].label); - labelMaxWidth = Math.max(labelMaxWidth, this._text.measureText(label, style).width); - } - - var legendWidth = Math.round(lbw + lbm*3 + labelMaxWidth), - legendHeight = Math.round(itemCount*(lbm+lbh) + lbm); - - if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight); - if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth); - - // Legend box - color = this.processColor(legend.backgroundColor || 'rgb(240,240,240)', {opacity: legend.backgroundOpacity || 0.1}); - - ctx.fillStyle = color; - ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight); - ctx.strokeStyle = legend.labelBoxBorderColor; - ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight); - - // Legend labels - var x = offsetX + lbm; - var y = offsetY + lbm; - for(i = 0; i < series.length; i++){ - if(!series[i].label || series[i].hide) continue; - label = legend.labelFormatter(series[i].label); - - ctx.fillStyle = series[i].color; - ctx.fillRect(x, y, lbw-1, lbh-1); - - ctx.strokeStyle = legend.labelBoxBorderColor; - ctx.lineWidth = 1; - ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2); - - // Legend text - Flotr.drawText(ctx, label, x + lbw + lbm, y + lbh, style); - - y += lbh + lbm; - } - } - else { - for(i = 0; i < series.length; ++i){ - if(!series[i].label || series[i].hide) continue; - - if(i % legend.noColumns === 0){ - fragments.push(rowStarted ? '' : ''); - rowStarted = true; - } - - // @TODO remove requirement on bars - var s = series[i], - boxWidth = legend.labelBoxWidth, - boxHeight = legend.labelBoxHeight, - opacityValue = (s.bars ? s.bars.fillOpacity : legend.labelBoxOpacity), - opacity = 'opacity:' + opacityValue + ';filter:alpha(opacity=' + opacityValue*100 + ');'; - - label = legend.labelFormatter(s.label); - color = 'background-color:' + ((s.bars && s.bars.show && s.bars.fillColor && s.bars.fill) ? s.bars.fillColor : s.color) + ';'; - - fragments.push( - '', - '
    ', - '
    ', // Border - '
    ', // Background - '
    ', - '
    ', - '', - '', label, '' - ); - } - if(rowStarted) fragments.push(''); - - if(fragments.length > 0){ - var table = '' + fragments.join('') + '
    '; - if(legend.container){ - D.insert(legend.container, table); - } - else { - var styles = {position: 'absolute', 'z-index': 2}; - - if(p.charAt(0) == 'n') { styles.top = (m + plotOffset.top) + 'px'; styles.bottom = 'auto'; } - else if(p.charAt(0) == 's') { styles.bottom = (m + plotOffset.bottom) + 'px'; styles.top = 'auto'; } - if(p.charAt(1) == 'e') { styles.right = (m + plotOffset.right) + 'px'; styles.left = 'auto'; } - else if(p.charAt(1) == 'w') { styles.left = (m + plotOffset.left) + 'px'; styles.right = 'auto'; } - - var div = D.create('div'), size; - div.className = 'flotr-legend'; - D.setStyles(div, styles); - D.insert(div, table); - D.insert(this.el, div); - - if(!legend.backgroundOpacity) - return; - - var c = legend.backgroundColor || options.grid.backgroundColor || '#ffffff'; - - _.extend(styles, D.size(div), { - 'backgroundColor': c, - 'z-index': 1 - }); - styles.width += 'px'; - styles.height += 'px'; - - // Put in the transparent background separately to avoid blended labels and - div = D.create('div'); - div.className = 'flotr-legend-bg'; - D.setStyles(div, styles); - D.opacity(div, legend.backgroundOpacity); - D.insert(div, ' '); - D.insert(this.el, div); - } - } - } - } - } -}); -})(); - -/** Spreadsheet **/ -(function() { - -function getRowLabel(value){ - if (this.options.spreadsheet.tickFormatter){ - //TODO maybe pass the xaxis formatter to the custom tick formatter as an opt-out? - return this.options.spreadsheet.tickFormatter(value); - } - else { - var t = _.find(this.axes.x.ticks, function(t){return t.v == value;}); - if (t) { - return t.label; - } - return value; - } -} - -var - D = Flotr.DOM, - _ = Flotr._; - -Flotr.addPlugin('spreadsheet', { - options: { - show: false, // => show the data grid using two tabs - tabGraphLabel: 'Graph', - tabDataLabel: 'Data', - toolbarDownload: 'Download CSV', // @todo: add better language support - toolbarSelectAll: 'Select all', - csvFileSeparator: ',', - decimalSeparator: '.', - tickFormatter: null, - initialTab: 'graph' - }, - /** - * Builds the tabs in the DOM - */ - callbacks: { - 'flotr:afterconstruct': function(){ - // @TODO necessary? - //this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove'); - - if (!this.options.spreadsheet.show) return; - - var ss = this.spreadsheet, - container = D.node('
    '), - graph = D.node('
    '+this.options.spreadsheet.tabGraphLabel+'
    '), - data = D.node('
    '+this.options.spreadsheet.tabDataLabel+'
    '), - offset; - - ss.tabsContainer = container; - ss.tabs = { graph : graph, data : data }; - - D.insert(container, graph); - D.insert(container, data); - D.insert(this.el, container); - - offset = D.size(data).height + 2; - this.plotOffset.bottom += offset; - - D.setStyles(container, {top: this.canvasHeight-offset+'px'}); - - this. - observe(graph, 'click', function(){ss.showTab('graph');}). - observe(data, 'click', function(){ss.showTab('data');}); - if (this.options.spreadsheet.initialTab !== 'graph'){ - ss.showTab(this.options.spreadsheet.initialTab); - } - } - }, - /** - * Builds a matrix of the data to make the correspondance between the x values and the y values : - * X value => Y values from the axes - * @return {Array} The data grid - */ - loadDataGrid: function(){ - if (this.seriesData) return this.seriesData; - - var s = this.series, - rows = {}; - - /* The data grid is a 2 dimensions array. There is a row for each X value. - * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one) - **/ - _.each(s, function(serie, i){ - _.each(serie.data, function (v) { - var x = v[0], - y = v[1], - r = rows[x]; - if (r) { - r[i+1] = y; - } else { - var newRow = []; - newRow[0] = x; - newRow[i+1] = y; - rows[x] = newRow; - } - }); - }); - - // The data grid is sorted by x value - this.seriesData = _.sortBy(rows, function(row, x){ - return parseInt(x, 10); - }); - return this.seriesData; - }, - /** - * Constructs the data table for the spreadsheet - * @todo make a spreadsheet manager (Flotr.Spreadsheet) - * @return {Element} The resulting table element - */ - constructDataGrid: function(){ - // If the data grid has already been built, nothing to do here - if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid; - - var s = this.series, - datagrid = this.spreadsheet.loadDataGrid(), - colgroup = [''], - buttonDownload, buttonSelect, t; - - // First row : series' labels - var html = ['']; - html.push(''); - _.each(s, function(serie,i){ - html.push(''); - colgroup.push(''); - }); - html.push(''); - // Data rows - _.each(datagrid, function(row){ - html.push(''); - _.times(s.length+1, function(i){ - var tag = 'td', - value = row[i], - // TODO: do we really want to handle problems with floating point - // precision here? - content = (!_.isUndefined(value) ? Math.round(value*100000)/100000 : ''); - if (i === 0) { - tag = 'th'; - var label = getRowLabel.call(this, content); - if (label) content = label; - } - - html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+''); - }, this); - html.push(''); - }, this); - colgroup.push(''); - t = D.node(html.join('')); - - /** - * @TODO disabled this - if (!Flotr.isIE || Flotr.isIE == 9) { - function handleMouseout(){ - t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover'); - } - function handleMouseover(e){ - var td = e.element(), - siblings = td.previousSiblings(); - t.select('th[scope=col]')[siblings.length-1].addClassName('hover'); - t.select('colgroup col')[siblings.length].addClassName('hover'); - } - _.each(t.select('td'), function(td) { - Flotr.EventAdapter. - observe(td, 'mouseover', handleMouseover). - observe(td, 'mouseout', handleMouseout); - }); - } - */ - - buttonDownload = D.node( - ''); - - buttonSelect = D.node( - ''); - - this. - observe(buttonDownload, 'click', _.bind(this.spreadsheet.downloadCSV, this)). - observe(buttonSelect, 'click', _.bind(this.spreadsheet.selectAllData, this)); - - var toolbar = D.node('
    '); - D.insert(toolbar, buttonDownload); - D.insert(toolbar, buttonSelect); - - var containerHeight =this.canvasHeight - D.size(this.spreadsheet.tabsContainer).height-2, - container = D.node('
    '); - - D.insert(container, toolbar); - D.insert(container, t); - D.insert(this.el, container); - this.spreadsheet.datagrid = t; - this.spreadsheet.container = container; - - return t; - }, - /** - * Shows the specified tab, by its name - * @todo make a tab manager (Flotr.Tabs) - * @param {String} tabName - The tab name - */ - showTab: function(tabName){ - if (this.spreadsheet.activeTab === tabName){ - return; - } - switch(tabName) { - case 'graph': - D.hide(this.spreadsheet.container); - D.removeClass(this.spreadsheet.tabs.data, 'selected'); - D.addClass(this.spreadsheet.tabs.graph, 'selected'); - break; - case 'data': - if (!this.spreadsheet.datagrid) - this.spreadsheet.constructDataGrid(); - D.show(this.spreadsheet.container); - D.addClass(this.spreadsheet.tabs.data, 'selected'); - D.removeClass(this.spreadsheet.tabs.graph, 'selected'); - break; - default: - throw 'Illegal tab name: ' + tabName; - } - this.spreadsheet.activeTab = tabName; - }, - /** - * Selects the data table in the DOM for copy/paste - */ - selectAllData: function(){ - if (this.spreadsheet.tabs) { - var selection, range, doc, win, node = this.spreadsheet.constructDataGrid(); - - this.spreadsheet.showTab('data'); - - // deferred to be able to select the table - setTimeout(function () { - if ((doc = node.ownerDocument) && (win = doc.defaultView) && - win.getSelection && doc.createRange && - (selection = window.getSelection()) && - selection.removeAllRanges) { - range = doc.createRange(); - range.selectNode(node); - selection.removeAllRanges(); - selection.addRange(range); - } - else if (document.body && document.body.createTextRange && - (range = document.body.createTextRange())) { - range.moveToElementText(node); - range.select(); - } - }, 0); - return true; - } - else return false; - }, - /** - * Converts the data into CSV in order to download a file - */ - downloadCSV: function(){ - var csv = '', - series = this.series, - options = this.options, - dg = this.spreadsheet.loadDataGrid(), - separator = encodeURIComponent(options.spreadsheet.csvFileSeparator); - - if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) { - throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")"; - } - - // The first row - _.each(series, function(serie, i){ - csv += separator+'"'+(serie.label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"'; - }); - - csv += "%0D%0A"; // \r\n - - // For each row - csv += _.reduce(dg, function(memo, row){ - var rowLabel = getRowLabel.call(this, row[0]) || ''; - rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"'; - var numbers = row.slice(1).join(separator); - if (options.spreadsheet.decimalSeparator !== '.') { - numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator); - } - return memo + rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n - }, '', this); - - if (Flotr.isIE && Flotr.isIE < 9) { - csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r'); - window.open().document.write(csv); - } - else window.open('data:text/csv,'+csv); - } -}); -})(); - -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('titles', { - callbacks: { - 'flotr:afterdraw': function() { - this.titles.drawTitles(); - } - }, - /** - * Draws the title and the subtitle - */ - drawTitles : function () { - var html, - options = this.options, - margin = options.grid.labelMargin, - ctx = this.ctx, - a = this.axes; - - if (!options.HtmlText && this.textEnabled) { - var style = { - size: options.fontSize, - color: options.grid.color, - textAlign: 'center' - }; - - // Add subtitle - if (options.subtitle){ - Flotr.drawText( - ctx, options.subtitle, - this.plotOffset.left + this.plotWidth/2, - this.titleHeight + this.subtitleHeight - 2, - style - ); - } - - style.weight = 1.5; - style.size *= 1.5; - - // Add title - if (options.title){ - Flotr.drawText( - ctx, options.title, - this.plotOffset.left + this.plotWidth/2, - this.titleHeight - 2, - style - ); - } - - style.weight = 1.8; - style.size *= 0.8; - - // Add x axis title - if (a.x.options.title && a.x.used){ - style.textAlign = a.x.options.titleAlign || 'center'; - style.textBaseline = 'top'; - style.angle = Flotr.toRad(a.x.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.x.options.title, - this.plotOffset.left + this.plotWidth/2, - this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin, - style - ); - } - - // Add x2 axis title - if (a.x2.options.title && a.x2.used){ - style.textAlign = a.x2.options.titleAlign || 'center'; - style.textBaseline = 'bottom'; - style.angle = Flotr.toRad(a.x2.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.x2.options.title, - this.plotOffset.left + this.plotWidth/2, - this.plotOffset.top - a.x2.maxLabel.height - 2 * margin, - style - ); - } - - // Add y axis title - if (a.y.options.title && a.y.used){ - style.textAlign = a.y.options.titleAlign || 'right'; - style.textBaseline = 'middle'; - style.angle = Flotr.toRad(a.y.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.y.options.title, - this.plotOffset.left - a.y.maxLabel.width - 2 * margin, - this.plotOffset.top + this.plotHeight / 2, - style - ); - } - - // Add y2 axis title - if (a.y2.options.title && a.y2.used){ - style.textAlign = a.y2.options.titleAlign || 'left'; - style.textBaseline = 'middle'; - style.angle = Flotr.toRad(a.y2.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.y2.options.title, - this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin, - this.plotOffset.top + this.plotHeight / 2, - style - ); - } - } - else { - html = []; - - // Add title - if (options.title) - html.push( - '
    ', options.title, '
    ' - ); - - // Add subtitle - if (options.subtitle) - html.push( - '
    ', options.subtitle, '
    ' - ); - - html.push(''); - - html.push('
    '); - - // Add x axis title - if (a.x.options.title && a.x.used) - html.push( - '
    ', a.x.options.title, '
    ' - ); - - // Add x2 axis title - if (a.x2.options.title && a.x2.used) - html.push( - '
    ', a.x2.options.title, '
    ' - ); - - // Add y axis title - if (a.y.options.title && a.y.used) - html.push( - '
    ', a.y.options.title, '
    ' - ); - - // Add y2 axis title - if (a.y2.options.title && a.y2.used) - html.push( - '
    ', a.y2.options.title, '
    ' - ); - - html = html.join(''); - - var div = D.create('div'); - D.setStyles({ - color: options.grid.color - }); - div.className = 'flotr-titles'; - D.insert(this.el, div); - D.insert(div, html); - } - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/flotr2.min.js b/addons/web_graph/static/lib/flotr2/flotr2.min.js deleted file mode 100644 index 0dce3034464..00000000000 --- a/addons/web_graph/static/lib/flotr2/flotr2.min.js +++ /dev/null @@ -1,27 +0,0 @@ -/*! - * bean.js - copyright Jacob Thornton 2011 - * https://github.com/fat/bean - * MIT License - * special thanks to: - * dean edwards: http://dean.edwards.name/ - * dperini: https://github.com/dperini/nwevents - * the entire mootools team: github.com/mootools/mootools-core - *//*global module:true, define:true*/ -!function(a,b,c){typeof module!="undefined"?module.exports=c(a,b):typeof define=="function"&&typeof define.amd=="object"?define(c):b[a]=c(a,b)}("bean",this,function(a,b){var c=window,d=b[a],e=/over|out/,f=/[^\.]*(?=\..*)\.|.*/,g=/\..*/,h="addEventListener",i="attachEvent",j="removeEventListener",k="detachEvent",l=document||{},m=l.documentElement||{},n=m[h],o=n?h:i,p=Array.prototype.slice,q=/click|mouse|menu|drag|drop/i,r=/^touch|^gesture/i,s={one:1},t=function(a,b,c){for(c=0;c0){b=b.split(" ");for(j=b.length;j--;)G(a,b[j],c);return a}h=l&&b.replace(g,""),h&&u[h]&&(h=u[h].type);if(!b||l){if(i=l&&b.replace(f,""))i=i.split(".");k(a,h,c,i)}else if(typeof b=="function")k(a,null,b);else for(d in b)b.hasOwnProperty(d)&&G(a,d,b[d]);return a},H=function(a,b,c,d,e){var f,g,h,i,j=c,k=c&&typeof c=="string";if(b&&!c&&typeof b=="object")for(f in b)b.hasOwnProperty(f)&&H.apply(this,[a,f,b[f]]);else{i=arguments.length>3?p.call(arguments,3):[],g=(k?c:b).split(" "),k&&(c=F(b,j=d,e))&&(i=p.call(i,1)),this===s&&(c=C(G,a,b,c,j));for(h=g.length;h--;)E(a,g[h],c,j,i)}return a},I=function(){return H.apply(s,arguments)},J=n?function(a,b,d){var e=l.createEvent(a?"HTMLEvents":"UIEvents");e[a?"initEvent":"initUIEvent"](b,!0,!0,c,1),d.dispatchEvent(e)}:function(a,b,c){c=w(c,a),a?c.fireEvent("on"+b,l.createEventObject()):c["_on"+b]++},K=function(a,b,c){var d,e,h,i,j,k=b.split(" ");for(d=k.length;d--;){b=k[d].replace(g,"");if(i=k[d].replace(f,""))i=i.split(".");if(!i&&!c&&a[o])J(t[b],b,a);else{j=y.get(a,b),c=[!1].concat(c);for(e=0,h=j.length;e=d.computed&&(d={value:a,computed:g})}),d.value},w.min=function(a,b,c){if(!b&&w.isArray(a))return Math.min.apply(Math,a);var d={computed:Infinity};return x(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;gd?1:0}),"value")},w.groupBy=function(a,b){var c={};return x(a,function(a,d){var e=b(a,d);(c[e]||(c[e]=[])).push(a)}),c},w.sortedIndex=function(a,b,c){c||(c=w.identity);var d=0,e=a.length;while(d>1;c(a[f])=0})})},w.difference=function(a,b){return w.filter(a,function(a){return!w.include(b,a)})},w.zip=function(){var a=g.call(arguments),b=w.max(w.pluck(a,"length")),c=new Array(b);for(var d=0;d=0;c--)b=[a[c].apply(this,b)];return b[0]}},w.after=function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}},w.keys=u||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[];for(var c in a)j.call(a,c)&&(b[b.length]=c);return b},w.values=function(a){return w.map(a,w.identity)},w.functions=w.methods=function(a){var b=[];for(var c in a)w.isFunction(a[c])&&b.push(c);return b.sort()},w.extend=function(a){return x(g.call(arguments,1),function(b){for(var c in b)b[c]!==void 0&&(a[c]=b[c])}),a},w.defaults=function(a){return x(g.call(arguments,1),function(b){for(var c in b)a[c]==null&&(a[c]=b[c])}),a},w.clone=function(a){return w.isArray(a)?a.slice():w.extend({},a)},w.tap=function(a,b){return b(a),a},w.isEqual=function(a,b){if(a===b)return!0;var c=typeof a,d=typeof b;if(c!=d)return!1;if(a==b)return!0;if(!a&&b||a&&!b)return!1;a._chain&&(a=a._wrapped),b._chain&&(b=b._wrapped);if(a.isEqual)return a.isEqual(b);if(b.isEqual)return b.isEqual(a);if(w.isDate(a)&&w.isDate(b))return a.getTime()===b.getTime();if(w.isNaN(a)&&w.isNaN(b))return!1;if(w.isRegExp(a)&&w.isRegExp(b))return a.source===b.source&&a.global===b.global&&a.ignoreCase===b.ignoreCase&&a.multiline===b.multiline;if(c!=="object")return!1;if(a.length&&a.length!==b.length)return!1;var e=w.keys(a),f=w.keys(b);if(e.length!=f.length)return!1;for(var g in a)if(!(g in b)||!w.isEqual(a[g],b[g]))return!1;return!0},w.isEmpty=function(a){if(w.isArray(a)||w.isString(a))return a.length===0;for(var b in a)if(j.call(a,b))return!1;return!0},w.isElement=function(a){return!!a&&a.nodeType==1},w.isArray=t||function(a){return i.call(a)==="[object Array]"},w.isObject=function(a){return a===Object(a)},w.isArguments=function(a){return!!a&&!!j.call(a,"callee")},w.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)},w.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)},w.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)},w.isNaN=function(a){return a!==a},w.isBoolean=function(a){return a===!0||a===!1},w.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)},w.isRegExp=function(a){return!(!(a&&a.test&&a.exec)||!a.ignoreCase&&a.ignoreCase!==!1)},w.isNull=function(a){return a===null},w.isUndefined=function(a){return a===void 0},w.noConflict=function(){return a._=b,this},w.identity=function(a){return a},w.times=function(a,b,c){for(var d=0;d/g,interpolate:/<%=([\s\S]+?)%>/g},w.template=function(a,b){var c=w.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(c.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(c.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj",d);return b?e(b):e};var B=function(a){this._wrapped=a};w.prototype=B.prototype;var C=function(a,b){return b?w(a).chain():a},D=function(a,b){B.prototype[a]=function(){var a=g.call(arguments);return h.call(a,this._wrapped),C(b.apply(w,a),this._chain)}};w.mixin(w),x(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=d[a];B.prototype[a]=function(){return b.apply(this._wrapped,arguments),C(this._wrapped,this._chain)}}),x(["concat","join","slice"],function(a){var b=d[a];B.prototype[a]=function(){return C(b.apply(this._wrapped,arguments),this._chain)}}),B.prototype.chain=function(){return this._chain=!0,this},B.prototype.value=function(){return this._wrapped}})(); -/** - * Flotr2 (c) 2012 Carl Sutherland - * MIT License - * Special thanks to: - * Flotr: http://code.google.com/p/flotr/ (fork) - * Flot: https://github.com/flot/flot (original fork) - */ -(function(){var a=this,b=this.Flotr,c;c={_:_,bean:bean,isIphone:/iphone/i.test(navigator.userAgent),isIE:navigator.appVersion.indexOf("MSIE")!=-1?parseFloat(navigator.appVersion.split("MSIE")[1]):!1,graphTypes:{},plugins:{},addType:function(a,b){c.graphTypes[a]=b,c.defaultOptions[a]=b.options||{},c.defaultOptions.defaultType=c.defaultOptions.defaultType||a},addPlugin:function(a,b){c.plugins[a]=b,c.defaultOptions[a]=b.options||{}},draw:function(a,b,d,e){return e=e||c.Graph,new e(a,b,d)},merge:function(a,b){var d,e,f=b||{};for(d in a)e=a[d],e&&typeof e=="object"?e.constructor===Array?f[d]=this._.clone(e):e.constructor!==RegExp&&!this._.isElement(e)?f[d]=c.merge(e,b?b[d]:undefined):f[d]=e:f[d]=e;return f},clone:function(a){return c.merge(a,{})},getTickSize:function(a,b,d,e){var f=(d-b)/a,g=c.getMagnitude(f),h=10,i=f/g;return i<1.5?h=1:i<2.25?h=2:i<3?h=e===0?2:2.5:i<7.5&&(h=5),h*g},defaultTickFormatter:function(a,b){return a+""},defaultTrackFormatter:function(a){return"("+a.x+", "+a.y+")"},engineeringNotation:function(a,b,c){var d=["Y","Z","E","P","T","G","M","k",""],e=["y","z","a","f","p","n","µ","m",""],f=d.length;c=c||1e3,b=Math.pow(10,b||2);if(a===0)return 0;if(a>1)while(f--&&a>=c)a/=c;else{d=e,f=d.length;while(f--&&a<1)a*=c}return Math.round(a*b)/b+d[f]},getMagnitude:function(a){return Math.pow(10,Math.floor(Math.log(a)/Math.LN10))},toPixel:function(a){return Math.floor(a)+.5},toRad:function(a){return-a*(Math.PI/180)},floorInBase:function(a,b){return b*Math.floor(a/b)},drawText:function(a,b,d,e,f){if(!a.fillText){a.drawText(b,d,e,f);return}f=this._.extend({size:c.defaultOptions.fontSize,color:"#000000",textAlign:"left",textBaseline:"bottom",weight:1,angle:0},f),a.save(),a.translate(d,e),a.rotate(f.angle),a.fillStyle=f.color,a.font=(f.weight>1?"bold ":"")+f.size*1.3+"px sans-serif",a.textAlign=f.textAlign,a.textBaseline=f.textBaseline,a.fillText(b,0,0),a.restore()},getBestTextAlign:function(a,b){return b=b||{textAlign:"center",textBaseline:"middle"},a+=c.getTextAngleFromAlign(b),Math.abs(Math.cos(a))>.01&&(b.textAlign=Math.cos(a)>0?"right":"left"),Math.abs(Math.sin(a))>.01&&(b.textBaseline=Math.sin(a)>0?"top":"bottom"),b},alignTable:{"right middle":0,"right top":Math.PI/4,"center top":Math.PI/2,"left top":3*(Math.PI/4),"left middle":Math.PI,"left bottom":-3*(Math.PI/4),"center bottom":-Math.PI/2,"right bottom":-Math.PI/4,"center middle":0},getTextAngleFromAlign:function(a){return c.alignTable[a.textAlign+" "+a.textBaseline]||0},noConflict:function(){return a.Flotr=b,this}},a.Flotr=c})(),Flotr.defaultOptions={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],ieBackgroundColor:"#FFFFFF",title:null,subtitle:null,shadowSize:4,defaultType:null,HtmlText:!0,fontColor:"#545454",fontSize:7.5,resolution:1,parseFloat:!0,xaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:0,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,mode:"normal",timeFormat:null,timeMode:"UTC",timeUnit:"millisecond",scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},x2axis:{},yaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:90,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},y2axis:{titleAngle:270},grid:{color:"#545454",backgroundColor:null,backgroundImage:null,watermarkAlpha:.4,tickColor:"#DDDDDD",labelMargin:3,verticalLines:!0,minorVerticalLines:null,horizontalLines:!0,minorHorizontalLines:null,outlineWidth:1,outline:"nsew",circular:!1},mouse:{track:!1,trackAll:!1,position:"se",relative:!1,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,trackY:!0,radius:3,fillColor:null,fillOpacity:.4}},function(){function b(a,b,c,d){this.rgba=["r","g","b","a"];var e=4;while(-1<--e)this[this.rgba[e]]=arguments[e]||(e==3?1:0);this.normalize()}var a=Flotr._,c={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]};b.prototype={scale:function(b,c,d,e){var f=4;while(-1<--f)a.isUndefined(arguments[f])||(this[this.rgba[f]]*=arguments[f]);return this.normalize()},alpha:function(b){return!a.isUndefined(b)&&!a.isNull(b)&&(this.a=b),this.normalize()},clone:function(){return new b(this.r,this.b,this.g,this.a)},limit:function(a,b,c){return Math.max(Math.min(a,c),b)},normalize:function(){var a=this.limit;return this.r=a(parseInt(this.r,10),0,255),this.g=a(parseInt(this.g,10),0,255),this.b=a(parseInt(this.b,10),0,255),this.a=a(this.a,0,1),this},distance:function(a){if(!a)return;a=new b.parse(a);var c=0,d=3;while(-1<--d)c+=Math.abs(this[this.rgba[d]]-a[this.rgba[d]]);return c},toString:function(){return this.a>=1?"rgb("+[this.r,this.g,this.b].join(",")+")":"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"},contrast:function(){var a=1-(.299*this.r+.587*this.g+.114*this.b)/255;return a<.5?"#000000":"#ffffff"}},a.extend(b,{parse:function(a){if(a instanceof b)return a;var d;if(d=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))return new b(parseInt(d[1],16),parseInt(d[2],16),parseInt(d[3],16));if(d=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))return new b(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10));if(d=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(a))return new b(parseInt(d[1]+d[1],16),parseInt(d[2]+d[2],16),parseInt(d[3]+d[3],16));if(d=/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(a))return new b(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10),parseFloat(d[4]));if(d=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(a))return new b(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55);if(d=/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(a))return new b(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55,parseFloat(d[4]));var e=(a+"").replace(/^\s*([\S\s]*?)\s*$/,"$1").toLowerCase();return e=="transparent"?new b(255,255,255,0):(d=c[e])?new b(d[0],d[1],d[2]):new b(0,0,0,0)},processColor:function(c,d){var e=d.opacity;if(!c)return"rgba(0, 0, 0, 0)";if(c instanceof b)return c.alpha(e).toString();if(a.isString(c))return b.parse(c).alpha(e).toString();var f=c.colors?c:{colors:c};if(!d.ctx)return a.isArray(f.colors)?b.parse(a.isArray(f.colors[0])?f.colors[0][1]:f.colors[0]).alpha(e).toString():"rgba(0, 0, 0, 0)";f=a.extend({start:"top",end:"bottom"},f),/top/i.test(f.start)&&(d.x1=0),/left/i.test(f.start)&&(d.y1=0),/bottom/i.test(f.end)&&(d.x2=0),/right/i.test(f.end)&&(d.y2=0);var g,h,i,j=d.ctx.createLinearGradient(d.x1,d.y1,d.x2,d.y2);for(g=0;g=m)break}m=e[p][0],n=e[p][1],n=="year"&&(m=Flotr.getTickSize(f.noTicks*d.year,i,j,0),m==.5&&(n="month",m=6)),a.tickUnit=n,a.tickSize=m;var q=new Date(i),r=m*d[n];switch(n){case"millisecond":s("Milliseconds");break;case"second":s("Seconds");break;case"minute":s("Minutes");break;case"hour":s("Hours");break;case"month":s("Month");break;case"year":s("FullYear")}r>=d.second&&b(q,"Milliseconds",g,0),r>=d.minute&&b(q,"Seconds",g,0),r>=d.hour&&b(q,"Minutes",g,0),r>=d.day&&b(q,"Hours",g,0),r>=d.day*4&&b(q,"Date",g,1),r>=d.year&&b(q,"Month",g,0);var t=0,u=NaN,v;do{v=u,u=q.getTime(),l.push({v:u/h,label:o(u/h,a)});if(n=="month")if(m<1){b(q,"Date",g,1);var w=q.getTime();b(q,"Month",g,c(q,"Month",g)+1);var x=q.getTime();q.setTime(u+t*d.hour+(x-w)*m),t=c(q,"Hours",g),b(q,"Hours",g,0)}else b(q,"Month",g,c(q,"Month",g)+m);else n=="year"?b(q,"FullYear",g,c(q,"FullYear",g)+m):q.setTime(u+r)}while(u0)return{x:b.touches[0].pageX,y:b.touches[0].pageY};if(!a._.isUndefined(b.changedTouches)&&b.changedTouches.length>0)return{x:b.changedTouches[0].pageX,y:b.changedTouches[0].pageY};if(b.pageX||b.pageY)return{x:b.pageX,y:b.pageY};if(b.clientX||b.clientY){var c=document,d=c.body,e=c.documentElement;return{x:b.clientX+d.scrollLeft+e.scrollLeft,y:b.clientY+d.scrollTop+e.scrollTop}}}}}(),function(){var a=Flotr,b=a.DOM,c=a._,d=function(a){this.o=a};d.prototype={dimensions:function(a,b,c,d){return a?this.o.html?this.html(a,this.o.element,c,d):this.canvas(a,b):{width:0,height:0}},canvas:function(b,c){if(!this.o.textEnabled)return;c=c||{};var d=this.measureText(b,c),e=d.width,f=c.size||a.defaultOptions.fontSize,g=c.angle||0,h=Math.cos(g),i=Math.sin(g),j=2,k=6,l;return l={width:Math.abs(h*e)+Math.abs(i*f)+j,height:Math.abs(i*e)+Math.abs(h*f)+k},l},html:function(a,c,d,e){var f=b.create("div");return b.setStyles(f,{position:"absolute",top:"-10000px"}),b.insert(f,'
    '+a+"
    "),b.insert(this.o.element,f),b.size(f)},measureText:function(b,d){var e=this.o.ctx,f;return!e.fillText||a.isIphone&&e.measure?{width:e.measure(b,d)}:(d=c.extend({size:a.defaultOptions.fontSize,weight:1,angle:0},d),e.save(),e.font=(d.weight>1?"bold ":"")+d.size*1.3+"px sans-serif",f=e.measureText(b),e.restore(),f)}},Flotr.Text=d}(),function(){function e(a,c,d){return b.observe.apply(this,arguments),this._handles.push(arguments),this}var a=Flotr.DOM,b=Flotr.EventAdapter,c=Flotr._,d=Flotr;Graph=function(a,e,f){this._setEl(a),this._initMembers(),this._initPlugins(),b.fire(this.el,"flotr:beforeinit",[this]),this.data=e,this.series=d.Series.getSeries(e),this._initOptions(f),this._initGraphTypes(),this._initCanvas(),this._text=new d.Text({element:this.el,ctx:this.ctx,html:this.options.HtmlText,textEnabled:this.textEnabled}),b.fire(this.el,"flotr:afterconstruct",[this]),this._initEvents(),this.findDataRanges(),this.calculateSpacing(),this.draw(c.bind(function(){b.fire(this.el,"flotr:afterinit",[this])},this))},Graph.prototype={destroy:function(){b.fire(this.el,"flotr:destroy"),c.each(this._handles,function(a){b.stopObserving.apply(this,a)}),this._handles=[],this.el.graph=null},observe:e,_observe:e,processColor:function(a,b){var e={x1:0,y1:0,x2:this.plotWidth,y2:this.plotHeight,opacity:1,ctx:this.ctx};return c.extend(e,b),d.Color.processColor(a,e)},findDataRanges:function(){var a=this.axes,b,e,f;c.each(this.series,function(a){f=a.getRange(),f&&(b=a.xaxis,e=a.yaxis,b.datamin=Math.min(f.xmin,b.datamin),b.datamax=Math.max(f.xmax,b.datamax),e.datamin=Math.min(f.ymin,e.datamin),e.datamax=Math.max(f.ymax,e.datamax),b.used=b.used||f.xused,e.used=e.used||f.yused)},this),!a.x.used&&!a.x2.used&&(a.x.used=!0),!a.y.used&&!a.y2.used&&(a.y.used=!0),c.each(a,function(a){a.calculateRange()});var g=c.keys(d.graphTypes),h=!1;c.each(this.series,function(a){if(a.hide)return;c.each(g,function(b){a[b]&&a[b].show&&(this.extendRange(b,a),h=!0)},this),h||this.extendRange(this.options.defaultType,a)},this)},extendRange:function(a,b){this[a].extendRange&&this[a].extendRange(b,b.data,b[a],this[a]),this[a].extendYRange&&this[a].extendYRange(b.yaxis,b.data,b[a],this[a]),this[a].extendXRange&&this[a].extendXRange(b.xaxis,b.data,b[a],this[a])},calculateSpacing:function(){var a=this.axes,b=this.options,d=this.series,e=b.grid.labelMargin,f=this._text,g=a.x,h=a.x2,i=a.y,j=a.y2,k=b.grid.outlineWidth,l,m,n,o;c.each(a,function(a){a.calculateTicks(),a.calculateTextDimensions(f,b)}),o=f.dimensions(b.title,{size:b.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title"),this.titleHeight=o.height,o=f.dimensions(b.subtitle,{size:b.fontSize},"font-size:smaller;","flotr-subtitle"),this.subtitleHeight=o.height;for(m=0;m1&&(this.multitouches=c.touches),b.fire(a,"flotr:mousedown",[event,this]),this.observe(document,"touchend",d)},this)),this.observe(this.overlay,"touchmove",c.bind(function(c){var d=this.getEventPosition(c);c.preventDefault(),e=!0,this.multitouches||c.touches&&c.touches.length>1?this.multitouches=c.touches:f||b.fire(a,"flotr:mousemove",[event,d,this]),this.lastMousePos=d},this))):this.observe(this.overlay,"mousedown",c.bind(this.mouseDownHandler,this)).observe(a,"mousemove",c.bind(this.mouseMoveHandler,this)).observe(this.overlay,"click",c.bind(this.clickHandler,this)).observe(a,"mouseout",function(){b.fire(a,"flotr:mouseout")})},_initCanvas:function(){function k(e,f){return e||(e=a.create("canvas"),typeof FlashCanvas!="undefined"&&typeof e.getContext=="function"&&FlashCanvas.initElement(e),e.className="flotr-"+f,e.style.cssText="position:absolute;left:0px;top:0px;",a.insert(b,e)),c.each(i,function(b,c){a.show(e);if(f=="canvas"&&e.getAttribute(c)===b)return;e.setAttribute(c,b*d.resolution),e.style[c]=b+"px"}),e.context_=null,e}function l(a){window.G_vmlCanvasManager&&window.G_vmlCanvasManager.initElement(a);var b=a.getContext("2d");return window.G_vmlCanvasManager||b.scale(d.resolution,d.resolution),b}var b=this.el,d=this.options,e=b.children,f=[],g,h,i,j;for(h=e.length;h--;)g=e[h],!this.canvas&&g.className==="flotr-canvas"?this.canvas=g:!this.overlay&&g.className==="flotr-overlay"?this.overlay=g:f.push(g);for(h=f.length;h--;)b.removeChild(f[h]);a.setStyles(b,{position:"relative"}),i={},i.width=b.clientWidth,i.height=b.clientHeight;if(i.width<=0||i.height<=0||d.resolution<=0)throw"Invalid dimensions for plot, width = "+i.width+", height = "+i.height+", resolution = "+d.resolution;this.canvas=k(this.canvas,"canvas"),this.overlay=k(this.overlay,"overlay"),this.ctx=l(this.canvas),this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.octx=l(this.overlay),this.octx.clearRect(0,0,this.overlay.width,this.overlay.height),this.canvasHeight=i.height,this.canvasWidth=i.width,this.textEnabled=!!this.ctx.drawText||!!this.ctx.fillText},_initPlugins:function(){c.each(d.plugins,function(a,b){c.each(a.callbacks,function(a,b){this.observe(this.el,b,c.bind(a,this))},this),this[b]=d.clone(a),c.each(this[b],function(a,d){c.isFunction(a)&&(this[b][d]=c.bind(a,this))},this)},this)},_initOptions:function(a){var e=d.clone(d.defaultOptions);e.x2axis=c.extend(c.clone(e.xaxis),e.x2axis),e.y2axis=c.extend(c.clone(e.yaxis),e.y2axis),this.options=d.merge(a||{},e),this.options.grid.minorVerticalLines===null&&this.options.xaxis.scaling==="logarithmic"&&(this.options.grid.minorVerticalLines=!0),this.options.grid.minorHorizontalLines===null&&this.options.yaxis.scaling==="logarithmic"&&(this.options.grid.minorHorizontalLines=!0),b.fire(this.el,"flotr:afterinitoptions",[this]),this.axes=d.Axis.getAxes(this.options);var f=[],g=[],h=this.series.length,i=this.series.length,j=this.options.colors,k=[],l=0,m,n,o,p;for(n=i-1;n>-1;--n)m=this.series[n].color,m&&(--i,c.isNumber(m)?f.push(m):k.push(d.Color.parse(m)));for(n=f.length-1;n>-1;--n)i=Math.max(i,f[n]+1);for(n=0;g.length=j.length&&(n=0,++l)}for(n=0,o=0;n10?b.minorTickFreq=0:g-h>5?b.minorTickFreq=2:b.minorTickFreq=5)}else a.tickSize=Flotr.getTickSize(b.noTicks,c,d,b.tickDecimals);a.min=c,a.max=d,b.min===null&&b.autoscale&&(a.min-=a.tickSize*e,a.min<0&&a.datamin>=0&&(a.min=0),a.min=a.tickSize*Math.floor(a.min/a.tickSize)),b.max===null&&b.autoscale&&(a.max+=a.tickSize*e,a.max>0&&a.datamax<=0&&a.datamax!=a.datamin&&(a.max=0),a.max=a.tickSize*Math.ceil(a.max/a.tickSize)),a.min==a.max&&(a.max=a.min+1)},calculateTextDimensions:function(a,b){var c="",d,e;if(this.options.showLabels)for(e=0;ec.length&&(c=this.ticks[e].label);this.maxLabel=a.dimensions(c,{size:b.fontSize,angle:Flotr.toRad(this.options.labelsAngle)},"font-size:smaller;","flotr-grid-label"),this.titleSize=a.dimensions(this.options.title,{size:b.fontSize*1.2,angle:Flotr.toRad(this.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},_cleanUserTicks:function(b,c){var d=this,e=this.options,f,g,h,i;a.isFunction(b)&&(b=b({min:d.min,max:d.max}));for(g=0;g1?i[1]:e.tickFormatter(f,{min:d.min,max:d.max})):(f=i,h=e.tickFormatter(f,{min:this.min,max:this.max})),c[g]={v:f,label:h}},_calculateTimeTicks:function(){this.ticks=Flotr.Date.generator(this)},_calculateLogTicks:function(){var a=this,b=a.options,c,d,e=Math.log(a.max);b.base!=Math.E&&(e/=Math.log(b.base)),e=Math.ceil(e);var f=Math.log(a.min);b.base!=Math.E&&(f/=Math.log(b.base)),f=Math.ceil(f);for(i=f;ie&&(e=i,g=!0),jf&&(f=j,h=!0);return{xmin:c,xmax:e,ymin:d,ymax:f,xused:g,yused:h}}},a.extend(b,{getSeries:function(c){return a.map(c,function(c){var d;return c.data?(d=new b,a.extend(d,c)):d=new b({data:c}),d})}}),Flotr.Series=b}(),Flotr.addType("lines",{options:{show:!1,lineWidth:2,fill:!1,fillBorder:!1,fillColor:null,fillOpacity:.4,steps:!1,stacked:!1},stack:{values:[]},draw:function(a){var b=a.context,c=a.lineWidth,d=a.shadowSize,e;b.save(),b.lineJoin="round",d&&(b.lineWidth=d/2,e=c/2+b.lineWidth/2,b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,e+d/2,!1),b.strokeStyle="rgba(0,0,0,0.2)",this.plot(a,e,!1)),b.lineWidth=c,b.strokeStyle=a.color,this.plot(a,0,!0),b.restore()},plot:function(a,b,c){var d=a.context,e=a.width,f=a.height,g=a.xScale,h=a.yScale,i=a.data,j=a.stacked?this.stack:!1,k=i.length-1,l=null,m=null,n=h(0),o,p,q,r,s,t,u;if(k<1)return;d.beginPath();for(u=0;uf&&r>f||q<0&&r<0||o<0&&p<0||o>e&&p>e)continue;(l!=o||m!=q+b)&&d.moveTo(o,q+b),l=p,m=r+b,a.steps?(d.lineTo(l+b/2,q+b),d.lineTo(l+b/2,m)):d.lineTo(l,m)}(!a.fill||a.fill&&!a.fillBorder)&&d.stroke(),!b&&a.fill&&(o=g(i[0][0]),d.fillStyle=a.fillStyle,d.lineTo(p,n),d.lineTo(o,n),d.lineTo(o,h(i[0][1])),d.fill(),a.fillBorder&&d.stroke()),d.closePath()},extendYRange:function(a,b,c,d){var e=a.options;if(c.stacked&&(!e.max&&e.max!==0||!e.min&&e.min!==0)){var f=a.max,g=a.min,h=d.positiveSums||{},i=d.negativeSums||{},j,k;for(k=0;k0?(h[j]=(h[j]||0)+b[k][1],f=Math.max(f,h[j])):(i[j]=(i[j]||0)+b[k][1],g=Math.min(g,i[j]));d.negativeSums=i,d.positiveSums=h,a.max=f,a.min=g}c.steps&&(this.hit=function(a){var b=a.data,c=a.args,d=a.yScale,e=c[0],f=b.length,g=c[1],h=e.x,i=e.relY,j;for(j=0;j=b[j][0]&&h<=b[j+1][0]){Math.abs(d(b[j][1])-i)<8&&(g.x=b[j][0],g.y=b[j][1],g.index=j,g.seriesIndex=a.index);break}},this.drawHit=function(a){var b=a.context,c=a.args,d=a.data,e=a.xScale,f=c.index,g=e(c.x),h=a.yScale(c.y),i;d.length-1>f&&(i=a.xScale(d[f+1][0]),b.save(),b.strokeStyle=a.color,b.lineWidth=a.lineWidth,b.beginPath(),b.moveTo(g,h),b.lineTo(i,h),b.stroke(),b.closePath(),b.restore())},this.clearHit=function(a){var b=a.context,c=a.args,d=a.data,e=a.xScale,f=a.lineWidth,g=c.index,h=e(c.x),i=a.yScale(c.y),j;d.length-1>g&&(j=a.xScale(d[g+1][0]),b.clearRect(h-f,i-f,j-h+2*f,2*f))})}}),Flotr.addType("bars",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,horizontal:!1,stacked:!1,centered:!0,topPadding:.1},stack:{positive:[],negative:[],_positive:[],_negative:[]},draw:function(a){var b=a.context;b.save(),b.lineJoin="miter",b.lineWidth=a.lineWidth,b.strokeStyle=a.color,a.fill&&(b.fillStyle=a.fillStyle),this.plot(a),b.restore()},plot:function(a){var b=a.data,c=a.context,d=a.shadowSize,e,f,g,h,i,j;if(b.length<1)return;this.translate(c,a.horizontal);for(e=0;e0?g.positive:g.negative,n=o[l]||n,o[l]=n+m),p=j(l-i),q=j(l+e-i),r=k(m+n),s=k(n),s<0&&(s=0),a===null||b===null?null:{x:l,y:m,xScale:j,yScale:k,top:r,left:Math.min(p,q)-h/2,width:Math.abs(q-p)-h,height:s-r}},hit:function(a){var b=a.data,c=a.args,d=c[0],e=c[1],f=d.x,g=d.y,h=this.getBarGeometry(f,g,a),i=h.width/2,j=h.left,k,l;for(l=b.length;l--;)k=this.getBarGeometry(b[l][0],b[l][1],a),k.y>h.y&&Math.abs(j-k.left)0?(j[l]=(j[l]||0)+m,g=Math.max(g,j[l])):(k[l]=(k[l]||0)+m,f=Math.min(f,k[l]));(i==1&&h||i==-1&&!h)&&c.topPadding&&(a.max===a.datamax||c.stacked&&this.stackMax!==g)&&(g+=c.topPadding*(g-f)),this.stackMin=f,this.stackMax=g,this.negativeSums=k,this.positiveSums=j,a.max=g,a.min=f}}),Flotr.addType("bubbles",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,baseRadius:2},draw:function(a){var b=a.context,c=a.shadowSize;b.save(),b.lineWidth=a.lineWidth,b.fillStyle="rgba(0,0,0,0.05)",b.strokeStyle="rgba(0,0,0,0.05)",this.plot(a,c/2),b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,c/4),b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e,f,g,h,i;b=b||0;for(f=0;fr?"downFillColor":"upFillColor"],a.fill&&!a.barcharts&&(c.fillStyle="rgba(0,0,0,0.05)",c.fillRect(s+g,x+g,t-s,w-x),c.save(),c.globalAlpha=a.fillOpacity,c.fillStyle=k,c.fillRect(s,x+h,t-s,w-x),c.restore());if(h||i)m=Math.floor((s+t)/2)+j,c.strokeStyle=k,c.beginPath(),a.barcharts?(c.moveTo(m,Math.floor(v+f)),c.lineTo(m,Math.floor(u+f)),n=Math.floor(o+f)+.5,c.moveTo(Math.floor(s)+j,n),c.lineTo(m,n),n=Math.floor(r+f)+.5,c.moveTo(Math.floor(t)+j,n),c.lineTo(m,n)):(c.strokeRect(s,x+h,t-s,w-x),c.moveTo(m,Math.floor(x+h)),c.lineTo(m,Math.floor(v+h)),c.moveTo(m,Math.floor(w+h)),c.lineTo(m,Math.floor(u+h))),c.closePath(),c.stroke()}},extendXRange:function(a,b,c){a.options.max===null&&(a.max=Math.max(a.datamax+.5,a.max),a.min=Math.min(a.datamin-.5,a.min))}}),Flotr.addType("gantt",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,centered:!0},draw:function(a){var b=this.ctx,c=a.gantt.barWidth,d=Math.min(a.gantt.lineWidth,c);b.save(),b.translate(this.plotOffset.left,this.plotOffset.top),b.lineJoin="miter",b.lineWidth=d,b.strokeStyle=a.color,b.save(),this.gantt.plotShadows(a,c,0,a.gantt.fill),b.restore();if(a.gantt.fill){var e=a.gantt.fillColor||a.color;b.fillStyle=this.processColor(e,{opacity:a.gantt.fillOpacity})}this.gantt.plot(a,c,0,a.gantt.fill),b.restore()},plot:function(a,b,c,d){var e=a.data;if(e.length<1)return;var f=a.xaxis,g=a.yaxis,h=this.ctx,i;for(i=0;if.max||sg.max)continue;pf.max&&(q=f.max,f.lastSerie!=a&&(n=!1)),rg.max&&(s=g.max,g.lastSerie!=a&&(n=!1)),d&&(h.beginPath(),h.moveTo(f.d2p(p),g.d2p(r)+c),h.lineTo(f.d2p(p),g.d2p(s)+c),h.lineTo(f.d2p(q),g.d2p(s)+c),h.lineTo(f.d2p(q),g.d2p(r)+c),h.fill(),h.closePath()),a.gantt.lineWidth&&(m||o||n)&&(h.beginPath(),h.moveTo(f.d2p(p),g.d2p(r)+c),h[m?"lineTo":"moveTo"](f.d2p(p),g.d2p(s)+c),h[n?"lineTo":"moveTo"](f.d2p(q),g.d2p(s)+c),h[o?"lineTo":"moveTo"](f.d2p(q),g.d2p(r)+c),h.stroke(),h.closePath())}},plotShadows:function(a,b,c){var d=a.data;if(d.length<1)return;var e,f,g,h,i=a.xaxis,j=a.yaxis,k=this.ctx,l=this.options.shadowSize;for(e=0;ei.max||pj.max)continue;mi.max&&(n=i.max),oj.max&&(p=j.max);var q=i.d2p(n)-i.d2p(m)-(i.d2p(n)+l<=this.plotWidth?0:l),r=j.d2p(o)-j.d2p(p)-(j.d2p(o)+l<=this.plotHeight?0:l);k.fillStyle="rgba(0,0,0,0.05)",k.fillRect(Math.min(i.d2p(m)+l,this.plotWidth),Math.min(j.d2p(p)+l,this.plotHeight),q,r)}},extendXRange:function(a){if(a.options.max===null){var b=a.min,c=a.max,d,e,f,g,h,i={},j={},k=null;for(d=0;db&&(b=a.max+g.barWidth)}}a.lastSerie=j,a.max=b,a.min=c,a.tickSize=Flotr.getTickSize(a.options.noTicks,c,b,a.options.tickDecimals)}}}),function(){function a(a){return typeof a=="object"&&a.constructor&&(Image?!0:a.constructor===Image)}Flotr.defaultMarkerFormatter=function(a){return Math.round(a.y*100)/100+""},Flotr.addType("markers",{options:{show:!1,lineWidth:1,color:"#000000",fill:!1,fillColor:"#FFFFFF",fillOpacity:.4,stroke:!1,position:"ct",verticalMargin:0,labelFormatter:Flotr.defaultMarkerFormatter,fontSize:Flotr.defaultOptions.fontSize,stacked:!1,stackingType:"b",horizontal:!1},stack:{positive:[],negative:[],values:[]},draw:function(a){function m(a,b){return g=d.negative[a]||0,f=d.positive[a]||0,b>0?(d.positive[a]=g+b,g+b):(d.negative[a]=f+b,f+b)}var b=a.data,c=a.context,d=a.stacked?a.stack:!1,e=a.stackingType,f,g,h,i,j,k,l;c.save(),c.lineJoin="round",c.lineWidth=a.lineWidth,c.strokeStyle="rgba(0,0,0,0.5)",c.fillStyle=a.fillStyle;for(i=0;i0?"top":"bottom",B,C,D,x,y;c.save(),c.translate(i/2,h/2),c.scale(1,q),C=Math.cos(u)*j,D=Math.sin(u)*j,f>0&&(this.plotSlice(C+f,D+f,n,s,t,c),l&&(c.fillStyle="rgba(0,0,0,0.1)",c.fill())),this.plotSlice(C,D,n,s,t,c),l&&(c.fillStyle=m,c.fill()),c.lineWidth=e,c.strokeStyle=k,c.stroke(),B={size:a.fontSize*1.2,color:a.fontColor,weight:1.5},v&&(a.htmlText||!a.textEnabled?(divStyle="position:absolute;"+A+":"+(h/2+(A==="top"?y:-y))+"px;",divStyle+=z+":"+(i/2+(z==="right"?-x:x))+"px;",p.push('
    ',v,"
    ")):(B.textAlign=z,B.textBaseline=A,Flotr.drawText(c,v,x,y,B)));if(a.htmlText||!a.textEnabled){var E=Flotr.DOM.node('
    ');Flotr.DOM.insert(E,p.join("")),Flotr.DOM.insert(a.element,E)}c.restore(),this.startAngle=t,this.slices=this.slices||[],this.slices.push({radius:Math.min(d.width,d.height)*g/2,x:C,y:D,explode:j,start:s,end:t})},plotSlice:function(a,b,c,d,e,f){f.beginPath(),f.moveTo(a,b),f.arc(a,b,c,d,e,!1),f.lineTo(a,b),f.closePath()},hit:function(a){var b=a.data[0],c=a.args,d=a.index,e=c[0],f=c[1],g=this.slices[d],h=e.relX-a.width/2,i=e.relY-a.height/2,j=Math.sqrt(h*h+i*i),k=Math.atan(i/h),l=Math.PI*2,m=g.explode||a.explode,n=g.start%l,o=g.end%l;h<0?k+=Math.PI:h>0&&i<0&&(k+=l),jm&&(n>o&&(kn)||k>n&&k0&&(b.lineWidth=d/2,b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,d/2+b.lineWidth/2),b.strokeStyle="rgba(0,0,0,0.2)",this.plot(a,b.lineWidth/2)),b.lineWidth=a.lineWidth,b.strokeStyle=a.color,b.fillStyle=a.fillColor||a.color,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e=a.xScale,f=a.yScale,g,h,i;for(g=c.length-1;g>-1;--g){i=c[g][1];if(i===null)continue;h=e(c[g][0]),i=f(i);if(h<0||h>a.width||i<0||i>a.height)continue;d.beginPath(),b?d.arc(h,i+b,a.radius,0,Math.PI,!1):(d.arc(h,i,a.radius,0,2*Math.PI,!0),a.fill&&d.fill()),d.stroke(),d.closePath()}}}),Flotr.addType("radar",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,radiusRatio:.9},draw:function(a){var b=a.context,c=a.shadowSize;b.save(),b.translate(a.width/2,a.height/2),b.lineWidth=a.lineWidth,b.fillStyle="rgba(0,0,0,0.05)",b.strokeStyle="rgba(0,0,0,0.05)",this.plot(a,c/2),b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,c/4),b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e=Math.min(a.height,a.width)*a.radiusRatio/2,f=2*Math.PI/c.length,g=-Math.PI/2,h,i;b=b||0,d.beginPath();for(h=0;hthis.plotWidth||b.relY>this.plotHeight){this.el.style.cursor=null,a.removeClass(this.el,"flotr-crosshair");return}d.hideCursor&&(this.el.style.cursor="none",a.addClass(this.el,"flotr-crosshair")),c.save(),c.strokeStyle=d.color,c.lineWidth=1,c.beginPath(),d.mode.indexOf("x")!=-1&&(c.moveTo(f,e.top),c.lineTo(f,e.top+this.plotHeight)),d.mode.indexOf("y")!=-1&&(c.moveTo(e.left,g),c.lineTo(e.left+this.plotWidth,g)),c.stroke(),c.restore()},clearCrosshair:function(){var a=this.plotOffset,b=this.lastMousePos,c=this.octx;b&&(c.clearRect(b.relX+a.left,a.top,1,this.plotHeight+1),c.clearRect(a.left,b.relY+a.top,this.plotWidth+1,1))}})}(),function(){function c(a,b,c,d){var e="image/"+a,f=b.toDataURL(e),g=new Image;return g.src=f,g}var a=Flotr.DOM,b=Flotr._;Flotr.addPlugin("download",{saveImage:function(d,e,f,g){var h=null;if(Flotr.isIE&&Flotr.isIE<9)return h=""+this.canvas.firstChild.innerHTML+"",window.open().document.write(h);if(d!=="jpeg"&&d!=="png")return;h=c(d,this.canvas,e,f);if(b.isElement(h)&&g)this.download.restoreCanvas(),a.hide(this.canvas),a.hide(this.overlay),a.setStyles({position:"absolute"}),a.insert(this.el,h),this.saveImageElement=h;else return window.open(h.src)},restoreCanvas:function(){a.show(this.canvas),a.show(this.overlay),this.saveImageElement&&this.el.removeChild(this.saveImageElement),this.saveImageElement=null}})}(),function(){var a=Flotr.EventAdapter,b=Flotr._;Flotr.addPlugin("graphGrid",{callbacks:{"flotr:beforedraw":function(){this.graphGrid.drawGrid()},"flotr:afterdraw":function(){this.graphGrid.drawOutline()}},drawGrid:function(){function p(a){for(n=0;n=l.max||(a==l.min||a==l.max)&&e.outlineWidth)return;d(Math.floor(l.d2p(a))+c.lineWidth/2)})}function r(a){c.moveTo(a,0),c.lineTo(a,j)}function s(a){c.moveTo(0,a),c.lineTo(k,a)}var c=this.ctx,d=this.options,e=d.grid,f=e.verticalLines,g=e.horizontalLines,h=e.minorVerticalLines,i=e.minorHorizontalLines,j=this.plotHeight,k=this.plotWidth,l,m,n,o;(f||h||g||i)&&a.fire(this.el,"flotr:beforegrid",[this.axes.x,this.axes.y,d,this]),c.save(),c.lineWidth=1,c.strokeStyle=e.tickColor;if(e.circular){c.translate(this.plotOffset.left+k/2,this.plotOffset.top+j/2);var t=Math.min(j,k)*d.radar.radiusRatio/2,u=this.axes.x.ticks.length,v=2*(Math.PI/u),w=-Math.PI/2;c.beginPath(),l=this.axes.y,g&&p(l.ticks),i&&p(l.minorTicks),f&&b.times(u,function(a){c.moveTo(0,0),c.lineTo(Math.cos(a*v+w)*t,Math.sin(a*v+w)*t)}),c.stroke()}else c.translate(this.plotOffset.left,this.plotOffset.top),e.backgroundColor&&(c.fillStyle=this.processColor(e.backgroundColor,{x1:0,y1:0,x2:k,y2:j}),c.fillRect(0,0,k,j)),c.beginPath(),l=this.axes.x,f&&q(l.ticks,r),h&&q(l.minorTicks,r),l=this.axes.y,g&&q(l.ticks,s),i&&q(l.minorTicks,s),c.stroke();c.restore(),(f||h||g||i)&&a.fire(this.el,"flotr:aftergrid",[this.axes.x,this.axes.y,d,this])},drawOutline:function(){var a=this,b=a.options,c=b.grid,d=c.outline,e=a.ctx,f=c.backgroundImage,g=a.plotOffset,h=g.left,j=g.top,k=a.plotWidth,l=a.plotHeight,m,n,o,p,q,r;if(!c.outlineWidth)return;e.save();if(c.circular){e.translate(h+k/2,j+l/2);var s=Math.min(l,k)*b.radar.radiusRatio/2,t=this.axes.x.ticks.length,u=2*(Math.PI/t),v=-Math.PI/2;e.beginPath(),e.lineWidth=c.outlineWidth,e.strokeStyle=c.color,e.lineJoin="round";for(i=0;i<=t;++i)e[i===0?"moveTo":"lineTo"](Math.cos(i*u+v)*s,Math.sin(i*u+v)*s);e.stroke()}else{e.translate(h,j);var w=c.outlineWidth,x=.5-w+(w+1)%2/2,y="lineTo",z="moveTo";e.lineWidth=w,e.strokeStyle=c.color,e.lineJoin="miter",e.beginPath(),e.moveTo(x,x),k-=w/2%1,l+=w/2,e[d.indexOf("n")!==-1?y:z](k,x),e[d.indexOf("e")!==-1?y:z](k,l),e[d.indexOf("s")!==-1?y:z](x,l),e[d.indexOf("w")!==-1?y:z](x,x),e.stroke(),e.closePath()}e.restore(),f&&(o=f.src||f,p=(parseInt(f.left,10)||0)+g.left,q=(parseInt(f.top,10)||0)+g.top,n=new Image,n.onload=function(){e.save(),f.alpha&&(e.globalAlpha=f.alpha),e.globalCompositeOperation="destination-over",e.drawImage(n,0,0,n.width,n.height,p,q,k,l),e.restore()},n.src=o)}})}(),function(){var a=Flotr.DOM,b=Flotr._,c=Flotr,d="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;";Flotr.addPlugin("hit",{callbacks:{"flotr:mousemove":function(a,b){this.hit.track(b)},"flotr:click":function(a){this.hit.track(a)},"flotr:mouseout":function(){this.hit.clearHit()}},track:function(a){(this.options.mouse.track||b.any(this.series,function(a){return a.mouse&&a.mouse.track}))&&this.hit.hit(a)},executeOnType:function(a,d,e){function h(a,h){b.each(b.keys(c.graphTypes),function(b){a[b]&&a[b].show&&this[b][d]&&(g=this.getOptions(a,b),g.fill=!!a.mouse.fillColor,g.fillStyle=this.processColor(a.mouse.fillColor||"#ffffff",{opacity:a.mouse.fillOpacity}),g.color=a.mouse.lineColor,g.context=this.octx,g.index=h,e&&(g.args=e),this[b][d].call(this[b],g),f=!0)},this)}var f=!1,g;return b.isArray(a)||(a=[a]),b.each(a,h,this),f},drawHit:function(a){var b=this.octx,c=a.series;if(c.mouse.lineColor){b.save(),b.lineWidth=c.points?c.points.lineWidth:1,b.strokeStyle=c.mouse.lineColor,b.fillStyle=this.processColor(c.mouse.fillColor||"#ffffff",{opacity:c.mouse.fillOpacity}),b.translate(this.plotOffset.left,this.plotOffset.top);if(!this.hit.executeOnType(c,"drawHit",a)){var d=a.xaxis,e=a.yaxis;b.beginPath(),b.arc(d.d2p(a.x),e.d2p(a.y),c.points.radius||c.mouse.radius,0,2*Math.PI,!0),b.fill(),b.stroke(),b.closePath()}b.restore()}this.prevHit=a},clearHit:function(){var b=this.prevHit,c=this.octx,d=this.plotOffset;c.save(),c.translate(d.left,d.top);if(b){if(!this.hit.executeOnType(b.series,"clearHit",this.prevHit)){var e=b.series,f=e.points?e.points.lineWidth:1;offset=(e.points.radius||e.mouse.radius)+f,c.clearRect(b.xaxis.d2p(b.x)-offset,b.yaxis.d2p(b.y)-offset,offset*2,offset*2)}a.hide(this.mouseTrack),this.prevHit=null}c.restore()},hit:function(a){var c=this.options,d=this.prevHit,e,f,g,h,i,j,k,l;if(this.series.length===0)return;n={relX:a.relX,relY:a.relY,absX:a.absX,absY:a.absY};if(c.mouse.trackY&&!c.mouse.trackAll&&this.hit.executeOnType(this.series,"hit",[a,n]))b.isUndefined(n.seriesIndex)||(i=this.series[n.seriesIndex],n.series=i,n.mouse=i.mouse,n.xaxis=i.xaxis,n.yaxis=i.yaxis);else{e=this.hit.closest(a);if(e){e=c.mouse.trackY?e.point:e.x,h=e.seriesIndex,i=this.series[h],k=i.xaxis,l=i.yaxis,f=2*i.mouse.sensibility;if(c.mouse.trackAll||e.distanceXk.xaxis.max)continue;n=Math.abs(p-d),o=Math.abs(q-e),m=n*n+o*o,m
    '),this.mouseTrack=i,a.insert(this.el,i));if(!b.mouse.relative)f.charAt(0)=="n"?c+="top:"+(g+n)+"px;bottom:auto;":f.charAt(0)=="s"&&(c+="bottom:"+(g+m)+"px;top:auto;"),f.charAt(1)=="e"?c+="right:"+(g+l)+"px;left:auto;":f.charAt(1)=="w"&&(c+="left:"+(g+k)+"px;right:auto;");else if(e.bars.show)c+="bottom:"+(g-n-b.yaxis.d2p(b.y/2)+this.canvasHeight)+"px;top:auto;",c+="left:"+(g+k+b.xaxis.d2p(b.x-p.bars.barWidth/2))+"px;right:auto;";else if(e.pie.show){var q={x:this.plotWidth/2,y:this.plotHeight/2},r=Math.min(this.canvasWidth,this.canvasHeight)*e.pie.sizeRatio/2,s=b.sAngle=5||Math.abs(a.second.y-a.first.y)>=5}})}(),function(){var a=Flotr.DOM;Flotr.addPlugin("labels",{callbacks:{"flotr:afterdraw":function(){this.labels.draw()}},draw:function(){function s(a,b,d){var e=d?b.minorTicks:b.ticks,f=b.orientation===1,h=b.n===1,k,m;k={color:b.options.color||o.grid.color,angle:Flotr.toRad(b.options.labelsAngle),textBaseline:"middle"};for(l=0;l(f?a.plotWidth:a.plotHeight))continue;Flotr.drawText(p,c.label,k(a,f,g,i),m(a,f,g,i),h),!f&&!g&&(p.save(),p.strokeStyle=h.color,p.beginPath(),p.moveTo(a.plotOffset.left+a.plotWidth-8,a.plotOffset.top+b.d2p(c.v)),p.lineTo(a.plotOffset.left+a.plotWidth,a.plotOffset.top+b.d2p(c.v)),p.stroke(),p.restore())}}function u(a,b){var d=b.orientation===1,e=b.n===1,g="",h,i,j,k=a.plotOffset;!d&&!e&&(p.save(),p.strokeStyle=b.options.color||o.grid.color,p.beginPath());if(b.options.showLabels&&(e?!0:b.used))for(l=0;l(d?a.canvasWidth:a.canvasHeight))continue;j=k.top+(d?(e?1:-1)*(a.plotHeight+o.grid.labelMargin):b.d2p(c.v)-b.maxLabel.height/2),h=d?k.left+b.d2p(c.v)-f/2:0,g="",l===0?g=" first":l===b.ticks.length-1&&(g=" last"),g+=d?" flotr-grid-label-x":" flotr-grid-label-y",m+=['
    '+c.label+"
    "].join(" "),!d&&!e&&(p.moveTo(k.left+a.plotWidth-8,k.top+b.d2p(c.v)),p.lineTo(k.left+a.plotWidth,k.top+b.d2p(c.v)))}}var b,c,d,e,f,g,h,i,j,k,l,m="",n=0,o=this.options,p=this.ctx,q=this.axes,r={size:o.fontSize};for(l=0;l-1;--m){if(!c[m].label||c[m].hide)continue;n=f.labelFormatter(c[m].label),v=Math.max(v,this._text.measureText(n,p).width)}var w=Math.round(q+s*3+v),x=Math.round(j*(s+r)+s);k.charAt(0)=="s"&&(u=d.top+this.plotHeight-(l+x)),k.charAt(1)=="e"&&(t=d.left+this.plotWidth-(l+w)),o=this.processColor(f.backgroundColor||"rgb(240,240,240)",{opacity:f.backgroundOpacity||.1}),i.fillStyle=o,i.fillRect(t,u,w,x),i.strokeStyle=f.labelBoxBorderColor,i.strokeRect(Flotr.toPixel(t),Flotr.toPixel(u),w,x);var y=t+s,z=u+s;for(m=0;m
    ":""),h=!0);var A=c[m],B=f.labelBoxWidth,C=f.labelBoxHeight,E=A.bars?A.bars.fillOpacity:f.labelBoxOpacity,F="opacity:"+E+";filter:alpha(opacity="+E*100+");";n=f.labelFormatter(A.label),o="background-color:"+(A.bars&&A.bars.show&&A.bars.fillColor&&A.bars.fill?A.bars.fillColor:A.color)+";",g.push('",'")}h&&g.push("");if(g.length>0){var G='
     '+(serie.label || String.fromCharCode(65+i))+'
    ','
    ','
    ','
    ',"
    ","
    ","
    ',n,"
    '+g.join("")+"
    ";if(f.container)a.insert(f.container,G);else{var H={position:"absolute","z-index":2};k.charAt(0)=="n"?(H.top=l+d.top+"px",H.bottom="auto"):k.charAt(0)=="s"&&(H.bottom=l+d.bottom+"px",H.top="auto"),k.charAt(1)=="e"?(H.right=l+d.right+"px",H.left="auto"):k.charAt(1)=="w"&&(H.left=l+d.left+"px",H.right="auto");var I=a.create("div"),J;I.className="flotr-legend",a.setStyles(I,H),a.insert(I,G),a.insert(this.el,I);if(!f.backgroundOpacity)return;var K=f.backgroundColor||e.grid.backgroundColor||"#ffffff";b.extend(H,a.size(I),{backgroundColor:K,"z-index":1}),H.width+="px",H.height+="px",I=a.create("div"),I.className="flotr-legend-bg",a.setStyles(I,H),a.opacity(I,f.backgroundOpacity),a.insert(I," "),a.insert(this.el,I)}}}}})}(),function(){function a(a){if(this.options.spreadsheet.tickFormatter)return this.options.spreadsheet.tickFormatter(a);var b=c.find(this.axes.x.ticks,function(b){return b.v==a});return b?b.label:a}var b=Flotr.DOM,c=Flotr._;Flotr.addPlugin("spreadsheet",{options:{show:!1,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all",csvFileSeparator:",",decimalSeparator:".",tickFormatter:null,initialTab:"graph"},callbacks:{"flotr:afterconstruct":function(){if(!this.options.spreadsheet.show)return;var a=this.spreadsheet,c=b.node('
    '),d=b.node('
    '+this.options.spreadsheet.tabGraphLabel+"
    "),e=b.node('
    '+this.options.spreadsheet.tabDataLabel+"
    "),f;a.tabsContainer=c,a.tabs={graph:d,data:e},b.insert(c,d),b.insert(c,e),b.insert(this.el,c),f=b.size(e).height+2,this.plotOffset.bottom+=f,b.setStyles(c,{top:this.canvasHeight-f+"px"}),this.observe(d,"click",function(){a.showTab("graph")}).observe(e,"click",function(){a.showTab("data")}),this.options.spreadsheet.initialTab!=="graph"&&a.showTab(this.options.spreadsheet.initialTab)}},loadDataGrid:function(){if(this.seriesData)return this.seriesData;var a=this.series,b={};return c.each(a,function(a,d){c.each(a.data,function(a){var c=a[0],e=a[1],f=b[c];if(f)f[d+1]=e;else{var g=[];g[0]=c,g[d+1]=e,b[c]=g}})}),this.seriesData=c.sortBy(b,function(a,b){return parseInt(b,10)}),this.seriesData},constructDataGrid:function(){if(this.spreadsheet.datagrid)return this.spreadsheet.datagrid;var d=this.series,e=this.spreadsheet.loadDataGrid(),f=[""],g,h,i,j=[''];j.push(""),c.each(d,function(a,b){j.push('"),f.push("")}),j.push(""),c.each(e,function(b){j.push(""),c.times(d.length+1,function(d){var e="td",f=b[d],g=c.isUndefined(f)?"":Math.round(f*1e5)/1e5;if(d===0){e="th";var h=a.call(this,g);h&&(g=h)}j.push("<"+e+(e=="th"?' scope="row"':"")+">"+g+"")},this),j.push("")},this),f.push(""),i=b.node(j.join("")),g=b.node('"),h=b.node('"),this.observe(g,"click",c.bind(this.spreadsheet.downloadCSV,this)).observe(h,"click",c.bind(this.spreadsheet.selectAllData,this));var k=b.node('
    ');b.insert(k,g),b.insert(k,h);var l=this.canvasHeight-b.size(this.spreadsheet.tabsContainer).height-2,m=b.node('
    ');return b.insert(m,k),b.insert(m,i),b.insert(this.el,m),this.spreadsheet.datagrid=i,this.spreadsheet.container=m,i},showTab:function(a){if(this.spreadsheet.activeTab===a)return;switch(a){case"graph":b.hide(this.spreadsheet.container),b.removeClass(this.spreadsheet.tabs.data,"selected"),b.addClass(this.spreadsheet.tabs.graph,"selected");break;case"data":this.spreadsheet.datagrid||this.spreadsheet.constructDataGrid(),b.show(this.spreadsheet.container),b.addClass(this.spreadsheet.tabs.data,"selected"),b.removeClass(this.spreadsheet.tabs.graph,"selected");break;default:throw"Illegal tab name: "+a}this.spreadsheet.activeTab=a},selectAllData:function(){if(this.spreadsheet.tabs){var a,b,c,d,e=this.spreadsheet.constructDataGrid();return this.spreadsheet.showTab("data"),setTimeout(function(){(c=e.ownerDocument)&&(d=c.defaultView)&&d.getSelection&&c.createRange&&(a=window.getSelection())&&a.removeAllRanges?(b=c.createRange(),b.selectNode(e),a.removeAllRanges(),a.addRange(b)):document.body&&document.body.createTextRange&&(b=document.body.createTextRange())&&(b.moveToElementText(e),b.select())},0),!0}return!1},downloadCSV:function(){var b="",d=this.series,e=this.options,f=this.spreadsheet.loadDataGrid(),g=encodeURIComponent(e.spreadsheet.csvFileSeparator);if(e.spreadsheet.decimalSeparator===e.spreadsheet.csvFileSeparator)throw"The decimal separator is the same as the column separator ("+e.spreadsheet.decimalSeparator+")";c.each(d,function(a,c){b+=g+'"'+(a.label||String.fromCharCode(65+c)).replace(/\"/g,'\\"')+'"'}),b+="%0D%0A",b+=c.reduce(f,function(b,c){var d=a.call(this,c[0])||"";d='"'+(d+"").replace(/\"/g,'\\"')+'"';var f=c.slice(1).join(g);return e.spreadsheet.decimalSeparator!=="."&&(f=f.replace(/\./g,e.spreadsheet.decimalSeparator)),b+d+g+f+"%0D%0A"},"",this),Flotr.isIE&&Flotr.isIE<9?(b=b.replace(new RegExp(g,"g"),decodeURIComponent(g)).replace(/%0A/g,"\n").replace(/%0D/g,"\r"),window.open().document.write(b)):window.open("data:text/csv,"+b)}})}(),function(){var a=Flotr.DOM;Flotr.addPlugin("titles",{callbacks:{"flotr:afterdraw":function(){this.titles.drawTitles()}},drawTitles:function(){var b,c=this.options,d=c.grid.labelMargin,e=this.ctx,f=this.axes;if(!c.HtmlText&&this.textEnabled){var g={size:c.fontSize,color:c.grid.color,textAlign:"center"};c.subtitle&&Flotr.drawText(e,c.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,g),g.weight=1.5,g.size*=1.5,c.title&&Flotr.drawText(e,c.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,g),g.weight=1.8,g.size*=.8,f.x.options.title&&f.x.used&&(g.textAlign=f.x.options.titleAlign||"center",g.textBaseline="top",g.angle=Flotr.toRad(f.x.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+f.x.maxLabel.height+this.plotHeight+2*d,g)),f.x2.options.title&&f.x2.used&&(g.textAlign=f.x2.options.titleAlign||"center",g.textBaseline="bottom",g.angle=Flotr.toRad(f.x2.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-f.x2.maxLabel.height-2*d,g)),f.y.options.title&&f.y.used&&(g.textAlign=f.y.options.titleAlign||"right",g.textBaseline="middle",g.angle=Flotr.toRad(f.y.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.y.options.title,this.plotOffset.left-f.y.maxLabel.width-2*d,this.plotOffset.top+this.plotHeight/2,g)),f.y2.options.title&&f.y2.used&&(g.textAlign=f.y2.options.titleAlign||"left",g.textBaseline="middle",g.angle=Flotr.toRad(f.y2.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.y2.options.title,this.plotOffset.left+this.plotWidth+f.y2.maxLabel.width+2*d,this.plotOffset.top+this.plotHeight/2,g))}else{b=[],c.title&&b.push('
    ',c.title,"
    "),c.subtitle&&b.push('
    ',c.subtitle,"
    "),b.push(""),b.push('
    '),f.x.options.title&&f.x.used&&b.push('
    ',f.x.options.title,"
    "),f.x2.options.title&&f.x2.used&&b.push('
    ',f.x2.options.title,"
    "),f.y.options.title&&f.y.used&&b.push('
    ',f.y.options.title,"
    "),f.y2.options.title&&f.y2.used&&b.push('
    ',f.y2.options.title,"
    "),b=b.join("");var h=a.create("div");a.setStyles({color:c.grid.color}),h.className="flotr-titles",a.insert(this.el,h),a.insert(h,b)}}})}(); diff --git a/addons/web_graph/static/lib/flotr2/js/Axis.js b/addons/web_graph/static/lib/flotr2/js/Axis.js deleted file mode 100644 index 1c697309ef8..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Axis.js +++ /dev/null @@ -1,303 +0,0 @@ -/** - * Flotr Axis Library - */ - -(function () { - -var - _ = Flotr._, - LOGARITHMIC = 'logarithmic'; - -function Axis (o) { - - this.orientation = 1; - this.offset = 0; - this.datamin = Number.MAX_VALUE; - this.datamax = -Number.MAX_VALUE; - - _.extend(this, o); - - this._setTranslations(); -} - - -// Prototype -Axis.prototype = { - - setScale : function () { - var length = this.length; - if (this.options.scaling == LOGARITHMIC) { - this.scale = length / (log(this.max, this.options.base) - log(this.min, this.options.base)); - } else { - this.scale = length / (this.max - this.min); - } - }, - - calculateTicks : function () { - var options = this.options; - - this.ticks = []; - this.minorTicks = []; - - // User Ticks - if(options.ticks){ - this._cleanUserTicks(options.ticks, this.ticks); - this._cleanUserTicks(options.minorTicks || [], this.minorTicks); - } - else { - if (options.mode == 'time') { - this._calculateTimeTicks(); - } else if (options.scaling === 'logarithmic') { - this._calculateLogTicks(); - } else { - this._calculateTicks(); - } - } - }, - - /** - * Calculates the range of an axis to apply autoscaling. - */ - calculateRange: function () { - - if (!this.used) return; - - var axis = this, - o = axis.options, - min = o.min !== null ? o.min : axis.datamin, - max = o.max !== null ? o.max : axis.datamax, - margin = o.autoscaleMargin; - - if (o.scaling == 'logarithmic') { - if (min <= 0) min = axis.datamin; - - // Let it widen later on - if (max <= 0) max = min; - } - - if (max == min) { - var widen = max ? 0.01 : 1.00; - if (o.min === null) min -= widen; - if (o.max === null) max += widen; - } - - if (o.scaling === 'logarithmic') { - if (min < 0) min = max / o.base; // Could be the result of widening - - var maxexp = Math.log(max); - if (o.base != Math.E) maxexp /= Math.log(o.base); - maxexp = Math.ceil(maxexp); - - var minexp = Math.log(min); - if (o.base != Math.E) minexp /= Math.log(o.base); - minexp = Math.ceil(minexp); - - axis.tickSize = Flotr.getTickSize(o.noTicks, minexp, maxexp, o.tickDecimals === null ? 0 : o.tickDecimals); - - // Try to determine a suitable amount of miniticks based on the length of a decade - if (o.minorTickFreq === null) { - if (maxexp - minexp > 10) - o.minorTickFreq = 0; - else if (maxexp - minexp > 5) - o.minorTickFreq = 2; - else - o.minorTickFreq = 5; - } - } else { - axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals); - } - - axis.min = min; - axis.max = max; //extendRange may use axis.min or axis.max, so it should be set before it is caled - - // Autoscaling. @todo This probably fails with log scale. Find a testcase and fix it - if(o.min === null && o.autoscale){ - axis.min -= axis.tickSize * margin; - // Make sure we don't go below zero if all values are positive. - if(axis.min < 0 && axis.datamin >= 0) axis.min = 0; - axis.min = axis.tickSize * Math.floor(axis.min / axis.tickSize); - } - - if(o.max === null && o.autoscale){ - axis.max += axis.tickSize * margin; - if(axis.max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) axis.max = 0; - axis.max = axis.tickSize * Math.ceil(axis.max / axis.tickSize); - } - - if (axis.min == axis.max) axis.max = axis.min + 1; - }, - - calculateTextDimensions : function (T, options) { - - var maxLabel = '', - length, - i; - - if (this.options.showLabels) { - for (i = 0; i < this.ticks.length; ++i) { - length = this.ticks[i].label.length; - if (length > maxLabel.length){ - maxLabel = this.ticks[i].label; - } - } - } - - this.maxLabel = T.dimensions( - maxLabel, - {size:options.fontSize, angle: Flotr.toRad(this.options.labelsAngle)}, - 'font-size:smaller;', - 'flotr-grid-label' - ); - - this.titleSize = T.dimensions( - this.options.title, - {size:options.fontSize*1.2, angle: Flotr.toRad(this.options.titleAngle)}, - 'font-weight:bold;', - 'flotr-axis-title' - ); - }, - - _cleanUserTicks : function (ticks, axisTicks) { - - var axis = this, options = this.options, - v, i, label, tick; - - if(_.isFunction(ticks)) ticks = ticks({min : axis.min, max : axis.max}); - - for(i = 0; i < ticks.length; ++i){ - tick = ticks[i]; - if(typeof(tick) === 'object'){ - v = tick[0]; - label = (tick.length > 1) ? tick[1] : options.tickFormatter(v, {min : axis.min, max : axis.max}); - } else { - v = tick; - label = options.tickFormatter(v, {min : this.min, max : this.max}); - } - axisTicks[i] = { v: v, label: label }; - } - }, - - _calculateTimeTicks : function () { - this.ticks = Flotr.Date.generator(this); - }, - - _calculateLogTicks : function () { - - var axis = this, - o = axis.options, - v, - decadeStart; - - var max = Math.log(axis.max); - if (o.base != Math.E) max /= Math.log(o.base); - max = Math.ceil(max); - - var min = Math.log(axis.min); - if (o.base != Math.E) min /= Math.log(o.base); - min = Math.ceil(min); - - for (i = min; i < max; i += axis.tickSize) { - decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i); - // Next decade begins here: - var decadeEnd = decadeStart * ((o.base == Math.E) ? Math.exp(axis.tickSize) : Math.pow(o.base, axis.tickSize)); - var stepSize = (decadeEnd - decadeStart) / o.minorTickFreq; - - axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})}); - for (v = decadeStart + stepSize; v < decadeEnd; v += stepSize) - axis.minorTicks.push({v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max})}); - } - - // Always show the value at the would-be start of next decade (end of this decade) - decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i); - axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})}); - }, - - _calculateTicks : function () { - - var axis = this, - o = axis.options, - tickSize = axis.tickSize, - min = axis.min, - max = axis.max, - start = tickSize * Math.ceil(min / tickSize), // Round to nearest multiple of tick size. - decimals, - minorTickSize, - v, v2, - i, j; - - if (o.minorTickFreq) - minorTickSize = tickSize / o.minorTickFreq; - - // Then store all possible ticks. - for (i = 0; (v = v2 = start + i * tickSize) <= max; ++i){ - - // Round (this is always needed to fix numerical instability). - decimals = o.tickDecimals; - if (decimals === null) decimals = 1 - Math.floor(Math.log(tickSize) / Math.LN10); - if (decimals < 0) decimals = 0; - - v = v.toFixed(decimals); - axis.ticks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) }); - - if (o.minorTickFreq) { - for (j = 0; j < o.minorTickFreq && (i * tickSize + j * minorTickSize) < max; ++j) { - v = v2 + j * minorTickSize; - axis.minorTicks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) }); - } - } - } - - }, - - _setTranslations : function (logarithmic) { - this.d2p = (logarithmic ? d2pLog : d2p); - this.p2d = (logarithmic ? p2dLog : p2d); - } -}; - - -// Static Methods -_.extend(Axis, { - getAxes : function (options) { - return { - x: new Axis({options: options.xaxis, n: 1, length: this.plotWidth}), - x2: new Axis({options: options.x2axis, n: 2, length: this.plotWidth}), - y: new Axis({options: options.yaxis, n: 1, length: this.plotHeight, offset: this.plotHeight, orientation: -1}), - y2: new Axis({options: options.y2axis, n: 2, length: this.plotHeight, offset: this.plotHeight, orientation: -1}) - }; - } -}); - - -// Helper Methods - -function d2p (dataValue) { - return this.offset + this.orientation * (dataValue - this.min) * this.scale; -} - -function p2d (pointValue) { - return (this.offset + this.orientation * pointValue) / this.scale + this.min; -} - -function d2pLog (dataValue) { - return this.offset + this.orientation * (log(dataValue, this.options.base) - log(this.min, this.options.base)) * this.scale; -} - -function p2dLog (pointValue) { - return exp((this.offset + this.orientation * pointValue) / this.scale + log(this.min, this.options.base), this.options.base); -} - -function log (value, base) { - value = Math.log(Math.max(value, Number.MIN_VALUE)); - if (base !== Math.E) - value /= Math.log(base); - return value; -} - -function exp (value, base) { - return (base === Math.E) ? Math.exp(value) : Math.pow(base, value); -} - -Flotr.Axis = Axis; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Color.js b/addons/web_graph/static/lib/flotr2/js/Color.js deleted file mode 100644 index ab4ba0b432d..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Color.js +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Flotr Color - */ - -(function () { - -var - _ = Flotr._; - -// Constructor -function Color (r, g, b, a) { - this.rgba = ['r','g','b','a']; - var x = 4; - while(-1<--x){ - this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0); - } - this.normalize(); -} - -// Constants -var COLOR_NAMES = { - 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] -}; - -Color.prototype = { - scale: function(rf, gf, bf, af){ - var x = 4; - while (-1 < --x) { - if (!_.isUndefined(arguments[x])) this[this.rgba[x]] *= arguments[x]; - } - return this.normalize(); - }, - alpha: function(alpha) { - if (!_.isUndefined(alpha) && !_.isNull(alpha)) { - this.a = alpha; - } - return this.normalize(); - }, - clone: function(){ - return new Color(this.r, this.b, this.g, this.a); - }, - limit: function(val,minVal,maxVal){ - return Math.max(Math.min(val, maxVal), minVal); - }, - normalize: function(){ - var limit = this.limit; - this.r = limit(parseInt(this.r, 10), 0, 255); - this.g = limit(parseInt(this.g, 10), 0, 255); - this.b = limit(parseInt(this.b, 10), 0, 255); - this.a = limit(this.a, 0, 1); - return this; - }, - distance: function(color){ - if (!color) return; - color = new Color.parse(color); - var dist = 0, x = 3; - while(-1<--x){ - dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]); - } - return dist; - }, - toString: function(){ - return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')'; - }, - contrast: function () { - var - test = 1 - ( 0.299 * this.r + 0.587 * this.g + 0.114 * this.b) / 255; - return (test < 0.5 ? '#000000' : '#ffffff'); - } -}; - -_.extend(Color, { - /** - * Parses a color string and returns a corresponding Color. - * The different tests are in order of probability to improve speed. - * @param {String, Color} str - string thats representing a color - * @return {Color} returns a Color object or false - */ - parse: function(color){ - if (color instanceof Color) return color; - - var result; - - // #a0b1c2 - if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))) - return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)); - - // rgb(num,num,num) - if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))) - return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10)); - - // #fff - if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))) - return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)); - - // rgba(num,num,num,num) - if((result = /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(color))) - return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4])); - - // rgb(num%,num%,num%) - if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))) - return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55); - - // rgba(num%,num%,num%,num) - if((result = /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(color))) - return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4])); - - // Otherwise, we're most likely dealing with a named color. - var name = (color+'').replace(/^\s*([\S\s]*?)\s*$/, '$1').toLowerCase(); - if(name == 'transparent'){ - return new Color(255, 255, 255, 0); - } - return (result = COLOR_NAMES[name]) ? new Color(result[0], result[1], result[2]) : new Color(0, 0, 0, 0); - }, - - /** - * Process color and options into color style. - */ - processColor: function(color, options) { - - var opacity = options.opacity; - if (!color) return 'rgba(0, 0, 0, 0)'; - if (color instanceof Color) return color.alpha(opacity).toString(); - if (_.isString(color)) return Color.parse(color).alpha(opacity).toString(); - - var grad = color.colors ? color : {colors: color}; - - if (!options.ctx) { - if (!_.isArray(grad.colors)) return 'rgba(0, 0, 0, 0)'; - return Color.parse(_.isArray(grad.colors[0]) ? grad.colors[0][1] : grad.colors[0]).alpha(opacity).toString(); - } - grad = _.extend({start: 'top', end: 'bottom'}, grad); - - if (/top/i.test(grad.start)) options.x1 = 0; - if (/left/i.test(grad.start)) options.y1 = 0; - if (/bottom/i.test(grad.end)) options.x2 = 0; - if (/right/i.test(grad.end)) options.y2 = 0; - - var i, c, stop, gradient = options.ctx.createLinearGradient(options.x1, options.y1, options.x2, options.y2); - for (i = 0; i < grad.colors.length; i++) { - c = grad.colors[i]; - if (_.isArray(c)) { - stop = c[0]; - c = c[1]; - } - else stop = i / (grad.colors.length-1); - gradient.addColorStop(stop, Color.parse(c).alpha(opacity)); - } - return gradient; - } -}); - -Flotr.Color = Color; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/DOM.js b/addons/web_graph/static/lib/flotr2/js/DOM.js deleted file mode 100644 index 43df857395b..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/DOM.js +++ /dev/null @@ -1,88 +0,0 @@ -(function () { - -var _ = Flotr._; - -Flotr.DOM = { - addClass: function(element, name){ - var classList = (element.className ? element.className : ''); - if (_.include(classList.split(/\s+/g), name)) return; - element.className = (classList ? classList + ' ' : '') + name; - }, - /** - * Create an element. - */ - create: function(tag){ - return document.createElement(tag); - }, - node: function(html) { - var div = Flotr.DOM.create('div'), n; - div.innerHTML = html; - n = div.children[0]; - div.innerHTML = ''; - return n; - }, - /** - * Remove all children. - */ - empty: function(element){ - element.innerHTML = ''; - /* - if (!element) return; - _.each(element.childNodes, function (e) { - Flotr.DOM.empty(e); - element.removeChild(e); - }); - */ - }, - hide: function(element){ - Flotr.DOM.setStyles(element, {display:'none'}); - }, - /** - * Insert a child. - * @param {Element} element - * @param {Element|String} Element or string to be appended. - */ - insert: function(element, child){ - if(_.isString(child)) - element.innerHTML += child; - else if (_.isElement(child)) - element.appendChild(child); - }, - // @TODO find xbrowser implementation - opacity: function(element, opacity) { - element.style.opacity = opacity; - }, - position: function(element, p){ - if (!element.offsetParent) - return {left: (element.offsetLeft || 0), top: (element.offsetTop || 0)}; - - p = this.position(element.offsetParent); - p.left += element.offsetLeft; - p.top += element.offsetTop; - return p; - }, - removeClass: function(element, name) { - var classList = (element.className ? element.className : ''); - element.className = _.filter(classList.split(/\s+/g), function (c) { - if (c != name) return true; } - ).join(' '); - }, - setStyles: function(element, o) { - _.each(o, function (value, key) { - element.style[key] = value; - }); - }, - show: function(element){ - Flotr.DOM.setStyles(element, {display:''}); - }, - /** - * Return element size. - */ - size: function(element){ - return { - height : element.offsetHeight, - width : element.offsetWidth }; - } -}; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Date.js b/addons/web_graph/static/lib/flotr2/js/Date.js deleted file mode 100644 index fe72dba709f..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Date.js +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Flotr Date - */ -Flotr.Date = { - - set : function (date, name, mode, value) { - mode = mode || 'UTC'; - name = 'set' + (mode === 'UTC' ? 'UTC' : '') + name; - date[name](value); - }, - - get : function (date, name, mode) { - mode = mode || 'UTC'; - name = 'get' + (mode === 'UTC' ? 'UTC' : '') + name; - return date[name](); - }, - - format: function(d, format, mode) { - if (!d) return; - - // We should maybe use an "official" date format spec, like PHP date() or ColdFusion - // http://fr.php.net/manual/en/function.date.php - // http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_c-d_29.html - var - get = this.get, - tokens = { - h: get(d, 'Hours', mode).toString(), - H: leftPad(get(d, 'Hours', mode)), - M: leftPad(get(d, 'Minutes', mode)), - S: leftPad(get(d, 'Seconds', mode)), - s: get(d, 'Milliseconds', mode), - d: get(d, 'Date', mode).toString(), - m: (get(d, 'Month') + 1).toString(), - y: get(d, 'FullYear').toString(), - b: Flotr.Date.monthNames[get(d, 'Month', mode)] - }; - - function leftPad(n){ - n += ''; - return n.length == 1 ? "0" + n : n; - } - - var r = [], c, - escape = false; - - for (var i = 0; i < format.length; ++i) { - c = format.charAt(i); - - if (escape) { - r.push(tokens[c] || c); - escape = false; - } - else if (c == "%") - escape = true; - else - r.push(c); - } - return r.join(''); - }, - getFormat: function(time, span) { - var tu = Flotr.Date.timeUnits; - if (time < tu.second) return "%h:%M:%S.%s"; - else if (time < tu.minute) return "%h:%M:%S"; - else if (time < tu.day) return (span < 2 * tu.day) ? "%h:%M" : "%b %d %h:%M"; - else if (time < tu.month) return "%b %d"; - else if (time < tu.year) return (span < tu.year) ? "%b" : "%b %y"; - else return "%y"; - }, - formatter: function (v, axis) { - var - options = axis.options, - scale = Flotr.Date.timeUnits[options.timeUnit], - d = new Date(v * scale); - - // first check global format - if (axis.options.timeFormat) - return Flotr.Date.format(d, options.timeFormat, options.timeMode); - - var span = (axis.max - axis.min) * scale, - t = axis.tickSize * Flotr.Date.timeUnits[axis.tickUnit]; - - return Flotr.Date.format(d, Flotr.Date.getFormat(t, span), options.timeMode); - }, - generator: function(axis) { - - var - set = this.set, - get = this.get, - timeUnits = this.timeUnits, - spec = this.spec, - options = axis.options, - mode = options.timeMode, - scale = timeUnits[options.timeUnit], - min = axis.min * scale, - max = axis.max * scale, - delta = (max - min) / options.noTicks, - ticks = [], - tickSize = axis.tickSize, - tickUnit, - formatter, i; - - // Use custom formatter or time tick formatter - formatter = (options.tickFormatter === Flotr.defaultTickFormatter ? - this.formatter : options.tickFormatter - ); - - for (i = 0; i < spec.length - 1; ++i) { - var d = spec[i][0] * timeUnits[spec[i][1]]; - if (delta < (d + spec[i+1][0] * timeUnits[spec[i+1][1]]) / 2 && d >= tickSize) - break; - } - tickSize = spec[i][0]; - tickUnit = spec[i][1]; - - // special-case the possibility of several years - if (tickUnit == "year") { - tickSize = Flotr.getTickSize(options.noTicks*timeUnits.year, min, max, 0); - - // Fix for 0.5 year case - if (tickSize == 0.5) { - tickUnit = "month"; - tickSize = 6; - } - } - - axis.tickUnit = tickUnit; - axis.tickSize = tickSize; - - var - d = new Date(min); - - var step = tickSize * timeUnits[tickUnit]; - - function setTick (name) { - set(d, name, mode, Flotr.floorInBase( - get(d, name, mode), tickSize - )); - } - - switch (tickUnit) { - case "millisecond": setTick('Milliseconds'); break; - case "second": setTick('Seconds'); break; - case "minute": setTick('Minutes'); break; - case "hour": setTick('Hours'); break; - case "month": setTick('Month'); break; - case "year": setTick('FullYear'); break; - } - - // reset smaller components - if (step >= timeUnits.second) set(d, 'Milliseconds', mode, 0); - if (step >= timeUnits.minute) set(d, 'Seconds', mode, 0); - if (step >= timeUnits.hour) set(d, 'Minutes', mode, 0); - if (step >= timeUnits.day) set(d, 'Hours', mode, 0); - if (step >= timeUnits.day * 4) set(d, 'Date', mode, 1); - if (step >= timeUnits.year) set(d, 'Month', mode, 0); - - var carry = 0, v = NaN, prev; - do { - prev = v; - v = d.getTime(); - ticks.push({ v: v / scale, label: formatter(v / scale, axis) }); - if (tickUnit == "month") { - if (tickSize < 1) { - /* a bit complicated - we'll divide the month up but we need to take care of fractions - so we don't end up in the middle of a day */ - set(d, 'Date', mode, 1); - var start = d.getTime(); - set(d, 'Month', mode, get(d, 'Month', mode) + 1) - var end = d.getTime(); - d.setTime(v + carry * timeUnits.hour + (end - start) * tickSize); - carry = get(d, 'Hours', mode) - set(d, 'Hours', mode, 0); - } - else - set(d, 'Month', mode, get(d, 'Month', mode) + tickSize); - } - else if (tickUnit == "year") { - set(d, 'FullYear', mode, get(d, 'FullYear', mode) + tickSize); - } - else - d.setTime(v + step); - - } while (v < max && v != prev); - - return ticks; - }, - timeUnits: { - millisecond: 1, - second: 1000, - minute: 1000 * 60, - hour: 1000 * 60 * 60, - day: 1000 * 60 * 60 * 24, - month: 1000 * 60 * 60 * 24 * 30, - year: 1000 * 60 * 60 * 24 * 365.2425 - }, - // the allowed tick sizes, after 1 year we use an integer algorithm - spec: [ - [1, "millisecond"], [20, "millisecond"], [50, "millisecond"], [100, "millisecond"], [200, "millisecond"], [500, "millisecond"], - [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"], [3, "month"], [6, "month"], - [1, "year"] - ], - monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] -}; diff --git a/addons/web_graph/static/lib/flotr2/js/DefaultOptions.js b/addons/web_graph/static/lib/flotr2/js/DefaultOptions.js deleted file mode 100644 index 7399553df6a..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/DefaultOptions.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Flotr Defaults - */ -Flotr.defaultOptions = { - colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated. - ieBackgroundColor: '#FFFFFF', // Background color for excanvas clipping - title: null, // => The graph's title - subtitle: null, // => The graph's subtitle - shadowSize: 4, // => size of the 'fake' shadow - defaultType: null, // => default series type - HtmlText: true, // => wether to draw the text using HTML or on the canvas - fontColor: '#545454', // => default font color - fontSize: 7.5, // => canvas' text font size - resolution: 1, // => resolution of the graph, to have printer-friendly graphs ! - parseFloat: true, // => whether to preprocess data for floats (ie. if input is string) - xaxis: { - ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] - minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3] - showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise - showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide - labelsAngle: 0, // => labels' angle, in degrees - title: null, // => axis title - titleAngle: 0, // => axis title's angle, in degrees - noTicks: 5, // => number of ticks for automagically generated ticks - minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks - tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string - tickDecimals: null, // => no. of decimals, null means auto - min: null, // => min. value to show, null means set automatically - max: null, // => max. value to show, null means set automatically - autoscale: false, // => Turns autoscaling on with true - autoscaleMargin: 0, // => margin in % to add if auto-setting min/max - color: null, // => color of the ticks - mode: 'normal', // => can be 'time' or 'normal' - timeFormat: null, - timeMode:'UTC', // => For UTC time ('local' for local time). - timeUnit:'millisecond',// => Unit for time (millisecond, second, minute, hour, day, month, year) - scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic' - base: Math.E, - titleAlign: 'center', - margin: true // => Turn off margins with false - }, - x2axis: {}, - yaxis: { - ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] - minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3] - showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise - showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide - labelsAngle: 0, // => labels' angle, in degrees - title: null, // => axis title - titleAngle: 90, // => axis title's angle, in degrees - noTicks: 5, // => number of ticks for automagically generated ticks - minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks - tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string - tickDecimals: null, // => no. of decimals, null means auto - min: null, // => min. value to show, null means set automatically - max: null, // => max. value to show, null means set automatically - autoscale: false, // => Turns autoscaling on with true - autoscaleMargin: 0, // => margin in % to add if auto-setting min/max - color: null, // => The color of the ticks - scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic' - base: Math.E, - titleAlign: 'center', - margin: true // => Turn off margins with false - }, - y2axis: { - titleAngle: 270 - }, - grid: { - color: '#545454', // => primary color used for outline and labels - backgroundColor: null, // => null for transparent, else color - backgroundImage: null, // => background image. String or object with src, left and top - watermarkAlpha: 0.4, // => - tickColor: '#DDDDDD', // => color used for the ticks - labelMargin: 3, // => margin in pixels - verticalLines: true, // => whether to show gridlines in vertical direction - minorVerticalLines: null, // => whether to show gridlines for minor ticks in vertical dir. - horizontalLines: true, // => whether to show gridlines in horizontal direction - minorHorizontalLines: null, // => whether to show gridlines for minor ticks in horizontal dir. - outlineWidth: 1, // => width of the grid outline/border in pixels - outline : 'nsew', // => walls of the outline to display - circular: false // => if set to true, the grid will be circular, must be used when radars are drawn - }, - mouse: { - track: false, // => true to track the mouse, no tracking otherwise - trackAll: false, - position: 'se', // => position of the value box (default south-east) - relative: false, // => next to the mouse cursor - trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box - margin: 5, // => margin in pixels of the valuebox - lineColor: '#FF3F19', // => line color of points that are drawn when mouse comes near a value of a series - trackDecimals: 1, // => decimals for the track values - sensibility: 2, // => the lower this number, the more precise you have to aim to show a value - trackY: true, // => whether or not to track the mouse in the y axis - radius: 3, // => radius of the track point - fillColor: null, // => color to fill our select bar with only applies to bar and similar graphs (only bars for now) - fillOpacity: 0.4 // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - } -}; diff --git a/addons/web_graph/static/lib/flotr2/js/EventAdapter.js b/addons/web_graph/static/lib/flotr2/js/EventAdapter.js deleted file mode 100644 index 062ceeab294..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/EventAdapter.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Flotr Event Adapter - */ -(function () { -var - F = Flotr, - bean = F.bean; -F.EventAdapter = { - observe: function(object, name, callback) { - bean.add(object, name, callback); - return this; - }, - fire: function(object, name, args) { - bean.fire(object, name, args); - if (typeof(Prototype) != 'undefined') - Event.fire(object, name, args); - // @TODO Someone who uses mootools, add mootools adapter for existing applciations. - return this; - }, - stopObserving: function(object, name, callback) { - bean.remove(object, name, callback); - return this; - }, - eventPointer: function(e) { - if (!F._.isUndefined(e.touches) && e.touches.length > 0) { - return { - x : e.touches[0].pageX, - y : e.touches[0].pageY - }; - } else if (!F._.isUndefined(e.changedTouches) && e.changedTouches.length > 0) { - return { - x : e.changedTouches[0].pageX, - y : e.changedTouches[0].pageY - }; - } else if (e.pageX || e.pageY) { - return { - x : e.pageX, - y : e.pageY - }; - } else if (e.clientX || e.clientY) { - var - d = document, - b = d.body, - de = d.documentElement; - return { - x: e.clientX + b.scrollLeft + de.scrollLeft, - y: e.clientY + b.scrollTop + de.scrollTop - }; - } - } -}; -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Flotr.js b/addons/web_graph/static/lib/flotr2/js/Flotr.js deleted file mode 100644 index e8256096adb..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Flotr.js +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Flotr2 (c) 2012 Carl Sutherland - * MIT License - * Special thanks to: - * Flotr: http://code.google.com/p/flotr/ (fork) - * Flot: https://github.com/flot/flot (original fork) - */ -(function () { - -var - global = this, - previousFlotr = this.Flotr, - Flotr; - -Flotr = { - _: _, - bean: bean, - isIphone: /iphone/i.test(navigator.userAgent), - isIE: (navigator.appVersion.indexOf("MSIE") != -1 ? parseFloat(navigator.appVersion.split("MSIE")[1]) : false), - - /** - * An object of the registered graph types. Use Flotr.addType(type, object) - * to add your own type. - */ - graphTypes: {}, - - /** - * The list of the registered plugins - */ - plugins: {}, - - /** - * Can be used to add your own chart type. - * @param {String} name - Type of chart, like 'pies', 'bars' etc. - * @param {String} graphType - The object containing the basic drawing functions (draw, etc) - */ - addType: function(name, graphType){ - Flotr.graphTypes[name] = graphType; - Flotr.defaultOptions[name] = graphType.options || {}; - Flotr.defaultOptions.defaultType = Flotr.defaultOptions.defaultType || name; - }, - - /** - * Can be used to add a plugin - * @param {String} name - The name of the plugin - * @param {String} plugin - The object containing the plugin's data (callbacks, options, function1, function2, ...) - */ - addPlugin: function(name, plugin){ - Flotr.plugins[name] = plugin; - Flotr.defaultOptions[name] = plugin.options || {}; - }, - - /** - * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha. - * You could also draw graphs by directly calling Flotr.Graph(element, data, options). - * @param {Element} el - element to insert the graph into - * @param {Object} data - an array or object of dataseries - * @param {Object} options - an object containing options - * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph - * @return {Object} returns a new graph object and of course draws the graph. - */ - draw: function(el, data, options, GraphKlass){ - GraphKlass = GraphKlass || Flotr.Graph; - return new GraphKlass(el, data, options); - }, - - /** - * Recursively merges two objects. - * @param {Object} src - source object (likely the object with the least properties) - * @param {Object} dest - destination object (optional, object with the most properties) - * @return {Object} recursively merged Object - * @TODO See if we can't remove this. - */ - merge: function(src, dest){ - var i, v, result = dest || {}; - - for (i in src) { - v = src[i]; - if (v && typeof(v) === 'object') { - if (v.constructor === Array) { - result[i] = this._.clone(v); - } else if (v.constructor !== RegExp && !this._.isElement(v)) { - result[i] = Flotr.merge(v, (dest ? dest[i] : undefined)); - } else { - result[i] = v; - } - } else { - result[i] = v; - } - } - - return result; - }, - - /** - * Recursively clones an object. - * @param {Object} object - The object to clone - * @return {Object} the clone - * @TODO See if we can't remove this. - */ - clone: function(object){ - return Flotr.merge(object, {}); - }, - - /** - * Function calculates the ticksize and returns it. - * @param {Integer} noTicks - number of ticks - * @param {Integer} min - lower bound integer value for the current axis - * @param {Integer} max - upper bound integer value for the current axis - * @param {Integer} decimals - number of decimals for the ticks - * @return {Integer} returns the ticksize in pixels - */ - getTickSize: function(noTicks, min, max, decimals){ - var delta = (max - min) / noTicks, - magn = Flotr.getMagnitude(delta), - tickSize = 10, - norm = delta / magn; // Norm is between 1.0 and 10.0. - - if(norm < 1.5) tickSize = 1; - else if(norm < 2.25) tickSize = 2; - else if(norm < 3) tickSize = ((decimals === 0) ? 2 : 2.5); - else if(norm < 7.5) tickSize = 5; - - return tickSize * magn; - }, - - /** - * Default tick formatter. - * @param {String, Integer} val - tick value integer - * @param {Object} axisOpts - the axis' options - * @return {String} formatted tick string - */ - defaultTickFormatter: function(val, axisOpts){ - return val+''; - }, - - /** - * Formats the mouse tracker values. - * @param {Object} obj - Track value Object {x:..,y:..} - * @return {String} Formatted track string - */ - defaultTrackFormatter: function(obj){ - return '('+obj.x+', '+obj.y+')'; - }, - - /** - * Utility function to convert file size values in bytes to kB, MB, ... - * @param value {Number} - The value to convert - * @param precision {Number} - The number of digits after the comma (default: 2) - * @param base {Number} - The base (default: 1000) - */ - engineeringNotation: function(value, precision, base){ - var sizes = ['Y','Z','E','P','T','G','M','k',''], - fractionSizes = ['y','z','a','f','p','n','µ','m',''], - total = sizes.length; - - base = base || 1000; - precision = Math.pow(10, precision || 2); - - if (value === 0) return 0; - - if (value > 1) { - while (total-- && (value >= base)) value /= base; - } - else { - sizes = fractionSizes; - total = sizes.length; - while (total-- && (value < 1)) value *= base; - } - - return (Math.round(value * precision) / precision) + sizes[total]; - }, - - /** - * Returns the magnitude of the input value. - * @param {Integer, Float} x - integer or float value - * @return {Integer, Float} returns the magnitude of the input value - */ - getMagnitude: function(x){ - return Math.pow(10, Math.floor(Math.log(x) / Math.LN10)); - }, - toPixel: function(val){ - return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val); - }, - toRad: function(angle){ - return -angle * (Math.PI/180); - }, - floorInBase: function(n, base) { - return base * Math.floor(n / base); - }, - drawText: function(ctx, text, x, y, style) { - if (!ctx.fillText) { - ctx.drawText(text, x, y, style); - return; - } - - style = this._.extend({ - size: Flotr.defaultOptions.fontSize, - color: '#000000', - textAlign: 'left', - textBaseline: 'bottom', - weight: 1, - angle: 0 - }, style); - - ctx.save(); - ctx.translate(x, y); - ctx.rotate(style.angle); - ctx.fillStyle = style.color; - ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif"; - ctx.textAlign = style.textAlign; - ctx.textBaseline = style.textBaseline; - ctx.fillText(text, 0, 0); - ctx.restore(); - }, - getBestTextAlign: function(angle, style) { - style = style || {textAlign: 'center', textBaseline: 'middle'}; - angle += Flotr.getTextAngleFromAlign(style); - - if (Math.abs(Math.cos(angle)) > 10e-3) - style.textAlign = (Math.cos(angle) > 0 ? 'right' : 'left'); - - if (Math.abs(Math.sin(angle)) > 10e-3) - style.textBaseline = (Math.sin(angle) > 0 ? 'top' : 'bottom'); - - return style; - }, - alignTable: { - 'right middle' : 0, - 'right top' : Math.PI/4, - 'center top' : Math.PI/2, - 'left top' : 3*(Math.PI/4), - 'left middle' : Math.PI, - 'left bottom' : -3*(Math.PI/4), - 'center bottom': -Math.PI/2, - 'right bottom' : -Math.PI/4, - 'center middle': 0 - }, - getTextAngleFromAlign: function(style) { - return Flotr.alignTable[style.textAlign+' '+style.textBaseline] || 0; - }, - noConflict : function () { - global.Flotr = previousFlotr; - return this; - } -}; - -global.Flotr = Flotr; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Graph.js b/addons/web_graph/static/lib/flotr2/js/Graph.js deleted file mode 100644 index 0e0310a5f4e..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Graph.js +++ /dev/null @@ -1,745 +0,0 @@ -/** - * Flotr Graph class that plots a graph on creation. - */ -(function () { - -var - D = Flotr.DOM, - E = Flotr.EventAdapter, - _ = Flotr._, - flotr = Flotr; -/** - * Flotr Graph constructor. - * @param {Element} el - element to insert the graph into - * @param {Object} data - an array or object of dataseries - * @param {Object} options - an object containing options - */ -Graph = function(el, data, options){ -// Let's see if we can get away with out this [JS] -// try { - this._setEl(el); - this._initMembers(); - this._initPlugins(); - - E.fire(this.el, 'flotr:beforeinit', [this]); - - this.data = data; - this.series = flotr.Series.getSeries(data); - this._initOptions(options); - this._initGraphTypes(); - this._initCanvas(); - this._text = new flotr.Text({ - element : this.el, - ctx : this.ctx, - html : this.options.HtmlText, - textEnabled : this.textEnabled - }); - E.fire(this.el, 'flotr:afterconstruct', [this]); - this._initEvents(); - - this.findDataRanges(); - this.calculateSpacing(); - - this.draw(_.bind(function() { - E.fire(this.el, 'flotr:afterinit', [this]); - }, this)); -/* - try { - } catch (e) { - try { - console.error(e); - } catch (e2) {} - }*/ -}; - -function observe (object, name, callback) { - E.observe.apply(this, arguments); - this._handles.push(arguments); - return this; -} - -Graph.prototype = { - - destroy: function () { - E.fire(this.el, 'flotr:destroy'); - _.each(this._handles, function (handle) { - E.stopObserving.apply(this, handle); - }); - this._handles = []; - this.el.graph = null; - }, - - observe : observe, - - /** - * @deprecated - */ - _observe : observe, - - processColor: function(color, options){ - var o = { x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight, opacity: 1, ctx: this.ctx }; - _.extend(o, options); - return flotr.Color.processColor(color, o); - }, - /** - * Function determines the min and max values for the xaxis and yaxis. - * - * TODO logarithmic range validation (consideration of 0) - */ - findDataRanges: function(){ - var a = this.axes, - xaxis, yaxis, range; - - _.each(this.series, function (series) { - range = series.getRange(); - if (range) { - xaxis = series.xaxis; - yaxis = series.yaxis; - xaxis.datamin = Math.min(range.xmin, xaxis.datamin); - xaxis.datamax = Math.max(range.xmax, xaxis.datamax); - yaxis.datamin = Math.min(range.ymin, yaxis.datamin); - yaxis.datamax = Math.max(range.ymax, yaxis.datamax); - xaxis.used = (xaxis.used || range.xused); - yaxis.used = (yaxis.used || range.yused); - } - }, this); - - // Check for empty data, no data case (none used) - if (!a.x.used && !a.x2.used) a.x.used = true; - if (!a.y.used && !a.y2.used) a.y.used = true; - - _.each(a, function (axis) { - axis.calculateRange(); - }); - - var - types = _.keys(flotr.graphTypes), - drawn = false; - - _.each(this.series, function (series) { - if (series.hide) return; - _.each(types, function (type) { - if (series[type] && series[type].show) { - this.extendRange(type, series); - drawn = true; - } - }, this); - if (!drawn) { - this.extendRange(this.options.defaultType, series); - } - }, this); - }, - - extendRange : function (type, series) { - if (this[type].extendRange) this[type].extendRange(series, series.data, series[type], this[type]); - if (this[type].extendYRange) this[type].extendYRange(series.yaxis, series.data, series[type], this[type]); - if (this[type].extendXRange) this[type].extendXRange(series.xaxis, series.data, series[type], this[type]); - }, - - /** - * Calculates axis label sizes. - */ - calculateSpacing: function(){ - - var a = this.axes, - options = this.options, - series = this.series, - margin = options.grid.labelMargin, - T = this._text, - x = a.x, - x2 = a.x2, - y = a.y, - y2 = a.y2, - maxOutset = options.grid.outlineWidth, - i, j, l, dim; - - // TODO post refactor, fix this - _.each(a, function (axis) { - axis.calculateTicks(); - axis.calculateTextDimensions(T, options); - }); - - // Title height - dim = T.dimensions( - options.title, - {size: options.fontSize*1.5}, - 'font-size:1em;font-weight:bold;', - 'flotr-title' - ); - this.titleHeight = dim.height; - - // Subtitle height - dim = T.dimensions( - options.subtitle, - {size: options.fontSize}, - 'font-size:smaller;', - 'flotr-subtitle' - ); - this.subtitleHeight = dim.height; - - for(j = 0; j < options.length; ++j){ - if (series[j].points.show){ - maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2); - } - } - - var p = this.plotOffset; - if (x.options.margin === false) { - p.bottom = 0; - p.top = 0; - } else { - p.bottom += (options.grid.circular ? 0 : (x.used && x.options.showLabels ? (x.maxLabel.height + margin) : 0)) + - (x.used && x.options.title ? (x.titleSize.height + margin) : 0) + maxOutset; - - p.top += (options.grid.circular ? 0 : (x2.used && x2.options.showLabels ? (x2.maxLabel.height + margin) : 0)) + - (x2.used && x2.options.title ? (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + maxOutset; - } - if (y.options.margin === false) { - p.left = 0; - p.right = 0; - } else { - p.left += (options.grid.circular ? 0 : (y.used && y.options.showLabels ? (y.maxLabel.width + margin) : 0)) + - (y.used && y.options.title ? (y.titleSize.width + margin) : 0) + maxOutset; - - p.right += (options.grid.circular ? 0 : (y2.used && y2.options.showLabels ? (y2.maxLabel.width + margin) : 0)) + - (y2.used && y2.options.title ? (y2.titleSize.width + margin) : 0) + maxOutset; - } - - p.top = Math.floor(p.top); // In order the outline not to be blured - - this.plotWidth = this.canvasWidth - p.left - p.right; - this.plotHeight = this.canvasHeight - p.bottom - p.top; - - // TODO post refactor, fix this - x.length = x2.length = this.plotWidth; - y.length = y2.length = this.plotHeight; - y.offset = y2.offset = this.plotHeight; - x.setScale(); - x2.setScale(); - y.setScale(); - y2.setScale(); - }, - /** - * Draws grid, labels, series and outline. - */ - draw: function(after) { - - var - context = this.ctx, - i; - - E.fire(this.el, 'flotr:beforedraw', [this.series, this]); - - if (this.series.length) { - - context.save(); - context.translate(this.plotOffset.left, this.plotOffset.top); - - for (i = 0; i < this.series.length; i++) { - if (!this.series[i].hide) this.drawSeries(this.series[i]); - } - - context.restore(); - this.clip(); - } - - E.fire(this.el, 'flotr:afterdraw', [this.series, this]); - if (after) after(); - }, - /** - * Actually draws the graph. - * @param {Object} series - series to draw - */ - drawSeries: function(series){ - - function drawChart (series, typeKey) { - var options = this.getOptions(series, typeKey); - this[typeKey].draw(options); - } - - var drawn = false; - series = series || this.series; - - _.each(flotr.graphTypes, function (type, typeKey) { - if (series[typeKey] && series[typeKey].show && this[typeKey]) { - drawn = true; - drawChart.call(this, series, typeKey); - } - }, this); - - if (!drawn) drawChart.call(this, series, this.options.defaultType); - }, - - getOptions : function (series, typeKey) { - var - type = series[typeKey], - graphType = this[typeKey], - options = { - context : this.ctx, - width : this.plotWidth, - height : this.plotHeight, - fontSize : this.options.fontSize, - fontColor : this.options.fontColor, - textEnabled : this.textEnabled, - htmlText : this.options.HtmlText, - text : this._text, // TODO Is this necessary? - element : this.el, - data : series.data, - color : series.color, - shadowSize : series.shadowSize, - xScale : _.bind(series.xaxis.d2p, series.xaxis), - yScale : _.bind(series.yaxis.d2p, series.yaxis) - }; - - options = flotr.merge(type, options); - - // Fill - options.fillStyle = this.processColor( - type.fillColor || series.color, - {opacity: type.fillOpacity} - ); - - return options; - }, - /** - * Calculates the coordinates from a mouse event object. - * @param {Event} event - Mouse Event object. - * @return {Object} Object with coordinates of the mouse. - */ - getEventPosition: function (e){ - - var - d = document, - b = d.body, - de = d.documentElement, - axes = this.axes, - plotOffset = this.plotOffset, - lastMousePos = this.lastMousePos, - pointer = E.eventPointer(e), - dx = pointer.x - lastMousePos.pageX, - dy = pointer.y - lastMousePos.pageY, - r, rx, ry; - - if ('ontouchstart' in this.el) { - r = D.position(this.overlay); - rx = pointer.x - r.left - plotOffset.left; - ry = pointer.y - r.top - plotOffset.top; - } else { - r = this.overlay.getBoundingClientRect(); - rx = e.clientX - r.left - plotOffset.left - b.scrollLeft - de.scrollLeft; - ry = e.clientY - r.top - plotOffset.top - b.scrollTop - de.scrollTop; - } - - return { - x: axes.x.p2d(rx), - x2: axes.x2.p2d(rx), - y: axes.y.p2d(ry), - y2: axes.y2.p2d(ry), - relX: rx, - relY: ry, - dX: dx, - dY: dy, - absX: pointer.x, - absY: pointer.y, - pageX: pointer.x, - pageY: pointer.y - }; - }, - /** - * Observes the 'click' event and fires the 'flotr:click' event. - * @param {Event} event - 'click' Event object. - */ - clickHandler: function(event){ - if(this.ignoreClick){ - this.ignoreClick = false; - return this.ignoreClick; - } - E.fire(this.el, 'flotr:click', [this.getEventPosition(event), this]); - }, - /** - * Observes mouse movement over the graph area. Fires the 'flotr:mousemove' event. - * @param {Event} event - 'mousemove' Event object. - */ - mouseMoveHandler: function(event){ - if (this.mouseDownMoveHandler) return; - var pos = this.getEventPosition(event); - E.fire(this.el, 'flotr:mousemove', [event, pos, this]); - this.lastMousePos = pos; - }, - /** - * Observes the 'mousedown' event. - * @param {Event} event - 'mousedown' Event object. - */ - mouseDownHandler: function (event){ - - /* - // @TODO Context menu? - if(event.isRightClick()) { - event.stop(); - - var overlay = this.overlay; - overlay.hide(); - - function cancelContextMenu () { - overlay.show(); - E.stopObserving(document, 'mousemove', cancelContextMenu); - } - E.observe(document, 'mousemove', cancelContextMenu); - return; - } - */ - - if (this.mouseUpHandler) return; - this.mouseUpHandler = _.bind(function (e) { - E.stopObserving(document, 'mouseup', this.mouseUpHandler); - E.stopObserving(document, 'mousemove', this.mouseDownMoveHandler); - this.mouseDownMoveHandler = null; - this.mouseUpHandler = null; - // @TODO why? - //e.stop(); - E.fire(this.el, 'flotr:mouseup', [e, this]); - }, this); - this.mouseDownMoveHandler = _.bind(function (e) { - var pos = this.getEventPosition(e); - E.fire(this.el, 'flotr:mousemove', [event, pos, this]); - this.lastMousePos = pos; - }, this); - E.observe(document, 'mouseup', this.mouseUpHandler); - E.observe(document, 'mousemove', this.mouseDownMoveHandler); - E.fire(this.el, 'flotr:mousedown', [event, this]); - this.ignoreClick = false; - }, - drawTooltip: function(content, x, y, options) { - var mt = this.getMouseTrack(), - style = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;', - p = options.position, - m = options.margin, - plotOffset = this.plotOffset; - - if(x !== null && y !== null){ - if (!options.relative) { // absolute to the canvas - if(p.charAt(0) == 'n') style += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;'; - else if(p.charAt(0) == 's') style += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;'; - if(p.charAt(1) == 'e') style += 'right:' + (m + plotOffset.right) + 'px;left:auto;'; - else if(p.charAt(1) == 'w') style += 'left:' + (m + plotOffset.left) + 'px;right:auto;'; - } - else { // relative to the mouse - if(p.charAt(0) == 'n') style += 'bottom:' + (m - plotOffset.top - y + this.canvasHeight) + 'px;top:auto;'; - else if(p.charAt(0) == 's') style += 'top:' + (m + plotOffset.top + y) + 'px;bottom:auto;'; - if(p.charAt(1) == 'e') style += 'left:' + (m + plotOffset.left + x) + 'px;right:auto;'; - else if(p.charAt(1) == 'w') style += 'right:' + (m - plotOffset.left - x + this.canvasWidth) + 'px;left:auto;'; - } - - mt.style.cssText = style; - D.empty(mt); - D.insert(mt, content); - D.show(mt); - } - else { - D.hide(mt); - } - }, - - clip: function () { - - var - ctx = this.ctx, - o = this.plotOffset, - w = this.canvasWidth, - h = this.canvasHeight; - - if (flotr.isIE && flotr.isIE < 9) { - // Clipping for excanvas :-( - ctx.save(); - ctx.fillStyle = this.processColor(this.options.ieBackgroundColor); - ctx.fillRect(0, 0, w, o.top); - ctx.fillRect(0, 0, o.left, h); - ctx.fillRect(0, h - o.bottom, w, o.bottom); - ctx.fillRect(w - o.right, 0, o.right,h); - ctx.restore(); - } else { - ctx.clearRect(0, 0, w, o.top); - ctx.clearRect(0, 0, o.left, h); - ctx.clearRect(0, h - o.bottom, w, o.bottom); - ctx.clearRect(w - o.right, 0, o.right,h); - } - }, - - _initMembers: function() { - this._handles = []; - this.lastMousePos = {pageX: null, pageY: null }; - this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0}; - this.ignoreClick = true; - this.prevHit = null; - }, - - _initGraphTypes: function() { - _.each(flotr.graphTypes, function(handler, graphType){ - this[graphType] = flotr.clone(handler); - }, this); - }, - - _initEvents: function () { - - var - el = this.el, - touchendHandler, movement, touchend; - - if ('ontouchstart' in el) { - - touchendHandler = _.bind(function (e) { - touchend = true; - E.stopObserving(document, 'touchend', touchendHandler); - E.fire(el, 'flotr:mouseup', [event, this]); - this.multitouches = null; - - if (!movement) { - this.clickHandler(e); - } - }, this); - - this.observe(this.overlay, 'touchstart', _.bind(function (e) { - movement = false; - touchend = false; - this.ignoreClick = false; - - if (e.touches && e.touches.length > 1) { - this.multitouches = e.touches; - } - - E.fire(el, 'flotr:mousedown', [event, this]); - this.observe(document, 'touchend', touchendHandler); - }, this)); - - this.observe(this.overlay, 'touchmove', _.bind(function (e) { - - var pos = this.getEventPosition(e); - - e.preventDefault(); - - movement = true; - - if (this.multitouches || (e.touches && e.touches.length > 1)) { - this.multitouches = e.touches; - } else { - if (!touchend) { - E.fire(el, 'flotr:mousemove', [event, pos, this]); - } - } - this.lastMousePos = pos; - }, this)); - - } else { - this. - observe(this.overlay, 'mousedown', _.bind(this.mouseDownHandler, this)). - observe(el, 'mousemove', _.bind(this.mouseMoveHandler, this)). - observe(this.overlay, 'click', _.bind(this.clickHandler, this)). - observe(el, 'mouseout', function () { - E.fire(el, 'flotr:mouseout'); - }); - } - }, - - /** - * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use - * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements - * are created, the elements are inserted into the container element. - */ - _initCanvas: function(){ - var el = this.el, - o = this.options, - children = el.children, - removedChildren = [], - child, i, - size, style; - - // Empty the el - for (i = children.length; i--;) { - child = children[i]; - if (!this.canvas && child.className === 'flotr-canvas') { - this.canvas = child; - } else if (!this.overlay && child.className === 'flotr-overlay') { - this.overlay = child; - } else { - removedChildren.push(child); - } - } - for (i = removedChildren.length; i--;) { - el.removeChild(removedChildren[i]); - } - - D.setStyles(el, {position: 'relative'}); // For positioning labels and overlay. - size = {}; - size.width = el.clientWidth; - size.height = el.clientHeight; - - if(size.width <= 0 || size.height <= 0 || o.resolution <= 0){ - throw 'Invalid dimensions for plot, width = ' + size.width + ', height = ' + size.height + ', resolution = ' + o.resolution; - } - - // Main canvas for drawing graph types - this.canvas = getCanvas(this.canvas, 'canvas'); - // Overlay canvas for interactive features - this.overlay = getCanvas(this.overlay, 'overlay'); - this.ctx = getContext(this.canvas); - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.octx = getContext(this.overlay); - this.octx.clearRect(0, 0, this.overlay.width, this.overlay.height); - this.canvasHeight = size.height; - this.canvasWidth = size.width; - this.textEnabled = !!this.ctx.drawText || !!this.ctx.fillText; // Enable text functions - - function getCanvas(canvas, name){ - if(!canvas){ - canvas = D.create('canvas'); - if (typeof FlashCanvas != "undefined" && typeof canvas.getContext === 'function') { - FlashCanvas.initElement(canvas); - } - canvas.className = 'flotr-'+name; - canvas.style.cssText = 'position:absolute;left:0px;top:0px;'; - D.insert(el, canvas); - } - _.each(size, function(size, attribute){ - D.show(canvas); - if (name == 'canvas' && canvas.getAttribute(attribute) === size) { - return; - } - canvas.setAttribute(attribute, size * o.resolution); - canvas.style[attribute] = size + 'px'; - }); - canvas.context_ = null; // Reset the ExCanvas context - return canvas; - } - - function getContext(canvas){ - if(window.G_vmlCanvasManager) window.G_vmlCanvasManager.initElement(canvas); // For ExCanvas - var context = canvas.getContext('2d'); - if(!window.G_vmlCanvasManager) context.scale(o.resolution, o.resolution); - return context; - } - }, - - _initPlugins: function(){ - // TODO Should be moved to flotr and mixed in. - _.each(flotr.plugins, function(plugin, name){ - _.each(plugin.callbacks, function(fn, c){ - this.observe(this.el, c, _.bind(fn, this)); - }, this); - this[name] = flotr.clone(plugin); - _.each(this[name], function(fn, p){ - if (_.isFunction(fn)) - this[name][p] = _.bind(fn, this); - }, this); - }, this); - }, - - /** - * Sets options and initializes some variables and color specific values, used by the constructor. - * @param {Object} opts - options object - */ - _initOptions: function(opts){ - var options = flotr.clone(flotr.defaultOptions); - options.x2axis = _.extend(_.clone(options.xaxis), options.x2axis); - options.y2axis = _.extend(_.clone(options.yaxis), options.y2axis); - this.options = flotr.merge(opts || {}, options); - - if (this.options.grid.minorVerticalLines === null && - this.options.xaxis.scaling === 'logarithmic') { - this.options.grid.minorVerticalLines = true; - } - if (this.options.grid.minorHorizontalLines === null && - this.options.yaxis.scaling === 'logarithmic') { - this.options.grid.minorHorizontalLines = true; - } - - E.fire(this.el, 'flotr:afterinitoptions', [this]); - - this.axes = flotr.Axis.getAxes(this.options); - - // Initialize some variables used throughout this function. - var assignedColors = [], - colors = [], - ln = this.series.length, - neededColors = this.series.length, - oc = this.options.colors, - usedColors = [], - variation = 0, - c, i, j, s; - - // Collect user-defined colors from series. - for(i = neededColors - 1; i > -1; --i){ - c = this.series[i].color; - if(c){ - --neededColors; - if(_.isNumber(c)) assignedColors.push(c); - else usedColors.push(flotr.Color.parse(c)); - } - } - - // Calculate the number of colors that need to be generated. - for(i = assignedColors.length - 1; i > -1; --i) - neededColors = Math.max(neededColors, assignedColors[i] + 1); - - // Generate needed number of colors. - for(i = 0; colors.length < neededColors;){ - c = (oc.length == i) ? new flotr.Color(100, 100, 100) : flotr.Color.parse(oc[i]); - - // Make sure each serie gets a different color. - var sign = variation % 2 == 1 ? -1 : 1, - factor = 1 + sign * Math.ceil(variation / 2) * 0.2; - c.scale(factor, factor, factor); - - /** - * @todo if we're getting too close to something else, we should probably skip this one - */ - colors.push(c); - - if(++i >= oc.length){ - i = 0; - ++variation; - } - } - - // Fill the options with the generated colors. - for(i = 0, j = 0; i < ln; ++i){ - s = this.series[i]; - - // Assign the color. - if (!s.color){ - s.color = colors[j++].toString(); - }else if(_.isNumber(s.color)){ - s.color = colors[s.color].toString(); - } - - // Every series needs an axis - if (!s.xaxis) s.xaxis = this.axes.x; - if (s.xaxis == 1) s.xaxis = this.axes.x; - else if (s.xaxis == 2) s.xaxis = this.axes.x2; - - if (!s.yaxis) s.yaxis = this.axes.y; - if (s.yaxis == 1) s.yaxis = this.axes.y; - else if (s.yaxis == 2) s.yaxis = this.axes.y2; - - // Apply missing options to the series. - for (var t in flotr.graphTypes){ - s[t] = _.extend(_.clone(this.options[t]), s[t]); - } - s.mouse = _.extend(_.clone(this.options.mouse), s.mouse); - - if (_.isUndefined(s.shadowSize)) s.shadowSize = this.options.shadowSize; - } - }, - - _setEl: function(el) { - if (!el) throw 'The target container doesn\'t exist'; - else if (el.graph instanceof Graph) el.graph.destroy(); - else if (!el.clientWidth) throw 'The target container must be visible'; - - el.graph = this; - this.el = el; - } -}; - -Flotr.Graph = Graph; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Series.js b/addons/web_graph/static/lib/flotr2/js/Series.js deleted file mode 100644 index e9a0c0fbeaa..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Series.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Flotr Series Library - */ - -(function () { - -var - _ = Flotr._; - -function Series (o) { - _.extend(this, o); -} - -Series.prototype = { - - getRange: function () { - - var - data = this.data, - length = data.length, - xmin = Number.MAX_VALUE, - ymin = Number.MAX_VALUE, - xmax = -Number.MAX_VALUE, - ymax = -Number.MAX_VALUE, - xused = false, - yused = false, - x, y, i; - - if (length < 0 || this.hide) return false; - - for (i = 0; i < length; i++) { - x = data[i][0]; - y = data[i][1]; - if (x < xmin) { xmin = x; xused = true; } - if (x > xmax) { xmax = x; xused = true; } - if (y < ymin) { ymin = y; yused = true; } - if (y > ymax) { ymax = y; yused = true; } - } - - return { - xmin : xmin, - xmax : xmax, - ymin : ymin, - ymax : ymax, - xused : xused, - yused : yused - }; - } -}; - -_.extend(Series, { - /** - * Collects dataseries from input and parses the series into the right format. It returns an Array - * of Objects each having at least the 'data' key set. - * @param {Array, Object} data - Object or array of dataseries - * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)}) - */ - getSeries: function(data){ - return _.map(data, function(s){ - var series; - if (s.data) { - series = new Series(); - _.extend(series, s); - } else { - series = new Series({data:s}); - } - return series; - }); - } -}); - -Flotr.Series = Series; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Text.js b/addons/web_graph/static/lib/flotr2/js/Text.js deleted file mode 100644 index f24cf7ac4bf..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Text.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Text Utilities - */ -(function () { - -var - F = Flotr, - D = F.DOM, - _ = F._, - -Text = function (o) { - this.o = o; -}; - -Text.prototype = { - - dimensions : function (text, canvasStyle, htmlStyle, className) { - - if (!text) return { width : 0, height : 0 }; - - return (this.o.html) ? - this.html(text, this.o.element, htmlStyle, className) : - this.canvas(text, canvasStyle); - }, - - canvas : function (text, style) { - - if (!this.o.textEnabled) return; - style = style || {}; - - var - metrics = this.measureText(text, style), - width = metrics.width, - height = style.size || F.defaultOptions.fontSize, - angle = style.angle || 0, - cosAngle = Math.cos(angle), - sinAngle = Math.sin(angle), - widthPadding = 2, - heightPadding = 6, - bounds; - - bounds = { - width: Math.abs(cosAngle * width) + Math.abs(sinAngle * height) + widthPadding, - height: Math.abs(sinAngle * width) + Math.abs(cosAngle * height) + heightPadding - }; - - return bounds; - }, - - html : function (text, element, style, className) { - - var div = D.create('div'); - - D.setStyles(div, { 'position' : 'absolute', 'top' : '-10000px' }); - D.insert(div, '
    ' + text + '
    '); - D.insert(this.o.element, div); - - return D.size(div); - }, - - measureText : function (text, style) { - - var - context = this.o.ctx, - metrics; - - if (!context.fillText || (F.isIphone && context.measure)) { - return { width : context.measure(text, style)}; - } - - style = _.extend({ - size: F.defaultOptions.fontSize, - weight: 1, - angle: 0 - }, style); - - context.save(); - context.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif"; - metrics = context.measureText(text); - context.restore(); - - return metrics; - } -}; - -Flotr.Text = Text; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/crosshair.js b/addons/web_graph/static/lib/flotr2/js/plugins/crosshair.js deleted file mode 100644 index 611ebf85800..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/crosshair.js +++ /dev/null @@ -1,84 +0,0 @@ -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('crosshair', { - options: { - mode: null, // => one of null, 'x', 'y' or 'xy' - color: '#FF0000', // => crosshair color - hideCursor: true // => hide the cursor when the crosshair is shown - }, - callbacks: { - 'flotr:mousemove': function(e, pos) { - if (this.options.crosshair.mode) { - this.crosshair.clearCrosshair(); - this.crosshair.drawCrosshair(pos); - } - } - }, - /** - * Draws the selection box. - */ - drawCrosshair: function(pos) { - var octx = this.octx, - options = this.options.crosshair, - plotOffset = this.plotOffset, - x = plotOffset.left + pos.relX + 0.5, - y = plotOffset.top + pos.relY + 0.5; - - if (pos.relX < 0 || pos.relY < 0 || pos.relX > this.plotWidth || pos.relY > this.plotHeight) { - this.el.style.cursor = null; - D.removeClass(this.el, 'flotr-crosshair'); - return; - } - - if (options.hideCursor) { - this.el.style.cursor = 'none'; - D.addClass(this.el, 'flotr-crosshair'); - } - - octx.save(); - octx.strokeStyle = options.color; - octx.lineWidth = 1; - octx.beginPath(); - - if (options.mode.indexOf('x') != -1) { - octx.moveTo(x, plotOffset.top); - octx.lineTo(x, plotOffset.top + this.plotHeight); - } - - if (options.mode.indexOf('y') != -1) { - octx.moveTo(plotOffset.left, y); - octx.lineTo(plotOffset.left + this.plotWidth, y); - } - - octx.stroke(); - octx.restore(); - }, - /** - * Removes the selection box from the overlay canvas. - */ - clearCrosshair: function() { - - var - plotOffset = this.plotOffset, - position = this.lastMousePos, - context = this.octx; - - if (position) { - context.clearRect( - position.relX + plotOffset.left, - plotOffset.top, - 1, - this.plotHeight + 1 - ); - context.clearRect( - plotOffset.left, - position.relY + plotOffset.top, - this.plotWidth + 1, - 1 - ); - } - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/download.js b/addons/web_graph/static/lib/flotr2/js/plugins/download.js deleted file mode 100644 index 5d585325be9..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/download.js +++ /dev/null @@ -1,51 +0,0 @@ -(function() { - -var - D = Flotr.DOM, - _ = Flotr._; - -function getImage (type, canvas, width, height) { - - // TODO add scaling for w / h - var - mime = 'image/'+type, - data = canvas.toDataURL(mime), - image = new Image(); - image.src = data; - return image; -} - -Flotr.addPlugin('download', { - - saveImage: function (type, width, height, replaceCanvas) { - var image = null; - if (Flotr.isIE && Flotr.isIE < 9) { - image = ''+this.canvas.firstChild.innerHTML+''; - return window.open().document.write(image); - } - - if (type !== 'jpeg' && type !== 'png') return; - - image = getImage(type, this.canvas, width, height); - - if (_.isElement(image) && replaceCanvas) { - this.download.restoreCanvas(); - D.hide(this.canvas); - D.hide(this.overlay); - D.setStyles({position: 'absolute'}); - D.insert(this.el, image); - this.saveImageElement = image; - } else { - return window.open(image.src); - } - }, - - restoreCanvas: function() { - D.show(this.canvas); - D.show(this.overlay); - if (this.saveImageElement) this.el.removeChild(this.saveImageElement); - this.saveImageElement = null; - } -}); - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/grid.js b/addons/web_graph/static/lib/flotr2/js/plugins/grid.js deleted file mode 100644 index ab90d8d591a..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/grid.js +++ /dev/null @@ -1,208 +0,0 @@ -(function () { - -var E = Flotr.EventAdapter, - _ = Flotr._; - -Flotr.addPlugin('graphGrid', { - - callbacks: { - 'flotr:beforedraw' : function () { - this.graphGrid.drawGrid(); - }, - 'flotr:afterdraw' : function () { - this.graphGrid.drawOutline(); - } - }, - - drawGrid: function(){ - - var - ctx = this.ctx, - options = this.options, - grid = options.grid, - verticalLines = grid.verticalLines, - horizontalLines = grid.horizontalLines, - minorVerticalLines = grid.minorVerticalLines, - minorHorizontalLines = grid.minorHorizontalLines, - plotHeight = this.plotHeight, - plotWidth = this.plotWidth, - a, v, i, j; - - if(verticalLines || minorVerticalLines || - horizontalLines || minorHorizontalLines){ - E.fire(this.el, 'flotr:beforegrid', [this.axes.x, this.axes.y, options, this]); - } - ctx.save(); - ctx.lineWidth = 1; - ctx.strokeStyle = grid.tickColor; - - function circularHorizontalTicks (ticks) { - for(i = 0; i < ticks.length; ++i){ - var ratio = ticks[i].v / a.max; - for(j = 0; j <= sides; ++j){ - ctx[j === 0 ? 'moveTo' : 'lineTo']( - Math.cos(j*coeff+angle)*radius*ratio, - Math.sin(j*coeff+angle)*radius*ratio - ); - } - } - } - function drawGridLines (ticks, callback) { - _.each(_.pluck(ticks, 'v'), function(v){ - // Don't show lines on upper and lower bounds. - if ((v <= a.min || v >= a.max) || - (v == a.min || v == a.max) && grid.outlineWidth) - return; - callback(Math.floor(a.d2p(v)) + ctx.lineWidth/2); - }); - } - function drawVerticalLines (x) { - ctx.moveTo(x, 0); - ctx.lineTo(x, plotHeight); - } - function drawHorizontalLines (y) { - ctx.moveTo(0, y); - ctx.lineTo(plotWidth, y); - } - - if (grid.circular) { - ctx.translate(this.plotOffset.left+plotWidth/2, this.plotOffset.top+plotHeight/2); - var radius = Math.min(plotHeight, plotWidth)*options.radar.radiusRatio/2, - sides = this.axes.x.ticks.length, - coeff = 2*(Math.PI/sides), - angle = -Math.PI/2; - - // Draw grid lines in vertical direction. - ctx.beginPath(); - - a = this.axes.y; - - if(horizontalLines){ - circularHorizontalTicks(a.ticks); - } - if(minorHorizontalLines){ - circularHorizontalTicks(a.minorTicks); - } - - if(verticalLines){ - _.times(sides, function(i){ - ctx.moveTo(0, 0); - ctx.lineTo(Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius); - }); - } - ctx.stroke(); - } - else { - ctx.translate(this.plotOffset.left, this.plotOffset.top); - - // Draw grid background, if present in options. - if(grid.backgroundColor){ - ctx.fillStyle = this.processColor(grid.backgroundColor, {x1: 0, y1: 0, x2: plotWidth, y2: plotHeight}); - ctx.fillRect(0, 0, plotWidth, plotHeight); - } - - ctx.beginPath(); - - a = this.axes.x; - if (verticalLines) drawGridLines(a.ticks, drawVerticalLines); - if (minorVerticalLines) drawGridLines(a.minorTicks, drawVerticalLines); - - a = this.axes.y; - if (horizontalLines) drawGridLines(a.ticks, drawHorizontalLines); - if (minorHorizontalLines) drawGridLines(a.minorTicks, drawHorizontalLines); - - ctx.stroke(); - } - - ctx.restore(); - if(verticalLines || minorVerticalLines || - horizontalLines || minorHorizontalLines){ - E.fire(this.el, 'flotr:aftergrid', [this.axes.x, this.axes.y, options, this]); - } - }, - - drawOutline: function(){ - var - that = this, - options = that.options, - grid = options.grid, - outline = grid.outline, - ctx = that.ctx, - backgroundImage = grid.backgroundImage, - plotOffset = that.plotOffset, - leftOffset = plotOffset.left, - topOffset = plotOffset.top, - plotWidth = that.plotWidth, - plotHeight = that.plotHeight, - v, img, src, left, top, globalAlpha; - - if (!grid.outlineWidth) return; - - ctx.save(); - - if (grid.circular) { - ctx.translate(leftOffset + plotWidth / 2, topOffset + plotHeight / 2); - var radius = Math.min(plotHeight, plotWidth) * options.radar.radiusRatio / 2, - sides = this.axes.x.ticks.length, - coeff = 2*(Math.PI/sides), - angle = -Math.PI/2; - - // Draw axis/grid border. - ctx.beginPath(); - ctx.lineWidth = grid.outlineWidth; - ctx.strokeStyle = grid.color; - ctx.lineJoin = 'round'; - - for(i = 0; i <= sides; ++i){ - ctx[i === 0 ? 'moveTo' : 'lineTo'](Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius); - } - //ctx.arc(0, 0, radius, 0, Math.PI*2, true); - - ctx.stroke(); - } - else { - ctx.translate(leftOffset, topOffset); - - // Draw axis/grid border. - var lw = grid.outlineWidth, - orig = 0.5-lw+((lw+1)%2/2), - lineTo = 'lineTo', - moveTo = 'moveTo'; - ctx.lineWidth = lw; - ctx.strokeStyle = grid.color; - ctx.lineJoin = 'miter'; - ctx.beginPath(); - ctx.moveTo(orig, orig); - plotWidth = plotWidth - (lw / 2) % 1; - plotHeight = plotHeight + lw / 2; - ctx[outline.indexOf('n') !== -1 ? lineTo : moveTo](plotWidth, orig); - ctx[outline.indexOf('e') !== -1 ? lineTo : moveTo](plotWidth, plotHeight); - ctx[outline.indexOf('s') !== -1 ? lineTo : moveTo](orig, plotHeight); - ctx[outline.indexOf('w') !== -1 ? lineTo : moveTo](orig, orig); - ctx.stroke(); - ctx.closePath(); - } - - ctx.restore(); - - if (backgroundImage) { - - src = backgroundImage.src || backgroundImage; - left = (parseInt(backgroundImage.left, 10) || 0) + plotOffset.left; - top = (parseInt(backgroundImage.top, 10) || 0) + plotOffset.top; - img = new Image(); - - img.onload = function() { - ctx.save(); - if (backgroundImage.alpha) ctx.globalAlpha = backgroundImage.alpha; - ctx.globalCompositeOperation = 'destination-over'; - ctx.drawImage(img, 0, 0, img.width, img.height, left, top, plotWidth, plotHeight); - ctx.restore(); - }; - - img.src = src; - } - } -}); - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/handles.js b/addons/web_graph/static/lib/flotr2/js/plugins/handles.js deleted file mode 100644 index 92fd3247fdf..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/handles.js +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Selection Handles Plugin - * - * Depends upon options.selection.mode - * - * Options - * show - True enables the handles plugin. - * drag - Left and Right drag handles - * scroll - Scrolling handle - */ -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('handles', { - - options: { - show: false, - drag: true, - scroll: true - }, - - callbacks: { - 'flotr:afterinit': init, - 'flotr:select': handleSelect, - 'flotr:mousedown': reset, - 'flotr:mousemove': mouseMoveHandler - } - -}); - - -function init() { - - var - options = this.options, - handles = this.handles, - el = this.el, - scroll, left, right, container; - - if (!options.selection.mode || !options.handles.show || 'ontouchstart' in el) return; - - handles.initialized = true; - - container = D.node('
    '); - options = options.handles; - - // Drag handles - if (options.drag) { - right = D.node('
    '); - left = D.node('
    '); - D.insert(container, right); - D.insert(container, left); - D.hide(left); - D.hide(right); - handles.left = left; - handles.right = right; - - this.observe(left, 'mousedown', function () { - handles.moveHandler = leftMoveHandler; - }); - this.observe(right, 'mousedown', function () { - handles.moveHandler = rightMoveHandler; - }); - } - - // Scroll handle - if (options.scroll) { - scroll = D.node('
    '); - D.insert(container, scroll); - D.hide(scroll); - handles.scroll = scroll; - this.observe(scroll, 'mousedown', function () { - handles.moveHandler = scrollMoveHandler; - }); - } - - this.observe(document, 'mouseup', function() { - handles.moveHandler = null; - }); - - D.insert(el, container); -} - - -function handleSelect(selection) { - - if (!this.handles.initialized) return; - - var - handles = this.handles, - options = this.options.handles, - left = handles.left, - right = handles.right, - scroll = handles.scroll; - - if (options) { - if (options.drag) { - positionDrag(this, left, selection.x1); - positionDrag(this, right, selection.x2); - } - - if (options.scroll) { - positionScroll( - this, - scroll, - selection.x1, - selection.x2 - ); - } - } -} - -function positionDrag(graph, handle, x) { - - D.show(handle); - - var size = D.size(handle), - l = Math.round(graph.axes.x.d2p(x) - size.width / 2), - t = (graph.plotHeight - size.height) / 2; - - D.setStyles(handle, { - 'left' : l+'px', - 'top' : t+'px' - }); -} - -function positionScroll(graph, handle, x1, x2) { - - D.show(handle); - - var size = D.size(handle), - l = Math.round(graph.axes.x.d2p(x1)), - t = (graph.plotHeight) - size.height / 2, - w = (graph.axes.x.d2p(x2) - graph.axes.x.d2p(x1)); - - D.setStyles(handle, { - 'left' : l+'px', - 'top' : t+'px', - 'width': w+'px' - }); -} - -function reset() { - - if (!this.handles.initialized) return; - - var - handles = this.handles; - if (handles) { - D.hide(handles.left); - D.hide(handles.right); - D.hide(handles.scroll); - } -} - -function mouseMoveHandler(e, position) { - - if (!this.handles.initialized) return; - if (!this.handles.moveHandler) return; - - var - delta = position.dX, - selection = this.selection.selection, - area = this.selection.getArea(), - handles = this.handles; - - handles.moveHandler(area, delta); - checkSwap(area, handles); - - this.selection.setSelection(area); -} - -function checkSwap (area, handles) { - var moveHandler = handles.moveHandler; - if (area.x1 > area.x2) { - if (moveHandler == leftMoveHandler) { - moveHandler = rightMoveHandler; - } else if (moveHandler == rightMoveHandler) { - moveHandler = leftMoveHandler; - } - handles.moveHandler = moveHandler; - } -} - -function leftMoveHandler(area, delta) { - area.x1 += delta; -} - -function rightMoveHandler(area, delta) { - area.x2 += delta; -} - -function scrollMoveHandler(area, delta) { - area.x1 += delta; - area.x2 += delta; -} - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/hit.js b/addons/web_graph/static/lib/flotr2/js/plugins/hit.js deleted file mode 100644 index eeff570df35..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/hit.js +++ /dev/null @@ -1,337 +0,0 @@ -(function () { - -var - D = Flotr.DOM, - _ = Flotr._, - flotr = Flotr, - S_MOUSETRACK = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;'; - -Flotr.addPlugin('hit', { - callbacks: { - 'flotr:mousemove': function(e, pos) { - this.hit.track(pos); - }, - 'flotr:click': function(pos) { - this.hit.track(pos); - }, - 'flotr:mouseout': function() { - this.hit.clearHit(); - } - }, - track : function (pos) { - if (this.options.mouse.track || _.any(this.series, function(s){return s.mouse && s.mouse.track;})) { - this.hit.hit(pos); - } - }, - /** - * Try a method on a graph type. If the method exists, execute it. - * @param {Object} series - * @param {String} method Method name. - * @param {Array} args Arguments applied to method. - * @return executed successfully or failed. - */ - executeOnType: function(s, method, args){ - var - success = false, - options; - - if (!_.isArray(s)) s = [s]; - - function e(s, index) { - _.each(_.keys(flotr.graphTypes), function (type) { - if (s[type] && s[type].show && this[type][method]) { - options = this.getOptions(s, type); - - options.fill = !!s.mouse.fillColor; - options.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity}); - options.color = s.mouse.lineColor; - options.context = this.octx; - options.index = index; - - if (args) options.args = args; - this[type][method].call(this[type], options); - success = true; - } - }, this); - } - _.each(s, e, this); - - return success; - }, - /** - * Updates the mouse tracking point on the overlay. - */ - drawHit: function(n){ - var octx = this.octx, - s = n.series; - - if (s.mouse.lineColor) { - octx.save(); - octx.lineWidth = (s.points ? s.points.lineWidth : 1); - octx.strokeStyle = s.mouse.lineColor; - octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity}); - octx.translate(this.plotOffset.left, this.plotOffset.top); - - if (!this.hit.executeOnType(s, 'drawHit', n)) { - var xa = n.xaxis, - ya = n.yaxis; - - octx.beginPath(); - // TODO fix this (points) should move to general testable graph mixin - octx.arc(xa.d2p(n.x), ya.d2p(n.y), s.points.radius || s.mouse.radius, 0, 2 * Math.PI, true); - octx.fill(); - octx.stroke(); - octx.closePath(); - } - octx.restore(); - } - this.prevHit = n; - }, - /** - * Removes the mouse tracking point from the overlay. - */ - clearHit: function(){ - var prev = this.prevHit, - octx = this.octx, - plotOffset = this.plotOffset; - octx.save(); - octx.translate(plotOffset.left, plotOffset.top); - if (prev) { - if (!this.hit.executeOnType(prev.series, 'clearHit', this.prevHit)) { - // TODO fix this (points) should move to general testable graph mixin - var - s = prev.series, - lw = (s.points ? s.points.lineWidth : 1); - offset = (s.points.radius || s.mouse.radius) + lw; - octx.clearRect( - prev.xaxis.d2p(prev.x) - offset, - prev.yaxis.d2p(prev.y) - offset, - offset*2, - offset*2 - ); - } - D.hide(this.mouseTrack); - this.prevHit = null; - } - octx.restore(); - }, - /** - * Retrieves the nearest data point from the mouse cursor. If it's within - * a certain range, draw a point on the overlay canvas and display the x and y - * value of the data. - * @param {Object} mouse - Object that holds the relative x and y coordinates of the cursor. - */ - hit: function(mouse){ - - var - options = this.options, - prevHit = this.prevHit, - closest, sensibility, dataIndex, seriesIndex, series, value, xaxis, yaxis; - - if (this.series.length === 0) return; - - // Nearest data element. - // dist, x, y, relX, relY, absX, absY, sAngle, eAngle, fraction, mouse, - // xaxis, yaxis, series, index, seriesIndex - n = { - relX : mouse.relX, - relY : mouse.relY, - absX : mouse.absX, - absY : mouse.absY - }; - - if (options.mouse.trackY && - !options.mouse.trackAll && - this.hit.executeOnType(this.series, 'hit', [mouse, n])) - { - - if (!_.isUndefined(n.seriesIndex)) { - series = this.series[n.seriesIndex]; - n.series = series; - n.mouse = series.mouse; - n.xaxis = series.xaxis; - n.yaxis = series.yaxis; - } - } else { - - closest = this.hit.closest(mouse); - - if (closest) { - - closest = options.mouse.trackY ? closest.point : closest.x; - seriesIndex = closest.seriesIndex; - series = this.series[seriesIndex]; - xaxis = series.xaxis; - yaxis = series.yaxis; - sensibility = 2 * series.mouse.sensibility; - - if - (options.mouse.trackAll || - (closest.distanceX < sensibility / xaxis.scale && - (!options.mouse.trackY || closest.distanceY < sensibility / yaxis.scale))) - { - n.series = series; - n.xaxis = series.xaxis; - n.yaxis = series.yaxis; - n.mouse = series.mouse; - n.x = closest.x; - n.y = closest.y; - n.dist = closest.distance; - n.index = closest.dataIndex; - n.seriesIndex = seriesIndex; - } - } - } - - if (!prevHit || (prevHit.index !== n.index || prevHit.seriesIndex !== n.seriesIndex)) { - this.hit.clearHit(); - if (n.series && n.mouse && n.mouse.track) { - this.hit.drawMouseTrack(n); - this.hit.drawHit(n); - Flotr.EventAdapter.fire(this.el, 'flotr:hit', [n, this]); - } - } - }, - - closest : function (mouse) { - - var - series = this.series, - options = this.options, - mouseX = mouse.x, - mouseY = mouse.y, - compare = Number.MAX_VALUE, - compareX = Number.MAX_VALUE, - closest = {}, - closestX = {}, - check = false, - serie, data, - distance, distanceX, distanceY, - x, y, i, j; - - function setClosest (o) { - o.distance = distance; - o.distanceX = distanceX; - o.distanceY = distanceY; - o.seriesIndex = i; - o.dataIndex = j; - o.x = x; - o.y = y; - } - - for (i = 0; i < series.length; i++) { - - serie = series[i]; - data = serie.data; - - if (data.length) check = true; - - for (j = data.length; j--;) { - - x = data[j][0]; - y = data[j][1]; - - if (x === null || y === null) continue; - - // don't check if the point isn't visible in the current range - if (x < serie.xaxis.min || x > serie.xaxis.max) continue; - - distanceX = Math.abs(x - mouseX); - distanceY = Math.abs(y - mouseY); - - // Skip square root for speed - distance = distanceX * distanceX + distanceY * distanceY; - - if (distance < compare) { - compare = distance; - setClosest(closest); - } - - if (distanceX < compareX) { - compareX = distanceX; - setClosest(closestX); - } - } - } - - return check ? { - point : closest, - x : closestX - } : false; - }, - - drawMouseTrack : function (n) { - - var - pos = '', - s = n.series, - p = n.mouse.position, - m = n.mouse.margin, - elStyle = S_MOUSETRACK, - mouseTrack = this.mouseTrack, - plotOffset = this.plotOffset, - left = plotOffset.left, - right = plotOffset.right, - bottom = plotOffset.bottom, - top = plotOffset.top, - decimals = n.mouse.trackDecimals, - options = this.options; - - // Create - if (!mouseTrack) { - mouseTrack = D.node('
    '); - this.mouseTrack = mouseTrack; - D.insert(this.el, mouseTrack); - } - - if (!n.mouse.relative) { // absolute to the canvas - - if (p.charAt(0) == 'n') pos += 'top:' + (m + top) + 'px;bottom:auto;'; - else if (p.charAt(0) == 's') pos += 'bottom:' + (m + bottom) + 'px;top:auto;'; - if (p.charAt(1) == 'e') pos += 'right:' + (m + right) + 'px;left:auto;'; - else if (p.charAt(1) == 'w') pos += 'left:' + (m + left) + 'px;right:auto;'; - - // Bars - } else if (s.bars.show) { - pos += 'bottom:' + (m - top - n.yaxis.d2p(n.y/2) + this.canvasHeight) + 'px;top:auto;'; - pos += 'left:' + (m + left + n.xaxis.d2p(n.x - options.bars.barWidth/2)) + 'px;right:auto;'; - - // Pie - } else if (s.pie.show) { - var center = { - x: (this.plotWidth)/2, - y: (this.plotHeight)/2 - }, - radius = (Math.min(this.canvasWidth, this.canvasHeight) * s.pie.sizeRatio) / 2, - bisection = n.sAngle (isX ? graph.plotWidth : graph.plotHeight)) { continue; } - - Flotr.drawText( - ctx, tick.label, - leftOffset(graph, isX, isFirst, offset), - topOffset(graph, isX, isFirst, offset), - style - ); - - // Only draw on axis y2 - if (!isX && !isFirst) { - ctx.save(); - ctx.strokeStyle = style.color; - ctx.beginPath(); - ctx.moveTo(graph.plotOffset.left + graph.plotWidth - 8, graph.plotOffset.top + axis.d2p(tick.v)); - ctx.lineTo(graph.plotOffset.left + graph.plotWidth, graph.plotOffset.top + axis.d2p(tick.v)); - ctx.stroke(); - ctx.restore(); - } - } - - function continueShowingLabels (axis) { - return axis.options.showLabels && axis.used; - } - function leftOffset (graph, isX, isFirst, offset) { - return graph.plotOffset.left + - (isX ? offset : - (isFirst ? - -options.grid.labelMargin : - options.grid.labelMargin + graph.plotWidth)); - } - function topOffset (graph, isX, isFirst, offset) { - return graph.plotOffset.top + - (isX ? options.grid.labelMargin : offset) + - ((isX && isFirst) ? graph.plotHeight : 0); - } - } - - function drawLabelHtml (graph, axis) { - var - isX = axis.orientation === 1, - isFirst = axis.n === 1, - name = '', - left, style, top, - offset = graph.plotOffset; - - if (!isX && !isFirst) { - ctx.save(); - ctx.strokeStyle = axis.options.color || options.grid.color; - ctx.beginPath(); - } - - if (axis.options.showLabels && (isFirst ? true : axis.used)) { - for (i = 0; i < axis.ticks.length; ++i) { - tick = axis.ticks[i]; - if (!tick.label || !tick.label.length || - ((isX ? offset.left : offset.top) + axis.d2p(tick.v) < 0) || - ((isX ? offset.left : offset.top) + axis.d2p(tick.v) > (isX ? graph.canvasWidth : graph.canvasHeight))) { - continue; - } - top = offset.top + - (isX ? - ((isFirst ? 1 : -1 ) * (graph.plotHeight + options.grid.labelMargin)) : - axis.d2p(tick.v) - axis.maxLabel.height / 2); - left = isX ? (offset.left + axis.d2p(tick.v) - xBoxWidth / 2) : 0; - - name = ''; - if (i === 0) { - name = ' first'; - } else if (i === axis.ticks.length - 1) { - name = ' last'; - } - name += isX ? ' flotr-grid-label-x' : ' flotr-grid-label-y'; - - html += [ - '
    ' + tick.label + '
    ' - ].join(' '); - - if (!isX && !isFirst) { - ctx.moveTo(offset.left + graph.plotWidth - 8, offset.top + axis.d2p(tick.v)); - ctx.lineTo(offset.left + graph.plotWidth, offset.top + axis.d2p(tick.v)); - } - } - } - } - } - -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/legend.js b/addons/web_graph/static/lib/flotr2/js/plugins/legend.js deleted file mode 100644 index d130cb5e90b..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/legend.js +++ /dev/null @@ -1,179 +0,0 @@ -(function () { - -var - D = Flotr.DOM, - _ = Flotr._; - -Flotr.addPlugin('legend', { - options: { - show: true, // => setting to true will show the legend, hide otherwise - noColumns: 1, // => number of colums in legend table // @todo: doesn't work for HtmlText = false - labelFormatter: function(v){return v;}, // => fn: string -> string - labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes - labelBoxWidth: 14, - labelBoxHeight: 10, - labelBoxMargin: 5, - labelBoxOpacity: 0.4, - container: null, // => container (as jQuery object) to put legend in, null means default on top of graph - position: 'nw', // => position of default legend container within plot - margin: 5, // => distance from grid edge to default legend container within plot - backgroundColor: null, // => null means auto-detect - backgroundOpacity: 0.85// => set to 0 to avoid background, set to 1 for a solid background - }, - callbacks: { - 'flotr:afterinit': function() { - this.legend.insertLegend(); - } - }, - /** - * Adds a legend div to the canvas container or draws it on the canvas. - */ - insertLegend: function(){ - - if(!this.options.legend.show) - return; - - var series = this.series, - plotOffset = this.plotOffset, - options = this.options, - legend = options.legend, - fragments = [], - rowStarted = false, - ctx = this.ctx, - itemCount = _.filter(series, function(s) {return (s.label && !s.hide);}).length, - p = legend.position, - m = legend.margin, - i, label, color; - - if (itemCount) { - if (!options.HtmlText && this.textEnabled && !legend.container) { - var style = { - size: options.fontSize*1.1, - color: options.grid.color - }; - - var lbw = legend.labelBoxWidth, - lbh = legend.labelBoxHeight, - lbm = legend.labelBoxMargin, - offsetX = plotOffset.left + m, - offsetY = plotOffset.top + m; - - // We calculate the labels' max width - var labelMaxWidth = 0; - for(i = series.length - 1; i > -1; --i){ - if(!series[i].label || series[i].hide) continue; - label = legend.labelFormatter(series[i].label); - labelMaxWidth = Math.max(labelMaxWidth, this._text.measureText(label, style).width); - } - - var legendWidth = Math.round(lbw + lbm*3 + labelMaxWidth), - legendHeight = Math.round(itemCount*(lbm+lbh) + lbm); - - if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight); - if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth); - - // Legend box - color = this.processColor(legend.backgroundColor || 'rgb(240,240,240)', {opacity: legend.backgroundOpacity || 0.1}); - - ctx.fillStyle = color; - ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight); - ctx.strokeStyle = legend.labelBoxBorderColor; - ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight); - - // Legend labels - var x = offsetX + lbm; - var y = offsetY + lbm; - for(i = 0; i < series.length; i++){ - if(!series[i].label || series[i].hide) continue; - label = legend.labelFormatter(series[i].label); - - ctx.fillStyle = series[i].color; - ctx.fillRect(x, y, lbw-1, lbh-1); - - ctx.strokeStyle = legend.labelBoxBorderColor; - ctx.lineWidth = 1; - ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2); - - // Legend text - Flotr.drawText(ctx, label, x + lbw + lbm, y + lbh, style); - - y += lbh + lbm; - } - } - else { - for(i = 0; i < series.length; ++i){ - if(!series[i].label || series[i].hide) continue; - - if(i % legend.noColumns === 0){ - fragments.push(rowStarted ? '
    ' : ''); - rowStarted = true; - } - - // @TODO remove requirement on bars - var s = series[i], - boxWidth = legend.labelBoxWidth, - boxHeight = legend.labelBoxHeight, - opacityValue = (s.bars ? s.bars.fillOpacity : legend.labelBoxOpacity), - opacity = 'opacity:' + opacityValue + ';filter:alpha(opacity=' + opacityValue*100 + ');'; - - label = legend.labelFormatter(s.label); - color = 'background-color:' + ((s.bars && s.bars.show && s.bars.fillColor && s.bars.fill) ? s.bars.fillColor : s.color) + ';'; - - fragments.push( - '', - '' - ); - } - if(rowStarted) fragments.push(''); - - if(fragments.length > 0){ - var table = '
     '+(a.label||String.fromCharCode(65+b))+"
    ', - '
    ', - '
    ', // Border - '
    ', // Background - '
    ', - '
    ', - '
    ', label, '
    ' + fragments.join('') + '
    '; - if(legend.container){ - D.insert(legend.container, table); - } - else { - var styles = {position: 'absolute', 'z-index': 2}; - - if(p.charAt(0) == 'n') { styles.top = (m + plotOffset.top) + 'px'; styles.bottom = 'auto'; } - else if(p.charAt(0) == 's') { styles.bottom = (m + plotOffset.bottom) + 'px'; styles.top = 'auto'; } - if(p.charAt(1) == 'e') { styles.right = (m + plotOffset.right) + 'px'; styles.left = 'auto'; } - else if(p.charAt(1) == 'w') { styles.left = (m + plotOffset.left) + 'px'; styles.right = 'auto'; } - - var div = D.create('div'), size; - div.className = 'flotr-legend'; - D.setStyles(div, styles); - D.insert(div, table); - D.insert(this.el, div); - - if(!legend.backgroundOpacity) - return; - - var c = legend.backgroundColor || options.grid.backgroundColor || '#ffffff'; - - _.extend(styles, D.size(div), { - 'backgroundColor': c, - 'z-index': 1 - }); - styles.width += 'px'; - styles.height += 'px'; - - // Put in the transparent background separately to avoid blended labels and - div = D.create('div'); - div.className = 'flotr-legend-bg'; - D.setStyles(div, styles); - D.opacity(div, legend.backgroundOpacity); - D.insert(div, ' '); - D.insert(this.el, div); - } - } - } - } - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/selection.js b/addons/web_graph/static/lib/flotr2/js/plugins/selection.js deleted file mode 100644 index bb508840d6d..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/selection.js +++ /dev/null @@ -1,278 +0,0 @@ -/** - * Selection Handles Plugin - * - * - * Options - * show - True enables the handles plugin. - * drag - Left and Right drag handles - * scroll - Scrolling handle - */ -(function () { - -function isLeftClick (e, type) { - return (e.which ? (e.which === 1) : (e.button === 0 || e.button === 1)); -} - -function boundX(x, graph) { - return Math.min(Math.max(0, x), graph.plotWidth - 1); -} - -function boundY(y, graph) { - return Math.min(Math.max(0, y), graph.plotHeight); -} - -var - D = Flotr.DOM, - E = Flotr.EventAdapter, - _ = Flotr._; - - -Flotr.addPlugin('selection', { - - options: { - pinchOnly: null, // Only select on pinch - mode: null, // => one of null, 'x', 'y' or 'xy' - color: '#B6D9FF', // => selection box color - fps: 20 // => frames-per-second - }, - - callbacks: { - 'flotr:mouseup' : function (event) { - - var - options = this.options.selection, - selection = this.selection, - pointer = this.getEventPosition(event); - - if (!options || !options.mode) return; - if (selection.interval) clearInterval(selection.interval); - - if (this.multitouches) { - selection.updateSelection(); - } else - if (!options.pinchOnly) { - selection.setSelectionPos(selection.selection.second, pointer); - } - selection.clearSelection(); - - if(selection.selecting && selection.selectionIsSane()){ - selection.drawSelection(); - selection.fireSelectEvent(); - this.ignoreClick = true; - } - }, - 'flotr:mousedown' : function (event) { - - var - options = this.options.selection, - selection = this.selection, - pointer = this.getEventPosition(event); - - if (!options || !options.mode) return; - if (!options.mode || (!isLeftClick(event) && _.isUndefined(event.touches))) return; - if (!options.pinchOnly) selection.setSelectionPos(selection.selection.first, pointer); - if (selection.interval) clearInterval(selection.interval); - - this.lastMousePos.pageX = null; - selection.selecting = false; - selection.interval = setInterval( - _.bind(selection.updateSelection, this), - 1000 / options.fps - ); - }, - 'flotr:destroy' : function (event) { - clearInterval(this.selection.interval); - } - }, - - // TODO This isn't used. Maybe it belongs in the draw area and fire select event methods? - getArea: function() { - - var s = this.selection.selection, - first = s.first, - second = s.second; - - return { - x1: Math.min(first.x, second.x), - x2: Math.max(first.x, second.x), - y1: Math.min(first.y, second.y), - y2: Math.max(first.y, second.y) - }; - }, - - selection: {first: {x: -1, y: -1}, second: {x: -1, y: -1}}, - prevSelection: null, - interval: null, - - /** - * Fires the 'flotr:select' event when the user made a selection. - */ - fireSelectEvent: function(name){ - var a = this.axes, - s = this.selection.selection, - x1, x2, y1, y2; - - name = name || 'select'; - - x1 = a.x.p2d(s.first.x); - x2 = a.x.p2d(s.second.x); - y1 = a.y.p2d(s.first.y); - y2 = a.y.p2d(s.second.y); - - E.fire(this.el, 'flotr:'+name, [{ - x1:Math.min(x1, x2), - y1:Math.min(y1, y2), - x2:Math.max(x1, x2), - y2:Math.max(y1, y2), - xfirst:x1, xsecond:x2, yfirst:y1, ysecond:y2 - }, this]); - }, - - /** - * Allows the user the manually select an area. - * @param {Object} area - Object with coordinates to select. - */ - setSelection: function(area, preventEvent){ - var options = this.options, - xa = this.axes.x, - ya = this.axes.y, - vertScale = ya.scale, - hozScale = xa.scale, - selX = options.selection.mode.indexOf('x') != -1, - selY = options.selection.mode.indexOf('y') != -1, - s = this.selection.selection; - - this.selection.clearSelection(); - - s.first.y = boundY((selX && !selY) ? 0 : (ya.max - area.y1) * vertScale, this); - s.second.y = boundY((selX && !selY) ? this.plotHeight - 1: (ya.max - area.y2) * vertScale, this); - s.first.x = boundX((selY && !selX) ? 0 : area.x1, this); - s.second.x = boundX((selY && !selX) ? this.plotWidth : area.x2, this); - - this.selection.drawSelection(); - if (!preventEvent) - this.selection.fireSelectEvent(); - }, - - /** - * Calculates the position of the selection. - * @param {Object} pos - Position object. - * @param {Event} event - Event object. - */ - setSelectionPos: function(pos, pointer) { - var mode = this.options.selection.mode, - selection = this.selection.selection; - - if(mode.indexOf('x') == -1) { - pos.x = (pos == selection.first) ? 0 : this.plotWidth; - }else{ - pos.x = boundX(pointer.relX, this); - } - - if (mode.indexOf('y') == -1) { - pos.y = (pos == selection.first) ? 0 : this.plotHeight - 1; - }else{ - pos.y = boundY(pointer.relY, this); - } - }, - /** - * Draws the selection box. - */ - drawSelection: function() { - - this.selection.fireSelectEvent('selecting'); - - var s = this.selection.selection, - octx = this.octx, - options = this.options, - plotOffset = this.plotOffset, - prevSelection = this.selection.prevSelection; - - if (prevSelection && - s.first.x == prevSelection.first.x && - s.first.y == prevSelection.first.y && - s.second.x == prevSelection.second.x && - s.second.y == prevSelection.second.y) { - return; - } - - octx.save(); - octx.strokeStyle = this.processColor(options.selection.color, {opacity: 0.8}); - octx.lineWidth = 1; - octx.lineJoin = 'miter'; - octx.fillStyle = this.processColor(options.selection.color, {opacity: 0.4}); - - this.selection.prevSelection = { - first: { x: s.first.x, y: s.first.y }, - second: { x: s.second.x, y: s.second.y } - }; - - var x = Math.min(s.first.x, s.second.x), - y = Math.min(s.first.y, s.second.y), - w = Math.abs(s.second.x - s.first.x), - h = Math.abs(s.second.y - s.first.y); - - octx.fillRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); - octx.strokeRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); - octx.restore(); - }, - - /** - * Updates (draws) the selection box. - */ - updateSelection: function(){ - if (!this.lastMousePos.pageX) return; - - this.selection.selecting = true; - - if (this.multitouches) { - this.selection.setSelectionPos(this.selection.selection.first, this.getEventPosition(this.multitouches[0])); - this.selection.setSelectionPos(this.selection.selection.second, this.getEventPosition(this.multitouches[1])); - } else - if (this.options.selection.pinchOnly) { - return; - } else { - this.selection.setSelectionPos(this.selection.selection.second, this.lastMousePos); - } - - this.selection.clearSelection(); - - if(this.selection.selectionIsSane()) { - this.selection.drawSelection(); - } - }, - - /** - * Removes the selection box from the overlay canvas. - */ - clearSelection: function() { - if (!this.selection.prevSelection) return; - - var prevSelection = this.selection.prevSelection, - lw = 1, - plotOffset = this.plotOffset, - x = Math.min(prevSelection.first.x, prevSelection.second.x), - y = Math.min(prevSelection.first.y, prevSelection.second.y), - w = Math.abs(prevSelection.second.x - prevSelection.first.x), - h = Math.abs(prevSelection.second.y - prevSelection.first.y); - - this.octx.clearRect(x + plotOffset.left - lw + 0.5, - y + plotOffset.top - lw, - w + 2 * lw + 0.5, - h + 2 * lw + 0.5); - - this.selection.prevSelection = null; - }, - /** - * Determines whether or not the selection is sane and should be drawn. - * @return {Boolean} - True when sane, false otherwise. - */ - selectionIsSane: function(){ - var s = this.selection.selection; - return Math.abs(s.second.x - s.first.x) >= 5 || - Math.abs(s.second.y - s.first.y) >= 5; - } - -}); - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/spreadsheet.js b/addons/web_graph/static/lib/flotr2/js/plugins/spreadsheet.js deleted file mode 100644 index d75cb1991b1..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/spreadsheet.js +++ /dev/null @@ -1,296 +0,0 @@ -/** Spreadsheet **/ -(function() { - -function getRowLabel(value){ - if (this.options.spreadsheet.tickFormatter){ - //TODO maybe pass the xaxis formatter to the custom tick formatter as an opt-out? - return this.options.spreadsheet.tickFormatter(value); - } - else { - var t = _.find(this.axes.x.ticks, function(t){return t.v == value;}); - if (t) { - return t.label; - } - return value; - } -} - -var - D = Flotr.DOM, - _ = Flotr._; - -Flotr.addPlugin('spreadsheet', { - options: { - show: false, // => show the data grid using two tabs - tabGraphLabel: 'Graph', - tabDataLabel: 'Data', - toolbarDownload: 'Download CSV', // @todo: add better language support - toolbarSelectAll: 'Select all', - csvFileSeparator: ',', - decimalSeparator: '.', - tickFormatter: null, - initialTab: 'graph' - }, - /** - * Builds the tabs in the DOM - */ - callbacks: { - 'flotr:afterconstruct': function(){ - // @TODO necessary? - //this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove'); - - if (!this.options.spreadsheet.show) return; - - var ss = this.spreadsheet, - container = D.node('
    '), - graph = D.node('
    '+this.options.spreadsheet.tabGraphLabel+'
    '), - data = D.node('
    '+this.options.spreadsheet.tabDataLabel+'
    '), - offset; - - ss.tabsContainer = container; - ss.tabs = { graph : graph, data : data }; - - D.insert(container, graph); - D.insert(container, data); - D.insert(this.el, container); - - offset = D.size(data).height + 2; - this.plotOffset.bottom += offset; - - D.setStyles(container, {top: this.canvasHeight-offset+'px'}); - - this. - observe(graph, 'click', function(){ss.showTab('graph');}). - observe(data, 'click', function(){ss.showTab('data');}); - if (this.options.spreadsheet.initialTab !== 'graph'){ - ss.showTab(this.options.spreadsheet.initialTab); - } - } - }, - /** - * Builds a matrix of the data to make the correspondance between the x values and the y values : - * X value => Y values from the axes - * @return {Array} The data grid - */ - loadDataGrid: function(){ - if (this.seriesData) return this.seriesData; - - var s = this.series, - rows = {}; - - /* The data grid is a 2 dimensions array. There is a row for each X value. - * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one) - **/ - _.each(s, function(serie, i){ - _.each(serie.data, function (v) { - var x = v[0], - y = v[1], - r = rows[x]; - if (r) { - r[i+1] = y; - } else { - var newRow = []; - newRow[0] = x; - newRow[i+1] = y; - rows[x] = newRow; - } - }); - }); - - // The data grid is sorted by x value - this.seriesData = _.sortBy(rows, function(row, x){ - return parseInt(x, 10); - }); - return this.seriesData; - }, - /** - * Constructs the data table for the spreadsheet - * @todo make a spreadsheet manager (Flotr.Spreadsheet) - * @return {Element} The resulting table element - */ - constructDataGrid: function(){ - // If the data grid has already been built, nothing to do here - if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid; - - var s = this.series, - datagrid = this.spreadsheet.loadDataGrid(), - colgroup = [''], - buttonDownload, buttonSelect, t; - - // First row : series' labels - var html = ['']; - html.push(''); - _.each(s, function(serie,i){ - html.push(''); - colgroup.push(''); - }); - html.push(''); - // Data rows - _.each(datagrid, function(row){ - html.push(''); - _.times(s.length+1, function(i){ - var tag = 'td', - value = row[i], - // TODO: do we really want to handle problems with floating point - // precision here? - content = (!_.isUndefined(value) ? Math.round(value*100000)/100000 : ''); - if (i === 0) { - tag = 'th'; - var label = getRowLabel.call(this, content); - if (label) content = label; - } - - html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+''); - }, this); - html.push(''); - }, this); - colgroup.push(''); - t = D.node(html.join('')); - - /** - * @TODO disabled this - if (!Flotr.isIE || Flotr.isIE == 9) { - function handleMouseout(){ - t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover'); - } - function handleMouseover(e){ - var td = e.element(), - siblings = td.previousSiblings(); - t.select('th[scope=col]')[siblings.length-1].addClassName('hover'); - t.select('colgroup col')[siblings.length].addClassName('hover'); - } - _.each(t.select('td'), function(td) { - Flotr.EventAdapter. - observe(td, 'mouseover', handleMouseover). - observe(td, 'mouseout', handleMouseout); - }); - } - */ - - buttonDownload = D.node( - ''); - - buttonSelect = D.node( - ''); - - this. - observe(buttonDownload, 'click', _.bind(this.spreadsheet.downloadCSV, this)). - observe(buttonSelect, 'click', _.bind(this.spreadsheet.selectAllData, this)); - - var toolbar = D.node('
    '); - D.insert(toolbar, buttonDownload); - D.insert(toolbar, buttonSelect); - - var containerHeight =this.canvasHeight - D.size(this.spreadsheet.tabsContainer).height-2, - container = D.node('
    '); - - D.insert(container, toolbar); - D.insert(container, t); - D.insert(this.el, container); - this.spreadsheet.datagrid = t; - this.spreadsheet.container = container; - - return t; - }, - /** - * Shows the specified tab, by its name - * @todo make a tab manager (Flotr.Tabs) - * @param {String} tabName - The tab name - */ - showTab: function(tabName){ - if (this.spreadsheet.activeTab === tabName){ - return; - } - switch(tabName) { - case 'graph': - D.hide(this.spreadsheet.container); - D.removeClass(this.spreadsheet.tabs.data, 'selected'); - D.addClass(this.spreadsheet.tabs.graph, 'selected'); - break; - case 'data': - if (!this.spreadsheet.datagrid) - this.spreadsheet.constructDataGrid(); - D.show(this.spreadsheet.container); - D.addClass(this.spreadsheet.tabs.data, 'selected'); - D.removeClass(this.spreadsheet.tabs.graph, 'selected'); - break; - default: - throw 'Illegal tab name: ' + tabName; - } - this.spreadsheet.activeTab = tabName; - }, - /** - * Selects the data table in the DOM for copy/paste - */ - selectAllData: function(){ - if (this.spreadsheet.tabs) { - var selection, range, doc, win, node = this.spreadsheet.constructDataGrid(); - - this.spreadsheet.showTab('data'); - - // deferred to be able to select the table - setTimeout(function () { - if ((doc = node.ownerDocument) && (win = doc.defaultView) && - win.getSelection && doc.createRange && - (selection = window.getSelection()) && - selection.removeAllRanges) { - range = doc.createRange(); - range.selectNode(node); - selection.removeAllRanges(); - selection.addRange(range); - } - else if (document.body && document.body.createTextRange && - (range = document.body.createTextRange())) { - range.moveToElementText(node); - range.select(); - } - }, 0); - return true; - } - else return false; - }, - /** - * Converts the data into CSV in order to download a file - */ - downloadCSV: function(){ - var csv = '', - series = this.series, - options = this.options, - dg = this.spreadsheet.loadDataGrid(), - separator = encodeURIComponent(options.spreadsheet.csvFileSeparator); - - if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) { - throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")"; - } - - // The first row - _.each(series, function(serie, i){ - csv += separator+'"'+(serie.label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"'; - }); - - csv += "%0D%0A"; // \r\n - - // For each row - csv += _.reduce(dg, function(memo, row){ - var rowLabel = getRowLabel.call(this, row[0]) || ''; - rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"'; - var numbers = row.slice(1).join(separator); - if (options.spreadsheet.decimalSeparator !== '.') { - numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator); - } - return memo + rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n - }, '', this); - - if (Flotr.isIE && Flotr.isIE < 9) { - csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r'); - window.open().document.write(csv); - } - else window.open('data:text/csv,'+csv); - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/titles.js b/addons/web_graph/static/lib/flotr2/js/plugins/titles.js deleted file mode 100644 index cf95d9e7c80..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/titles.js +++ /dev/null @@ -1,177 +0,0 @@ -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('titles', { - callbacks: { - 'flotr:afterdraw': function() { - this.titles.drawTitles(); - } - }, - /** - * Draws the title and the subtitle - */ - drawTitles : function () { - var html, - options = this.options, - margin = options.grid.labelMargin, - ctx = this.ctx, - a = this.axes; - - if (!options.HtmlText && this.textEnabled) { - var style = { - size: options.fontSize, - color: options.grid.color, - textAlign: 'center' - }; - - // Add subtitle - if (options.subtitle){ - Flotr.drawText( - ctx, options.subtitle, - this.plotOffset.left + this.plotWidth/2, - this.titleHeight + this.subtitleHeight - 2, - style - ); - } - - style.weight = 1.5; - style.size *= 1.5; - - // Add title - if (options.title){ - Flotr.drawText( - ctx, options.title, - this.plotOffset.left + this.plotWidth/2, - this.titleHeight - 2, - style - ); - } - - style.weight = 1.8; - style.size *= 0.8; - - // Add x axis title - if (a.x.options.title && a.x.used){ - style.textAlign = a.x.options.titleAlign || 'center'; - style.textBaseline = 'top'; - style.angle = Flotr.toRad(a.x.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.x.options.title, - this.plotOffset.left + this.plotWidth/2, - this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin, - style - ); - } - - // Add x2 axis title - if (a.x2.options.title && a.x2.used){ - style.textAlign = a.x2.options.titleAlign || 'center'; - style.textBaseline = 'bottom'; - style.angle = Flotr.toRad(a.x2.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.x2.options.title, - this.plotOffset.left + this.plotWidth/2, - this.plotOffset.top - a.x2.maxLabel.height - 2 * margin, - style - ); - } - - // Add y axis title - if (a.y.options.title && a.y.used){ - style.textAlign = a.y.options.titleAlign || 'right'; - style.textBaseline = 'middle'; - style.angle = Flotr.toRad(a.y.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.y.options.title, - this.plotOffset.left - a.y.maxLabel.width - 2 * margin, - this.plotOffset.top + this.plotHeight / 2, - style - ); - } - - // Add y2 axis title - if (a.y2.options.title && a.y2.used){ - style.textAlign = a.y2.options.titleAlign || 'left'; - style.textBaseline = 'middle'; - style.angle = Flotr.toRad(a.y2.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.y2.options.title, - this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin, - this.plotOffset.top + this.plotHeight / 2, - style - ); - } - } - else { - html = []; - - // Add title - if (options.title) - html.push( - '
    ', options.title, '
    ' - ); - - // Add subtitle - if (options.subtitle) - html.push( - '
    ', options.subtitle, '
    ' - ); - - html.push(''); - - html.push('
    '); - - // Add x axis title - if (a.x.options.title && a.x.used) - html.push( - '
    ', a.x.options.title, '
    ' - ); - - // Add x2 axis title - if (a.x2.options.title && a.x2.used) - html.push( - '
    ', a.x2.options.title, '
    ' - ); - - // Add y axis title - if (a.y.options.title && a.y.used) - html.push( - '
    ', a.y.options.title, '
    ' - ); - - // Add y2 axis title - if (a.y2.options.title && a.y2.used) - html.push( - '
    ', a.y2.options.title, '
    ' - ); - - html = html.join(''); - - var div = D.create('div'); - D.setStyles({ - color: options.grid.color - }); - div.className = 'flotr-titles'; - D.insert(this.el, div); - D.insert(div, html); - } - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/types/bars.js b/addons/web_graph/static/lib/flotr2/js/types/bars.js deleted file mode 100644 index 03e7988b785..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/bars.js +++ /dev/null @@ -1,274 +0,0 @@ -/** Bars **/ -Flotr.addType('bars', { - - options: { - show: false, // => setting to true will show bars, false will hide - lineWidth: 2, // => in pixels - barWidth: 1, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - horizontal: false, // => horizontal bars (x and y inverted) - stacked: false, // => stacked bar charts - centered: true, // => center the bars to their x axis value - topPadding: 0.1 // => top padding in percent - }, - - stack : { - positive : [], - negative : [], - _positive : [], // Shadow - _negative : [] // Shadow - }, - - draw : function (options) { - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - // @TODO linewidth not interpreted the right way. - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - if (options.fill) context.fillStyle = options.fillStyle; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - shadowSize = options.shadowSize, - i, geometry, left, top, width, height; - - if (data.length < 1) return; - - this.translate(context, options.horizontal); - - for (i = 0; i < data.length; i++) { - - geometry = this.getBarGeometry(data[i][0], data[i][1], options); - if (geometry === null) continue; - - left = geometry.left; - top = geometry.top; - width = geometry.width; - height = geometry.height; - - if (options.fill) context.fillRect(left, top, width, height); - if (shadowSize) { - context.save(); - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.fillRect(left + shadowSize, top + shadowSize, width, height); - context.restore(); - } - if (options.lineWidth) { - context.strokeRect(left, top, width, height); - } - } - }, - - translate : function (context, horizontal) { - if (horizontal) { - context.rotate(-Math.PI / 2); - context.scale(-1, 1); - } - }, - - getBarGeometry : function (x, y, options) { - - var - horizontal = options.horizontal, - barWidth = options.barWidth, - centered = options.centered, - stack = options.stacked ? this.stack : false, - lineWidth = options.lineWidth, - bisection = centered ? barWidth / 2 : 0, - xScale = horizontal ? options.yScale : options.xScale, - yScale = horizontal ? options.xScale : options.yScale, - xValue = horizontal ? y : x, - yValue = horizontal ? x : y, - stackOffset = 0, - stackValue, left, right, top, bottom; - - // Stacked bars - if (stack) { - stackValue = yValue > 0 ? stack.positive : stack.negative; - stackOffset = stackValue[xValue] || stackOffset; - stackValue[xValue] = stackOffset + yValue; - } - - left = xScale(xValue - bisection); - right = xScale(xValue + barWidth - bisection); - top = yScale(yValue + stackOffset); - bottom = yScale(stackOffset); - - // TODO for test passing... probably looks better without this - if (bottom < 0) bottom = 0; - - // TODO Skipping... - // if (right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; - - return (x === null || y === null) ? null : { - x : xValue, - y : yValue, - xScale : xScale, - yScale : yScale, - top : top, - left : Math.min(left, right) - lineWidth / 2, - width : Math.abs(right - left) - lineWidth, - height : bottom - top - }; - }, - - hit : function (options) { - var - data = options.data, - args = options.args, - mouse = args[0], - n = args[1], - x = mouse.x, - y = mouse.y, - hitGeometry = this.getBarGeometry(x, y, options), - width = hitGeometry.width / 2, - left = hitGeometry.left, - geometry, i; - - for (i = data.length; i--;) { - geometry = this.getBarGeometry(data[i][0], data[i][1], options); - if (geometry.y > hitGeometry.y && Math.abs(left - geometry.left) < width) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - } - }, - - drawHit : function (options) { - // TODO hits for stacked bars; implement using calculateStack option? - var - context = options.context, - args = options.args, - geometry = this.getBarGeometry(args.x, args.y, options), - left = geometry.left, - top = geometry.top, - width = geometry.width, - height = geometry.height; - - context.save(); - context.strokeStyle = options.color; - context.lineWidth = options.lineWidth; - this.translate(context, options.horizontal); - - // Draw highlight - context.beginPath(); - context.moveTo(left, top + height); - context.lineTo(left, top); - context.lineTo(left + width, top); - context.lineTo(left + width, top + height); - if (options.fill) { - context.fillStyle = options.fillStyle; - context.fill(); - } - context.stroke(); - context.closePath(); - - context.restore(); - }, - - clearHit: function (options) { - var - context = options.context, - args = options.args, - geometry = this.getBarGeometry(args.x, args.y, options), - left = geometry.left, - width = geometry.width, - top = geometry.top, - height = geometry.height, - lineWidth = 2 * options.lineWidth; - - context.save(); - this.translate(context, options.horizontal); - context.clearRect( - left - lineWidth, - Math.min(top, top + height) - lineWidth, - width + 2 * lineWidth, - Math.abs(height) + 2 * lineWidth - ); - context.restore(); - }, - - extendXRange : function (axis, data, options, bars) { - this._extendRange(axis, data, options, bars); - }, - - extendYRange : function (axis, data, options, bars) { - this._extendRange(axis, data, options, bars); - }, - _extendRange: function (axis, data, options, bars) { - - var - max = axis.options.max; - - if (_.isNumber(max) || _.isString(max)) return; - - var - newmin = axis.min, - newmax = axis.max, - horizontal = options.horizontal, - orientation = axis.orientation, - positiveSums = this.positiveSums || {}, - negativeSums = this.negativeSums || {}, - value, datum, index, j; - - // Sides of bars - if ((orientation == 1 && !horizontal) || (orientation == -1 && horizontal)) { - if (options.centered) { - newmax = Math.max(axis.datamax + options.barWidth, newmax); - newmin = Math.min(axis.datamin - options.barWidth, newmin); - } - } - - if (options.stacked && - ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal))){ - - for (j = data.length; j--;) { - value = data[j][(orientation == 1 ? 1 : 0)]+''; - datum = data[j][(orientation == 1 ? 0 : 1)]; - - // Positive - if (datum > 0) { - positiveSums[value] = (positiveSums[value] || 0) + datum; - newmax = Math.max(newmax, positiveSums[value]); - } - - // Negative - else { - negativeSums[value] = (negativeSums[value] || 0) + datum; - newmin = Math.min(newmin, negativeSums[value]); - } - } - } - - // End of bars - if ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal)) { - if (options.topPadding && (axis.max === axis.datamax || (options.stacked && this.stackMax !== newmax))) { - newmax += options.topPadding * (newmax - newmin); - } - } - - this.stackMin = newmin; - this.stackMax = newmax; - this.negativeSums = negativeSums; - this.positiveSums = positiveSums; - - axis.max = newmax; - axis.min = newmin; - } - -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/bubbles.js b/addons/web_graph/static/lib/flotr2/js/types/bubbles.js deleted file mode 100644 index a97c5a28a93..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/bubbles.js +++ /dev/null @@ -1,119 +0,0 @@ -/** Bubbles **/ -Flotr.addType('bubbles', { - options: { - show: false, // => setting to true will show radar chart, false will hide - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - baseRadius: 2 // => ratio of the radar, against the plot size - }, - draw : function (options) { - var - context = options.context, - shadowSize = options.shadowSize; - - context.save(); - context.lineWidth = options.lineWidth; - - // Shadows - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.strokeStyle = 'rgba(0,0,0,0.05)'; - this.plot(options, shadowSize / 2); - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 4); - - // Chart - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - this.plot(options); - - context.restore(); - }, - plot : function (options, offset) { - - var - data = options.data, - context = options.context, - geometry, - i, x, y, z; - - offset = offset || 0; - - for (i = 0; i < data.length; ++i){ - - geometry = this.getGeometry(data[i], options); - - context.beginPath(); - context.arc(geometry.x + offset, geometry.y + offset, geometry.z, 0, 2 * Math.PI, true); - context.stroke(); - if (options.fill) context.fill(); - context.closePath(); - } - }, - getGeometry : function (point, options) { - return { - x : options.xScale(point[0]), - y : options.yScale(point[1]), - z : point[2] * options.baseRadius - }; - }, - hit : function (options) { - var - data = options.data, - args = options.args, - mouse = args[0], - n = args[1], - x = mouse.x, - y = mouse.y, - geometry, - dx, dy; - - for (i = data.length; i--;) { - geometry = this.getGeometry(data[i], options); - - dx = geometry.x - options.xScale(x); - dy = geometry.y - options.yScale(y); - - if (Math.sqrt(dx * dx + dy * dy) < geometry.z) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - } - }, - drawHit : function (options) { - - var - context = options.context, - geometry = this.getGeometry(options.data[options.args.index], options); - - context.save(); - context.lineWidth = options.lineWidth; - context.fillStyle = options.fillStyle; - context.strokeStyle = options.color; - context.beginPath(); - context.arc(geometry.x, geometry.y, geometry.z, 0, 2 * Math.PI, true); - context.fill(); - context.stroke(); - context.closePath(); - context.restore(); - }, - clearHit : function (options) { - - var - context = options.context, - geometry = this.getGeometry(options.data[options.args.index], options), - offset = geometry.z + options.lineWidth; - - context.save(); - context.clearRect( - geometry.x - offset, - geometry.y - offset, - 2 * offset, - 2 * offset - ); - context.restore(); - } - // TODO Add a hit calculation method (like pie) -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/candles.js b/addons/web_graph/static/lib/flotr2/js/types/candles.js deleted file mode 100644 index 0e484b366cc..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/candles.js +++ /dev/null @@ -1,127 +0,0 @@ -/** Candles **/ -Flotr.addType('candles', { - options: { - show: false, // => setting to true will show candle sticks, false will hide - lineWidth: 1, // => in pixels - wickLineWidth: 1, // => in pixels - candleWidth: 0.6, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - upFillColor: '#00A8F0',// => up sticks fill color - downFillColor: '#CB4B4B',// => down sticks fill color - fillOpacity: 0.5, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - // TODO Test this barcharts option. - barcharts: false // => draw as barcharts (not standard bars but financial barcharts) - }, - - draw : function (options) { - - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - context.lineCap = 'butt'; - // @TODO linewidth not interpreted the right way. - context.lineWidth = options.wickLineWidth || options.lineWidth; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - width = options.candleWidth / 2, - shadowSize = options.shadowSize, - lineWidth = options.lineWidth, - wickLineWidth = options.wickLineWidth, - pixelOffset = (wickLineWidth % 2) / 2, - color, - datum, x, y, - open, high, low, close, - left, right, bottom, top, bottom2, top2, - i; - - if (data.length < 1) return; - - for (i = 0; i < data.length; i++) { - datum = data[i]; - x = datum[0]; - open = datum[1]; - high = datum[2]; - low = datum[3]; - close = datum[4]; - left = xScale(x - width); - right = xScale(x + width); - bottom = yScale(low); - top = yScale(high); - bottom2 = yScale(Math.min(open, close)); - top2 = yScale(Math.max(open, close)); - - /* - // TODO skipping - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - */ - - color = options[open > close ? 'downFillColor' : 'upFillColor']; - - // Fill the candle. - // TODO Test the barcharts option - if (options.fill && !options.barcharts) { - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.fillRect(left + shadowSize, top2 + shadowSize, right - left, bottom2 - top2); - context.save(); - context.globalAlpha = options.fillOpacity; - context.fillStyle = color; - context.fillRect(left, top2 + lineWidth, right - left, bottom2 - top2); - context.restore(); - } - - // Draw candle outline/border, high, low. - if (lineWidth || wickLineWidth) { - - x = Math.floor((left + right) / 2) + pixelOffset; - - context.strokeStyle = color; - context.beginPath(); - - // TODO Again with the bartcharts - if (options.barcharts) { - - context.moveTo(x, Math.floor(top + width)); - context.lineTo(x, Math.floor(bottom + width)); - - y = Math.floor(open + width) + 0.5; - context.moveTo(Math.floor(left) + pixelOffset, y); - context.lineTo(x, y); - - y = Math.floor(close + width) + 0.5; - context.moveTo(Math.floor(right) + pixelOffset, y); - context.lineTo(x, y); - } else { - context.strokeRect(left, top2 + lineWidth, right - left, bottom2 - top2); - - context.moveTo(x, Math.floor(top2 + lineWidth)); - context.lineTo(x, Math.floor(top + lineWidth)); - context.moveTo(x, Math.floor(bottom2 + lineWidth)); - context.lineTo(x, Math.floor(bottom + lineWidth)); - } - - context.closePath(); - context.stroke(); - } - } - }, - extendXRange: function (axis, data, options) { - if (axis.options.max === null) { - axis.max = Math.max(axis.datamax + 0.5, axis.max); - axis.min = Math.min(axis.datamin - 0.5, axis.min); - } - } -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/gantt.js b/addons/web_graph/static/lib/flotr2/js/types/gantt.js deleted file mode 100644 index 1498bcb51cf..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/gantt.js +++ /dev/null @@ -1,229 +0,0 @@ -/** Gantt - * Base on data in form [s,y,d] where: - * y - executor or simply y value - * s - task start value - * d - task duration - * **/ -Flotr.addType('gantt', { - options: { - show: false, // => setting to true will show gantt, false will hide - lineWidth: 2, // => in pixels - barWidth: 1, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - centered: true // => center the bars to their x axis value - }, - /** - * Draws gantt series in the canvas element. - * @param {Object} series - Series with options.gantt.show = true. - */ - draw: function(series) { - var ctx = this.ctx, - bw = series.gantt.barWidth, - lw = Math.min(series.gantt.lineWidth, bw); - - ctx.save(); - ctx.translate(this.plotOffset.left, this.plotOffset.top); - ctx.lineJoin = 'miter'; - - /** - * @todo linewidth not interpreted the right way. - */ - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - - ctx.save(); - this.gantt.plotShadows(series, bw, 0, series.gantt.fill); - ctx.restore(); - - if(series.gantt.fill){ - var color = series.gantt.fillColor || series.color; - ctx.fillStyle = this.processColor(color, {opacity: series.gantt.fillOpacity}); - } - - this.gantt.plot(series, bw, 0, series.gantt.fill); - ctx.restore(); - }, - plot: function(series, barWidth, offset, fill){ - var data = series.data; - if(data.length < 1) return; - - var xa = series.xaxis, - ya = series.yaxis, - ctx = this.ctx, i; - - for(i = 0; i < data.length; i++){ - var y = data[i][0], - s = data[i][1], - d = data[i][2], - drawLeft = true, drawTop = true, drawRight = true; - - if (s === null || d === null) continue; - - var left = s, - right = s + d, - bottom = y - (series.gantt.centered ? barWidth/2 : 0), - top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0); - - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - - if(left < xa.min){ - left = xa.min; - drawLeft = false; - } - - if(right > xa.max){ - right = xa.max; - if (xa.lastSerie != series) - drawTop = false; - } - - if(bottom < ya.min) - bottom = ya.min; - - if(top > ya.max){ - top = ya.max; - if (ya.lastSerie != series) - drawTop = false; - } - - /** - * Fill the bar. - */ - if(fill){ - ctx.beginPath(); - ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset); - ctx.lineTo(xa.d2p(left), ya.d2p(top) + offset); - ctx.lineTo(xa.d2p(right), ya.d2p(top) + offset); - ctx.lineTo(xa.d2p(right), ya.d2p(bottom) + offset); - ctx.fill(); - ctx.closePath(); - } - - /** - * Draw bar outline/border. - */ - if(series.gantt.lineWidth && (drawLeft || drawRight || drawTop)){ - ctx.beginPath(); - ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset); - - ctx[drawLeft ?'lineTo':'moveTo'](xa.d2p(left), ya.d2p(top) + offset); - ctx[drawTop ?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(top) + offset); - ctx[drawRight?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(bottom) + offset); - - ctx.stroke(); - ctx.closePath(); - } - } - }, - plotShadows: function(series, barWidth, offset){ - var data = series.data; - if(data.length < 1) return; - - var i, y, s, d, - xa = series.xaxis, - ya = series.yaxis, - ctx = this.ctx, - sw = this.options.shadowSize; - - for(i = 0; i < data.length; i++){ - y = data[i][0]; - s = data[i][1]; - d = data[i][2]; - - if (s === null || d === null) continue; - - var left = s, - right = s + d, - bottom = y - (series.gantt.centered ? barWidth/2 : 0), - top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0); - - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - - if(left < xa.min) left = xa.min; - if(right > xa.max) right = xa.max; - if(bottom < ya.min) bottom = ya.min; - if(top > ya.max) top = ya.max; - - var width = xa.d2p(right)-xa.d2p(left)-((xa.d2p(right)+sw <= this.plotWidth) ? 0 : sw); - var height = ya.d2p(bottom)-ya.d2p(top)-((ya.d2p(bottom)+sw <= this.plotHeight) ? 0 : sw ); - - ctx.fillStyle = 'rgba(0,0,0,0.05)'; - ctx.fillRect(Math.min(xa.d2p(left)+sw, this.plotWidth), Math.min(ya.d2p(top)+sw, this.plotHeight), width, height); - } - }, - extendXRange: function(axis) { - if(axis.options.max === null){ - var newmin = axis.min, - newmax = axis.max, - i, j, x, s, g, - stackedSumsPos = {}, - stackedSumsNeg = {}, - lastSerie = null; - - for(i = 0; i < this.series.length; ++i){ - s = this.series[i]; - g = s.gantt; - - if(g.show && s.xaxis == axis) { - for (j = 0; j < s.data.length; j++) { - if (g.show) { - y = s.data[j][0]+''; - stackedSumsPos[y] = Math.max((stackedSumsPos[y] || 0), s.data[j][1]+s.data[j][2]); - lastSerie = s; - } - } - for (j in stackedSumsPos) { - newmax = Math.max(stackedSumsPos[j], newmax); - } - } - } - axis.lastSerie = lastSerie; - axis.max = newmax; - axis.min = newmin; - } - }, - extendYRange: function(axis){ - if(axis.options.max === null){ - var newmax = Number.MIN_VALUE, - newmin = Number.MAX_VALUE, - i, j, s, g, - stackedSumsPos = {}, - stackedSumsNeg = {}, - lastSerie = null; - - for(i = 0; i < this.series.length; ++i){ - s = this.series[i]; - g = s.gantt; - - if (g.show && !s.hide && s.yaxis == axis) { - var datamax = Number.MIN_VALUE, datamin = Number.MAX_VALUE; - for(j=0; j < s.data.length; j++){ - datamax = Math.max(datamax,s.data[j][0]); - datamin = Math.min(datamin,s.data[j][0]); - } - - if (g.centered) { - newmax = Math.max(datamax + 0.5, newmax); - newmin = Math.min(datamin - 0.5, newmin); - } - else { - newmax = Math.max(datamax + 1, newmax); - newmin = Math.min(datamin, newmin); - } - // For normal horizontal bars - if (g.barWidth + datamax > newmax){ - newmax = axis.max + g.barWidth; - } - } - } - axis.lastSerie = lastSerie; - axis.max = newmax; - axis.min = newmin; - axis.tickSize = Flotr.getTickSize(axis.options.noTicks, newmin, newmax, axis.options.tickDecimals); - } - } -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/lines.js b/addons/web_graph/static/lib/flotr2/js/types/lines.js deleted file mode 100644 index d65971b2dfe..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/lines.js +++ /dev/null @@ -1,275 +0,0 @@ -/** Lines **/ -Flotr.addType('lines', { - options: { - show: false, // => setting to true will show lines, false will hide - lineWidth: 2, // => line width in pixels - fill: false, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillBorder: false, // => draw a border around the fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - steps: false, // => draw steps - stacked: false // => setting to true will show stacked lines, false will show normal lines - }, - - stack : { - values : [] - }, - - /** - * Draws lines series in the canvas element. - * @param {Object} options - */ - draw : function (options) { - - var - context = options.context, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize, - offset; - - context.save(); - context.lineJoin = 'round'; - - if (shadowSize) { - - context.lineWidth = shadowSize / 2; - offset = lineWidth / 2 + context.lineWidth / 2; - - // @TODO do this instead with a linear gradient - context.strokeStyle = "rgba(0,0,0,0.1)"; - this.plot(options, offset + shadowSize / 2, false); - - context.strokeStyle = "rgba(0,0,0,0.2)"; - this.plot(options, offset, false); - } - - context.lineWidth = lineWidth; - context.strokeStyle = options.color; - - this.plot(options, 0, true); - - context.restore(); - }, - - plot : function (options, shadowOffset, incStack) { - - var - context = options.context, - width = options.width, - height = options.height, - xScale = options.xScale, - yScale = options.yScale, - data = options.data, - stack = options.stacked ? this.stack : false, - length = data.length - 1, - prevx = null, - prevy = null, - zero = yScale(0), - x1, x2, y1, y2, stack1, stack2, i; - - if (length < 1) return; - - context.beginPath(); - - for (i = 0; i < length; ++i) { - - // To allow empty values - if (data[i][1] === null || data[i+1][1] === null) continue; - - // Zero is infinity for log scales - // TODO handle zero for logarithmic - // if (xa.options.scaling === 'logarithmic' && (data[i][0] <= 0 || data[i+1][0] <= 0)) continue; - // if (ya.options.scaling === 'logarithmic' && (data[i][1] <= 0 || data[i+1][1] <= 0)) continue; - - x1 = xScale(data[i][0]); - x2 = xScale(data[i+1][0]); - - if (stack) { - - stack1 = stack.values[data[i][0]] || 0; - stack2 = stack.values[data[i+1][0]] || stack.values[data[i][0]] || 0; - - y1 = yScale(data[i][1] + stack1); - y2 = yScale(data[i+1][1] + stack2); - - if(incStack){ - stack.values[data[i][0]] = data[i][1]+stack1; - - if(i == length-1) - stack.values[data[i+1][0]] = data[i+1][1]+stack2; - } - } - else{ - y1 = yScale(data[i][1]); - y2 = yScale(data[i+1][1]); - } - - if ( - (y1 > height && y2 > height) || - (y1 < 0 && y2 < 0) || - (x1 < 0 && x2 < 0) || - (x1 > width && x2 > width) - ) continue; - - if((prevx != x1) || (prevy != y1 + shadowOffset)) - context.moveTo(x1, y1 + shadowOffset); - - prevx = x2; - prevy = y2 + shadowOffset; - if (options.steps) { - context.lineTo(prevx + shadowOffset / 2, y1 + shadowOffset); - context.lineTo(prevx + shadowOffset / 2, prevy); - } else { - context.lineTo(prevx, prevy); - } - } - - if (!options.fill || options.fill && !options.fillBorder) context.stroke(); - - // TODO stacked lines - if(!shadowOffset && options.fill){ - x1 = xScale(data[0][0]); - context.fillStyle = options.fillStyle; - context.lineTo(x2, zero); - context.lineTo(x1, zero); - context.lineTo(x1, yScale(data[0][1])); - context.fill(); - if (options.fillBorder) { - context.stroke(); - } - } - - context.closePath(); - }, - - // Perform any pre-render precalculations (this should be run on data first) - // - Pie chart total for calculating measures - // - Stacks for lines and bars - // precalculate : function () { - // } - // - // - // Get any bounds after pre calculation (axis can fetch this if does not have explicit min/max) - // getBounds : function () { - // } - // getMin : function () { - // } - // getMax : function () { - // } - // - // - // Padding around rendered elements - // getPadding : function () { - // } - - extendYRange : function (axis, data, options, lines) { - - var o = axis.options; - - // If stacked and auto-min - if (options.stacked && ((!o.max && o.max !== 0) || (!o.min && o.min !== 0))) { - - var - newmax = axis.max, - newmin = axis.min, - positiveSums = lines.positiveSums || {}, - negativeSums = lines.negativeSums || {}, - x, j; - - for (j = 0; j < data.length; j++) { - - x = data[j][0] + ''; - - // Positive - if (data[j][1] > 0) { - positiveSums[x] = (positiveSums[x] || 0) + data[j][1]; - newmax = Math.max(newmax, positiveSums[x]); - } - - // Negative - else { - negativeSums[x] = (negativeSums[x] || 0) + data[j][1]; - newmin = Math.min(newmin, negativeSums[x]); - } - } - - lines.negativeSums = negativeSums; - lines.positiveSums = positiveSums; - - axis.max = newmax; - axis.min = newmin; - } - - if (options.steps) { - - this.hit = function (options) { - var - data = options.data, - args = options.args, - yScale = options.yScale, - mouse = args[0], - length = data.length, - n = args[1], - x = mouse.x, - relY = mouse.relY, - i; - - for (i = 0; i < length - 1; i++) { - if (x >= data[i][0] && x <= data[i+1][0]) { - if (Math.abs(yScale(data[i][1]) - relY) < 8) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - break; - } - } - }; - - this.drawHit = function (options) { - var - context = options.context, - args = options.args, - data = options.data, - xScale = options.xScale, - index = args.index, - x = xScale(args.x), - y = options.yScale(args.y), - x2; - - if (data.length - 1 > index) { - x2 = options.xScale(data[index + 1][0]); - context.save(); - context.strokeStyle = options.color; - context.lineWidth = options.lineWidth; - context.beginPath(); - context.moveTo(x, y); - context.lineTo(x2, y); - context.stroke(); - context.closePath(); - context.restore(); - } - }; - - this.clearHit = function (options) { - var - context = options.context, - args = options.args, - data = options.data, - xScale = options.xScale, - width = options.lineWidth, - index = args.index, - x = xScale(args.x), - y = options.yScale(args.y), - x2; - - if (data.length - 1 > index) { - x2 = options.xScale(data[index + 1][0]); - context.clearRect(x - width, y - width, x2 - x + 2 * width, 2 * width); - } - }; - } - } - -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/markers.js b/addons/web_graph/static/lib/flotr2/js/types/markers.js deleted file mode 100644 index ee1104e3568..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/markers.js +++ /dev/null @@ -1,140 +0,0 @@ -/** Markers **/ -/** - * Formats the marker labels. - * @param {Object} obj - Marker value Object {x:..,y:..} - * @return {String} Formatted marker string - */ -(function () { - -Flotr.defaultMarkerFormatter = function(obj){ - return (Math.round(obj.y*100)/100)+''; -}; - -Flotr.addType('markers', { - options: { - show: false, // => setting to true will show markers, false will hide - lineWidth: 1, // => line width of the rectangle around the marker - color: '#000000', // => text color - fill: false, // => fill or not the marekers' rectangles - fillColor: "#FFFFFF", // => fill color - fillOpacity: 0.4, // => fill opacity - stroke: false, // => draw the rectangle around the markers - position: 'ct', // => the markers position (vertical align: b, m, t, horizontal align: l, c, r) - verticalMargin: 0, // => the margin between the point and the text. - labelFormatter: Flotr.defaultMarkerFormatter, - fontSize: Flotr.defaultOptions.fontSize, - stacked: false, // => true if markers should be stacked - stackingType: 'b', // => define staching behavior, (b- bars like, a - area like) (see Issue 125 for details) - horizontal: false // => true if markers should be horizontal (For now only in a case on horizontal stacked bars, stacks should be calculated horizontaly) - }, - - // TODO test stacked markers. - stack : { - positive : [], - negative : [], - values : [] - }, - - draw : function (options) { - - var - data = options.data, - context = options.context, - stack = options.stacked ? options.stack : false, - stackType = options.stackingType, - stackOffsetNeg, - stackOffsetPos, - stackOffset, - i, x, y, label; - - context.save(); - context.lineJoin = 'round'; - context.lineWidth = options.lineWidth; - context.strokeStyle = 'rgba(0,0,0,0.5)'; - context.fillStyle = options.fillStyle; - - function stackPos (a, b) { - stackOffsetPos = stack.negative[a] || 0; - stackOffsetNeg = stack.positive[a] || 0; - if (b > 0) { - stack.positive[a] = stackOffsetPos + b; - return stackOffsetPos + b; - } else { - stack.negative[a] = stackOffsetNeg + b; - return stackOffsetNeg + b; - } - } - - for (i = 0; i < data.length; ++i) { - - x = data[i][0]; - y = data[i][1]; - - if (stack) { - if (stackType == 'b') { - if (options.horizontal) y = stackPos(y, x); - else x = stackPos(x, y); - } else if (stackType == 'a') { - stackOffset = stack.values[x] || 0; - stack.values[x] = stackOffset + y; - y = stackOffset + y; - } - } - - label = options.labelFormatter({x: x, y: y, index: i, data : data}); - this.plot(options.xScale(x), options.yScale(y), label, options); - } - context.restore(); - }, - plot: function(x, y, label, options) { - var context = options.context; - if (isImage(label) && !label.complete) { - throw 'Marker image not loaded.'; - } else { - this._plot(x, y, label, options); - } - }, - - _plot: function(x, y, label, options) { - var context = options.context, - margin = 2, - left = x, - top = y, - dim; - - if (isImage(label)) - dim = {height : label.height, width: label.width}; - else - dim = options.text.canvas(label); - - dim.width = Math.floor(dim.width+margin*2); - dim.height = Math.floor(dim.height+margin*2); - - if (options.position.indexOf('c') != -1) left -= dim.width/2 + margin; - else if (options.position.indexOf('l') != -1) left -= dim.width; - - if (options.position.indexOf('m') != -1) top -= dim.height/2 + margin; - else if (options.position.indexOf('t') != -1) top -= dim.height + options.verticalMargin; - else top += options.verticalMargin; - - left = Math.floor(left)+0.5; - top = Math.floor(top)+0.5; - - if(options.fill) - context.fillRect(left, top, dim.width, dim.height); - - if(options.stroke) - context.strokeRect(left, top, dim.width, dim.height); - - if (isImage(label)) - context.drawImage(label, left+margin, top+margin); - else - Flotr.drawText(context, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left', size: options.fontSize, color: options.color}); - } -}); - -function isImage (i) { - return typeof i === 'object' && i.constructor && (Image ? true : i.constructor === Image); -} - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/types/pie.js b/addons/web_graph/static/lib/flotr2/js/types/pie.js deleted file mode 100644 index ec932be5e6d..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/pie.js +++ /dev/null @@ -1,210 +0,0 @@ -/** Pie **/ -/** - * Formats the pies labels. - * @param {Object} slice - Slice object - * @return {String} Formatted pie label string - */ -(function () { - -var - _ = Flotr._; - -Flotr.defaultPieLabelFormatter = function (total, value) { - return (100 * value / total).toFixed(2)+'%'; -}; - -Flotr.addType('pie', { - options: { - show: false, // => setting to true will show bars, false will hide - lineWidth: 1, // => in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.6, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - explode: 6, // => the number of pixels the splices will be far from the center - sizeRatio: 0.6, // => the size ratio of the pie relative to the plot - startAngle: Math.PI/4, // => the first slice start angle - labelFormatter: Flotr.defaultPieLabelFormatter, - pie3D: false, // => whether to draw the pie in 3 dimenstions or not (ineffective) - pie3DviewAngle: (Math.PI/2 * 0.8), - pie3DspliceThickness: 20 - }, - - draw : function (options) { - - // TODO 3D charts what? - - var - data = options.data, - context = options.context, - canvas = context.canvas, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize, - sizeRatio = options.sizeRatio, - height = options.height, - width = options.width, - explode = options.explode, - color = options.color, - fill = options.fill, - fillStyle = options.fillStyle, - radius = Math.min(canvas.width, canvas.height) * sizeRatio / 2, - value = data[0][1], - html = [], - vScale = 1,//Math.cos(series.pie.viewAngle); - measure = Math.PI * 2 * value / this.total, - startAngle = this.startAngle || (2 * Math.PI * options.startAngle), // TODO: this initial startAngle is already in radians (fixing will be test-unstable) - endAngle = startAngle + measure, - bisection = startAngle + measure / 2, - label = options.labelFormatter(this.total, value), - //plotTickness = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale; - explodeCoeff = explode + radius + 4, - distX = Math.cos(bisection) * explodeCoeff, - distY = Math.sin(bisection) * explodeCoeff, - textAlign = distX < 0 ? 'right' : 'left', - textBaseline = distY > 0 ? 'top' : 'bottom', - style, - x, y, - distX, distY; - - context.save(); - context.translate(width / 2, height / 2); - context.scale(1, vScale); - - x = Math.cos(bisection) * explode; - y = Math.sin(bisection) * explode; - - // Shadows - if (shadowSize > 0) { - this.plotSlice(x + shadowSize, y + shadowSize, radius, startAngle, endAngle, context); - if (fill) { - context.fillStyle = 'rgba(0,0,0,0.1)'; - context.fill(); - } - } - - this.plotSlice(x, y, radius, startAngle, endAngle, context); - if (fill) { - context.fillStyle = fillStyle; - context.fill(); - } - context.lineWidth = lineWidth; - context.strokeStyle = color; - context.stroke(); - - style = { - size : options.fontSize * 1.2, - color : options.fontColor, - weight : 1.5 - }; - - if (label) { - if (options.htmlText || !options.textEnabled) { - divStyle = 'position:absolute;' + textBaseline + ':' + (height / 2 + (textBaseline === 'top' ? distY : -distY)) + 'px;'; - divStyle += textAlign + ':' + (width / 2 + (textAlign === 'right' ? -distX : distX)) + 'px;'; - html.push('
    ', label, '
    '); - } - else { - style.textAlign = textAlign; - style.textBaseline = textBaseline; - Flotr.drawText(context, label, distX, distY, style); - } - } - - if (options.htmlText || !options.textEnabled) { - var div = Flotr.DOM.node('
    '); - Flotr.DOM.insert(div, html.join('')); - Flotr.DOM.insert(options.element, div); - } - - context.restore(); - - // New start angle - this.startAngle = endAngle; - this.slices = this.slices || []; - this.slices.push({ - radius : Math.min(canvas.width, canvas.height) * sizeRatio / 2, - x : x, - y : y, - explode : explode, - start : startAngle, - end : endAngle - }); - }, - plotSlice : function (x, y, radius, startAngle, endAngle, context) { - context.beginPath(); - context.moveTo(x, y); - context.arc(x, y, radius, startAngle, endAngle, false); - context.lineTo(x, y); - context.closePath(); - }, - hit : function (options) { - - var - data = options.data[0], - args = options.args, - index = options.index, - mouse = args[0], - n = args[1], - slice = this.slices[index], - x = mouse.relX - options.width / 2, - y = mouse.relY - options.height / 2, - r = Math.sqrt(x * x + y * y), - theta = Math.atan(y / x), - circle = Math.PI * 2, - explode = slice.explode || options.explode, - start = slice.start % circle, - end = slice.end % circle; - - if (x < 0) { - theta += Math.PI; - } else if (x > 0 && y < 0) { - theta += circle; - } - - if (r < slice.radius + explode && r > explode) { - if ((start > end && (theta < end || theta > start)) || - (theta > start && theta < end)) { - - // TODO Decouple this from hit plugin (chart shouldn't know what n means) - n.x = data[0]; - n.y = data[1]; - n.sAngle = start; - n.eAngle = end; - n.index = 0; - n.seriesIndex = index; - n.fraction = data[1] / this.total; - } - } - }, - drawHit: function (options) { - var - context = options.context, - slice = this.slices[options.args.seriesIndex]; - - context.save(); - context.translate(options.width / 2, options.height / 2); - this.plotSlice(slice.x, slice.y, slice.radius, slice.start, slice.end, context); - context.stroke(); - context.restore(); - }, - clearHit : function (options) { - var - context = options.context, - slice = this.slices[options.args.seriesIndex], - padding = 2 * options.lineWidth, - radius = slice.radius + padding; - - context.save(); - context.translate(options.width / 2, options.height / 2); - context.clearRect( - slice.x - radius, - slice.y - radius, - 2 * radius + padding, - 2 * radius + padding - ); - context.restore(); - }, - extendYRange : function (axis, data) { - this.total = (this.total || 0) + data[0][1]; - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/types/points.js b/addons/web_graph/static/lib/flotr2/js/types/points.js deleted file mode 100644 index ebb360e6fae..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/points.js +++ /dev/null @@ -1,66 +0,0 @@ -/** Points **/ -Flotr.addType('points', { - options: { - show: false, // => setting to true will show points, false will hide - radius: 3, // => point radius (pixels) - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the points with a color, false for (transparent) no fill - fillColor: '#FFFFFF', // => fill color - fillOpacity: 0.4 // => opacity of color inside the points - }, - - draw : function (options) { - var - context = options.context, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize; - - context.save(); - - if (shadowSize > 0) { - context.lineWidth = shadowSize / 2; - - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 2 + context.lineWidth / 2); - - context.strokeStyle = 'rgba(0,0,0,0.2)'; - this.plot(options, context.lineWidth / 2); - } - - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - context.fillStyle = options.fillColor || options.color; - - this.plot(options); - context.restore(); - }, - - plot : function (options, offset) { - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - i, x, y; - - for (i = data.length - 1; i > -1; --i) { - y = data[i][1]; - if (y === null) continue; - - x = xScale(data[i][0]); - y = yScale(y); - - if (x < 0 || x > options.width || y < 0 || y > options.height) continue; - - context.beginPath(); - if (offset) { - context.arc(x, y + offset, options.radius, 0, Math.PI, false); - } else { - context.arc(x, y, options.radius, 0, 2 * Math.PI, true); - if (options.fill) context.fill(); - } - context.stroke(); - context.closePath(); - } - } -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/radar.js b/addons/web_graph/static/lib/flotr2/js/types/radar.js deleted file mode 100644 index d693ddc78ec..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/radar.js +++ /dev/null @@ -1,60 +0,0 @@ -/** Radar **/ -Flotr.addType('radar', { - options: { - show: false, // => setting to true will show radar chart, false will hide - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - radiusRatio: 0.90 // => ratio of the radar, against the plot size - }, - draw : function (options) { - var - context = options.context, - shadowSize = options.shadowSize; - - context.save(); - context.translate(options.width / 2, options.height / 2); - context.lineWidth = options.lineWidth; - - // Shadow - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.strokeStyle = 'rgba(0,0,0,0.05)'; - this.plot(options, shadowSize / 2); - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 4); - - // Chart - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - this.plot(options); - - context.restore(); - }, - plot : function (options, offset) { - var - data = options.data, - context = options.context, - radius = Math.min(options.height, options.width) * options.radiusRatio / 2, - step = 2 * Math.PI / data.length, - angle = -Math.PI / 2, - i, ratio; - - offset = offset || 0; - - context.beginPath(); - for (i = 0; i < data.length; ++i) { - ratio = data[i][1] / this.max; - - context[i === 0 ? 'moveTo' : 'lineTo']( - Math.cos(i * step + angle) * radius * ratio + offset, - Math.sin(i * step + angle) * radius * ratio + offset - ); - } - context.closePath(); - if (options.fill) context.fill(); - context.stroke(); - }, - extendYRange : function (axis, data) { - this.max = Math.max(axis.max, this.max || -Number.MAX_VALUE); - } -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/timeline.js b/addons/web_graph/static/lib/flotr2/js/types/timeline.js deleted file mode 100644 index 59ea6094c01..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/timeline.js +++ /dev/null @@ -1,90 +0,0 @@ -Flotr.addType('timeline', { - options: { - show: false, - lineWidth: 1, - barWidth: 0.2, - fill: true, - fillColor: null, - fillOpacity: 0.4, - centered: true - }, - - draw : function (options) { - - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - barWidth = options.barWidth, - lineWidth = options.lineWidth, - i; - - Flotr._.each(data, function (timeline) { - - var - x = timeline[0], - y = timeline[1], - w = timeline[2], - h = barWidth, - - xt = Math.ceil(xScale(x)), - wt = Math.ceil(xScale(x + w)) - xt, - yt = Math.round(yScale(y)), - ht = Math.round(yScale(y - h)) - yt, - - x0 = xt - lineWidth / 2, - y0 = Math.round(yt - ht / 2) - lineWidth / 2; - - context.strokeRect(x0, y0, wt, ht); - context.fillRect(x0, y0, wt, ht); - - }); - }, - - extendRange : function (series) { - - var - data = series.data, - xa = series.xaxis, - ya = series.yaxis, - w = series.timeline.barWidth; - - if (xa.options.min === null) - xa.min = xa.datamin - w / 2; - - if (xa.options.max === null) { - - var - max = xa.max; - - Flotr._.each(data, function (timeline) { - max = Math.max(max, timeline[0] + timeline[2]); - }, this); - - xa.max = max + w / 2; - } - - if (ya.options.min === null) - ya.min = ya.datamin - w; - if (ya.options.min === null) - ya.max = ya.datamax + w; - } - -}); diff --git a/addons/web_graph/static/lib/flotr2/lib/base64.js b/addons/web_graph/static/lib/flotr2/lib/base64.js deleted file mode 100644 index 3306e49f9f7..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/base64.js +++ /dev/null @@ -1,113 +0,0 @@ -/* Copyright (C) 1999 Masanao Izumo - * Version: 1.0 - * LastModified: Dec 25 1999 - * This library is free. You can redistribute it and/or modify it. - */ - -/* - * Interfaces: - * b64 = base64encode(data); - * data = base64decode(b64); - */ - -(function() { - -var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -var base64DecodeChars = [ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1]; - -function base64encode(str) { - var out, i, len; - var c1, c2, c3; - - len = str.length; - i = 0; - out = ""; - while(i < len) { - c1 = str.charCodeAt(i++) & 0xff; - if(i == len) - { - out += base64EncodeChars.charAt(c1 >> 2); - out += base64EncodeChars.charAt((c1 & 0x3) << 4); - out += "=="; - break; - } - c2 = str.charCodeAt(i++); - if(i == len) - { - out += base64EncodeChars.charAt(c1 >> 2); - out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); - out += base64EncodeChars.charAt((c2 & 0xF) << 2); - out += "="; - break; - } - c3 = str.charCodeAt(i++); - out += base64EncodeChars.charAt(c1 >> 2); - out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); - out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)); - out += base64EncodeChars.charAt(c3 & 0x3F); - } - return out; -} - -function base64decode(str) { - var c1, c2, c3, c4; - var i, len, out; - - len = str.length; - i = 0; - out = ""; - while(i < len) { - /* c1 */ - do { - c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; - } while(i < len && c1 == -1); - if(c1 == -1) - break; - - /* c2 */ - do { - c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; - } while(i < len && c2 == -1); - if(c2 == -1) - break; - - out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); - - /* c3 */ - do { - c3 = str.charCodeAt(i++) & 0xff; - if(c3 == 61) - return out; - c3 = base64DecodeChars[c3]; - } while(i < len && c3 == -1); - if(c3 == -1) - break; - - out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); - - /* c4 */ - do { - c4 = str.charCodeAt(i++) & 0xff; - if(c4 == 61) - return out; - c4 = base64DecodeChars[c4]; - } while(i < len && c4 == -1); - if(c4 == -1) - break; - out += String.fromCharCode(((c3 & 0x03) << 6) | c4); - } - return out; -} - -if (!window.btoa) window.btoa = base64encode; -if (!window.atob) window.atob = base64decode; - -})(); \ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/lib/bean-min.js b/addons/web_graph/static/lib/flotr2/lib/bean-min.js deleted file mode 100644 index 2471420fe25..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/bean-min.js +++ /dev/null @@ -1,10 +0,0 @@ -/*! - * bean.js - copyright Jacob Thornton 2011 - * https://github.com/fat/bean - * MIT License - * special thanks to: - * dean edwards: http://dean.edwards.name/ - * dperini: https://github.com/dperini/nwevents - * the entire mootools team: github.com/mootools/mootools-core - *//*global module:true, define:true*/ -!function(a,b,c){typeof module!="undefined"?module.exports=c(a,b):typeof define=="function"&&typeof define.amd=="object"?define(c):b[a]=c(a,b)}("bean",this,function(a,b){var c=window,d=b[a],e=/over|out/,f=/[^\.]*(?=\..*)\.|.*/,g=/\..*/,h="addEventListener",i="attachEvent",j="removeEventListener",k="detachEvent",l=document||{},m=l.documentElement||{},n=m[h],o=n?h:i,p=Array.prototype.slice,q=/click|mouse|menu|drag|drop/i,r=/^touch|^gesture/i,s={one:1},t=function(a,b,c){for(c=0;c0){b=b.split(" ");for(j=b.length;j--;)G(a,b[j],c);return a}h=l&&b.replace(g,""),h&&u[h]&&(h=u[h].type);if(!b||l){if(i=l&&b.replace(f,""))i=i.split(".");k(a,h,c,i)}else if(typeof b=="function")k(a,null,b);else for(d in b)b.hasOwnProperty(d)&&G(a,d,b[d]);return a},H=function(a,b,c,d,e){var f,g,h,i,j=c,k=c&&typeof c=="string";if(b&&!c&&typeof b=="object")for(f in b)b.hasOwnProperty(f)&&H.apply(this,[a,f,b[f]]);else{i=arguments.length>3?p.call(arguments,3):[],g=(k?c:b).split(" "),k&&(c=F(b,j=d,e))&&(i=p.call(i,1)),this===s&&(c=C(G,a,b,c,j));for(h=g.length;h--;)E(a,g[h],c,j,i)}return a},I=function(){return H.apply(s,arguments)},J=n?function(a,b,d){var e=l.createEvent(a?"HTMLEvents":"UIEvents");e[a?"initEvent":"initUIEvent"](b,!0,!0,c,1),d.dispatchEvent(e)}:function(a,b,c){c=w(c,a),a?c.fireEvent("on"+b,l.createEventObject()):c["_on"+b]++},K=function(a,b,c){var d,e,h,i,j,k=b.split(" ");for(d=k.length;d--;){b=k[d].replace(g,"");if(i=k[d].replace(f,""))i=i.split(".");if(!i&&!c&&a[o])J(t[b],b,a);else{j=y.get(a,b),c=[!1].concat(c);for(e=0,h=j.length;e 0) { - // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3') - typeSpec = typeSpec.split(' ') - for (i = typeSpec.length; i--;) - remove(element, typeSpec[i], fn) - return element - } - type = isString && typeSpec.replace(nameRegex, '') - if (type && customEvents[type]) - type = customEvents[type].type - if (!typeSpec || isString) { - // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3) - if (namespaces = isString && typeSpec.replace(namespaceRegex, '')) - namespaces = namespaces.split('.') - rm(element, type, fn, namespaces) - } else if (typeof typeSpec === 'function') { - // remove(el, fn) - rm(element, null, typeSpec) - } else { - // remove(el, { t1: fn1, t2, fn2 }) - for (k in typeSpec) { - if (typeSpec.hasOwnProperty(k)) - remove(element, k, typeSpec[k]) - } - } - return element - } - - , add = function (element, events, fn, delfn, $) { - var type, types, i, args - , originalFn = fn - , isDel = fn && typeof fn === 'string' - - if (events && !fn && typeof events === 'object') { - for (type in events) { - if (events.hasOwnProperty(type)) - add.apply(this, [ element, type, events[type] ]) - } - } else { - args = arguments.length > 3 ? slice.call(arguments, 3) : [] - types = (isDel ? fn : events).split(' ') - isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1)) - // special case for one() - this === ONE && (fn = once(remove, element, events, fn, originalFn)) - for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args) - } - return element - } - - , one = function () { - return add.apply(ONE, arguments) - } - - , fireListener = W3C_MODEL ? function (isNative, type, element) { - var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents') - evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1) - element.dispatchEvent(evt) - } : function (isNative, type, element) { - element = targetElement(element, isNative) - // if not-native then we're using onpropertychange so we just increment a custom property - isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++ - } - - , fire = function (element, type, args) { - var i, j, l, names, handlers - , types = type.split(' ') - - for (i = types.length; i--;) { - type = types[i].replace(nameRegex, '') - if (names = types[i].replace(namespaceRegex, '')) - names = names.split('.') - if (!names && !args && element[eventSupport]) { - fireListener(nativeEvents[type], type, element) - } else { - // non-native event, either because of a namespace, arguments or a non DOM element - // iterate over all listeners and manually 'fire' - handlers = registry.get(element, type) - args = [false].concat(args) - for (j = 0, l = handlers.length; j < l; j++) { - if (handlers[j].inNamespaces(names)) - handlers[j].handler.apply(element, args) - } - } - } - return element - } - - , clone = function (element, from, type) { - var i = 0 - , handlers = registry.get(from, type) - , l = handlers.length - - for (;i < l; i++) - handlers[i].original && add(element, handlers[i].type, handlers[i].original) - return element - } - - , bean = { - add: add - , one: one - , remove: remove - , clone: clone - , fire: fire - , noConflict: function () { - context[name] = old - return this - } - } - - if (win[attachEvent]) { - // for IE, clean up on unload to avoid leaks - var cleanup = function () { - var i, entries = registry.entries() - for (i in entries) { - if (entries[i].type && entries[i].type !== 'unload') - remove(entries[i].element, entries[i].type) - } - win[detachEvent]('onunload', cleanup) - win.CollectGarbage && win.CollectGarbage() - } - win[attachEvent]('onunload', cleanup) - } - - return bean -}); diff --git a/addons/web_graph/static/lib/flotr2/lib/canvas2image.js b/addons/web_graph/static/lib/flotr2/lib/canvas2image.js deleted file mode 100644 index e064cdac8b9..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/canvas2image.js +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Canvas2Image v0.1 - * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com - * MIT License [http://www.opensource.org/licenses/mit-license.php] - */ - -var Canvas2Image = (function() { - // check if we have canvas support - var oCanvas = document.createElement("canvas"), - sc = String.fromCharCode, - strDownloadMime = "image/octet-stream", - bReplaceDownloadMime = false; - - // no canvas, bail out. - if (!oCanvas.getContext) { - return { - saveAsBMP : function(){}, - saveAsPNG : function(){}, - saveAsJPEG : function(){} - } - } - - var bHasImageData = !!(oCanvas.getContext("2d").getImageData), - bHasDataURL = !!(oCanvas.toDataURL), - bHasBase64 = !!(window.btoa); - - // ok, we're good - var readCanvasData = function(oCanvas) { - var iWidth = parseInt(oCanvas.width), - iHeight = parseInt(oCanvas.height); - return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight); - } - - // base64 encodes either a string or an array of charcodes - var encodeData = function(data) { - var i, aData, strData = ""; - - if (typeof data == "string") { - strData = data; - } else { - aData = data; - for (i = 0; i < aData.length; i++) { - strData += sc(aData[i]); - } - } - return btoa(strData); - } - - // creates a base64 encoded string containing BMP data takes an imagedata object as argument - var createBMP = function(oData) { - var strHeader = '', - iWidth = oData.width, - iHeight = oData.height; - - strHeader += 'BM'; - - var iFileSize = iWidth*iHeight*4 + 54; // total header size = 54 bytes - strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); - strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); - strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); - strHeader += sc(iFileSize % 256); - - strHeader += sc(0, 0, 0, 0, 54, 0, 0, 0); // data offset - strHeader += sc(40, 0, 0, 0); // info header size - - var iImageWidth = iWidth; - strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); - strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); - strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); - strHeader += sc(iImageWidth % 256); - - var iImageHeight = iHeight; - strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); - strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); - strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); - strHeader += sc(iImageHeight % 256); - - strHeader += sc(1, 0, 32, 0); // num of planes & num of bits per pixel - strHeader += sc(0, 0, 0, 0); // compression = none - - var iDataSize = iWidth*iHeight*4; - strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); - strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); - strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); - strHeader += sc(iDataSize % 256); - - strHeader += sc(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); // these bytes are not used - - var aImgData = oData.data, - strPixelData = "", - c, x, y = iHeight, - iOffsetX, iOffsetY, strPixelRow; - - do { - iOffsetY = iWidth*(y-1)*4; - strPixelRow = ""; - for (x = 0; x < iWidth; x++) { - iOffsetX = 4*x; - strPixelRow += sc( - aImgData[iOffsetY + iOffsetX + 2], // B - aImgData[iOffsetY + iOffsetX + 1], // G - aImgData[iOffsetY + iOffsetX], // R - aImgData[iOffsetY + iOffsetX + 3] // A - ); - } - strPixelData += strPixelRow; - } while (--y); - - return encodeData(strHeader + strPixelData); - } - - // sends the generated file to the client - var saveFile = function(strData) { - if (!window.open(strData)) { - document.location.href = strData; - } - } - - var makeDataURI = function(strData, strMime) { - return "data:" + strMime + ";base64," + strData; - } - - // generates a object containing the imagedata - var makeImageObject = function(strSource) { - var oImgElement = document.createElement("img"); - oImgElement.src = strSource; - return oImgElement; - } - - var scaleCanvas = function(oCanvas, iWidth, iHeight) { - if (iWidth && iHeight) { - var oSaveCanvas = document.createElement("canvas"); - - oSaveCanvas.width = iWidth; - oSaveCanvas.height = iHeight; - oSaveCanvas.style.width = iWidth+"px"; - oSaveCanvas.style.height = iHeight+"px"; - - var oSaveCtx = oSaveCanvas.getContext("2d"); - - oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iWidth); - - return oSaveCanvas; - } - return oCanvas; - } - - return { - saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) { - if (!bHasDataURL) return false; - - var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight), - strMime = "image/png", - strData = oScaledCanvas.toDataURL(strMime); - - if (bReturnImg) { - return makeImageObject(strData); - } else { - saveFile(bReplaceDownloadMime ? strData.replace(strMime, strDownloadMime) : strData); - } - return true; - }, - - saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) { - if (!bHasDataURL) return false; - - var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight), - strMime = "image/jpeg", - strData = oScaledCanvas.toDataURL(strMime); - - // check if browser actually supports jpeg by looking for the mime type in the data uri. if not, return false - if (strData.indexOf(strMime) != 5) return false; - - if (bReturnImg) { - return makeImageObject(strData); - } else { - saveFile(bReplaceDownloadMime ? strData.replace(strMime, strDownloadMime) : strData); - } - return true; - }, - - saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) { - if (!(bHasDataURL && bHasImageData && bHasBase64)) return false; - - var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight), - strMime = "image/bmp", - oData = readCanvasData(oScaledCanvas), - strImgData = createBMP(oData); - - if (bReturnImg) { - return makeImageObject(makeDataURI(strImgData, strMime)); - } else { - saveFile(makeDataURI(strImgData, strMime)); - } - return true; - } - }; -})(); \ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/lib/canvastext.js b/addons/web_graph/static/lib/flotr2/lib/canvastext.js deleted file mode 100644 index 2df45bae0d8..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/canvastext.js +++ /dev/null @@ -1,429 +0,0 @@ -/** - * This code is released to the public domain by Jim Studt, 2007. - * He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/ - * It as been modified by Fabien Ménager to handle font style like size, weight, color and rotation. - * A partial support for special characters has been added too. - */ -var CanvasText = { - /** The letters definition. It is a list of letters, - * with their width, and the coordinates of points compositing them. - * The syntax for the points is : [x, y], null value means "pen up" - */ - letters: { - '\n':{ width: -1, points: [] }, - ' ': { width: 10, points: [] }, - '!': { width: 10, points: [[5,21],[5,7],null,[5,2],[4,1],[5,0],[6,1],[5,2]] }, - '"': { width: 16, points: [[4,21],[4,14],null,[12,21],[12,14]] }, - '#': { width: 21, points: [[11,25],[4,-7],null,[17,25],[10,-7],null,[4,12],[18,12],null,[3,6],[17,6]] }, - '$': { width: 20, points: [[8,25],[8,-4],null,[12,25],[12,-4],null,[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] }, - '%': { width: 24, points: [[21,21],[3,0],null,[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],null,[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] }, - '&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] }, - '\'':{ width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] }, - '(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] }, - ')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] }, - '*': { width: 16, points: [[8,21],[8,9],null,[3,18],[13,12],null,[13,18],[3,12]] }, - '+': { width: 26, points: [[13,18],[13,0],null,[4,9],[22,9]] }, - ',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] }, - '-': { width: 26, points: [[4,9],[22,9]] }, - '.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] }, - '/': { width: 22, points: [[20,25],[2,-7]] }, - '0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] }, - '1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] }, - '2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] }, - '3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] }, - '4': { width: 20, points: [[13,21],[3,7],[18,7],null,[13,21],[13,0]] }, - '5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] }, - '6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] }, - '7': { width: 20, points: [[17,21],[7,0],null,[3,21],[17,21]] }, - '8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] }, - '9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] }, - ':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[5,2],[4,1],[5,0],[6,1],[5,2]] }, - ';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] }, - '<': { width: 24, points: [[20,18],[4,9],[20,0]] }, - '=': { width: 26, points: [[4,12],[22,12],null,[4,6],[22,6]] }, - '>': { width: 24, points: [[4,18],[20,9],[4,0]] }, - '?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],null,[9,2],[8,1],[9,0],[10,1],[9,2]] }, - '@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],null,[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],null,[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],null,[19,16],[18,8],[18,6],[19,5]] }, - 'A': { width: 18, points: [[9,21],[1,0],null,[9,21],[17,0],null,[4,7],[14,7]] }, - 'B': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],null,[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] }, - 'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] }, - 'D': { width: 21, points: [[4,21],[4,0],null,[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] }, - 'E': { width: 19, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11],null,[4,0],[17,0]] }, - 'F': { width: 18, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11]] }, - 'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],null,[13,8],[18,8]] }, - 'H': { width: 22, points: [[4,21],[4,0],null,[18,21],[18,0],null,[4,11],[18,11]] }, - 'I': { width: 8, points: [[4,21],[4,0]] }, - 'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] }, - 'K': { width: 21, points: [[4,21],[4,0],null,[18,21],[4,7],null,[9,12],[18,0]] }, - 'L': { width: 17, points: [[4,21],[4,0],null,[4,0],[16,0]] }, - 'M': { width: 24, points: [[4,21],[4,0],null,[4,21],[12,0],null,[20,21],[12,0],null,[20,21],[20,0]] }, - 'N': { width: 22, points: [[4,21],[4,0],null,[4,21],[18,0],null,[18,21],[18,0]] }, - 'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] }, - 'P': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] }, - 'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],null,[12,4],[18,-2]] }, - 'R': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],null,[11,11],[18,0]] }, - 'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] }, - 'T': { width: 16, points: [[8,21],[8,0],null,[1,21],[15,21]] }, - 'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] }, - 'V': { width: 18, points: [[1,21],[9,0],null,[17,21],[9,0]] }, - 'W': { width: 24, points: [[2,21],[7,0],null,[12,21],[7,0],null,[12,21],[17,0],null,[22,21],[17,0]] }, - 'X': { width: 20, points: [[3,21],[17,0],null,[17,21],[3,0]] }, - 'Y': { width: 18, points: [[1,21],[9,11],[9,0],null,[17,21],[9,11]] }, - 'Z': { width: 20, points: [[17,21],[3,0],null,[3,21],[17,21],null,[3,0],[17,0]] }, - '[': { width: 14, points: [[4,25],[4,-7],null,[5,25],[5,-7],null,[4,25],[11,25],null,[4,-7],[11,-7]] }, - '\\':{ width: 14, points: [[0,21],[14,-3]] }, - ']': { width: 14, points: [[9,25],[9,-7],null,[10,25],[10,-7],null,[3,25],[10,25],null,[3,-7],[10,-7]] }, - '^': { width: 14, points: [[3,10],[8,18],[13,10]] }, - '_': { width: 16, points: [[0,-2],[16,-2]] }, - '`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] }, - 'a': { width: 19, points: [[15,14],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'b': { width: 19, points: [[4,21],[4,0],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] }, - 'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'd': { width: 19, points: [[15,21],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],null,[2,14],[9,14]] }, - 'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'h': { width: 19, points: [[4,21],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] }, - 'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],null,[4,14],[4,0]] }, - 'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] }, - 'k': { width: 17, points: [[4,21],[4,0],null,[14,14],[4,4],null,[8,8],[15,0]] }, - 'l': { width: 8, points: [[4,21],[4,0]] }, - 'm': { width: 30, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],null,[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] }, - 'n': { width: 19, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] }, - 'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] }, - 'p': { width: 19, points: [[4,14],[4,-7],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] }, - 'q': { width: 19, points: [[15,14],[15,-7],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'r': { width: 13, points: [[4,14],[4,0],null,[4,8],[5,11],[7,13],[9,14],[12,14]] }, - 's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] }, - 't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],null,[2,14],[9,14]] }, - 'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],null,[15,14],[15,0]] }, - 'v': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0]] }, - 'w': { width: 22, points: [[3,14],[7,0],null,[11,14],[7,0],null,[11,14],[15,0],null,[19,14],[15,0]] }, - 'x': { width: 17, points: [[3,14],[14,0],null,[14,14],[3,0]] }, - 'y': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] }, - 'z': { width: 17, points: [[14,14],[3,0],null,[3,14],[14,14],null,[3,0],[14,0]] }, - '{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],null,[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],null,[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] }, - '|': { width: 8, points: [[4,25],[4,-7]] }, - '}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],null,[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],null,[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] }, - '~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],null,[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] }, - - // Lower case Latin-1 - 'à': { diacritic: '`', letter: 'a' }, - 'á': { diacritic: '´', letter: 'a' }, - 'â': { diacritic: '^', letter: 'a' }, - 'ä': { diacritic: '¨', letter: 'a' }, - 'ã': { diacritic: '~', letter: 'a' }, - - 'è': { diacritic: '`', letter: 'e' }, - 'é': { diacritic: '´', letter: 'e' }, - 'ê': { diacritic: '^', letter: 'e' }, - 'ë': { diacritic: '¨', letter: 'e' }, - - 'ì': { diacritic: '`', letter: 'i' }, - 'í': { diacritic: '´', letter: 'i' }, - 'î': { diacritic: '^', letter: 'i' }, - 'ï': { diacritic: '¨', letter: 'i' }, - - 'ò': { diacritic: '`', letter: 'o' }, - 'ó': { diacritic: '´', letter: 'o' }, - 'ô': { diacritic: '^', letter: 'o' }, - 'ö': { diacritic: '¨', letter: 'o' }, - 'õ': { diacritic: '~', letter: 'o' }, - - 'ù': { diacritic: '`', letter: 'u' }, - 'ú': { diacritic: '´', letter: 'u' }, - 'û': { diacritic: '^', letter: 'u' }, - 'ü': { diacritic: '¨', letter: 'u' }, - - 'ý': { diacritic: '´', letter: 'y' }, - 'ÿ': { diacritic: '¨', letter: 'y' }, - - 'ç': { diacritic: '¸', letter: 'c' }, - 'ñ': { diacritic: '~', letter: 'n' }, - - // Upper case Latin-1 - 'À': { diacritic: '`', letter: 'A' }, - 'Á': { diacritic: '´', letter: 'A' }, - 'Â': { diacritic: '^', letter: 'A' }, - 'Ä': { diacritic: '¨', letter: 'A' }, - 'Ã': { diacritic: '~', letter: 'A' }, - - 'È': { diacritic: '`', letter: 'E' }, - 'É': { diacritic: '´', letter: 'E' }, - 'Ê': { diacritic: '^', letter: 'E' }, - 'Ë': { diacritic: '¨', letter: 'E' }, - - 'Ì': { diacritic: '`', letter: 'I' }, - 'Í': { diacritic: '´', letter: 'I' }, - 'Î': { diacritic: '^', letter: 'I' }, - 'Ï': { diacritic: '¨', letter: 'I' }, - - 'Ò': { diacritic: '`', letter: 'O' }, - 'Ó': { diacritic: '´', letter: 'O' }, - 'Ô': { diacritic: '^', letter: 'O' }, - 'Ö': { diacritic: '¨', letter: 'O' }, - 'Õ': { diacritic: '~', letter: 'O' }, - - 'Ù': { diacritic: '`', letter: 'U' }, - 'Ú': { diacritic: '´', letter: 'U' }, - 'Û': { diacritic: '^', letter: 'U' }, - 'Ü': { diacritic: '¨', letter: 'U' }, - - 'Ý': { diacritic: '´', letter: 'Y' }, - - 'Ç': { diacritic: '¸', letter: 'C' }, - 'Ñ': { diacritic: '~', letter: 'N' } - }, - - specialchars: { - 'pi': { width: 19, points: [[6,14],[6,0],null,[14,14],[14,0],null,[2,13],[6,16],[13,13],[17,16]] } - }, - - /** Diacritics, used to draw accentuated letters */ - diacritics: { - '¸': { entity: 'cedil', points: [[6,-4],[4,-6],[2,-7],[1,-7]] }, - '´': { entity: 'acute', points: [[8,19],[13,22]] }, - '`': { entity: 'grave', points: [[7,22],[12,19]] }, - '^': { entity: 'circ', points: [[5.5,19],[9.5,23],[12.5,19]] }, - '¨': { entity: 'trema', points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[12,21],[13,20],[14,21],[13,22],[12,21]] }, - '~': { entity: 'tilde', points: [[4,18],[7,22],[10,18],[13,22]] } - }, - - /** The default font styling */ - style: { - size: 8, // font height in pixels - font: null, // not yet implemented - color: '#000000', // font color - weight: 1, // float, 1 for 'normal' - textAlign: 'left', // left, right, center - textBaseline: 'bottom', // top, middle, bottom - adjustAlign: false, // modifies the alignments if the angle is different from 0 to make the spin point always at the good position - angle: 0, // in radians, anticlockwise - tracking: 1, // space between the letters, float, 1 for 'normal' - boundingBoxColor: '#ff0000', // color of the bounding box (null to hide), can be used for debug and font drawing - originPointColor: '#000000' // color of the bounding box (null to hide), can be used for debug and font drawing - }, - - debug: false, - _bufferLexemes: {}, - - extend: function(dest, src) { - for (var property in src) { - if (property in dest) continue; - dest[property] = src[property]; - } - return dest; - }, - - /** Get the letter data corresponding to a char - * @param {String} ch - The char - */ - letter: function(ch) { - return CanvasText.letters[ch]; - }, - - parseLexemes: function(str) { - if (CanvasText._bufferLexemes[str]) - return CanvasText._bufferLexemes[str]; - - var i, c, matches = str.match(/&[A-Za-z]{2,5};|\s|./g), - result = [], chars = []; - - for (i = 0; i < matches.length; i++) { - c = matches[i]; - if (c.length == 1) - chars.push(c); - else { - var entity = c.substring(1, c.length-1); - if (CanvasText.specialchars[entity]) - chars.push(entity); - else - chars = chars.concat(c.toArray()); - } - } - for (i = 0; i < chars.length; i++) { - c = chars[i]; - if (c = CanvasText.letters[c] || CanvasText.specialchars[c]) result.push(c); - } - for (i = 0; i < result.length; i++) { - if (result === null || typeof result === 'undefined') - delete result[i]; - } - return CanvasText._bufferLexemes[str] = result; - }, - - /** Get the font ascent for a given style - * @param {Object} style - The reference style - */ - ascent: function(style) { - style = style || CanvasText.style; - return (style.size || CanvasText.style.size); - }, - - /** Get the font descent for a given style - * @param {Object} style - The reference style - * */ - descent: function(style) { - style = style || CanvasText.style; - return 7.0*(style.size || CanvasText.style.size)/25.0; - }, - - /** Measure the text horizontal size - * @param {String} str - The text - * @param {Object} style - Text style - * */ - measure: function(str, style) { - if (!str) return; - style = style || CanvasText.style; - - var i, width, lexemes = CanvasText.parseLexemes(str), - total = 0; - - for (i = lexemes.length-1; i > -1; --i) { - c = lexemes[i]; - width = (c.diacritic) ? CanvasText.letter(c.letter).width : c.width; - total += width * (style.tracking || CanvasText.style.tracking) * (style.size || CanvasText.style.size) / 25.0; - } - return total; - }, - - getDimensions: function(str, style) { - style = style || CanvasText.style; - - var width = CanvasText.measure(str, style), - height = style.size || CanvasText.style.size, - angle = style.angle || CanvasText.style.angle; - - if (style.angle == 0) return {width: width, height: height}; - return { - width: Math.abs(Math.cos(angle) * width) + Math.abs(Math.sin(angle) * height), - height: Math.abs(Math.sin(angle) * width) + Math.abs(Math.cos(angle) * height) - } - }, - - /** Draws serie of points at given coordinates - * @param {Canvas context} ctx - The canvas context - * @param {Array} points - The points to draw - * @param {Number} x - The X coordinate - * @param {Number} y - The Y coordinate - * @param {Number} mag - The scale - */ - drawPoints: function (ctx, points, x, y, mag, offset) { - var i, a, penUp = true, needStroke = 0; - offset = offset || {x:0, y:0}; - - ctx.beginPath(); - for (i = 0; i < points.length; i++) { - a = points[i]; - if (!a) { - penUp = true; - continue; - } - if (penUp) { - ctx.moveTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y); - penUp = false; - } - else { - ctx.lineTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y); - } - } - ctx.stroke(); - ctx.closePath(); - }, - - /** Draws a text at given coordinates and with a given style - * @param {String} str - The text to draw - * @param {Number} xOrig - The X coordinate - * @param {Number} yOrig - The Y coordinate - * @param {Object} style - The font style - */ - draw: function(str, xOrig, yOrig, style) { - if (!str) return; - CanvasText.extend(style, CanvasText.style); - - var i, c, total = 0, - mag = style.size / 25.0, - x = 0, y = 0, - lexemes = CanvasText.parseLexemes(str), - offset = {x: 0, y: 0}, - measure = CanvasText.measure(str, style), - align; - - if (style.adjustAlign) { - align = CanvasText.getBestAlign(style.angle, style); - CanvasText.extend(style, align); - } - - switch (style.textAlign) { - case 'left': break; - case 'center': offset.x = -measure / 2; break; - case 'right': offset.x = -measure; break; - } - - switch (style.textBaseline) { - case 'bottom': break; - case 'middle': offset.y = style.size / 2; break; - case 'top': offset.y = style.size; break; - } - - this.save(); - this.translate(xOrig, yOrig); - this.rotate(style.angle); - this.lineCap = "round"; - this.lineWidth = 2.0 * mag * (style.weight || CanvasText.style.weight); - this.strokeStyle = style.color || CanvasText.style.color; - - for (i = 0; i < lexemes.length; i++) { - c = lexemes[i]; - if (c.width == -1) { - x = 0; - y = style.size * 1.4; - continue; - } - - var points = c.points, - width = c.width; - - if (c.diacritic) { - var dia = CanvasText.diacritics[c.diacritic], - character = CanvasText.letter(c.letter); - - CanvasText.drawPoints(this, dia.points, x, y - (c.letter.toUpperCase() == c.letter ? 3 : 0), mag, offset); - points = character.points; - width = character.width; - } - - CanvasText.drawPoints(this, points, x, y, mag, offset); - - if (CanvasText.debug) { - this.save(); - this.lineJoin = "miter"; - this.lineWidth = 0.5; - this.strokeStyle = (style.boundingBoxColor || CanvasText.style.boundingBoxColor); - this.strokeRect(x+offset.x, y+offset.y, width*mag, -style.size); - - this.fillStyle = (style.originPointColor || CanvasText.style.originPointColor); - this.beginPath(); - this.arc(0, 0, 1.5, 0, Math.PI*2, true); - this.fill(); - this.closePath(); - this.restore(); - } - - x += width*mag*(style.tracking || CanvasText.style.tracking); - } - this.restore(); - return total; - } -}; - -/** The text functions are bound to the CanvasRenderingContext2D prototype */ -CanvasText.proto = window.CanvasRenderingContext2D ? window.CanvasRenderingContext2D.prototype : document.createElement('canvas').getContext('2d').__proto__; - -if (CanvasText.proto) { - CanvasText.proto.drawText = CanvasText.draw; - CanvasText.proto.measure = CanvasText.measure; - CanvasText.proto.getTextBounds = CanvasText.getDimensions; - CanvasText.proto.fontAscent = CanvasText.ascent; - CanvasText.proto.fontDescent = CanvasText.descent; -} \ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/lib/excanvas.js b/addons/web_graph/static/lib/flotr2/lib/excanvas.js deleted file mode 100644 index 005bab1186f..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/excanvas.js +++ /dev/null @@ -1,1425 +0,0 @@ -// Copyright 2006 Google Inc. -// -// 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. - - -// Known Issues: -// -// * Patterns only support repeat. -// * Radial gradient are not implemented. The VML version of these look very -// different from the canvas one. -// * Clipping paths are not implemented. -// * Coordsize. The width and height attribute have higher priority than the -// width and height style values which isn't correct. -// * Painting mode isn't implemented. -// * Canvas width/height should is using content-box by default. IE in -// Quirks mode will draw the canvas using border-box. Either change your -// doctype to HTML5 -// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) -// or use Box Sizing Behavior from WebFX -// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) -// * Non uniform scaling does not correctly scale strokes. -// * Optimize. There is always room for speed improvements. - -// Only add this code if we do not already have a canvas implementation -if (!document.createElement('canvas').getContext) { - -(function() { - - // alias some functions to make (compiled) code shorter - var m = Math; - var mr = m.round; - var ms = m.sin; - var mc = m.cos; - var abs = m.abs; - var sqrt = m.sqrt; - - // this is used for sub pixel precision - var Z = 10; - var Z2 = Z / 2; - - var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1]; - - /** - * This funtion is assigned to the elements as element.getContext(). - * @this {HTMLElement} - * @return {CanvasRenderingContext2D_} - */ - function getContext() { - return this.context_ || - (this.context_ = new CanvasRenderingContext2D_(this)); - } - - var slice = Array.prototype.slice; - - /** - * Binds a function to an object. The returned function will always use the - * passed in {@code obj} as {@code this}. - * - * Example: - * - * g = bind(f, obj, a, b) - * g(c, d) // will do f.call(obj, a, b, c, d) - * - * @param {Function} f The function to bind the object to - * @param {Object} obj The object that should act as this when the function - * is called - * @param {*} var_args Rest arguments that will be used as the initial - * arguments when the function is called - * @return {Function} A new function that has bound this - */ - function bind(f, obj, var_args) { - var a = slice.call(arguments, 2); - return function() { - return f.apply(obj, a.concat(slice.call(arguments))); - }; - } - - function encodeHtmlAttribute(s) { - return String(s).replace(/&/g, '&').replace(/"/g, '"'); - } - - function addNamespace(doc, prefix, urn) { - if (!doc.namespaces[prefix]) { - doc.namespaces.add(prefix, urn, '#default#VML'); - } - } - - function addNamespacesAndStylesheet(doc) { - addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml'); - addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office'); - - // Setup default CSS. Only add one style sheet per document - if (!doc.styleSheets['ex_canvas_']) { - var ss = doc.createStyleSheet(); - ss.owningElement.id = 'ex_canvas_'; - ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + - // default size is 300x150 in Gecko and Opera - 'text-align:left;width:300px;height:150px}'; - } - } - - // Add namespaces and stylesheet at startup. - addNamespacesAndStylesheet(document); - - var G_vmlCanvasManager_ = { - init: function(opt_doc) { - var doc = opt_doc || document; - // Create a dummy element so that IE will allow canvas elements to be - // recognized. - doc.createElement('canvas'); - doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); - }, - - init_: function(doc) { - // find all canvas elements - var els = doc.getElementsByTagName('canvas'); - for (var i = 0; i < els.length; i++) { - this.initElement(els[i]); - } - }, - - /** - * Public initializes a canvas element so that it can be used as canvas - * element from now on. This is called automatically before the page is - * loaded but if you are creating elements using createElement you need to - * make sure this is called on the element. - * @param {HTMLElement} el The canvas element to initialize. - * @return {HTMLElement} the element that was created. - */ - initElement: function(el) { - if (!el.getContext) { - el.getContext = getContext; - - // Add namespaces and stylesheet to document of the element. - addNamespacesAndStylesheet(el.ownerDocument); - - // Remove fallback content. There is no way to hide text nodes so we - // just remove all childNodes. We could hide all elements and remove - // text nodes but who really cares about the fallback content. - el.innerHTML = ''; - - // do not use inline function because that will leak memory - el.attachEvent('onpropertychange', onPropertyChange); - el.attachEvent('onresize', onResize); - - var attrs = el.attributes; - if (attrs.width && attrs.width.specified) { - // TODO: use runtimeStyle and coordsize - // el.getContext().setWidth_(attrs.width.nodeValue); - el.style.width = attrs.width.nodeValue + 'px'; - } else { - el.width = el.clientWidth; - } - if (attrs.height && attrs.height.specified) { - // TODO: use runtimeStyle and coordsize - // el.getContext().setHeight_(attrs.height.nodeValue); - el.style.height = attrs.height.nodeValue + 'px'; - } else { - el.height = el.clientHeight; - } - //el.getContext().setCoordsize_() - } - return el; - } - }; - - function onPropertyChange(e) { - var el = e.srcElement; - - switch (e.propertyName) { - case 'width': - el.getContext().clearRect(); - el.style.width = el.attributes.width.nodeValue + 'px'; - // In IE8 this does not trigger onresize. - if (el.firstChild) { - el.firstChild.style.width = el.clientWidth + 'px'; - } - break; - case 'height': - el.getContext().clearRect(); - el.style.height = el.attributes.height.nodeValue + 'px'; - if (el.firstChild) { - el.firstChild.style.height = el.clientHeight + 'px'; - } - break; - } - } - - function onResize(e) { - var el = e.srcElement; - if (el.firstChild) { - el.firstChild.style.width = el.clientWidth + 'px'; - el.firstChild.style.height = el.clientHeight + 'px'; - } - } - - G_vmlCanvasManager_.init(); - - // precompute "00" to "FF" - var decToHex = []; - for (var i = 0; i < 16; i++) { - for (var j = 0; j < 16; j++) { - decToHex[i * 16 + j] = i.toString(16) + j.toString(16); - } - } - - function createMatrixIdentity() { - return [ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1] - ]; - } - - function matrixMultiply(m1, m2) { - var result = createMatrixIdentity(); - - for (var x = 0; x < 3; x++) { - for (var y = 0; y < 3; y++) { - var sum = 0; - - for (var z = 0; z < 3; z++) { - sum += m1[x][z] * m2[z][y]; - } - - result[x][y] = sum; - } - } - return result; - } - - function copyState(o1, o2) { - o2.fillStyle = o1.fillStyle; - o2.lineCap = o1.lineCap; - o2.lineJoin = o1.lineJoin; - o2.lineWidth = o1.lineWidth; - o2.miterLimit = o1.miterLimit; - o2.shadowBlur = o1.shadowBlur; - o2.shadowColor = o1.shadowColor; - o2.shadowOffsetX = o1.shadowOffsetX; - o2.shadowOffsetY = o1.shadowOffsetY; - o2.strokeStyle = o1.strokeStyle; - o2.globalAlpha = o1.globalAlpha; - o2.font = o1.font; - o2.textAlign = o1.textAlign; - o2.textBaseline = o1.textBaseline; - o2.arcScaleX_ = o1.arcScaleX_; - o2.arcScaleY_ = o1.arcScaleY_; - o2.lineScale_ = o1.lineScale_; - } - - var colorData = { - aliceblue: '#F0F8FF', - antiquewhite: '#FAEBD7', - aquamarine: '#7FFFD4', - azure: '#F0FFFF', - beige: '#F5F5DC', - bisque: '#FFE4C4', - black: '#000000', - blanchedalmond: '#FFEBCD', - blueviolet: '#8A2BE2', - brown: '#A52A2A', - burlywood: '#DEB887', - cadetblue: '#5F9EA0', - chartreuse: '#7FFF00', - chocolate: '#D2691E', - coral: '#FF7F50', - cornflowerblue: '#6495ED', - cornsilk: '#FFF8DC', - crimson: '#DC143C', - cyan: '#00FFFF', - darkblue: '#00008B', - darkcyan: '#008B8B', - darkgoldenrod: '#B8860B', - darkgray: '#A9A9A9', - darkgreen: '#006400', - darkgrey: '#A9A9A9', - darkkhaki: '#BDB76B', - darkmagenta: '#8B008B', - darkolivegreen: '#556B2F', - darkorange: '#FF8C00', - darkorchid: '#9932CC', - darkred: '#8B0000', - darksalmon: '#E9967A', - darkseagreen: '#8FBC8F', - darkslateblue: '#483D8B', - darkslategray: '#2F4F4F', - darkslategrey: '#2F4F4F', - darkturquoise: '#00CED1', - darkviolet: '#9400D3', - deeppink: '#FF1493', - deepskyblue: '#00BFFF', - dimgray: '#696969', - dimgrey: '#696969', - dodgerblue: '#1E90FF', - firebrick: '#B22222', - floralwhite: '#FFFAF0', - forestgreen: '#228B22', - gainsboro: '#DCDCDC', - ghostwhite: '#F8F8FF', - gold: '#FFD700', - goldenrod: '#DAA520', - grey: '#808080', - greenyellow: '#ADFF2F', - honeydew: '#F0FFF0', - hotpink: '#FF69B4', - indianred: '#CD5C5C', - indigo: '#4B0082', - ivory: '#FFFFF0', - khaki: '#F0E68C', - lavender: '#E6E6FA', - lavenderblush: '#FFF0F5', - lawngreen: '#7CFC00', - lemonchiffon: '#FFFACD', - lightblue: '#ADD8E6', - lightcoral: '#F08080', - lightcyan: '#E0FFFF', - lightgoldenrodyellow: '#FAFAD2', - lightgreen: '#90EE90', - lightgrey: '#D3D3D3', - lightpink: '#FFB6C1', - lightsalmon: '#FFA07A', - lightseagreen: '#20B2AA', - lightskyblue: '#87CEFA', - lightslategray: '#778899', - lightslategrey: '#778899', - lightsteelblue: '#B0C4DE', - lightyellow: '#FFFFE0', - limegreen: '#32CD32', - linen: '#FAF0E6', - magenta: '#FF00FF', - mediumaquamarine: '#66CDAA', - mediumblue: '#0000CD', - mediumorchid: '#BA55D3', - mediumpurple: '#9370DB', - mediumseagreen: '#3CB371', - mediumslateblue: '#7B68EE', - mediumspringgreen: '#00FA9A', - mediumturquoise: '#48D1CC', - mediumvioletred: '#C71585', - midnightblue: '#191970', - mintcream: '#F5FFFA', - mistyrose: '#FFE4E1', - moccasin: '#FFE4B5', - navajowhite: '#FFDEAD', - oldlace: '#FDF5E6', - olivedrab: '#6B8E23', - orange: '#FFA500', - orangered: '#FF4500', - orchid: '#DA70D6', - palegoldenrod: '#EEE8AA', - palegreen: '#98FB98', - paleturquoise: '#AFEEEE', - palevioletred: '#DB7093', - papayawhip: '#FFEFD5', - peachpuff: '#FFDAB9', - peru: '#CD853F', - pink: '#FFC0CB', - plum: '#DDA0DD', - powderblue: '#B0E0E6', - rosybrown: '#BC8F8F', - royalblue: '#4169E1', - saddlebrown: '#8B4513', - salmon: '#FA8072', - sandybrown: '#F4A460', - seagreen: '#2E8B57', - seashell: '#FFF5EE', - sienna: '#A0522D', - skyblue: '#87CEEB', - slateblue: '#6A5ACD', - slategray: '#708090', - slategrey: '#708090', - snow: '#FFFAFA', - springgreen: '#00FF7F', - steelblue: '#4682B4', - tan: '#D2B48C', - thistle: '#D8BFD8', - tomato: '#FF6347', - turquoise: '#40E0D0', - violet: '#EE82EE', - wheat: '#F5DEB3', - whitesmoke: '#F5F5F5', - yellowgreen: '#9ACD32' - }; - - - function getRgbHslContent(styleString) { - var start = styleString.indexOf('(', 3); - var end = styleString.indexOf(')', start + 1); - var parts = styleString.substring(start + 1, end).split(','); - // add alpha if needed - if (parts.length != 4 || styleString.charAt(3) != 'a') { - parts[3] = 1; - } - return parts; - } - - function percent(s) { - return parseFloat(s) / 100; - } - - function clamp(v, min, max) { - return Math.min(max, Math.max(min, v)); - } - - function hslToRgb(parts){ - var r, g, b, h, s, l; - h = parseFloat(parts[0]) / 360 % 360; - if (h < 0) - h++; - s = clamp(percent(parts[1]), 0, 1); - l = clamp(percent(parts[2]), 0, 1); - if (s == 0) { - r = g = b = l; // achromatic - } else { - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hueToRgb(p, q, h + 1 / 3); - g = hueToRgb(p, q, h); - b = hueToRgb(p, q, h - 1 / 3); - } - - return '#' + decToHex[Math.floor(r * 255)] + - decToHex[Math.floor(g * 255)] + - decToHex[Math.floor(b * 255)]; - } - - function hueToRgb(m1, m2, h) { - if (h < 0) - h++; - if (h > 1) - h--; - - if (6 * h < 1) - return m1 + (m2 - m1) * 6 * h; - else if (2 * h < 1) - return m2; - else if (3 * h < 2) - return m1 + (m2 - m1) * (2 / 3 - h) * 6; - else - return m1; - } - - var processStyleCache = {}; - - function processStyle(styleString) { - if (styleString in processStyleCache) { - return processStyleCache[styleString]; - } - - var str, alpha = 1; - - styleString = String(styleString); - if (styleString.charAt(0) == '#') { - str = styleString; - } else if (/^rgb/.test(styleString)) { - var parts = getRgbHslContent(styleString); - var str = '#', n; - for (var i = 0; i < 3; i++) { - if (parts[i].indexOf('%') != -1) { - n = Math.floor(percent(parts[i]) * 255); - } else { - n = +parts[i]; - } - str += decToHex[clamp(n, 0, 255)]; - } - alpha = +parts[3]; - } else if (/^hsl/.test(styleString)) { - var parts = getRgbHslContent(styleString); - str = hslToRgb(parts); - alpha = parts[3]; - } else { - str = colorData[styleString] || styleString; - } - return processStyleCache[styleString] = {color: str, alpha: alpha}; - } - - var DEFAULT_STYLE = { - style: 'normal', - variant: 'normal', - weight: 'normal', - size: 10, - family: 'sans-serif' - }; - - // Internal text style cache - var fontStyleCache = {}; - - function processFontStyle(styleString) { - if (fontStyleCache[styleString]) { - return fontStyleCache[styleString]; - } - - var el = document.createElement('div'); - var style = el.style; - try { - style.font = styleString; - } catch (ex) { - // Ignore failures to set to invalid font. - } - - return fontStyleCache[styleString] = { - style: style.fontStyle || DEFAULT_STYLE.style, - variant: style.fontVariant || DEFAULT_STYLE.variant, - weight: style.fontWeight || DEFAULT_STYLE.weight, - size: style.fontSize || DEFAULT_STYLE.size, - family: style.fontFamily || DEFAULT_STYLE.family - }; - } - - function getComputedStyle(style, element) { - var computedStyle = {}; - - for (var p in style) { - computedStyle[p] = style[p]; - } - - // Compute the size - var canvasFontSize = parseFloat(element.currentStyle.fontSize), - fontSize = parseFloat(style.size); - - if (typeof style.size == 'number') { - computedStyle.size = style.size; - } else if (style.size.indexOf('px') != -1) { - computedStyle.size = fontSize; - } else if (style.size.indexOf('em') != -1) { - computedStyle.size = canvasFontSize * fontSize; - } else if(style.size.indexOf('%') != -1) { - computedStyle.size = (canvasFontSize / 100) * fontSize; - } else if (style.size.indexOf('pt') != -1) { - computedStyle.size = fontSize / .75; - } else { - computedStyle.size = canvasFontSize; - } - - // Different scaling between normal text and VML text. This was found using - // trial and error to get the same size as non VML text. - //computedStyle.size *= 0.981; - - return computedStyle; - } - - function buildStyle(style) { - return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + - style.size + 'px ' + style.family; - } - - var lineCapMap = { - 'butt': 'flat', - 'round': 'round' - }; - - function processLineCap(lineCap) { - return lineCapMap[lineCap] || 'square'; - } - - /** - * This class implements CanvasRenderingContext2D interface as described by - * the WHATWG. - * @param {HTMLElement} canvasElement The element that the 2D context should - * be associated with - */ - function CanvasRenderingContext2D_(canvasElement) { - this.m_ = createMatrixIdentity(); - - this.mStack_ = []; - this.aStack_ = []; - this.currentPath_ = []; - - // Canvas context properties - this.strokeStyle = '#000'; - this.fillStyle = '#000'; - - this.lineWidth = 1; - this.lineJoin = 'miter'; - this.lineCap = 'butt'; - this.miterLimit = Z * 1; - this.globalAlpha = 1; - this.font = '10px sans-serif'; - this.textAlign = 'left'; - this.textBaseline = 'alphabetic'; - this.canvas = canvasElement; - - var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + - canvasElement.clientHeight + 'px;overflow:hidden;position:absolute'; - var el = canvasElement.ownerDocument.createElement('div'); - el.style.cssText = cssText; - canvasElement.appendChild(el); - - var overlayEl = el.cloneNode(false); - // Use a non transparent background. - overlayEl.style.backgroundColor = 'red'; - overlayEl.style.filter = 'alpha(opacity=0)'; - canvasElement.appendChild(overlayEl); - - this.element_ = el; - this.arcScaleX_ = 1; - this.arcScaleY_ = 1; - this.lineScale_ = 1; - } - - var contextPrototype = CanvasRenderingContext2D_.prototype; - contextPrototype.clearRect = function() { - if (this.textMeasureEl_) { - this.textMeasureEl_.removeNode(true); - this.textMeasureEl_ = null; - } - this.element_.innerHTML = ''; - }; - - contextPrototype.beginPath = function() { - // TODO: Branch current matrix so that save/restore has no effect - // as per safari docs. - this.currentPath_ = []; - }; - - contextPrototype.moveTo = function(aX, aY) { - var p = getCoords(this, aX, aY); - this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); - this.currentX_ = p.x; - this.currentY_ = p.y; - }; - - contextPrototype.lineTo = function(aX, aY) { - var p = getCoords(this, aX, aY); - this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); - - this.currentX_ = p.x; - this.currentY_ = p.y; - }; - - contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, - aCP2x, aCP2y, - aX, aY) { - var p = getCoords(this, aX, aY); - var cp1 = getCoords(this, aCP1x, aCP1y); - var cp2 = getCoords(this, aCP2x, aCP2y); - bezierCurveTo(this, cp1, cp2, p); - }; - - // Helper function that takes the already fixed cordinates. - function bezierCurveTo(self, cp1, cp2, p) { - self.currentPath_.push({ - type: 'bezierCurveTo', - cp1x: cp1.x, - cp1y: cp1.y, - cp2x: cp2.x, - cp2y: cp2.y, - x: p.x, - y: p.y - }); - self.currentX_ = p.x; - self.currentY_ = p.y; - } - - contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { - // the following is lifted almost directly from - // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes - - var cp = getCoords(this, aCPx, aCPy); - var p = getCoords(this, aX, aY); - - var cp1 = { - x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), - y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) - }; - var cp2 = { - x: cp1.x + (p.x - this.currentX_) / 3.0, - y: cp1.y + (p.y - this.currentY_) / 3.0 - }; - - bezierCurveTo(this, cp1, cp2, p); - }; - - contextPrototype.arc = function(aX, aY, aRadius, - aStartAngle, aEndAngle, aClockwise) { - aRadius *= Z; - var arcType = aClockwise ? 'at' : 'wa'; - - var xStart = aX + mc(aStartAngle) * aRadius - Z2; - var yStart = aY + ms(aStartAngle) * aRadius - Z2; - - var xEnd = aX + mc(aEndAngle) * aRadius - Z2; - var yEnd = aY + ms(aEndAngle) * aRadius - Z2; - - // IE won't render arches drawn counter clockwise if xStart == xEnd. - if ((abs(xStart - xEnd) < 10e-8) && !aClockwise) { - xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something - // that can be represented in binary - } - // IE won't render arches drawn clockwise if yStart is very close to yEnd. - if ((abs(yStart - yEnd) < 10e-8) && aClockwise) { - yStart -= 0.125; // Offset yStart by 1/80 of a pixel. Use something - // that can be represented in binary - } - - var p = getCoords(this, aX, aY); - var pStart = getCoords(this, xStart, yStart); - var pEnd = getCoords(this, xEnd, yEnd); - - this.currentPath_.push({type: arcType, - x: p.x, - y: p.y, - radius: aRadius, - xStart: pStart.x, - yStart: pStart.y, - xEnd: pEnd.x, - yEnd: pEnd.y}); - - }; - - contextPrototype.rect = function(aX, aY, aWidth, aHeight) { - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - }; - - contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { - var oldPath = this.currentPath_; - this.beginPath(); - - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - this.stroke(); - - this.currentPath_ = oldPath; - }; - - contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { - var oldPath = this.currentPath_; - this.beginPath(); - - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - this.fill(); - - this.currentPath_ = oldPath; - }; - - contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { - var gradient = new CanvasGradient_('gradient'); - gradient.x0_ = aX0; - gradient.y0_ = aY0; - gradient.x1_ = aX1; - gradient.y1_ = aY1; - return gradient; - }; - - contextPrototype.createRadialGradient = function(aX0, aY0, aR0, - aX1, aY1, aR1) { - var gradient = new CanvasGradient_('gradientradial'); - gradient.x0_ = aX0; - gradient.y0_ = aY0; - gradient.r0_ = aR0; - gradient.x1_ = aX1; - gradient.y1_ = aY1; - gradient.r1_ = aR1; - return gradient; - }; - - contextPrototype.drawImage = function(image, var_args) { - var dx, dy, dw, dh, sx, sy, sw, sh; - - // to find the original width we overide the width and height - var oldRuntimeWidth = image.runtimeStyle.width; - var oldRuntimeHeight = image.runtimeStyle.height; - image.runtimeStyle.width = 'auto'; - image.runtimeStyle.height = 'auto'; - - // get the original size - var w = image.width; - var h = image.height; - - // and remove overides - image.runtimeStyle.width = oldRuntimeWidth; - image.runtimeStyle.height = oldRuntimeHeight; - - if (arguments.length == 3) { - dx = arguments[1]; - dy = arguments[2]; - sx = sy = 0; - sw = dw = w; - sh = dh = h; - } else if (arguments.length == 5) { - dx = arguments[1]; - dy = arguments[2]; - dw = arguments[3]; - dh = arguments[4]; - sx = sy = 0; - sw = w; - sh = h; - } else if (arguments.length == 9) { - sx = arguments[1]; - sy = arguments[2]; - sw = arguments[3]; - sh = arguments[4]; - dx = arguments[5]; - dy = arguments[6]; - dw = arguments[7]; - dh = arguments[8]; - } else { - throw Error('Invalid number of arguments'); - } - - var d = getCoords(this, dx, dy); - - var w2 = sw / 2; - var h2 = sh / 2; - - var vmlStr = []; - - var W = 10; - var H = 10; - - // For some reason that I've now forgotten, using divs didn't work - vmlStr.push(' ' , - '', - ''); - - this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join('')); - }; - - contextPrototype.stroke = function(aFill) { - var lineStr = []; - var lineOpen = false; - - var W = 10; - var H = 10; - - lineStr.push(''); - - if (!aFill) { - appendStroke(this, lineStr); - } else { - appendFill(this, lineStr, min, max); - } - - lineStr.push(''); - - this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); - }; - - function appendStroke(ctx, lineStr) { - var a = processStyle(ctx.strokeStyle); - var color = a.color; - var opacity = a.alpha * ctx.globalAlpha; - var lineWidth = ctx.lineScale_ * ctx.lineWidth; - - // VML cannot correctly render a line if the width is less than 1px. - // In that case, we dilute the color to make the line look thinner. - if (lineWidth < 1) { - opacity *= lineWidth; - } - - lineStr.push( - '' - ); - } - - function appendFill(ctx, lineStr, min, max) { - var fillStyle = ctx.fillStyle; - var arcScaleX = ctx.arcScaleX_; - var arcScaleY = ctx.arcScaleY_; - var width = max.x - min.x; - var height = max.y - min.y; - if (fillStyle instanceof CanvasGradient_) { - // TODO: Gradients transformed with the transformation matrix. - var angle = 0; - var focus = {x: 0, y: 0}; - - // additional offset - var shift = 0; - // scale factor for offset - var expansion = 1; - - if (fillStyle.type_ == 'gradient') { - var x0 = fillStyle.x0_ / arcScaleX; - var y0 = fillStyle.y0_ / arcScaleY; - var x1 = fillStyle.x1_ / arcScaleX; - var y1 = fillStyle.y1_ / arcScaleY; - var p0 = getCoords(ctx, x0, y0); - var p1 = getCoords(ctx, x1, y1); - var dx = p1.x - p0.x; - var dy = p1.y - p0.y; - angle = Math.atan2(dx, dy) * 180 / Math.PI; - - // The angle should be a non-negative number. - if (angle < 0) { - angle += 360; - } - - // Very small angles produce an unexpected result because they are - // converted to a scientific notation string. - if (angle < 1e-6) { - angle = 0; - } - } else { - var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_); - focus = { - x: (p0.x - min.x) / width, - y: (p0.y - min.y) / height - }; - - width /= arcScaleX * Z; - height /= arcScaleY * Z; - var dimension = m.max(width, height); - shift = 2 * fillStyle.r0_ / dimension; - expansion = 2 * fillStyle.r1_ / dimension - shift; - } - - // We need to sort the color stops in ascending order by offset, - // otherwise IE won't interpret it correctly. - var stops = fillStyle.colors_; - stops.sort(function(cs1, cs2) { - return cs1.offset - cs2.offset; - }); - - var length = stops.length; - var color1 = stops[0].color; - var color2 = stops[length - 1].color; - var opacity1 = stops[0].alpha * ctx.globalAlpha; - var opacity2 = stops[length - 1].alpha * ctx.globalAlpha; - - var colors = []; - for (var i = 0; i < length; i++) { - var stop = stops[i]; - colors.push(stop.offset * expansion + shift + ' ' + stop.color); - } - - // When colors attribute is used, the meanings of opacity and o:opacity2 - // are reversed. - lineStr.push(''); - } else if (fillStyle instanceof CanvasPattern_) { - if (width && height) { - var deltaLeft = -min.x; - var deltaTop = -min.y; - lineStr.push(''); - } - } else { - var a = processStyle(ctx.fillStyle); - var color = a.color; - var opacity = a.alpha * ctx.globalAlpha; - lineStr.push(''); - } - } - - contextPrototype.fill = function() { - this.stroke(true); - }; - - contextPrototype.closePath = function() { - this.currentPath_.push({type: 'close'}); - }; - - function getCoords(ctx, aX, aY) { - var m = ctx.m_; - return { - x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, - y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 - }; - }; - - contextPrototype.save = function() { - var o = {}; - copyState(this, o); - this.aStack_.push(o); - this.mStack_.push(this.m_); - this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); - }; - - contextPrototype.restore = function() { - if (this.aStack_.length) { - copyState(this.aStack_.pop(), this); - this.m_ = this.mStack_.pop(); - } - }; - - function matrixIsFinite(m) { - return isFinite(m[0][0]) && isFinite(m[0][1]) && - isFinite(m[1][0]) && isFinite(m[1][1]) && - isFinite(m[2][0]) && isFinite(m[2][1]); - } - - function setM(ctx, m, updateLineScale) { - if (!matrixIsFinite(m)) { - return; - } - ctx.m_ = m; - - if (updateLineScale) { - // Get the line scale. - // Determinant of this.m_ means how much the area is enlarged by the - // transformation. So its square root can be used as a scale factor - // for width. - var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; - ctx.lineScale_ = sqrt(abs(det)); - } - } - - contextPrototype.translate = function(aX, aY) { - var m1 = [ - [1, 0, 0], - [0, 1, 0], - [aX, aY, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), false); - }; - - contextPrototype.rotate = function(aRot) { - var c = mc(aRot); - var s = ms(aRot); - - var m1 = [ - [c, s, 0], - [-s, c, 0], - [0, 0, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), false); - }; - - contextPrototype.scale = function(aX, aY) { - this.arcScaleX_ *= aX; - this.arcScaleY_ *= aY; - var m1 = [ - [aX, 0, 0], - [0, aY, 0], - [0, 0, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), true); - }; - - contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { - var m1 = [ - [m11, m12, 0], - [m21, m22, 0], - [dx, dy, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), true); - }; - - contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { - var m = [ - [m11, m12, 0], - [m21, m22, 0], - [dx, dy, 1] - ]; - - setM(this, m, true); - }; - - /** - * The text drawing function. - * The maxWidth argument isn't taken in account, since no browser supports - * it yet. - */ - contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) { - var m = this.m_, - delta = 1000, - left = 0, - right = delta, - offset = {x: 0, y: 0}, - lineStr = []; - - var fontStyle = getComputedStyle(processFontStyle(this.font), - this.element_); - - var fontStyleString = buildStyle(fontStyle); - - var elementStyle = this.element_.currentStyle; - var textAlign = this.textAlign.toLowerCase(); - switch (textAlign) { - case 'left': - case 'center': - case 'right': - break; - case 'end': - textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left'; - break; - case 'start': - textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left'; - break; - default: - textAlign = 'left'; - } - - // 1.75 is an arbitrary number, as there is no info about the text baseline - switch (this.textBaseline) { - case 'hanging': - case 'top': - offset.y = fontStyle.size / 1.75; - break; - case 'middle': - break; - default: - case null: - case 'alphabetic': - case 'ideographic': - case 'bottom': - offset.y = -fontStyle.size / 2.25; - break; - } - - switch(textAlign) { - case 'right': - left = delta; - right = 0.05; - break; - case 'center': - left = right = delta / 2; - break; - } - - var d = getCoords(this, x + offset.x, y + offset.y); - - lineStr.push(''); - - if (stroke) { - appendStroke(this, lineStr); - } else { - // TODO: Fix the min and max params. - appendFill(this, lineStr, {x: -left, y: 0}, - {x: right, y: fontStyle.size}); - } - - var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + - m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0'; - - var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z); - - lineStr.push('', - '', - ''); - - this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); - }; - - contextPrototype.fillText = function(text, x, y, maxWidth) { - this.drawText_(text, x, y, maxWidth, false); - }; - - contextPrototype.strokeText = function(text, x, y, maxWidth) { - this.drawText_(text, x, y, maxWidth, true); - }; - - contextPrototype.measureText = function(text) { - if (!this.textMeasureEl_) { - var s = ''; - this.element_.insertAdjacentHTML('beforeEnd', s); - this.textMeasureEl_ = this.element_.lastChild; - } - var doc = this.element_.ownerDocument; - this.textMeasureEl_.innerHTML = ''; - this.textMeasureEl_.style.font = this.font; - // Don't use innerHTML or innerText because they allow markup/whitespace. - this.textMeasureEl_.appendChild(doc.createTextNode(text)); - return {width: this.textMeasureEl_.offsetWidth}; - }; - - /******** STUBS ********/ - contextPrototype.clip = function() { - // TODO: Implement - }; - - contextPrototype.arcTo = function() { - // TODO: Implement - }; - - contextPrototype.createPattern = function(image, repetition) { - return new CanvasPattern_(image, repetition); - }; - - // Gradient / Pattern Stubs - function CanvasGradient_(aType) { - this.type_ = aType; - this.x0_ = 0; - this.y0_ = 0; - this.r0_ = 0; - this.x1_ = 0; - this.y1_ = 0; - this.r1_ = 0; - this.colors_ = []; - } - - CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { - aColor = processStyle(aColor); - this.colors_.push({offset: aOffset, - color: aColor.color, - alpha: aColor.alpha}); - }; - - function CanvasPattern_(image, repetition) { - assertImageIsValid(image); - switch (repetition) { - case 'repeat': - case null: - case '': - this.repetition_ = 'repeat'; - break - case 'repeat-x': - case 'repeat-y': - case 'no-repeat': - this.repetition_ = repetition; - break; - default: - throwException('SYNTAX_ERR'); - } - - this.src_ = image.src; - this.width_ = image.width; - this.height_ = image.height; - } - - function throwException(s) { - throw new DOMException_(s); - } - - function assertImageIsValid(img) { - if (!img || img.nodeType != 1 || img.tagName != 'IMG') { - throwException('TYPE_MISMATCH_ERR'); - } - if (img.readyState != 'complete') { - throwException('INVALID_STATE_ERR'); - } - } - - function DOMException_(s) { - this.code = this[s]; - this.message = s +': DOM Exception ' + this.code; - } - var p = DOMException_.prototype = new Error; - p.INDEX_SIZE_ERR = 1; - p.DOMSTRING_SIZE_ERR = 2; - p.HIERARCHY_REQUEST_ERR = 3; - p.WRONG_DOCUMENT_ERR = 4; - p.INVALID_CHARACTER_ERR = 5; - p.NO_DATA_ALLOWED_ERR = 6; - p.NO_MODIFICATION_ALLOWED_ERR = 7; - p.NOT_FOUND_ERR = 8; - p.NOT_SUPPORTED_ERR = 9; - p.INUSE_ATTRIBUTE_ERR = 10; - p.INVALID_STATE_ERR = 11; - p.SYNTAX_ERR = 12; - p.INVALID_MODIFICATION_ERR = 13; - p.NAMESPACE_ERR = 14; - p.INVALID_ACCESS_ERR = 15; - p.VALIDATION_ERR = 16; - p.TYPE_MISMATCH_ERR = 17; - - // set up externs - G_vmlCanvasManager = G_vmlCanvasManager_; - CanvasRenderingContext2D = CanvasRenderingContext2D_; - CanvasGradient = CanvasGradient_; - CanvasPattern = CanvasPattern_; - DOMException = DOMException_; -})(); - -} // if diff --git a/addons/web_graph/static/lib/flotr2/lib/imagediff.js b/addons/web_graph/static/lib/flotr2/lib/imagediff.js deleted file mode 100644 index ac1d2745018..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/imagediff.js +++ /dev/null @@ -1,343 +0,0 @@ -/*! imagediff.js 1.0.2 - * (c) 2011 Carl Sutherland, Humble Software Development - * imagediff.js is freely distributable under the MIT license. - * Thanks to Jacob Thornton for the node/amd integration bits. - * For details and documentation: - * https://github.com/HumbleSoftware/js-imagediff - */ -(function (name, definition) { - var root = this; - if (typeof module != 'undefined') { - module.exports = definition(); - } else if (typeof define == 'function' && typeof define.amd == 'object') { - define(definition); - } else { - root[name] = definition(root, name); - } -})('imagediff', function (root, name) { - - var - TYPE_ARRAY = '[object Array]', - TYPE_CANVAS = '[object HTMLCanvasElement]', - TYPE_CONTEXT = '[object CanvasRenderingContext2D]', - TYPE_IMAGE = '[object HTMLImageElement]', - - OBJECT = 'object', - UNDEFINED = 'undefined', - - canvas = getCanvas(), - context = canvas.getContext('2d'), - previous = root[name], - imagediff, jasmine; - - // Creation - function getCanvas (width, height) { - var - canvas = document.createElement('canvas'); - if (width) canvas.width = width; - if (height) canvas.height = height; - return canvas; - } - function getImageData (width, height) { - canvas.width = width; - canvas.height = height; - context.clearRect(0, 0, width, height); - return context.createImageData(width, height); - } - - - // Type Checking - function isImage (object) { - return isType(object, TYPE_IMAGE); - } - function isCanvas (object) { - return isType(object, TYPE_CANVAS); - } - function isContext (object) { - return isType(object, TYPE_CONTEXT); - } - function isImageData (object) { - var - imageData = getImageData(1, 1); - isImageData = function (object) { - return (object && imageData.constructor === object.constructor ? true : false); - }; - return isImageData(object); - } - function isImageType (object) { - return ( - isImage(object) || - isCanvas(object) || - isContext(object) || - isImageData(object) - ); - } - function isType (object, type) { - return typeof (object) === 'object' && Object.prototype.toString.apply(object) === type; - } - - - // Type Conversion - function copyImageData (imageData) { - var - height = imageData.height, - width = imageData.width; - canvas.width = width; - canvas.height = height; - context.putImageData(imageData, 0, 0); - return context.getImageData(0, 0, width, height); - } - function toImageData (object) { - if (isImage(object)) { return toImageDataFromImage(object); } - if (isCanvas(object)) { return toImageDataFromCanvas(object); } - if (isContext(object)) { return toImageDataFromContext(object); } - if (isImageData(object)) { return object; } - } - function toImageDataFromImage (image) { - var - height = image.height, - width = image.width; - canvas.width = width; - canvas.height = height; - context.clearRect(0, 0, width, height); - context.drawImage(image, 0, 0); - return context.getImageData(0, 0, width, height); - } - function toImageDataFromCanvas (canvas) { - var - height = canvas.height, - width = canvas.width, - context = canvas.getContext('2d'); - return context.getImageData(0, 0, width, height); - } - function toImageDataFromContext (context) { - var - canvas = context.canvas, - height = canvas.height, - width = canvas.width; - return context.getImageData(0, 0, width, height); - } - function toCanvas (object) { - var - data = toImageData(object), - canvas = getCanvas(data.width, data.height), - context = canvas.getContext('2d'); - - context.putImageData(data, 0, 0); - return canvas; - } - - - // ImageData Equality Operators - function equalWidth (a, b) { - return a.width === b.width; - } - function equalHeight (a, b) { - return a.height === b.height; - } - function equalDimensions (a, b) { - return equalHeight(a, b) && equalWidth(a, b); - } - function equal (a, b, tolerance) { - - var - aData = a.data, - bData = b.data, - length = aData.length, - tolerance = tolerance || 0, - i; - - if (!equalDimensions(a, b)) return false; - for (i = length; i--;) if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) return false; - - return true; - } - - - // Diff - function diff (a, b) { - return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b); - } - function diffEqual (a, b) { - - var - height = a.height, - width = a.width, - c = getImageData(width, height), // c = a - b - aData = a.data, - bData = b.data, - cData = c.data, - length = cData.length, - row, column, - i, j, k, v; - - for (i = 0; i < length; i += 4) { - cData[i] = Math.abs(aData[i] - bData[i]); - cData[i+1] = Math.abs(aData[i+1] - bData[i+1]); - cData[i+2] = Math.abs(aData[i+2] - bData[i+2]); - cData[i+3] = Math.abs(255 - aData[i+3] - bData[i+3]); - } - - return c; - } - function diffUnequal (a, b) { - - var - height = Math.max(a.height, b.height), - width = Math.max(a.width, b.width), - c = getImageData(width, height), // c = a - b - aData = a.data, - bData = b.data, - cData = c.data, - rowOffset, - columnOffset, - row, column, - i, j, k, v; - - - for (i = cData.length - 1; i > 0; i = i - 4) { - cData[i] = 255; - } - - // Add First Image - offsets(a); - for (row = a.height; row--;){ - for (column = a.width; column--;) { - i = 4 * ((row + rowOffset) * width + (column + columnOffset)); - j = 4 * (row * a.width + column); - cData[i+0] = aData[j+0]; // r - cData[i+1] = aData[j+1]; // g - cData[i+2] = aData[j+2]; // b - // cData[i+3] = aData[j+3]; // a - } - } - - // Subtract Second Image - offsets(b); - for (row = b.height; row--;){ - for (column = b.width; column--;) { - i = 4 * ((row + rowOffset) * width + (column + columnOffset)); - j = 4 * (row * b.width + column); - cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r - cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g - cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b - } - } - - // Helpers - function offsets (imageData) { - rowOffset = Math.floor((height - imageData.height) / 2); - columnOffset = Math.floor((width - imageData.width) / 2); - } - - return c; - } - - - // Validation - function checkType () { - var i; - for (i = 0; i < arguments.length; i++) { - if (!isImageType(arguments[i])) { - throw { - name : 'ImageTypeError', - message : 'Submitted object was not an image.' - }; - } - } - } - - - // Jasmine Matchers - function get (element, content) { - element = document.createElement(element); - if (element && content) { - element.innerHTML = content; - } - return element; - } - jasmine = { - - toBeImageData : function () { - return imagediff.isImageData(this.actual); - }, - - toImageDiffEqual : function (expected, tolerance) { - - this.message = function() { - - var - div = get('div'), - a = get('div', '
    Actual:
    '), - b = get('div', '
    Expected:
    '), - c = get('div', '
    Diff:
    '), - diff = imagediff.diff(this.actual, expected), - canvas = getCanvas(), - context; - - canvas.height = diff.height; - canvas.width = diff.width; - - context = canvas.getContext('2d'); - context.putImageData(diff, 0, 0); - - a.appendChild(toCanvas(this.actual)); - b.appendChild(toCanvas(expected)); - c.appendChild(canvas); - - div.appendChild(a); - div.appendChild(b); - div.appendChild(c); - - return [ - div, - "Expected not to be equal." - ]; - }; - - return imagediff.equal(this.actual, expected, tolerance); - } - }; - - // Definition - imagediff = { - - createCanvas : getCanvas, - createImageData : getImageData, - - isImage : isImage, - isCanvas : isCanvas, - isContext : isContext, - isImageData : isImageData, - isImageType : isImageType, - - toImageData : function (object) { - checkType(object); - if (isImageData(object)) { return copyImageData(object); } - return toImageData(object); - }, - - equal : function (a, b, tolerance) { - checkType(a, b); - a = toImageData(a); - b = toImageData(b); - return equal(a, b, tolerance); - }, - diff : function (a, b) { - checkType(a, b); - a = toImageData(a); - b = toImageData(b); - return diff(a, b); - }, - - jasmine : jasmine, - - // Compatibility - noConflict : function () { - root[name] = previous; - return imagediff; - } - }; - - return imagediff; -}); diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/MIT.LICENSE b/addons/web_graph/static/lib/flotr2/lib/jasmine/MIT.LICENSE deleted file mode 100644 index 7c435baaec8..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/jasmine/MIT.LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2008-2011 Pivotal Labs - -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. diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine-html.js b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine-html.js deleted file mode 100644 index 73834010f60..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine-html.js +++ /dev/null @@ -1,190 +0,0 @@ -jasmine.TrivialReporter = function(doc) { - this.document = doc || document; - this.suiteDivs = {}; - this.logRunningSpecs = false; -}; - -jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { - var el = document.createElement(type); - - for (var i = 2; i < arguments.length; i++) { - var child = arguments[i]; - - if (typeof child === 'string') { - el.appendChild(document.createTextNode(child)); - } else { - if (child) { el.appendChild(child); } - } - } - - for (var attr in attrs) { - if (attr == "className") { - el[attr] = attrs[attr]; - } else { - el.setAttribute(attr, attrs[attr]); - } - } - - return el; -}; - -jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { - var showPassed, showSkipped; - - this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, - this.createDom('div', { className: 'banner' }, - this.createDom('div', { className: 'logo' }, - this.createDom('span', { className: 'title' }, "Jasmine"), - this.createDom('span', { className: 'version' }, runner.env.versionString())), - this.createDom('div', { className: 'options' }, - "Show ", - showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), - this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), - showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), - this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") - ) - ), - - this.runnerDiv = this.createDom('div', { className: 'runner running' }, - this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), - this.runnerMessageSpan = this.createDom('span', {}, "Running..."), - this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) - ); - - this.document.body.appendChild(this.outerDiv); - - var suites = runner.suites(); - for (var i = 0; i < suites.length; i++) { - var suite = suites[i]; - var suiteDiv = this.createDom('div', { className: 'suite' }, - this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), - this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); - this.suiteDivs[suite.id] = suiteDiv; - var parentDiv = this.outerDiv; - if (suite.parentSuite) { - parentDiv = this.suiteDivs[suite.parentSuite.id]; - } - parentDiv.appendChild(suiteDiv); - } - - this.startedAt = new Date(); - - var self = this; - showPassed.onclick = function(evt) { - if (showPassed.checked) { - self.outerDiv.className += ' show-passed'; - } else { - self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); - } - }; - - showSkipped.onclick = function(evt) { - if (showSkipped.checked) { - self.outerDiv.className += ' show-skipped'; - } else { - self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); - } - }; -}; - -jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { - var results = runner.results(); - var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; - this.runnerDiv.setAttribute("class", className); - //do it twice for IE - this.runnerDiv.setAttribute("className", className); - var specs = runner.specs(); - var specCount = 0; - for (var i = 0; i < specs.length; i++) { - if (this.specFilter(specs[i])) { - specCount++; - } - } - var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); - message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; - this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); - - this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); -}; - -jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { - var results = suite.results(); - var status = results.passed() ? 'passed' : 'failed'; - if (results.totalCount === 0) { // todo: change this to check results.skipped - status = 'skipped'; - } - this.suiteDivs[suite.id].className += " " + status; -}; - -jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { - if (this.logRunningSpecs) { - this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); - } -}; - -jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { - var results = spec.results(); - var status = results.passed() ? 'passed' : 'failed'; - if (results.skipped) { - status = 'skipped'; - } - var specDiv = this.createDom('div', { className: 'spec ' + status }, - this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), - this.createDom('a', { - className: 'description', - href: '?spec=' + encodeURIComponent(spec.getFullName()), - title: spec.getFullName() - }, spec.description)); - - - var resultItems = results.getItems(); - var messagesDiv = this.createDom('div', { className: 'messages' }); - for (var i = 0; i < resultItems.length; i++) { - var result = resultItems[i]; - - if (result.type == 'log') { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); - } else if (result.type == 'expect' && result.passed && !result.passed()) { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); - - if (result.trace.stack) { - messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); - } - } - } - - if (messagesDiv.childNodes.length > 0) { - specDiv.appendChild(messagesDiv); - } - - this.suiteDivs[spec.suite.id].appendChild(specDiv); -}; - -jasmine.TrivialReporter.prototype.log = function() { - var console = jasmine.getGlobal().console; - if (console && console.log) { - if (console.log.apply) { - console.log.apply(console, arguments); - } else { - console.log(arguments); // ie fix: console.log.apply doesn't exist on ie - } - } -}; - -jasmine.TrivialReporter.prototype.getLocation = function() { - return this.document.location; -}; - -jasmine.TrivialReporter.prototype.specFilter = function(spec) { - var paramMap = {}; - var params = this.getLocation().search.substring(1).split('&'); - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); - } - - if (!paramMap.spec) { - return true; - } - return spec.getFullName().indexOf(paramMap.spec) === 0; -}; diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.css b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.css deleted file mode 100644 index 6583fe7c66d..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.css +++ /dev/null @@ -1,166 +0,0 @@ -body { - font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; -} - - -.jasmine_reporter a:visited, .jasmine_reporter a { - color: #303; -} - -.jasmine_reporter a:hover, .jasmine_reporter a:active { - color: blue; -} - -.run_spec { - float:right; - padding-right: 5px; - font-size: .8em; - text-decoration: none; -} - -.jasmine_reporter { - margin: 0 5px; -} - -.banner { - color: #303; - background-color: #fef; - padding: 5px; -} - -.logo { - float: left; - font-size: 1.1em; - padding-left: 5px; -} - -.logo .version { - font-size: .6em; - padding-left: 1em; -} - -.runner.running { - background-color: yellow; -} - - -.options { - text-align: right; - font-size: .8em; -} - - - - -.suite { - border: 1px outset gray; - margin: 5px 0; - padding-left: 1em; -} - -.suite .suite { - margin: 5px; -} - -.suite.passed { - background-color: #dfd; -} - -.suite.failed { - background-color: #fdd; -} - -.spec { - margin: 5px; - padding-left: 1em; - clear: both; -} - -.spec.failed, .spec.passed, .spec.skipped { - padding-bottom: 5px; - border: 1px solid gray; -} - -.spec.failed { - background-color: #fbb; - border-color: red; -} - -.spec.passed { - background-color: #bfb; - border-color: green; -} - -.spec.skipped { - background-color: #bbb; -} - -.messages { - border-left: 1px dashed gray; - padding-left: 1em; - padding-right: 1em; -} - -.passed { - background-color: #cfc; - display: none; -} - -.failed { - background-color: #fbb; -} - -.skipped { - color: #777; - background-color: #eee; - display: none; -} - - -/*.resultMessage {*/ - /*white-space: pre;*/ -/*}*/ - -.resultMessage span.result { - display: block; - line-height: 2em; - color: black; -} - -.resultMessage .mismatch { - color: black; -} - -.stackTrace { - white-space: pre; - font-size: .8em; - margin-left: 10px; - max-height: 5em; - overflow: auto; - border: 1px inset red; - padding: 1em; - background: #eef; -} - -.finished-at { - padding-left: 1em; - font-size: .6em; -} - -.show-passed .passed, -.show-skipped .skipped { - display: block; -} - - -#jasmine_content { - position:fixed; - right: 100%; -} - -.runner { - border: 1px solid gray; - display: block; - margin: 5px 0; - padding: 2px 0 2px 10px; -} diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.js b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.js deleted file mode 100644 index c3d2dc7d2d3..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.js +++ /dev/null @@ -1,2476 +0,0 @@ -var isCommonJS = typeof window == "undefined"; - -/** - * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. - * - * @namespace - */ -var jasmine = {}; -if (isCommonJS) exports.jasmine = jasmine; -/** - * @private - */ -jasmine.unimplementedMethod_ = function() { - throw new Error("unimplemented method"); -}; - -/** - * Use jasmine.undefined instead of undefined, since undefined is just - * a plain old variable and may be redefined by somebody else. - * - * @private - */ -jasmine.undefined = jasmine.___undefined___; - -/** - * Show diagnostic messages in the console if set to true - * - */ -jasmine.VERBOSE = false; - -/** - * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. - * - */ -jasmine.DEFAULT_UPDATE_INTERVAL = 250; - -/** - * Default timeout interval in milliseconds for waitsFor() blocks. - */ -jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; - -jasmine.getGlobal = function() { - function getGlobal() { - return this; - } - - return getGlobal(); -}; - -/** - * Allows for bound functions to be compared. Internal use only. - * - * @ignore - * @private - * @param base {Object} bound 'this' for the function - * @param name {Function} function to find - */ -jasmine.bindOriginal_ = function(base, name) { - var original = base[name]; - if (original.apply) { - return function() { - return original.apply(base, arguments); - }; - } else { - // IE support - return jasmine.getGlobal()[name]; - } -}; - -jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); -jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); -jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); -jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); - -jasmine.MessageResult = function(values) { - this.type = 'log'; - this.values = values; - this.trace = new Error(); // todo: test better -}; - -jasmine.MessageResult.prototype.toString = function() { - var text = ""; - for (var i = 0; i < this.values.length; i++) { - if (i > 0) text += " "; - if (jasmine.isString_(this.values[i])) { - text += this.values[i]; - } else { - text += jasmine.pp(this.values[i]); - } - } - return text; -}; - -jasmine.ExpectationResult = function(params) { - this.type = 'expect'; - this.matcherName = params.matcherName; - this.passed_ = params.passed; - this.expected = params.expected; - this.actual = params.actual; - this.message = this.passed_ ? 'Passed.' : params.message; - - var trace = (params.trace || new Error(this.message)); - this.trace = this.passed_ ? '' : trace; -}; - -jasmine.ExpectationResult.prototype.toString = function () { - return this.message; -}; - -jasmine.ExpectationResult.prototype.passed = function () { - return this.passed_; -}; - -/** - * Getter for the Jasmine environment. Ensures one gets created - */ -jasmine.getEnv = function() { - var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); - return env; -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isArray_ = function(value) { - return jasmine.isA_("Array", value); -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isString_ = function(value) { - return jasmine.isA_("String", value); -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isNumber_ = function(value) { - return jasmine.isA_("Number", value); -}; - -/** - * @ignore - * @private - * @param {String} typeName - * @param value - * @returns {Boolean} - */ -jasmine.isA_ = function(typeName, value) { - return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; -}; - -/** - * Pretty printer for expecations. Takes any object and turns it into a human-readable string. - * - * @param value {Object} an object to be outputted - * @returns {String} - */ -jasmine.pp = function(value) { - var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); - stringPrettyPrinter.format(value); - return stringPrettyPrinter.string; -}; - -/** - * Returns true if the object is a DOM Node. - * - * @param {Object} obj object to check - * @returns {Boolean} - */ -jasmine.isDomNode = function(obj) { - return obj.nodeType > 0; -}; - -/** - * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. - * - * @example - * // don't care about which function is passed in, as long as it's a function - * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); - * - * @param {Class} clazz - * @returns matchable object of the type clazz - */ -jasmine.any = function(clazz) { - return new jasmine.Matchers.Any(clazz); -}; - -/** - * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. - * - * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine - * expectation syntax. Spies can be checked if they were called or not and what the calling params were. - * - * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). - * - * Spies are torn down at the end of every spec. - * - * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. - * - * @example - * // a stub - * var myStub = jasmine.createSpy('myStub'); // can be used anywhere - * - * // spy example - * var foo = { - * not: function(bool) { return !bool; } - * } - * - * // actual foo.not will not be called, execution stops - * spyOn(foo, 'not'); - - // foo.not spied upon, execution will continue to implementation - * spyOn(foo, 'not').andCallThrough(); - * - * // fake example - * var foo = { - * not: function(bool) { return !bool; } - * } - * - * // foo.not(val) will return val - * spyOn(foo, 'not').andCallFake(function(value) {return value;}); - * - * // mock example - * foo.not(7 == 7); - * expect(foo.not).toHaveBeenCalled(); - * expect(foo.not).toHaveBeenCalledWith(true); - * - * @constructor - * @see spyOn, jasmine.createSpy, jasmine.createSpyObj - * @param {String} name - */ -jasmine.Spy = function(name) { - /** - * The name of the spy, if provided. - */ - this.identity = name || 'unknown'; - /** - * Is this Object a spy? - */ - this.isSpy = true; - /** - * The actual function this spy stubs. - */ - this.plan = function() { - }; - /** - * Tracking of the most recent call to the spy. - * @example - * var mySpy = jasmine.createSpy('foo'); - * mySpy(1, 2); - * mySpy.mostRecentCall.args = [1, 2]; - */ - this.mostRecentCall = {}; - - /** - * Holds arguments for each call to the spy, indexed by call count - * @example - * var mySpy = jasmine.createSpy('foo'); - * mySpy(1, 2); - * mySpy(7, 8); - * mySpy.mostRecentCall.args = [7, 8]; - * mySpy.argsForCall[0] = [1, 2]; - * mySpy.argsForCall[1] = [7, 8]; - */ - this.argsForCall = []; - this.calls = []; -}; - -/** - * Tells a spy to call through to the actual implemenatation. - * - * @example - * var foo = { - * bar: function() { // do some stuff } - * } - * - * // defining a spy on an existing property: foo.bar - * spyOn(foo, 'bar').andCallThrough(); - */ -jasmine.Spy.prototype.andCallThrough = function() { - this.plan = this.originalValue; - return this; -}; - -/** - * For setting the return value of a spy. - * - * @example - * // defining a spy from scratch: foo() returns 'baz' - * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); - * - * // defining a spy on an existing property: foo.bar() returns 'baz' - * spyOn(foo, 'bar').andReturn('baz'); - * - * @param {Object} value - */ -jasmine.Spy.prototype.andReturn = function(value) { - this.plan = function() { - return value; - }; - return this; -}; - -/** - * For throwing an exception when a spy is called. - * - * @example - * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' - * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); - * - * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' - * spyOn(foo, 'bar').andThrow('baz'); - * - * @param {String} exceptionMsg - */ -jasmine.Spy.prototype.andThrow = function(exceptionMsg) { - this.plan = function() { - throw exceptionMsg; - }; - return this; -}; - -/** - * Calls an alternate implementation when a spy is called. - * - * @example - * var baz = function() { - * // do some stuff, return something - * } - * // defining a spy from scratch: foo() calls the function baz - * var foo = jasmine.createSpy('spy on foo').andCall(baz); - * - * // defining a spy on an existing property: foo.bar() calls an anonymnous function - * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); - * - * @param {Function} fakeFunc - */ -jasmine.Spy.prototype.andCallFake = function(fakeFunc) { - this.plan = fakeFunc; - return this; -}; - -/** - * Resets all of a spy's the tracking variables so that it can be used again. - * - * @example - * spyOn(foo, 'bar'); - * - * foo.bar(); - * - * expect(foo.bar.callCount).toEqual(1); - * - * foo.bar.reset(); - * - * expect(foo.bar.callCount).toEqual(0); - */ -jasmine.Spy.prototype.reset = function() { - this.wasCalled = false; - this.callCount = 0; - this.argsForCall = []; - this.calls = []; - this.mostRecentCall = {}; -}; - -jasmine.createSpy = function(name) { - - var spyObj = function() { - spyObj.wasCalled = true; - spyObj.callCount++; - var args = jasmine.util.argsToArray(arguments); - spyObj.mostRecentCall.object = this; - spyObj.mostRecentCall.args = args; - spyObj.argsForCall.push(args); - spyObj.calls.push({object: this, args: args}); - return spyObj.plan.apply(this, arguments); - }; - - var spy = new jasmine.Spy(name); - - for (var prop in spy) { - spyObj[prop] = spy[prop]; - } - - spyObj.reset(); - - return spyObj; -}; - -/** - * Determines whether an object is a spy. - * - * @param {jasmine.Spy|Object} putativeSpy - * @returns {Boolean} - */ -jasmine.isSpy = function(putativeSpy) { - return putativeSpy && putativeSpy.isSpy; -}; - -/** - * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something - * large in one call. - * - * @param {String} baseName name of spy class - * @param {Array} methodNames array of names of methods to make spies - */ -jasmine.createSpyObj = function(baseName, methodNames) { - if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { - throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); - } - var obj = {}; - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); - } - return obj; -}; - -/** - * All parameters are pretty-printed and concatenated together, then written to the current spec's output. - * - * Be careful not to leave calls to jasmine.log in production code. - */ -jasmine.log = function() { - var spec = jasmine.getEnv().currentSpec; - spec.log.apply(spec, arguments); -}; - -/** - * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. - * - * @example - * // spy example - * var foo = { - * not: function(bool) { return !bool; } - * } - * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops - * - * @see jasmine.createSpy - * @param obj - * @param methodName - * @returns a Jasmine spy that can be chained with all spy methods - */ -var spyOn = function(obj, methodName) { - return jasmine.getEnv().currentSpec.spyOn(obj, methodName); -}; -if (isCommonJS) exports.spyOn = spyOn; - -/** - * Creates a Jasmine spec that will be added to the current suite. - * - * // TODO: pending tests - * - * @example - * it('should be true', function() { - * expect(true).toEqual(true); - * }); - * - * @param {String} desc description of this specification - * @param {Function} func defines the preconditions and expectations of the spec - */ -var it = function(desc, func) { - return jasmine.getEnv().it(desc, func); -}; -if (isCommonJS) exports.it = it; - -/** - * Creates a disabled Jasmine spec. - * - * A convenience method that allows existing specs to be disabled temporarily during development. - * - * @param {String} desc description of this specification - * @param {Function} func defines the preconditions and expectations of the spec - */ -var xit = function(desc, func) { - return jasmine.getEnv().xit(desc, func); -}; -if (isCommonJS) exports.xit = xit; - -/** - * Starts a chain for a Jasmine expectation. - * - * It is passed an Object that is the actual value and should chain to one of the many - * jasmine.Matchers functions. - * - * @param {Object} actual Actual value to test against and expected value - */ -var expect = function(actual) { - return jasmine.getEnv().currentSpec.expect(actual); -}; -if (isCommonJS) exports.expect = expect; - -/** - * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. - * - * @param {Function} func Function that defines part of a jasmine spec. - */ -var runs = function(func) { - jasmine.getEnv().currentSpec.runs(func); -}; -if (isCommonJS) exports.runs = runs; - -/** - * Waits a fixed time period before moving to the next block. - * - * @deprecated Use waitsFor() instead - * @param {Number} timeout milliseconds to wait - */ -var waits = function(timeout) { - jasmine.getEnv().currentSpec.waits(timeout); -}; -if (isCommonJS) exports.waits = waits; - -/** - * Waits for the latchFunction to return true before proceeding to the next block. - * - * @param {Function} latchFunction - * @param {String} optional_timeoutMessage - * @param {Number} optional_timeout - */ -var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { - jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); -}; -if (isCommonJS) exports.waitsFor = waitsFor; - -/** - * A function that is called before each spec in a suite. - * - * Used for spec setup, including validating assumptions. - * - * @param {Function} beforeEachFunction - */ -var beforeEach = function(beforeEachFunction) { - jasmine.getEnv().beforeEach(beforeEachFunction); -}; -if (isCommonJS) exports.beforeEach = beforeEach; - -/** - * A function that is called after each spec in a suite. - * - * Used for restoring any state that is hijacked during spec execution. - * - * @param {Function} afterEachFunction - */ -var afterEach = function(afterEachFunction) { - jasmine.getEnv().afterEach(afterEachFunction); -}; -if (isCommonJS) exports.afterEach = afterEach; - -/** - * Defines a suite of specifications. - * - * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared - * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization - * of setup in some tests. - * - * @example - * // TODO: a simple suite - * - * // TODO: a simple suite with a nested describe block - * - * @param {String} description A string, usually the class under test. - * @param {Function} specDefinitions function that defines several specs. - */ -var describe = function(description, specDefinitions) { - return jasmine.getEnv().describe(description, specDefinitions); -}; -if (isCommonJS) exports.describe = describe; - -/** - * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. - * - * @param {String} description A string, usually the class under test. - * @param {Function} specDefinitions function that defines several specs. - */ -var xdescribe = function(description, specDefinitions) { - return jasmine.getEnv().xdescribe(description, specDefinitions); -}; -if (isCommonJS) exports.xdescribe = xdescribe; - - -// Provide the XMLHttpRequest class for IE 5.x-6.x: -jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { - function tryIt(f) { - try { - return f(); - } catch(e) { - } - return null; - } - - var xhr = tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP.6.0"); - }) || - tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP.3.0"); - }) || - tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP"); - }) || - tryIt(function() { - return new ActiveXObject("Microsoft.XMLHTTP"); - }); - - if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); - - return xhr; -} : XMLHttpRequest; -/** - * @namespace - */ -jasmine.util = {}; - -/** - * Declare that a child class inherit it's prototype from the parent class. - * - * @private - * @param {Function} childClass - * @param {Function} parentClass - */ -jasmine.util.inherit = function(childClass, parentClass) { - /** - * @private - */ - var subclass = function() { - }; - subclass.prototype = parentClass.prototype; - childClass.prototype = new subclass(); -}; - -jasmine.util.formatException = function(e) { - var lineNumber; - if (e.line) { - lineNumber = e.line; - } - else if (e.lineNumber) { - lineNumber = e.lineNumber; - } - - var file; - - if (e.sourceURL) { - file = e.sourceURL; - } - else if (e.fileName) { - file = e.fileName; - } - - var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); - - if (file && lineNumber) { - message += ' in ' + file + ' (line ' + lineNumber + ')'; - } - - return message; -}; - -jasmine.util.htmlEscape = function(str) { - if (!str) return str; - return str.replace(/&/g, '&') - .replace(//g, '>'); -}; - -jasmine.util.argsToArray = function(args) { - var arrayOfArgs = []; - for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); - return arrayOfArgs; -}; - -jasmine.util.extend = function(destination, source) { - for (var property in source) destination[property] = source[property]; - return destination; -}; - -/** - * Environment for Jasmine - * - * @constructor - */ -jasmine.Env = function() { - this.currentSpec = null; - this.currentSuite = null; - this.currentRunner_ = new jasmine.Runner(this); - - this.reporter = new jasmine.MultiReporter(); - - this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; - this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; - this.lastUpdate = 0; - this.specFilter = function() { - return true; - }; - - this.nextSpecId_ = 0; - this.nextSuiteId_ = 0; - this.equalityTesters_ = []; - - // wrap matchers - this.matchersClass = function() { - jasmine.Matchers.apply(this, arguments); - }; - jasmine.util.inherit(this.matchersClass, jasmine.Matchers); - - jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); -}; - - -jasmine.Env.prototype.setTimeout = jasmine.setTimeout; -jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; -jasmine.Env.prototype.setInterval = jasmine.setInterval; -jasmine.Env.prototype.clearInterval = jasmine.clearInterval; - -/** - * @returns an object containing jasmine version build info, if set. - */ -jasmine.Env.prototype.version = function () { - if (jasmine.version_) { - return jasmine.version_; - } else { - throw new Error('Version not set'); - } -}; - -/** - * @returns string containing jasmine version build info, if set. - */ -jasmine.Env.prototype.versionString = function() { - if (!jasmine.version_) { - return "version unknown"; - } - - var version = this.version(); - var versionString = version.major + "." + version.minor + "." + version.build; - if (version.release_candidate) { - versionString += ".rc" + version.release_candidate; - } - versionString += " revision " + version.revision; - return versionString; -}; - -/** - * @returns a sequential integer starting at 0 - */ -jasmine.Env.prototype.nextSpecId = function () { - return this.nextSpecId_++; -}; - -/** - * @returns a sequential integer starting at 0 - */ -jasmine.Env.prototype.nextSuiteId = function () { - return this.nextSuiteId_++; -}; - -/** - * Register a reporter to receive status updates from Jasmine. - * @param {jasmine.Reporter} reporter An object which will receive status updates. - */ -jasmine.Env.prototype.addReporter = function(reporter) { - this.reporter.addReporter(reporter); -}; - -jasmine.Env.prototype.execute = function() { - this.currentRunner_.execute(); -}; - -jasmine.Env.prototype.describe = function(description, specDefinitions) { - var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); - - var parentSuite = this.currentSuite; - if (parentSuite) { - parentSuite.add(suite); - } else { - this.currentRunner_.add(suite); - } - - this.currentSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch(e) { - declarationError = e; - } - - if (declarationError) { - this.it("encountered a declaration exception", function() { - throw declarationError; - }); - } - - this.currentSuite = parentSuite; - - return suite; -}; - -jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { - if (this.currentSuite) { - this.currentSuite.beforeEach(beforeEachFunction); - } else { - this.currentRunner_.beforeEach(beforeEachFunction); - } -}; - -jasmine.Env.prototype.currentRunner = function () { - return this.currentRunner_; -}; - -jasmine.Env.prototype.afterEach = function(afterEachFunction) { - if (this.currentSuite) { - this.currentSuite.afterEach(afterEachFunction); - } else { - this.currentRunner_.afterEach(afterEachFunction); - } - -}; - -jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { - return { - execute: function() { - } - }; -}; - -jasmine.Env.prototype.it = function(description, func) { - var spec = new jasmine.Spec(this, this.currentSuite, description); - this.currentSuite.add(spec); - this.currentSpec = spec; - - if (func) { - spec.runs(func); - } - - return spec; -}; - -jasmine.Env.prototype.xit = function(desc, func) { - return { - id: this.nextSpecId(), - runs: function() { - } - }; -}; - -jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { - if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { - return true; - } - - a.__Jasmine_been_here_before__ = b; - b.__Jasmine_been_here_before__ = a; - - var hasKey = function(obj, keyName) { - return obj !== null && obj[keyName] !== jasmine.undefined; - }; - - for (var property in b) { - if (!hasKey(a, property) && hasKey(b, property)) { - mismatchKeys.push("expected has key '" + property + "', but missing from actual."); - } - } - for (property in a) { - if (!hasKey(b, property) && hasKey(a, property)) { - mismatchKeys.push("expected missing key '" + property + "', but present in actual."); - } - } - for (property in b) { - if (property == '__Jasmine_been_here_before__') continue; - if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { - mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); - } - } - - if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { - mismatchValues.push("arrays were not the same length"); - } - - delete a.__Jasmine_been_here_before__; - delete b.__Jasmine_been_here_before__; - return (mismatchKeys.length === 0 && mismatchValues.length === 0); -}; - -jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { - mismatchKeys = mismatchKeys || []; - mismatchValues = mismatchValues || []; - - for (var i = 0; i < this.equalityTesters_.length; i++) { - var equalityTester = this.equalityTesters_[i]; - var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); - if (result !== jasmine.undefined) return result; - } - - if (a === b) return true; - - if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { - return (a == jasmine.undefined && b == jasmine.undefined); - } - - if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { - return a === b; - } - - if (a instanceof Date && b instanceof Date) { - return a.getTime() == b.getTime(); - } - - if (a instanceof jasmine.Matchers.Any) { - return a.matches(b); - } - - if (b instanceof jasmine.Matchers.Any) { - return b.matches(a); - } - - if (jasmine.isString_(a) && jasmine.isString_(b)) { - return (a == b); - } - - if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { - return (a == b); - } - - if (typeof a === "object" && typeof b === "object") { - return this.compareObjects_(a, b, mismatchKeys, mismatchValues); - } - - //Straight check - return (a === b); -}; - -jasmine.Env.prototype.contains_ = function(haystack, needle) { - if (jasmine.isArray_(haystack)) { - for (var i = 0; i < haystack.length; i++) { - if (this.equals_(haystack[i], needle)) return true; - } - return false; - } - return haystack.indexOf(needle) >= 0; -}; - -jasmine.Env.prototype.addEqualityTester = function(equalityTester) { - this.equalityTesters_.push(equalityTester); -}; -/** No-op base class for Jasmine reporters. - * - * @constructor - */ -jasmine.Reporter = function() { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportRunnerResults = function(runner) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSuiteResults = function(suite) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSpecStarting = function(spec) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSpecResults = function(spec) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.log = function(str) { -}; - -/** - * Blocks are functions with executable code that make up a spec. - * - * @constructor - * @param {jasmine.Env} env - * @param {Function} func - * @param {jasmine.Spec} spec - */ -jasmine.Block = function(env, func, spec) { - this.env = env; - this.func = func; - this.spec = spec; -}; - -jasmine.Block.prototype.execute = function(onComplete) { - try { - this.func.apply(this.spec); - } catch (e) { - this.spec.fail(e); - } - onComplete(); -}; -/** JavaScript API reporter. - * - * @constructor - */ -jasmine.JsApiReporter = function() { - this.started = false; - this.finished = false; - this.suites_ = []; - this.results_ = {}; -}; - -jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { - this.started = true; - var suites = runner.topLevelSuites(); - for (var i = 0; i < suites.length; i++) { - var suite = suites[i]; - this.suites_.push(this.summarize_(suite)); - } -}; - -jasmine.JsApiReporter.prototype.suites = function() { - return this.suites_; -}; - -jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { - var isSuite = suiteOrSpec instanceof jasmine.Suite; - var summary = { - id: suiteOrSpec.id, - name: suiteOrSpec.description, - type: isSuite ? 'suite' : 'spec', - children: [] - }; - - if (isSuite) { - var children = suiteOrSpec.children(); - for (var i = 0; i < children.length; i++) { - summary.children.push(this.summarize_(children[i])); - } - } - return summary; -}; - -jasmine.JsApiReporter.prototype.results = function() { - return this.results_; -}; - -jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { - return this.results_[specId]; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { - this.finished = true; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { - this.results_[spec.id] = { - messages: spec.results().getItems(), - result: spec.results().failedCount > 0 ? "failed" : "passed" - }; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.log = function(str) { -}; - -jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ - var results = {}; - for (var i = 0; i < specIds.length; i++) { - var specId = specIds[i]; - results[specId] = this.summarizeResult_(this.results_[specId]); - } - return results; -}; - -jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ - var summaryMessages = []; - var messagesLength = result.messages.length; - for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { - var resultMessage = result.messages[messageIndex]; - summaryMessages.push({ - text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, - passed: resultMessage.passed ? resultMessage.passed() : true, - type: resultMessage.type, - message: resultMessage.message, - trace: { - stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined - } - }); - } - - return { - result : result.result, - messages : summaryMessages - }; -}; - -/** - * @constructor - * @param {jasmine.Env} env - * @param actual - * @param {jasmine.Spec} spec - */ -jasmine.Matchers = function(env, actual, spec, opt_isNot) { - this.env = env; - this.actual = actual; - this.spec = spec; - this.isNot = opt_isNot || false; - this.reportWasCalled_ = false; -}; - -// todo: @deprecated as of Jasmine 0.11, remove soon [xw] -jasmine.Matchers.pp = function(str) { - throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); -}; - -// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] -jasmine.Matchers.prototype.report = function(result, failing_message, details) { - throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); -}; - -jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { - for (var methodName in prototype) { - if (methodName == 'report') continue; - var orig = prototype[methodName]; - matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); - } -}; - -jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { - return function() { - var matcherArgs = jasmine.util.argsToArray(arguments); - var result = matcherFunction.apply(this, arguments); - - if (this.isNot) { - result = !result; - } - - if (this.reportWasCalled_) return result; - - var message; - if (!result) { - if (this.message) { - message = this.message.apply(this, arguments); - if (jasmine.isArray_(message)) { - message = message[this.isNot ? 1 : 0]; - } - } else { - var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; - if (matcherArgs.length > 0) { - for (var i = 0; i < matcherArgs.length; i++) { - if (i > 0) message += ","; - message += " " + jasmine.pp(matcherArgs[i]); - } - } - message += "."; - } - } - var expectationResult = new jasmine.ExpectationResult({ - matcherName: matcherName, - passed: result, - expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], - actual: this.actual, - message: message - }); - this.spec.addMatcherResult(expectationResult); - return jasmine.undefined; - }; -}; - - - - -/** - * toBe: compares the actual to the expected using === - * @param expected - */ -jasmine.Matchers.prototype.toBe = function(expected) { - return this.actual === expected; -}; - -/** - * toNotBe: compares the actual to the expected using !== - * @param expected - * @deprecated as of 1.0. Use not.toBe() instead. - */ -jasmine.Matchers.prototype.toNotBe = function(expected) { - return this.actual !== expected; -}; - -/** - * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. - * - * @param expected - */ -jasmine.Matchers.prototype.toEqual = function(expected) { - return this.env.equals_(this.actual, expected); -}; - -/** - * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual - * @param expected - * @deprecated as of 1.0. Use not.toNotEqual() instead. - */ -jasmine.Matchers.prototype.toNotEqual = function(expected) { - return !this.env.equals_(this.actual, expected); -}; - -/** - * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes - * a pattern or a String. - * - * @param expected - */ -jasmine.Matchers.prototype.toMatch = function(expected) { - return new RegExp(expected).test(this.actual); -}; - -/** - * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch - * @param expected - * @deprecated as of 1.0. Use not.toMatch() instead. - */ -jasmine.Matchers.prototype.toNotMatch = function(expected) { - return !(new RegExp(expected).test(this.actual)); -}; - -/** - * Matcher that compares the actual to jasmine.undefined. - */ -jasmine.Matchers.prototype.toBeDefined = function() { - return (this.actual !== jasmine.undefined); -}; - -/** - * Matcher that compares the actual to jasmine.undefined. - */ -jasmine.Matchers.prototype.toBeUndefined = function() { - return (this.actual === jasmine.undefined); -}; - -/** - * Matcher that compares the actual to null. - */ -jasmine.Matchers.prototype.toBeNull = function() { - return (this.actual === null); -}; - -/** - * Matcher that boolean not-nots the actual. - */ -jasmine.Matchers.prototype.toBeTruthy = function() { - return !!this.actual; -}; - - -/** - * Matcher that boolean nots the actual. - */ -jasmine.Matchers.prototype.toBeFalsy = function() { - return !this.actual; -}; - - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was called. - */ -jasmine.Matchers.prototype.toHaveBeenCalled = function() { - if (arguments.length > 0) { - throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); - } - - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy " + this.actual.identity + " to have been called.", - "Expected spy " + this.actual.identity + " not to have been called." - ]; - }; - - return this.actual.wasCalled; -}; - -/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ -jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was not called. - * - * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead - */ -jasmine.Matchers.prototype.wasNotCalled = function() { - if (arguments.length > 0) { - throw new Error('wasNotCalled does not take arguments'); - } - - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy " + this.actual.identity + " to not have been called.", - "Expected spy " + this.actual.identity + " to have been called." - ]; - }; - - return !this.actual.wasCalled; -}; - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. - * - * @example - * - */ -jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { - var expectedArgs = jasmine.util.argsToArray(arguments); - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - this.message = function() { - if (this.actual.callCount === 0) { - // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] - return [ - "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", - "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." - ]; - } else { - return [ - "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), - "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) - ]; - } - }; - - return this.env.contains_(this.actual.argsForCall, expectedArgs); -}; - -/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ -jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; - -/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ -jasmine.Matchers.prototype.wasNotCalledWith = function() { - var expectedArgs = jasmine.util.argsToArray(arguments); - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", - "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" - ]; - }; - - return !this.env.contains_(this.actual.argsForCall, expectedArgs); -}; - -/** - * Matcher that checks that the expected item is an element in the actual Array. - * - * @param {Object} expected - */ -jasmine.Matchers.prototype.toContain = function(expected) { - return this.env.contains_(this.actual, expected); -}; - -/** - * Matcher that checks that the expected item is NOT an element in the actual Array. - * - * @param {Object} expected - * @deprecated as of 1.0. Use not.toNotContain() instead. - */ -jasmine.Matchers.prototype.toNotContain = function(expected) { - return !this.env.contains_(this.actual, expected); -}; - -jasmine.Matchers.prototype.toBeLessThan = function(expected) { - return this.actual < expected; -}; - -jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { - return this.actual > expected; -}; - -/** - * Matcher that checks that the expected item is equal to the actual item - * up to a given level of decimal precision (default 2). - * - * @param {Number} expected - * @param {Number} precision - */ -jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { - if (!(precision === 0)) { - precision = precision || 2; - } - var multiplier = Math.pow(10, precision); - var actual = Math.round(this.actual * multiplier); - expected = Math.round(expected * multiplier); - return expected == actual; -}; - -/** - * Matcher that checks that the expected exception was thrown by the actual. - * - * @param {String} expected - */ -jasmine.Matchers.prototype.toThrow = function(expected) { - var result = false; - var exception; - if (typeof this.actual != 'function') { - throw new Error('Actual is not a function'); - } - try { - this.actual(); - } catch (e) { - exception = e; - } - if (exception) { - result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); - } - - var not = this.isNot ? "not " : ""; - - this.message = function() { - if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { - return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); - } else { - return "Expected function to throw an exception."; - } - }; - - return result; -}; - -jasmine.Matchers.Any = function(expectedClass) { - this.expectedClass = expectedClass; -}; - -jasmine.Matchers.Any.prototype.matches = function(other) { - if (this.expectedClass == String) { - return typeof other == 'string' || other instanceof String; - } - - if (this.expectedClass == Number) { - return typeof other == 'number' || other instanceof Number; - } - - if (this.expectedClass == Function) { - return typeof other == 'function' || other instanceof Function; - } - - if (this.expectedClass == Object) { - return typeof other == 'object'; - } - - return other instanceof this.expectedClass; -}; - -jasmine.Matchers.Any.prototype.toString = function() { - return ''; -}; - -/** - * @constructor - */ -jasmine.MultiReporter = function() { - this.subReporters_ = []; -}; -jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); - -jasmine.MultiReporter.prototype.addReporter = function(reporter) { - this.subReporters_.push(reporter); -}; - -(function() { - var functionNames = [ - "reportRunnerStarting", - "reportRunnerResults", - "reportSuiteResults", - "reportSpecStarting", - "reportSpecResults", - "log" - ]; - for (var i = 0; i < functionNames.length; i++) { - var functionName = functionNames[i]; - jasmine.MultiReporter.prototype[functionName] = (function(functionName) { - return function() { - for (var j = 0; j < this.subReporters_.length; j++) { - var subReporter = this.subReporters_[j]; - if (subReporter[functionName]) { - subReporter[functionName].apply(subReporter, arguments); - } - } - }; - })(functionName); - } -})(); -/** - * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults - * - * @constructor - */ -jasmine.NestedResults = function() { - /** - * The total count of results - */ - this.totalCount = 0; - /** - * Number of passed results - */ - this.passedCount = 0; - /** - * Number of failed results - */ - this.failedCount = 0; - /** - * Was this suite/spec skipped? - */ - this.skipped = false; - /** - * @ignore - */ - this.items_ = []; -}; - -/** - * Roll up the result counts. - * - * @param result - */ -jasmine.NestedResults.prototype.rollupCounts = function(result) { - this.totalCount += result.totalCount; - this.passedCount += result.passedCount; - this.failedCount += result.failedCount; -}; - -/** - * Adds a log message. - * @param values Array of message parts which will be concatenated later. - */ -jasmine.NestedResults.prototype.log = function(values) { - this.items_.push(new jasmine.MessageResult(values)); -}; - -/** - * Getter for the results: message & results. - */ -jasmine.NestedResults.prototype.getItems = function() { - return this.items_; -}; - -/** - * Adds a result, tracking counts (total, passed, & failed) - * @param {jasmine.ExpectationResult|jasmine.NestedResults} result - */ -jasmine.NestedResults.prototype.addResult = function(result) { - if (result.type != 'log') { - if (result.items_) { - this.rollupCounts(result); - } else { - this.totalCount++; - if (result.passed()) { - this.passedCount++; - } else { - this.failedCount++; - } - } - } - this.items_.push(result); -}; - -/** - * @returns {Boolean} True if everything below passed - */ -jasmine.NestedResults.prototype.passed = function() { - return this.passedCount === this.totalCount; -}; -/** - * Base class for pretty printing for expectation results. - */ -jasmine.PrettyPrinter = function() { - this.ppNestLevel_ = 0; -}; - -/** - * Formats a value in a nice, human-readable string. - * - * @param value - */ -jasmine.PrettyPrinter.prototype.format = function(value) { - if (this.ppNestLevel_ > 40) { - throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); - } - - this.ppNestLevel_++; - try { - if (value === jasmine.undefined) { - this.emitScalar('undefined'); - } else if (value === null) { - this.emitScalar('null'); - } else if (value === jasmine.getGlobal()) { - this.emitScalar(''); - } else if (value instanceof jasmine.Matchers.Any) { - this.emitScalar(value.toString()); - } else if (typeof value === 'string') { - this.emitString(value); - } else if (jasmine.isSpy(value)) { - this.emitScalar("spy on " + value.identity); - } else if (value instanceof RegExp) { - this.emitScalar(value.toString()); - } else if (typeof value === 'function') { - this.emitScalar('Function'); - } else if (typeof value.nodeType === 'number') { - this.emitScalar('HTMLNode'); - } else if (value instanceof Date) { - this.emitScalar('Date(' + value + ')'); - } else if (value.__Jasmine_been_here_before__) { - this.emitScalar(''); - } else if (jasmine.isArray_(value) || typeof value == 'object') { - value.__Jasmine_been_here_before__ = true; - if (jasmine.isArray_(value)) { - this.emitArray(value); - } else { - this.emitObject(value); - } - delete value.__Jasmine_been_here_before__; - } else { - this.emitScalar(value.toString()); - } - } finally { - this.ppNestLevel_--; - } -}; - -jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { - for (var property in obj) { - if (property == '__Jasmine_been_here_before__') continue; - fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && - obj.__lookupGetter__(property) !== null) : false); - } -}; - -jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; - -jasmine.StringPrettyPrinter = function() { - jasmine.PrettyPrinter.call(this); - - this.string = ''; -}; -jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); - -jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { - this.append(value); -}; - -jasmine.StringPrettyPrinter.prototype.emitString = function(value) { - this.append("'" + value + "'"); -}; - -jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { - this.append('[ '); - for (var i = 0; i < array.length; i++) { - if (i > 0) { - this.append(', '); - } - this.format(array[i]); - } - this.append(' ]'); -}; - -jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { - var self = this; - this.append('{ '); - var first = true; - - this.iterateObject(obj, function(property, isGetter) { - if (first) { - first = false; - } else { - self.append(', '); - } - - self.append(property); - self.append(' : '); - if (isGetter) { - self.append(''); - } else { - self.format(obj[property]); - } - }); - - this.append(' }'); -}; - -jasmine.StringPrettyPrinter.prototype.append = function(value) { - this.string += value; -}; -jasmine.Queue = function(env) { - this.env = env; - this.blocks = []; - this.running = false; - this.index = 0; - this.offset = 0; - this.abort = false; -}; - -jasmine.Queue.prototype.addBefore = function(block) { - this.blocks.unshift(block); -}; - -jasmine.Queue.prototype.add = function(block) { - this.blocks.push(block); -}; - -jasmine.Queue.prototype.insertNext = function(block) { - this.blocks.splice((this.index + this.offset + 1), 0, block); - this.offset++; -}; - -jasmine.Queue.prototype.start = function(onComplete) { - this.running = true; - this.onComplete = onComplete; - this.next_(); -}; - -jasmine.Queue.prototype.isRunning = function() { - return this.running; -}; - -jasmine.Queue.LOOP_DONT_RECURSE = true; - -jasmine.Queue.prototype.next_ = function() { - var self = this; - var goAgain = true; - - while (goAgain) { - goAgain = false; - - if (self.index < self.blocks.length && !this.abort) { - var calledSynchronously = true; - var completedSynchronously = false; - - var onComplete = function () { - if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { - completedSynchronously = true; - return; - } - - if (self.blocks[self.index].abort) { - self.abort = true; - } - - self.offset = 0; - self.index++; - - var now = new Date().getTime(); - if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { - self.env.lastUpdate = now; - self.env.setTimeout(function() { - self.next_(); - }, 0); - } else { - if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { - goAgain = true; - } else { - self.next_(); - } - } - }; - self.blocks[self.index].execute(onComplete); - - calledSynchronously = false; - if (completedSynchronously) { - onComplete(); - } - - } else { - self.running = false; - if (self.onComplete) { - self.onComplete(); - } - } - } -}; - -jasmine.Queue.prototype.results = function() { - var results = new jasmine.NestedResults(); - for (var i = 0; i < this.blocks.length; i++) { - if (this.blocks[i].results) { - results.addResult(this.blocks[i].results()); - } - } - return results; -}; - - -/** - * Runner - * - * @constructor - * @param {jasmine.Env} env - */ -jasmine.Runner = function(env) { - var self = this; - self.env = env; - self.queue = new jasmine.Queue(env); - self.before_ = []; - self.after_ = []; - self.suites_ = []; -}; - -jasmine.Runner.prototype.execute = function() { - var self = this; - if (self.env.reporter.reportRunnerStarting) { - self.env.reporter.reportRunnerStarting(this); - } - self.queue.start(function () { - self.finishCallback(); - }); -}; - -jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { - beforeEachFunction.typeName = 'beforeEach'; - this.before_.splice(0,0,beforeEachFunction); -}; - -jasmine.Runner.prototype.afterEach = function(afterEachFunction) { - afterEachFunction.typeName = 'afterEach'; - this.after_.splice(0,0,afterEachFunction); -}; - - -jasmine.Runner.prototype.finishCallback = function() { - this.env.reporter.reportRunnerResults(this); -}; - -jasmine.Runner.prototype.addSuite = function(suite) { - this.suites_.push(suite); -}; - -jasmine.Runner.prototype.add = function(block) { - if (block instanceof jasmine.Suite) { - this.addSuite(block); - } - this.queue.add(block); -}; - -jasmine.Runner.prototype.specs = function () { - var suites = this.suites(); - var specs = []; - for (var i = 0; i < suites.length; i++) { - specs = specs.concat(suites[i].specs()); - } - return specs; -}; - -jasmine.Runner.prototype.suites = function() { - return this.suites_; -}; - -jasmine.Runner.prototype.topLevelSuites = function() { - var topLevelSuites = []; - for (var i = 0; i < this.suites_.length; i++) { - if (!this.suites_[i].parentSuite) { - topLevelSuites.push(this.suites_[i]); - } - } - return topLevelSuites; -}; - -jasmine.Runner.prototype.results = function() { - return this.queue.results(); -}; -/** - * Internal representation of a Jasmine specification, or test. - * - * @constructor - * @param {jasmine.Env} env - * @param {jasmine.Suite} suite - * @param {String} description - */ -jasmine.Spec = function(env, suite, description) { - if (!env) { - throw new Error('jasmine.Env() required'); - } - if (!suite) { - throw new Error('jasmine.Suite() required'); - } - var spec = this; - spec.id = env.nextSpecId ? env.nextSpecId() : null; - spec.env = env; - spec.suite = suite; - spec.description = description; - spec.queue = new jasmine.Queue(env); - - spec.afterCallbacks = []; - spec.spies_ = []; - - spec.results_ = new jasmine.NestedResults(); - spec.results_.description = description; - spec.matchersClass = null; -}; - -jasmine.Spec.prototype.getFullName = function() { - return this.suite.getFullName() + ' ' + this.description + '.'; -}; - - -jasmine.Spec.prototype.results = function() { - return this.results_; -}; - -/** - * All parameters are pretty-printed and concatenated together, then written to the spec's output. - * - * Be careful not to leave calls to jasmine.log in production code. - */ -jasmine.Spec.prototype.log = function() { - return this.results_.log(arguments); -}; - -jasmine.Spec.prototype.runs = function (func) { - var block = new jasmine.Block(this.env, func, this); - this.addToQueue(block); - return this; -}; - -jasmine.Spec.prototype.addToQueue = function (block) { - if (this.queue.isRunning()) { - this.queue.insertNext(block); - } else { - this.queue.add(block); - } -}; - -/** - * @param {jasmine.ExpectationResult} result - */ -jasmine.Spec.prototype.addMatcherResult = function(result) { - this.results_.addResult(result); -}; - -jasmine.Spec.prototype.expect = function(actual) { - var positive = new (this.getMatchersClass_())(this.env, actual, this); - positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); - return positive; -}; - -/** - * Waits a fixed time period before moving to the next block. - * - * @deprecated Use waitsFor() instead - * @param {Number} timeout milliseconds to wait - */ -jasmine.Spec.prototype.waits = function(timeout) { - var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); - this.addToQueue(waitsFunc); - return this; -}; - -/** - * Waits for the latchFunction to return true before proceeding to the next block. - * - * @param {Function} latchFunction - * @param {String} optional_timeoutMessage - * @param {Number} optional_timeout - */ -jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { - var latchFunction_ = null; - var optional_timeoutMessage_ = null; - var optional_timeout_ = null; - - for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; - switch (typeof arg) { - case 'function': - latchFunction_ = arg; - break; - case 'string': - optional_timeoutMessage_ = arg; - break; - case 'number': - optional_timeout_ = arg; - break; - } - } - - var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); - this.addToQueue(waitsForFunc); - return this; -}; - -jasmine.Spec.prototype.fail = function (e) { - var expectationResult = new jasmine.ExpectationResult({ - passed: false, - message: e ? jasmine.util.formatException(e) : 'Exception', - trace: { stack: e.stack } - }); - this.results_.addResult(expectationResult); -}; - -jasmine.Spec.prototype.getMatchersClass_ = function() { - return this.matchersClass || this.env.matchersClass; -}; - -jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { - var parent = this.getMatchersClass_(); - var newMatchersClass = function() { - parent.apply(this, arguments); - }; - jasmine.util.inherit(newMatchersClass, parent); - jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); - this.matchersClass = newMatchersClass; -}; - -jasmine.Spec.prototype.finishCallback = function() { - this.env.reporter.reportSpecResults(this); -}; - -jasmine.Spec.prototype.finish = function(onComplete) { - this.removeAllSpies(); - this.finishCallback(); - if (onComplete) { - onComplete(); - } -}; - -jasmine.Spec.prototype.after = function(doAfter) { - if (this.queue.isRunning()) { - this.queue.add(new jasmine.Block(this.env, doAfter, this)); - } else { - this.afterCallbacks.unshift(doAfter); - } -}; - -jasmine.Spec.prototype.execute = function(onComplete) { - var spec = this; - if (!spec.env.specFilter(spec)) { - spec.results_.skipped = true; - spec.finish(onComplete); - return; - } - - this.env.reporter.reportSpecStarting(this); - - spec.env.currentSpec = spec; - - spec.addBeforesAndAftersToQueue(); - - spec.queue.start(function () { - spec.finish(onComplete); - }); -}; - -jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { - var runner = this.env.currentRunner(); - var i; - - for (var suite = this.suite; suite; suite = suite.parentSuite) { - for (i = 0; i < suite.before_.length; i++) { - this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); - } - } - for (i = 0; i < runner.before_.length; i++) { - this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); - } - for (i = 0; i < this.afterCallbacks.length; i++) { - this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); - } - for (suite = this.suite; suite; suite = suite.parentSuite) { - for (i = 0; i < suite.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); - } - } - for (i = 0; i < runner.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); - } -}; - -jasmine.Spec.prototype.explodes = function() { - throw 'explodes function should not have been called'; -}; - -jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { - if (obj == jasmine.undefined) { - throw "spyOn could not find an object to spy upon for " + methodName + "()"; - } - - if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { - throw methodName + '() method does not exist'; - } - - if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { - throw new Error(methodName + ' has already been spied upon'); - } - - var spyObj = jasmine.createSpy(methodName); - - this.spies_.push(spyObj); - spyObj.baseObj = obj; - spyObj.methodName = methodName; - spyObj.originalValue = obj[methodName]; - - obj[methodName] = spyObj; - - return spyObj; -}; - -jasmine.Spec.prototype.removeAllSpies = function() { - for (var i = 0; i < this.spies_.length; i++) { - var spy = this.spies_[i]; - spy.baseObj[spy.methodName] = spy.originalValue; - } - this.spies_ = []; -}; - -/** - * Internal representation of a Jasmine suite. - * - * @constructor - * @param {jasmine.Env} env - * @param {String} description - * @param {Function} specDefinitions - * @param {jasmine.Suite} parentSuite - */ -jasmine.Suite = function(env, description, specDefinitions, parentSuite) { - var self = this; - self.id = env.nextSuiteId ? env.nextSuiteId() : null; - self.description = description; - self.queue = new jasmine.Queue(env); - self.parentSuite = parentSuite; - self.env = env; - self.before_ = []; - self.after_ = []; - self.children_ = []; - self.suites_ = []; - self.specs_ = []; -}; - -jasmine.Suite.prototype.getFullName = function() { - var fullName = this.description; - for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { - fullName = parentSuite.description + ' ' + fullName; - } - return fullName; -}; - -jasmine.Suite.prototype.finish = function(onComplete) { - this.env.reporter.reportSuiteResults(this); - this.finished = true; - if (typeof(onComplete) == 'function') { - onComplete(); - } -}; - -jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { - beforeEachFunction.typeName = 'beforeEach'; - this.before_.unshift(beforeEachFunction); -}; - -jasmine.Suite.prototype.afterEach = function(afterEachFunction) { - afterEachFunction.typeName = 'afterEach'; - this.after_.unshift(afterEachFunction); -}; - -jasmine.Suite.prototype.results = function() { - return this.queue.results(); -}; - -jasmine.Suite.prototype.add = function(suiteOrSpec) { - this.children_.push(suiteOrSpec); - if (suiteOrSpec instanceof jasmine.Suite) { - this.suites_.push(suiteOrSpec); - this.env.currentRunner().addSuite(suiteOrSpec); - } else { - this.specs_.push(suiteOrSpec); - } - this.queue.add(suiteOrSpec); -}; - -jasmine.Suite.prototype.specs = function() { - return this.specs_; -}; - -jasmine.Suite.prototype.suites = function() { - return this.suites_; -}; - -jasmine.Suite.prototype.children = function() { - return this.children_; -}; - -jasmine.Suite.prototype.execute = function(onComplete) { - var self = this; - this.queue.start(function () { - self.finish(onComplete); - }); -}; -jasmine.WaitsBlock = function(env, timeout, spec) { - this.timeout = timeout; - jasmine.Block.call(this, env, null, spec); -}; - -jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); - -jasmine.WaitsBlock.prototype.execute = function (onComplete) { - if (jasmine.VERBOSE) { - this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); - } - this.env.setTimeout(function () { - onComplete(); - }, this.timeout); -}; -/** - * A block which waits for some condition to become true, with timeout. - * - * @constructor - * @extends jasmine.Block - * @param {jasmine.Env} env The Jasmine environment. - * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. - * @param {Function} latchFunction A function which returns true when the desired condition has been met. - * @param {String} message The message to display if the desired condition hasn't been met within the given time period. - * @param {jasmine.Spec} spec The Jasmine spec. - */ -jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { - this.timeout = timeout || env.defaultTimeoutInterval; - this.latchFunction = latchFunction; - this.message = message; - this.totalTimeSpentWaitingForLatch = 0; - jasmine.Block.call(this, env, null, spec); -}; -jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); - -jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; - -jasmine.WaitsForBlock.prototype.execute = function(onComplete) { - if (jasmine.VERBOSE) { - this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); - } - var latchFunctionResult; - try { - latchFunctionResult = this.latchFunction.apply(this.spec); - } catch (e) { - this.spec.fail(e); - onComplete(); - return; - } - - if (latchFunctionResult) { - onComplete(); - } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { - var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); - this.spec.fail({ - name: 'timeout', - message: message - }); - - this.abort = true; - onComplete(); - } else { - this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; - var self = this; - this.env.setTimeout(function() { - self.execute(onComplete); - }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); - } -}; -// Mock setTimeout, clearTimeout -// Contributed by Pivotal Computer Systems, www.pivotalsf.com - -jasmine.FakeTimer = function() { - this.reset(); - - var self = this; - self.setTimeout = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); - return self.timeoutsMade; - }; - - self.setInterval = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); - return self.timeoutsMade; - }; - - self.clearTimeout = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - - self.clearInterval = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - -}; - -jasmine.FakeTimer.prototype.reset = function() { - this.timeoutsMade = 0; - this.scheduledFunctions = {}; - this.nowMillis = 0; -}; - -jasmine.FakeTimer.prototype.tick = function(millis) { - var oldMillis = this.nowMillis; - var newMillis = oldMillis + millis; - this.runFunctionsWithinRange(oldMillis, newMillis); - this.nowMillis = newMillis; -}; - -jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { - var scheduledFunc; - var funcsToRun = []; - for (var timeoutKey in this.scheduledFunctions) { - scheduledFunc = this.scheduledFunctions[timeoutKey]; - if (scheduledFunc != jasmine.undefined && - scheduledFunc.runAtMillis >= oldMillis && - scheduledFunc.runAtMillis <= nowMillis) { - funcsToRun.push(scheduledFunc); - this.scheduledFunctions[timeoutKey] = jasmine.undefined; - } - } - - if (funcsToRun.length > 0) { - funcsToRun.sort(function(a, b) { - return a.runAtMillis - b.runAtMillis; - }); - for (var i = 0; i < funcsToRun.length; ++i) { - try { - var funcToRun = funcsToRun[i]; - this.nowMillis = funcToRun.runAtMillis; - funcToRun.funcToCall(); - if (funcToRun.recurring) { - this.scheduleFunction(funcToRun.timeoutKey, - funcToRun.funcToCall, - funcToRun.millis, - true); - } - } catch(e) { - } - } - this.runFunctionsWithinRange(oldMillis, nowMillis); - } -}; - -jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { - this.scheduledFunctions[timeoutKey] = { - runAtMillis: this.nowMillis + millis, - funcToCall: funcToCall, - recurring: recurring, - timeoutKey: timeoutKey, - millis: millis - }; -}; - -/** - * @namespace - */ -jasmine.Clock = { - defaultFakeTimer: new jasmine.FakeTimer(), - - reset: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.reset(); - }, - - tick: function(millis) { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.tick(millis); - }, - - runFunctionsWithinRange: function(oldMillis, nowMillis) { - jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); - }, - - scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { - jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); - }, - - useMock: function() { - if (!jasmine.Clock.isInstalled()) { - var spec = jasmine.getEnv().currentSpec; - spec.after(jasmine.Clock.uninstallMock); - - jasmine.Clock.installMock(); - } - }, - - installMock: function() { - jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; - }, - - uninstallMock: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.installed = jasmine.Clock.real; - }, - - real: { - setTimeout: jasmine.getGlobal().setTimeout, - clearTimeout: jasmine.getGlobal().clearTimeout, - setInterval: jasmine.getGlobal().setInterval, - clearInterval: jasmine.getGlobal().clearInterval - }, - - assertInstalled: function() { - if (!jasmine.Clock.isInstalled()) { - throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); - } - }, - - isInstalled: function() { - return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; - }, - - installed: null -}; -jasmine.Clock.installed = jasmine.Clock.real; - -//else for IE support -jasmine.getGlobal().setTimeout = function(funcToCall, millis) { - if (jasmine.Clock.installed.setTimeout.apply) { - return jasmine.Clock.installed.setTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.setTimeout(funcToCall, millis); - } -}; - -jasmine.getGlobal().setInterval = function(funcToCall, millis) { - if (jasmine.Clock.installed.setInterval.apply) { - return jasmine.Clock.installed.setInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.setInterval(funcToCall, millis); - } -}; - -jasmine.getGlobal().clearTimeout = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearTimeout(timeoutKey); - } -}; - -jasmine.getGlobal().clearInterval = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearInterval(timeoutKey); - } -}; - -jasmine.version_= { - "major": 1, - "minor": 1, - "build": 0, - "revision": 1315677058 -}; diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine_favicon.png b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine_favicon.png deleted file mode 100644 index bd1479552544d199a5556d83dc2569f11bf2851d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 700 zcmV;t0z>_YP)x|DS(-`hVk&m6uaeBHAzBIr9m`fBO0H{}I}dDlaR1Gn{|`Rg{=ezgwExSlw*0^H>eT;B zw@?2+e0ulCc}u44OGpea6B6VTN6{=MDkK{n9ayyMz=p>l#n)e6_`mJ$?Em$Lz5egG zH~;^$&k$F=`2PI=qxbjzmy~5sWCDc+4-c16NT^Q^FwibOczfp`EWkh-F1|YUf60}` z|CfL?hz|=NP+)G~zu~61n6LtajkR&`ffL(bzWVX}|1*$gXe@xD?9Qtj{~v#T@c+S^ zyZ)U_N-Z=4Y z+upS=Za%pJHW1|Wy+^jZNK6c?$t_6jYHX>P+1yq++u6w~93BS3Lj2-(cIJK_9u5&2 z>Pn_SZxw^173gG;i;iDB_)=0*Or3#PU}~!GdhOB0@8E<5^z)H(`(8*%ifIsSfVh~D z{O-eB?!Nr-;{TIR5C1o{RxAMJ3!)8RW@ch-@2+2d_WJR+MI{+Mq9TG)45R`RV_hdt iPlrTCMg}GZS^)r2gqD6|@^jb#0000 -1, - WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && - navigator.userAgent.indexOf('KHTML') === -1, - MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) - }, - - BrowserFeatures: { - XPath: !!document.evaluate, - SelectorsAPI: !!document.querySelector, - ElementExtensions: !!window.HTMLElement, - SpecificElementExtensions: - document.createElement('div')['__proto__'] && - document.createElement('div')['__proto__'] !== - document.createElement('form')['__proto__'] - }, - - ScriptFragment: ']*>([\\S\\s]*?)<\/script>', - JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, - - emptyFunction: function() { }, - K: function(x) { return x } -}; - -if (Prototype.Browser.MobileSafari) - Prototype.BrowserFeatures.SpecificElementExtensions = false; - - -/* Based on Alex Arnell's inheritance implementation. */ -var Class = { - create: function() { - var parent = null, properties = $A(arguments); - if (Object.isFunction(properties[0])) - parent = properties.shift(); - - function klass() { - this.initialize.apply(this, arguments); - } - - Object.extend(klass, Class.Methods); - klass.superclass = parent; - klass.subclasses = []; - - if (parent) { - var subclass = function() { }; - subclass.prototype = parent.prototype; - klass.prototype = new subclass; - parent.subclasses.push(klass); - } - - for (var i = 0; i < properties.length; i++) - klass.addMethods(properties[i]); - - if (!klass.prototype.initialize) - klass.prototype.initialize = Prototype.emptyFunction; - - klass.prototype.constructor = klass; - - return klass; - } -}; - -Class.Methods = { - addMethods: function(source) { - var ancestor = this.superclass && this.superclass.prototype; - var properties = Object.keys(source); - - if (!Object.keys({ toString: true }).length) - properties.push("toString", "valueOf"); - - for (var i = 0, length = properties.length; i < length; i++) { - var property = properties[i], value = source[property]; - if (ancestor && Object.isFunction(value) && - value.argumentNames().first() == "$super") { - var method = value; - value = (function(m) { - return function() { return ancestor[m].apply(this, arguments) }; - })(property).wrap(method); - - value.valueOf = method.valueOf.bind(method); - value.toString = method.toString.bind(method); - } - this.prototype[property] = value; - } - - return this; - } -}; - -var Abstract = { }; - -Object.extend = function(destination, source) { - for (var property in source) - destination[property] = source[property]; - return destination; -}; - -Object.extend(Object, { - inspect: function(object) { - try { - if (Object.isUndefined(object)) return 'undefined'; - if (object === null) return 'null'; - return object.inspect ? object.inspect() : String(object); - } catch (e) { - if (e instanceof RangeError) return '...'; - throw e; - } - }, - - toJSON: function(object) { - var type = typeof object; - switch (type) { - case 'undefined': - case 'function': - case 'unknown': return; - case 'boolean': return object.toString(); - } - - if (object === null) return 'null'; - if (object.toJSON) return object.toJSON(); - if (Object.isElement(object)) return; - - var results = []; - for (var property in object) { - var value = Object.toJSON(object[property]); - if (!Object.isUndefined(value)) - results.push(property.toJSON() + ': ' + value); - } - - return '{' + results.join(', ') + '}'; - }, - - toQueryString: function(object) { - return $H(object).toQueryString(); - }, - - toHTML: function(object) { - return object && object.toHTML ? object.toHTML() : String.interpret(object); - }, - - keys: function(object) { - var keys = []; - for (var property in object) - keys.push(property); - return keys; - }, - - values: function(object) { - var values = []; - for (var property in object) - values.push(object[property]); - return values; - }, - - clone: function(object) { - return Object.extend({ }, object); - }, - - isElement: function(object) { - return !!(object && object.nodeType == 1); - }, - - isArray: function(object) { - return object != null && typeof object == "object" && - 'splice' in object && 'join' in object; - }, - - isHash: function(object) { - return object instanceof Hash; - }, - - isFunction: function(object) { - return typeof object == "function"; - }, - - isString: function(object) { - return typeof object == "string"; - }, - - isNumber: function(object) { - return typeof object == "number"; - }, - - isUndefined: function(object) { - return typeof object == "undefined"; - } -}); - -Object.extend(Function.prototype, { - argumentNames: function() { - var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] - .replace(/\s+/g, '').split(','); - return names.length == 1 && !names[0] ? [] : names; - }, - - bind: function() { - if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; - var __method = this, args = $A(arguments), object = args.shift(); - return function() { - return __method.apply(object, args.concat($A(arguments))); - } - }, - - bindAsEventListener: function() { - var __method = this, args = $A(arguments), object = args.shift(); - return function(event) { - return __method.apply(object, [event || window.event].concat(args)); - } - }, - - curry: function() { - if (!arguments.length) return this; - var __method = this, args = $A(arguments); - return function() { - return __method.apply(this, args.concat($A(arguments))); - } - }, - - delay: function() { - var __method = this, args = $A(arguments), timeout = args.shift() * 1000; - return window.setTimeout(function() { - return __method.apply(__method, args); - }, timeout); - }, - - defer: function() { - var args = [0.01].concat($A(arguments)); - return this.delay.apply(this, args); - }, - - wrap: function(wrapper) { - var __method = this; - return function() { - return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); - } - }, - - methodize: function() { - if (this._methodized) return this._methodized; - var __method = this; - return this._methodized = function() { - return __method.apply(null, [this].concat($A(arguments))); - }; - } -}); - -Date.prototype.toJSON = function() { - return '"' + this.getUTCFullYear() + '-' + - (this.getUTCMonth() + 1).toPaddedString(2) + '-' + - this.getUTCDate().toPaddedString(2) + 'T' + - this.getUTCHours().toPaddedString(2) + ':' + - this.getUTCMinutes().toPaddedString(2) + ':' + - this.getUTCSeconds().toPaddedString(2) + 'Z"'; -}; - -var Try = { - these: function() { - var returnValue; - - for (var i = 0, length = arguments.length; i < length; i++) { - var lambda = arguments[i]; - try { - returnValue = lambda(); - break; - } catch (e) { } - } - - return returnValue; - } -}; - -RegExp.prototype.match = RegExp.prototype.test; - -RegExp.escape = function(str) { - return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); -}; - -/*--------------------------------------------------------------------------*/ - -var PeriodicalExecuter = Class.create({ - initialize: function(callback, frequency) { - this.callback = callback; - this.frequency = frequency; - this.currentlyExecuting = false; - - this.registerCallback(); - }, - - registerCallback: function() { - this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - execute: function() { - this.callback(this); - }, - - stop: function() { - if (!this.timer) return; - clearInterval(this.timer); - this.timer = null; - }, - - onTimerEvent: function() { - if (!this.currentlyExecuting) { - try { - this.currentlyExecuting = true; - this.execute(); - } finally { - this.currentlyExecuting = false; - } - } - } -}); -Object.extend(String, { - interpret: function(value) { - return value == null ? '' : String(value); - }, - specialChar: { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '\\': '\\\\' - } -}); - -Object.extend(String.prototype, { - gsub: function(pattern, replacement) { - var result = '', source = this, match; - replacement = arguments.callee.prepareReplacement(replacement); - - while (source.length > 0) { - if (match = source.match(pattern)) { - result += source.slice(0, match.index); - result += String.interpret(replacement(match)); - source = source.slice(match.index + match[0].length); - } else { - result += source, source = ''; - } - } - return result; - }, - - sub: function(pattern, replacement, count) { - replacement = this.gsub.prepareReplacement(replacement); - count = Object.isUndefined(count) ? 1 : count; - - return this.gsub(pattern, function(match) { - if (--count < 0) return match[0]; - return replacement(match); - }); - }, - - scan: function(pattern, iterator) { - this.gsub(pattern, iterator); - return String(this); - }, - - truncate: function(length, truncation) { - length = length || 30; - truncation = Object.isUndefined(truncation) ? '...' : truncation; - return this.length > length ? - this.slice(0, length - truncation.length) + truncation : String(this); - }, - - strip: function() { - return this.replace(/^\s+/, '').replace(/\s+$/, ''); - }, - - stripTags: function() { - return this.replace(/<\/?[^>]+>/gi, ''); - }, - - stripScripts: function() { - return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); - }, - - extractScripts: function() { - var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); - var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); - return (this.match(matchAll) || []).map(function(scriptTag) { - return (scriptTag.match(matchOne) || ['', ''])[1]; - }); - }, - - evalScripts: function() { - return this.extractScripts().map(function(script) { return eval(script) }); - }, - - escapeHTML: function() { - var self = arguments.callee; - self.text.data = this; - return self.div.innerHTML; - }, - - unescapeHTML: function() { - var div = new Element('div'); - div.innerHTML = this.stripTags(); - return div.childNodes[0] ? (div.childNodes.length > 1 ? - $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : - div.childNodes[0].nodeValue) : ''; - }, - - toQueryParams: function(separator) { - var match = this.strip().match(/([^?#]*)(#.*)?$/); - if (!match) return { }; - - return match[1].split(separator || '&').inject({ }, function(hash, pair) { - if ((pair = pair.split('='))[0]) { - var key = decodeURIComponent(pair.shift()); - var value = pair.length > 1 ? pair.join('=') : pair[0]; - if (value != undefined) value = decodeURIComponent(value); - - if (key in hash) { - if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; - hash[key].push(value); - } - else hash[key] = value; - } - return hash; - }); - }, - - toArray: function() { - return this.split(''); - }, - - succ: function() { - return this.slice(0, this.length - 1) + - String.fromCharCode(this.charCodeAt(this.length - 1) + 1); - }, - - times: function(count) { - return count < 1 ? '' : new Array(count + 1).join(this); - }, - - camelize: function() { - var parts = this.split('-'), len = parts.length; - if (len == 1) return parts[0]; - - var camelized = this.charAt(0) == '-' - ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) - : parts[0]; - - for (var i = 1; i < len; i++) - camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); - - return camelized; - }, - - capitalize: function() { - return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); - }, - - underscore: function() { - return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); - }, - - dasherize: function() { - return this.gsub(/_/,'-'); - }, - - inspect: function(useDoubleQuotes) { - var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { - var character = String.specialChar[match[0]]; - return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); - }); - if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; - return "'" + escapedString.replace(/'/g, '\\\'') + "'"; - }, - - toJSON: function() { - return this.inspect(true); - }, - - unfilterJSON: function(filter) { - return this.sub(filter || Prototype.JSONFilter, '#{1}'); - }, - - isJSON: function() { - var str = this; - if (str.blank()) return false; - str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); - return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); - }, - - evalJSON: function(sanitize) { - var json = this.unfilterJSON(); - try { - if (!sanitize || json.isJSON()) return eval('(' + json + ')'); - } catch (e) { } - throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); - }, - - include: function(pattern) { - return this.indexOf(pattern) > -1; - }, - - startsWith: function(pattern) { - return this.indexOf(pattern) === 0; - }, - - endsWith: function(pattern) { - var d = this.length - pattern.length; - return d >= 0 && this.lastIndexOf(pattern) === d; - }, - - empty: function() { - return this == ''; - }, - - blank: function() { - return /^\s*$/.test(this); - }, - - interpolate: function(object, pattern) { - return new Template(this, pattern).evaluate(object); - } -}); - -if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { - escapeHTML: function() { - return this.replace(/&/g,'&').replace(//g,'>'); - }, - unescapeHTML: function() { - return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); - } -}); - -String.prototype.gsub.prepareReplacement = function(replacement) { - if (Object.isFunction(replacement)) return replacement; - var template = new Template(replacement); - return function(match) { return template.evaluate(match) }; -}; - -String.prototype.parseQuery = String.prototype.toQueryParams; - -Object.extend(String.prototype.escapeHTML, { - div: document.createElement('div'), - text: document.createTextNode('') -}); - -String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); - -var Template = Class.create({ - initialize: function(template, pattern) { - this.template = template.toString(); - this.pattern = pattern || Template.Pattern; - }, - - evaluate: function(object) { - if (Object.isFunction(object.toTemplateReplacements)) - object = object.toTemplateReplacements(); - - return this.template.gsub(this.pattern, function(match) { - if (object == null) return ''; - - var before = match[1] || ''; - if (before == '\\') return match[2]; - - var ctx = object, expr = match[3]; - var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; - match = pattern.exec(expr); - if (match == null) return before; - - while (match != null) { - var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; - ctx = ctx[comp]; - if (null == ctx || '' == match[3]) break; - expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); - match = pattern.exec(expr); - } - - return before + String.interpret(ctx); - }); - } -}); -Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; - -var $break = { }; - -var Enumerable = { - each: function(iterator, context) { - var index = 0; - try { - this._each(function(value) { - iterator.call(context, value, index++); - }); - } catch (e) { - if (e != $break) throw e; - } - return this; - }, - - eachSlice: function(number, iterator, context) { - var index = -number, slices = [], array = this.toArray(); - if (number < 1) return array; - while ((index += number) < array.length) - slices.push(array.slice(index, index+number)); - return slices.collect(iterator, context); - }, - - all: function(iterator, context) { - iterator = iterator || Prototype.K; - var result = true; - this.each(function(value, index) { - result = result && !!iterator.call(context, value, index); - if (!result) throw $break; - }); - return result; - }, - - any: function(iterator, context) { - iterator = iterator || Prototype.K; - var result = false; - this.each(function(value, index) { - if (result = !!iterator.call(context, value, index)) - throw $break; - }); - return result; - }, - - collect: function(iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - this.each(function(value, index) { - results.push(iterator.call(context, value, index)); - }); - return results; - }, - - detect: function(iterator, context) { - var result; - this.each(function(value, index) { - if (iterator.call(context, value, index)) { - result = value; - throw $break; - } - }); - return result; - }, - - findAll: function(iterator, context) { - var results = []; - this.each(function(value, index) { - if (iterator.call(context, value, index)) - results.push(value); - }); - return results; - }, - - grep: function(filter, iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - - if (Object.isString(filter)) - filter = new RegExp(filter); - - this.each(function(value, index) { - if (filter.match(value)) - results.push(iterator.call(context, value, index)); - }); - return results; - }, - - include: function(object) { - if (Object.isFunction(this.indexOf)) - if (this.indexOf(object) != -1) return true; - - var found = false; - this.each(function(value) { - if (value == object) { - found = true; - throw $break; - } - }); - return found; - }, - - inGroupsOf: function(number, fillWith) { - fillWith = Object.isUndefined(fillWith) ? null : fillWith; - return this.eachSlice(number, function(slice) { - while(slice.length < number) slice.push(fillWith); - return slice; - }); - }, - - inject: function(memo, iterator, context) { - this.each(function(value, index) { - memo = iterator.call(context, memo, value, index); - }); - return memo; - }, - - invoke: function(method) { - var args = $A(arguments).slice(1); - return this.map(function(value) { - return value[method].apply(value, args); - }); - }, - - max: function(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value >= result) - result = value; - }); - return result; - }, - - min: function(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value < result) - result = value; - }); - return result; - }, - - partition: function(iterator, context) { - iterator = iterator || Prototype.K; - var trues = [], falses = []; - this.each(function(value, index) { - (iterator.call(context, value, index) ? - trues : falses).push(value); - }); - return [trues, falses]; - }, - - pluck: function(property) { - var results = []; - this.each(function(value) { - results.push(value[property]); - }); - return results; - }, - - reject: function(iterator, context) { - var results = []; - this.each(function(value, index) { - if (!iterator.call(context, value, index)) - results.push(value); - }); - return results; - }, - - sortBy: function(iterator, context) { - return this.map(function(value, index) { - return { - value: value, - criteria: iterator.call(context, value, index) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }).pluck('value'); - }, - - toArray: function() { - return this.map(); - }, - - zip: function() { - var iterator = Prototype.K, args = $A(arguments); - if (Object.isFunction(args.last())) - iterator = args.pop(); - - var collections = [this].concat(args).map($A); - return this.map(function(value, index) { - return iterator(collections.pluck(index)); - }); - }, - - size: function() { - return this.toArray().length; - }, - - inspect: function() { - return '#'; - } -}; - -Object.extend(Enumerable, { - map: Enumerable.collect, - find: Enumerable.detect, - select: Enumerable.findAll, - filter: Enumerable.findAll, - member: Enumerable.include, - entries: Enumerable.toArray, - every: Enumerable.all, - some: Enumerable.any -}); -function $A(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - var length = iterable.length || 0, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; -} - -if (Prototype.Browser.WebKit) { - $A = function(iterable) { - if (!iterable) return []; - // In Safari, only use the `toArray` method if it's not a NodeList. - // A NodeList is a function, has an function `item` property, and a numeric - // `length` property. Adapted from Google Doctype. - if (!(typeof iterable === 'function' && typeof iterable.length === - 'number' && typeof iterable.item === 'function') && iterable.toArray) - return iterable.toArray(); - var length = iterable.length || 0, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; - }; -} - -Array.from = $A; - -Object.extend(Array.prototype, Enumerable); - -if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; - -Object.extend(Array.prototype, { - _each: function(iterator) { - for (var i = 0, length = this.length; i < length; i++) - iterator(this[i]); - }, - - clear: function() { - this.length = 0; - return this; - }, - - first: function() { - return this[0]; - }, - - last: function() { - return this[this.length - 1]; - }, - - compact: function() { - return this.select(function(value) { - return value != null; - }); - }, - - flatten: function() { - return this.inject([], function(array, value) { - return array.concat(Object.isArray(value) ? - value.flatten() : [value]); - }); - }, - - without: function() { - var values = $A(arguments); - return this.select(function(value) { - return !values.include(value); - }); - }, - - reverse: function(inline) { - return (inline !== false ? this : this.toArray())._reverse(); - }, - - reduce: function() { - return this.length > 1 ? this : this[0]; - }, - - uniq: function(sorted) { - return this.inject([], function(array, value, index) { - if (0 == index || (sorted ? array.last() != value : !array.include(value))) - array.push(value); - return array; - }); - }, - - intersect: function(array) { - return this.uniq().findAll(function(item) { - return array.detect(function(value) { return item === value }); - }); - }, - - clone: function() { - return [].concat(this); - }, - - size: function() { - return this.length; - }, - - inspect: function() { - return '[' + this.map(Object.inspect).join(', ') + ']'; - }, - - toJSON: function() { - var results = []; - this.each(function(object) { - var value = Object.toJSON(object); - if (!Object.isUndefined(value)) results.push(value); - }); - return '[' + results.join(', ') + ']'; - } -}); - -// use native browser JS 1.6 implementation if available -if (Object.isFunction(Array.prototype.forEach)) - Array.prototype._each = Array.prototype.forEach; - -if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { - i || (i = 0); - var length = this.length; - if (i < 0) i = length + i; - for (; i < length; i++) - if (this[i] === item) return i; - return -1; -}; - -if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { - i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; - var n = this.slice(0, i).reverse().indexOf(item); - return (n < 0) ? n : i - n - 1; -}; - -Array.prototype.toArray = Array.prototype.clone; - -function $w(string) { - if (!Object.isString(string)) return []; - string = string.strip(); - return string ? string.split(/\s+/) : []; -} - -if (Prototype.Browser.Opera){ - Array.prototype.concat = function() { - var array = []; - for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); - for (var i = 0, length = arguments.length; i < length; i++) { - if (Object.isArray(arguments[i])) { - for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) - array.push(arguments[i][j]); - } else { - array.push(arguments[i]); - } - } - return array; - }; -} -Object.extend(Number.prototype, { - toColorPart: function() { - return this.toPaddedString(2, 16); - }, - - succ: function() { - return this + 1; - }, - - times: function(iterator, context) { - $R(0, this, true).each(iterator, context); - return this; - }, - - toPaddedString: function(length, radix) { - var string = this.toString(radix || 10); - return '0'.times(length - string.length) + string; - }, - - toJSON: function() { - return isFinite(this) ? this.toString() : 'null'; - } -}); - -$w('abs round ceil floor').each(function(method){ - Number.prototype[method] = Math[method].methodize(); -}); -function $H(object) { - return new Hash(object); -}; - -var Hash = Class.create(Enumerable, (function() { - - function toQueryPair(key, value) { - if (Object.isUndefined(value)) return key; - return key + '=' + encodeURIComponent(String.interpret(value)); - } - - return { - initialize: function(object) { - this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); - }, - - _each: function(iterator) { - for (var key in this._object) { - var value = this._object[key], pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); - } - }, - - set: function(key, value) { - return this._object[key] = value; - }, - - get: function(key) { - // simulating poorly supported hasOwnProperty - if (this._object[key] !== Object.prototype[key]) - return this._object[key]; - }, - - unset: function(key) { - var value = this._object[key]; - delete this._object[key]; - return value; - }, - - toObject: function() { - return Object.clone(this._object); - }, - - keys: function() { - return this.pluck('key'); - }, - - values: function() { - return this.pluck('value'); - }, - - index: function(value) { - var match = this.detect(function(pair) { - return pair.value === value; - }); - return match && match.key; - }, - - merge: function(object) { - return this.clone().update(object); - }, - - update: function(object) { - return new Hash(object).inject(this, function(result, pair) { - result.set(pair.key, pair.value); - return result; - }); - }, - - toQueryString: function() { - return this.inject([], function(results, pair) { - var key = encodeURIComponent(pair.key), values = pair.value; - - if (values && typeof values == 'object') { - if (Object.isArray(values)) - return results.concat(values.map(toQueryPair.curry(key))); - } else results.push(toQueryPair(key, values)); - return results; - }).join('&'); - }, - - inspect: function() { - return '#'; - }, - - toJSON: function() { - return Object.toJSON(this.toObject()); - }, - - clone: function() { - return new Hash(this); - } - } -})()); - -Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; -Hash.from = $H; -var ObjectRange = Class.create(Enumerable, { - initialize: function(start, end, exclusive) { - this.start = start; - this.end = end; - this.exclusive = exclusive; - }, - - _each: function(iterator) { - var value = this.start; - while (this.include(value)) { - iterator(value); - value = value.succ(); - } - }, - - include: function(value) { - if (value < this.start) - return false; - if (this.exclusive) - return value < this.end; - return value <= this.end; - } -}); - -var $R = function(start, end, exclusive) { - return new ObjectRange(start, end, exclusive); -}; - -var Ajax = { - getTransport: function() { - return Try.these( - function() {return new XMLHttpRequest()}, - function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')} - ) || false; - }, - - activeRequestCount: 0 -}; - -Ajax.Responders = { - responders: [], - - _each: function(iterator) { - this.responders._each(iterator); - }, - - register: function(responder) { - if (!this.include(responder)) - this.responders.push(responder); - }, - - unregister: function(responder) { - this.responders = this.responders.without(responder); - }, - - dispatch: function(callback, request, transport, json) { - this.each(function(responder) { - if (Object.isFunction(responder[callback])) { - try { - responder[callback].apply(responder, [request, transport, json]); - } catch (e) { } - } - }); - } -}; - -Object.extend(Ajax.Responders, Enumerable); - -Ajax.Responders.register({ - onCreate: function() { Ajax.activeRequestCount++ }, - onComplete: function() { Ajax.activeRequestCount-- } -}); - -Ajax.Base = Class.create({ - initialize: function(options) { - this.options = { - method: 'post', - asynchronous: true, - contentType: 'application/x-www-form-urlencoded', - encoding: 'UTF-8', - parameters: '', - evalJSON: true, - evalJS: true - }; - Object.extend(this.options, options || { }); - - this.options.method = this.options.method.toLowerCase(); - - if (Object.isString(this.options.parameters)) - this.options.parameters = this.options.parameters.toQueryParams(); - else if (Object.isHash(this.options.parameters)) - this.options.parameters = this.options.parameters.toObject(); - } -}); - -Ajax.Request = Class.create(Ajax.Base, { - _complete: false, - - initialize: function($super, url, options) { - $super(options); - this.transport = Ajax.getTransport(); - this.request(url); - }, - - request: function(url) { - this.url = url; - this.method = this.options.method; - var params = Object.clone(this.options.parameters); - - if (!['get', 'post'].include(this.method)) { - // simulate other verbs over post - params['_method'] = this.method; - this.method = 'post'; - } - - this.parameters = params; - - if (params = Object.toQueryString(params)) { - // when GET, append parameters to URL - if (this.method == 'get') - this.url += (this.url.include('?') ? '&' : '?') + params; - else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) - params += '&_='; - } - - try { - var response = new Ajax.Response(this); - if (this.options.onCreate) this.options.onCreate(response); - Ajax.Responders.dispatch('onCreate', this, response); - - this.transport.open(this.method.toUpperCase(), this.url, - this.options.asynchronous); - - if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); - - this.transport.onreadystatechange = this.onStateChange.bind(this); - this.setRequestHeaders(); - - this.body = this.method == 'post' ? (this.options.postBody || params) : null; - this.transport.send(this.body); - - /* Force Firefox to handle ready state 4 for synchronous requests */ - if (!this.options.asynchronous && this.transport.overrideMimeType) - this.onStateChange(); - - } - catch (e) { - this.dispatchException(e); - } - }, - - onStateChange: function() { - var readyState = this.transport.readyState; - if (readyState > 1 && !((readyState == 4) && this._complete)) - this.respondToReadyState(this.transport.readyState); - }, - - setRequestHeaders: function() { - var headers = { - 'X-Requested-With': 'XMLHttpRequest', - 'X-Prototype-Version': Prototype.Version, - 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' - }; - - if (this.method == 'post') { - headers['Content-type'] = this.options.contentType + - (this.options.encoding ? '; charset=' + this.options.encoding : ''); - - /* Force "Connection: close" for older Mozilla browsers to work - * around a bug where XMLHttpRequest sends an incorrect - * Content-length header. See Mozilla Bugzilla #246651. - */ - if (this.transport.overrideMimeType && - (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) - headers['Connection'] = 'close'; - } - - // user-defined headers - if (typeof this.options.requestHeaders == 'object') { - var extras = this.options.requestHeaders; - - if (Object.isFunction(extras.push)) - for (var i = 0, length = extras.length; i < length; i += 2) - headers[extras[i]] = extras[i+1]; - else - $H(extras).each(function(pair) { headers[pair.key] = pair.value }); - } - - for (var name in headers) - this.transport.setRequestHeader(name, headers[name]); - }, - - success: function() { - var status = this.getStatus(); - return !status || (status >= 200 && status < 300); - }, - - getStatus: function() { - try { - return this.transport.status || 0; - } catch (e) { return 0 } - }, - - respondToReadyState: function(readyState) { - var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); - - if (state == 'Complete') { - try { - this._complete = true; - (this.options['on' + response.status] - || this.options['on' + (this.success() ? 'Success' : 'Failure')] - || Prototype.emptyFunction)(response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - var contentType = response.getHeader('Content-type'); - if (this.options.evalJS == 'force' - || (this.options.evalJS && this.isSameOrigin() && contentType - && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) - this.evalResponse(); - } - - try { - (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); - Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - if (state == 'Complete') { - // avoid memory leak in MSIE: clean up - this.transport.onreadystatechange = Prototype.emptyFunction; - } - }, - - isSameOrigin: function() { - var m = this.url.match(/^\s*https?:\/\/[^\/]*/); - return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ - protocol: location.protocol, - domain: document.domain, - port: location.port ? ':' + location.port : '' - })); - }, - - getHeader: function(name) { - try { - return this.transport.getResponseHeader(name) || null; - } catch (e) { return null } - }, - - evalResponse: function() { - try { - return eval((this.transport.responseText || '').unfilterJSON()); - } catch (e) { - this.dispatchException(e); - } - }, - - dispatchException: function(exception) { - (this.options.onException || Prototype.emptyFunction)(this, exception); - Ajax.Responders.dispatch('onException', this, exception); - } -}); - -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; - -Ajax.Response = Class.create({ - initialize: function(request){ - this.request = request; - var transport = this.transport = request.transport, - readyState = this.readyState = transport.readyState; - - if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { - this.status = this.getStatus(); - this.statusText = this.getStatusText(); - this.responseText = String.interpret(transport.responseText); - this.headerJSON = this._getHeaderJSON(); - } - - if(readyState == 4) { - var xml = transport.responseXML; - this.responseXML = Object.isUndefined(xml) ? null : xml; - this.responseJSON = this._getResponseJSON(); - } - }, - - status: 0, - statusText: '', - - getStatus: Ajax.Request.prototype.getStatus, - - getStatusText: function() { - try { - return this.transport.statusText || ''; - } catch (e) { return '' } - }, - - getHeader: Ajax.Request.prototype.getHeader, - - getAllHeaders: function() { - try { - return this.getAllResponseHeaders(); - } catch (e) { return null } - }, - - getResponseHeader: function(name) { - return this.transport.getResponseHeader(name); - }, - - getAllResponseHeaders: function() { - return this.transport.getAllResponseHeaders(); - }, - - _getHeaderJSON: function() { - var json = this.getHeader('X-JSON'); - if (!json) return null; - json = decodeURIComponent(escape(json)); - try { - return json.evalJSON(this.request.options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - }, - - _getResponseJSON: function() { - var options = this.request.options; - if (!options.evalJSON || (options.evalJSON != 'force' && - !(this.getHeader('Content-type') || '').include('application/json')) || - this.responseText.blank()) - return null; - try { - return this.responseText.evalJSON(options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - } -}); - -Ajax.Updater = Class.create(Ajax.Request, { - initialize: function($super, container, url, options) { - this.container = { - success: (container.success || container), - failure: (container.failure || (container.success ? null : container)) - }; - - options = Object.clone(options); - var onComplete = options.onComplete; - options.onComplete = (function(response, json) { - this.updateContent(response.responseText); - if (Object.isFunction(onComplete)) onComplete(response, json); - }).bind(this); - - $super(url, options); - }, - - updateContent: function(responseText) { - var receiver = this.container[this.success() ? 'success' : 'failure'], - options = this.options; - - if (!options.evalScripts) responseText = responseText.stripScripts(); - - if (receiver = $(receiver)) { - if (options.insertion) { - if (Object.isString(options.insertion)) { - var insertion = { }; insertion[options.insertion] = responseText; - receiver.insert(insertion); - } - else options.insertion(receiver, responseText); - } - else receiver.update(responseText); - } - } -}); - -Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { - initialize: function($super, container, url, options) { - $super(options); - this.onComplete = this.options.onComplete; - - this.frequency = (this.options.frequency || 2); - this.decay = (this.options.decay || 1); - - this.updater = { }; - this.container = container; - this.url = url; - - this.start(); - }, - - start: function() { - this.options.onComplete = this.updateComplete.bind(this); - this.onTimerEvent(); - }, - - stop: function() { - this.updater.options.onComplete = undefined; - clearTimeout(this.timer); - (this.onComplete || Prototype.emptyFunction).apply(this, arguments); - }, - - updateComplete: function(response) { - if (this.options.decay) { - this.decay = (response.responseText == this.lastText ? - this.decay * this.options.decay : 1); - - this.lastText = response.responseText; - } - this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); - }, - - onTimerEvent: function() { - this.updater = new Ajax.Updater(this.container, this.url, this.options); - } -}); -function $(element) { - if (arguments.length > 1) { - for (var i = 0, elements = [], length = arguments.length; i < length; i++) - elements.push($(arguments[i])); - return elements; - } - if (Object.isString(element)) - element = document.getElementById(element); - return Element.extend(element); -} - -if (Prototype.BrowserFeatures.XPath) { - document._getElementsByXPath = function(expression, parentElement) { - var results = []; - var query = document.evaluate(expression, $(parentElement) || document, - null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - for (var i = 0, length = query.snapshotLength; i < length; i++) - results.push(Element.extend(query.snapshotItem(i))); - return results; - }; -} - -/*--------------------------------------------------------------------------*/ - -if (!window.Node) var Node = { }; - -if (!Node.ELEMENT_NODE) { - // DOM level 2 ECMAScript Language Binding - Object.extend(Node, { - ELEMENT_NODE: 1, - ATTRIBUTE_NODE: 2, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - ENTITY_REFERENCE_NODE: 5, - ENTITY_NODE: 6, - PROCESSING_INSTRUCTION_NODE: 7, - COMMENT_NODE: 8, - DOCUMENT_NODE: 9, - DOCUMENT_TYPE_NODE: 10, - DOCUMENT_FRAGMENT_NODE: 11, - NOTATION_NODE: 12 - }); -} - -(function() { - var element = this.Element; - this.Element = function(tagName, attributes) { - attributes = attributes || { }; - tagName = tagName.toLowerCase(); - var cache = Element.cache; - if (Prototype.Browser.IE && attributes.name) { - tagName = '<' + tagName + ' name="' + attributes.name + '">'; - delete attributes.name; - return Element.writeAttribute(document.createElement(tagName), attributes); - } - if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); - return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); - }; - Object.extend(this.Element, element || { }); - if (element) this.Element.prototype = element.prototype; -}).call(window); - -Element.cache = { }; - -Element.Methods = { - visible: function(element) { - return $(element).style.display != 'none'; - }, - - toggle: function(element) { - element = $(element); - Element[Element.visible(element) ? 'hide' : 'show'](element); - return element; - }, - - hide: function(element) { - element = $(element); - element.style.display = 'none'; - return element; - }, - - show: function(element) { - element = $(element); - element.style.display = ''; - return element; - }, - - remove: function(element) { - element = $(element); - element.parentNode.removeChild(element); - return element; - }, - - update: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - content = Object.toHTML(content); - element.innerHTML = content.stripScripts(); - content.evalScripts.bind(content).defer(); - return element; - }, - - replace: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - else if (!Object.isElement(content)) { - content = Object.toHTML(content); - var range = element.ownerDocument.createRange(); - range.selectNode(element); - content.evalScripts.bind(content).defer(); - content = range.createContextualFragment(content.stripScripts()); - } - element.parentNode.replaceChild(content, element); - return element; - }, - - insert: function(element, insertions) { - element = $(element); - - if (Object.isString(insertions) || Object.isNumber(insertions) || - Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) - insertions = {bottom:insertions}; - - var content, insert, tagName, childNodes; - - for (var position in insertions) { - content = insertions[position]; - position = position.toLowerCase(); - insert = Element._insertionTranslations[position]; - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - insert(element, content); - continue; - } - - content = Object.toHTML(content); - - tagName = ((position == 'before' || position == 'after') - ? element.parentNode : element).tagName.toUpperCase(); - - childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - - if (position == 'top' || position == 'after') childNodes.reverse(); - childNodes.each(insert.curry(element)); - - content.evalScripts.bind(content).defer(); - } - - return element; - }, - - wrap: function(element, wrapper, attributes) { - element = $(element); - if (Object.isElement(wrapper)) - $(wrapper).writeAttribute(attributes || { }); - else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); - else wrapper = new Element('div', wrapper); - if (element.parentNode) - element.parentNode.replaceChild(wrapper, element); - wrapper.appendChild(element); - return wrapper; - }, - - inspect: function(element) { - element = $(element); - var result = '<' + element.tagName.toLowerCase(); - $H({'id': 'id', 'className': 'class'}).each(function(pair) { - var property = pair.first(), attribute = pair.last(); - var value = (element[property] || '').toString(); - if (value) result += ' ' + attribute + '=' + value.inspect(true); - }); - return result + '>'; - }, - - recursivelyCollect: function(element, property) { - element = $(element); - var elements = []; - while (element = element[property]) - if (element.nodeType == 1) - elements.push(Element.extend(element)); - return elements; - }, - - ancestors: function(element) { - return $(element).recursivelyCollect('parentNode'); - }, - - descendants: function(element) { - return $(element).select("*"); - }, - - firstDescendant: function(element) { - element = $(element).firstChild; - while (element && element.nodeType != 1) element = element.nextSibling; - return $(element); - }, - - immediateDescendants: function(element) { - if (!(element = $(element).firstChild)) return []; - while (element && element.nodeType != 1) element = element.nextSibling; - if (element) return [element].concat($(element).nextSiblings()); - return []; - }, - - previousSiblings: function(element) { - return $(element).recursivelyCollect('previousSibling'); - }, - - nextSiblings: function(element) { - return $(element).recursivelyCollect('nextSibling'); - }, - - siblings: function(element) { - element = $(element); - return element.previousSiblings().reverse().concat(element.nextSiblings()); - }, - - match: function(element, selector) { - if (Object.isString(selector)) - selector = new Selector(selector); - return selector.match($(element)); - }, - - up: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(element.parentNode); - var ancestors = element.ancestors(); - return Object.isNumber(expression) ? ancestors[expression] : - Selector.findElement(ancestors, expression, index); - }, - - down: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return element.firstDescendant(); - return Object.isNumber(expression) ? element.descendants()[expression] : - Element.select(element, expression)[index || 0]; - }, - - previous: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); - var previousSiblings = element.previousSiblings(); - return Object.isNumber(expression) ? previousSiblings[expression] : - Selector.findElement(previousSiblings, expression, index); - }, - - next: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); - var nextSiblings = element.nextSiblings(); - return Object.isNumber(expression) ? nextSiblings[expression] : - Selector.findElement(nextSiblings, expression, index); - }, - - select: function() { - var args = $A(arguments), element = $(args.shift()); - return Selector.findChildElements(element, args); - }, - - adjacent: function() { - var args = $A(arguments), element = $(args.shift()); - return Selector.findChildElements(element.parentNode, args).without(element); - }, - - identify: function(element) { - element = $(element); - var id = element.readAttribute('id'), self = arguments.callee; - if (id) return id; - do { id = 'anonymous_element_' + self.counter++ } while ($(id)); - element.writeAttribute('id', id); - return id; - }, - - readAttribute: function(element, name) { - element = $(element); - if (Prototype.Browser.IE) { - var t = Element._attributeTranslations.read; - if (t.values[name]) return t.values[name](element, name); - if (t.names[name]) name = t.names[name]; - if (name.include(':')) { - return (!element.attributes || !element.attributes[name]) ? null : - element.attributes[name].value; - } - } - return element.getAttribute(name); - }, - - writeAttribute: function(element, name, value) { - element = $(element); - var attributes = { }, t = Element._attributeTranslations.write; - - if (typeof name == 'object') attributes = name; - else attributes[name] = Object.isUndefined(value) ? true : value; - - for (var attr in attributes) { - name = t.names[attr] || attr; - value = attributes[attr]; - if (t.values[attr]) name = t.values[attr](element, value); - if (value === false || value === null) - element.removeAttribute(name); - else if (value === true) - element.setAttribute(name, name); - else element.setAttribute(name, value); - } - return element; - }, - - getHeight: function(element) { - return $(element).getDimensions().height; - }, - - getWidth: function(element) { - return $(element).getDimensions().width; - }, - - classNames: function(element) { - return new Element.ClassNames(element); - }, - - hasClassName: function(element, className) { - if (!(element = $(element))) return; - var elementClassName = element.className; - return (elementClassName.length > 0 && (elementClassName == className || - new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); - }, - - addClassName: function(element, className) { - if (!(element = $(element))) return; - if (!element.hasClassName(className)) - element.className += (element.className ? ' ' : '') + className; - return element; - }, - - removeClassName: function(element, className) { - if (!(element = $(element))) return; - element.className = element.className.replace( - new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); - return element; - }, - - toggleClassName: function(element, className) { - if (!(element = $(element))) return; - return element[element.hasClassName(className) ? - 'removeClassName' : 'addClassName'](className); - }, - - // removes whitespace-only text node children - cleanWhitespace: function(element) { - element = $(element); - var node = element.firstChild; - while (node) { - var nextNode = node.nextSibling; - if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) - element.removeChild(node); - node = nextNode; - } - return element; - }, - - empty: function(element) { - return $(element).innerHTML.blank(); - }, - - descendantOf: function(element, ancestor) { - element = $(element), ancestor = $(ancestor); - - if (element.compareDocumentPosition) - return (element.compareDocumentPosition(ancestor) & 8) === 8; - - if (ancestor.contains) - return ancestor.contains(element) && ancestor !== element; - - while (element = element.parentNode) - if (element == ancestor) return true; - - return false; - }, - - scrollTo: function(element) { - element = $(element); - var pos = element.cumulativeOffset(); - window.scrollTo(pos[0], pos[1]); - return element; - }, - - getStyle: function(element, style) { - element = $(element); - style = style == 'float' ? 'cssFloat' : style.camelize(); - var value = element.style[style]; - if (!value || value == 'auto') { - var css = document.defaultView.getComputedStyle(element, null); - value = css ? css[style] : null; - } - if (style == 'opacity') return value ? parseFloat(value) : 1.0; - return value == 'auto' ? null : value; - }, - - getOpacity: function(element) { - return $(element).getStyle('opacity'); - }, - - setStyle: function(element, styles) { - element = $(element); - var elementStyle = element.style, match; - if (Object.isString(styles)) { - element.style.cssText += ';' + styles; - return styles.include('opacity') ? - element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; - } - for (var property in styles) - if (property == 'opacity') element.setOpacity(styles[property]); - else - elementStyle[(property == 'float' || property == 'cssFloat') ? - (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : - property] = styles[property]; - - return element; - }, - - setOpacity: function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - return element; - }, - - getDimensions: function(element) { - element = $(element); - var display = element.getStyle('display'); - if (display != 'none' && display != null) // Safari bug - return {width: element.offsetWidth, height: element.offsetHeight}; - - // All *Width and *Height properties give 0 on elements with display none, - // so enable the element temporarily - var els = element.style; - var originalVisibility = els.visibility; - var originalPosition = els.position; - var originalDisplay = els.display; - els.visibility = 'hidden'; - els.position = 'absolute'; - els.display = 'block'; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - els.display = originalDisplay; - els.position = originalPosition; - els.visibility = originalVisibility; - return {width: originalWidth, height: originalHeight}; - }, - - makePositioned: function(element) { - element = $(element); - var pos = Element.getStyle(element, 'position'); - if (pos == 'static' || !pos) { - element._madePositioned = true; - element.style.position = 'relative'; - // Opera returns the offset relative to the positioning context, when an - // element is position relative but top and left have not been defined - if (Prototype.Browser.Opera) { - element.style.top = 0; - element.style.left = 0; - } - } - return element; - }, - - undoPositioned: function(element) { - element = $(element); - if (element._madePositioned) { - element._madePositioned = undefined; - element.style.position = - element.style.top = - element.style.left = - element.style.bottom = - element.style.right = ''; - } - return element; - }, - - makeClipping: function(element) { - element = $(element); - if (element._overflow) return element; - element._overflow = Element.getStyle(element, 'overflow') || 'auto'; - if (element._overflow !== 'hidden') - element.style.overflow = 'hidden'; - return element; - }, - - undoClipping: function(element) { - element = $(element); - if (!element._overflow) return element; - element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; - element._overflow = null; - return element; - }, - - cumulativeOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - positionedOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - if (element) { - if (element.tagName.toUpperCase() == 'BODY') break; - var p = Element.getStyle(element, 'position'); - if (p !== 'static') break; - } - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - absolutize: function(element) { - element = $(element); - if (element.getStyle('position') == 'absolute') return element; - // Position.prepare(); // To be done manually by Scripty when it needs it. - - var offsets = element.positionedOffset(); - var top = offsets[1]; - var left = offsets[0]; - var width = element.clientWidth; - var height = element.clientHeight; - - element._originalLeft = left - parseFloat(element.style.left || 0); - element._originalTop = top - parseFloat(element.style.top || 0); - element._originalWidth = element.style.width; - element._originalHeight = element.style.height; - - element.style.position = 'absolute'; - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.width = width + 'px'; - element.style.height = height + 'px'; - return element; - }, - - relativize: function(element) { - element = $(element); - if (element.getStyle('position') == 'relative') return element; - // Position.prepare(); // To be done manually by Scripty when it needs it. - - element.style.position = 'relative'; - var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); - var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); - - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.height = element._originalHeight; - element.style.width = element._originalWidth; - return element; - }, - - cumulativeScrollOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.scrollTop || 0; - valueL += element.scrollLeft || 0; - element = element.parentNode; - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - getOffsetParent: function(element) { - if (element.offsetParent) return $(element.offsetParent); - if (element == document.body) return $(element); - - while ((element = element.parentNode) && element != document.body) - if (Element.getStyle(element, 'position') != 'static') - return $(element); - - return $(document.body); - }, - - viewportOffset: function(forElement) { - var valueT = 0, valueL = 0; - - var element = forElement; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - - // Safari fix - if (element.offsetParent == document.body && - Element.getStyle(element, 'position') == 'absolute') break; - - } while (element = element.offsetParent); - - element = forElement; - do { - if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { - valueT -= element.scrollTop || 0; - valueL -= element.scrollLeft || 0; - } - } while (element = element.parentNode); - - return Element._returnOffset(valueL, valueT); - }, - - clonePosition: function(element, source) { - var options = Object.extend({ - setLeft: true, - setTop: true, - setWidth: true, - setHeight: true, - offsetTop: 0, - offsetLeft: 0 - }, arguments[2] || { }); - - // find page position of source - source = $(source); - var p = source.viewportOffset(); - - // find coordinate system to use - element = $(element); - var delta = [0, 0]; - var parent = null; - // delta [0,0] will do fine with position: fixed elements, - // position:absolute needs offsetParent deltas - if (Element.getStyle(element, 'position') == 'absolute') { - parent = element.getOffsetParent(); - delta = parent.viewportOffset(); - } - - // correct by body offsets (fixes Safari) - if (parent == document.body) { - delta[0] -= document.body.offsetLeft; - delta[1] -= document.body.offsetTop; - } - - // set position - if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; - if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; - if (options.setWidth) element.style.width = source.offsetWidth + 'px'; - if (options.setHeight) element.style.height = source.offsetHeight + 'px'; - return element; - } -}; - -Element.Methods.identify.counter = 1; - -Object.extend(Element.Methods, { - getElementsBySelector: Element.Methods.select, - childElements: Element.Methods.immediateDescendants -}); - -Element._attributeTranslations = { - write: { - names: { - className: 'class', - htmlFor: 'for' - }, - values: { } - } -}; - -if (Prototype.Browser.Opera) { - Element.Methods.getStyle = Element.Methods.getStyle.wrap( - function(proceed, element, style) { - switch (style) { - case 'left': case 'top': case 'right': case 'bottom': - if (proceed(element, 'position') === 'static') return null; - case 'height': case 'width': - // returns '0px' for hidden elements; we want it to return null - if (!Element.visible(element)) return null; - - // returns the border-box dimensions rather than the content-box - // dimensions, so we subtract padding and borders from the value - var dim = parseInt(proceed(element, style), 10); - - if (dim !== element['offset' + style.capitalize()]) - return dim + 'px'; - - var properties; - if (style === 'height') { - properties = ['border-top-width', 'padding-top', - 'padding-bottom', 'border-bottom-width']; - } - else { - properties = ['border-left-width', 'padding-left', - 'padding-right', 'border-right-width']; - } - return properties.inject(dim, function(memo, property) { - var val = proceed(element, property); - return val === null ? memo : memo - parseInt(val, 10); - }) + 'px'; - default: return proceed(element, style); - } - } - ); - - Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( - function(proceed, element, attribute) { - if (attribute === 'title') return element.title; - return proceed(element, attribute); - } - ); -} - -else if (Prototype.Browser.IE) { - // IE doesn't report offsets correctly for static elements, so we change them - // to "relative" to get the values, then change them back. - Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( - function(proceed, element) { - element = $(element); - // IE throws an error if element is not in document - try { element.offsetParent } - catch(e) { return $(document.body) } - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - - $w('positionedOffset viewportOffset').each(function(method) { - Element.Methods[method] = Element.Methods[method].wrap( - function(proceed, element) { - element = $(element); - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - // Trigger hasLayout on the offset parent so that IE6 reports - // accurate offsetTop and offsetLeft values for position: fixed. - var offsetParent = element.getOffsetParent(); - if (offsetParent && offsetParent.getStyle('position') === 'fixed') - offsetParent.setStyle({ zoom: 1 }); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - }); - - Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( - function(proceed, element) { - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - return proceed(element); - } - ); - - Element.Methods.getStyle = function(element, style) { - element = $(element); - style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); - var value = element.style[style]; - if (!value && element.currentStyle) value = element.currentStyle[style]; - - if (style == 'opacity') { - if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) - if (value[1]) return parseFloat(value[1]) / 100; - return 1.0; - } - - if (value == 'auto') { - if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) - return element['offset' + style.capitalize()] + 'px'; - return null; - } - return value; - }; - - Element.Methods.setOpacity = function(element, value) { - function stripAlpha(filter){ - return filter.replace(/alpha\([^\)]*\)/gi,''); - } - element = $(element); - var currentStyle = element.currentStyle; - if ((currentStyle && !currentStyle.hasLayout) || - (!currentStyle && element.style.zoom == 'normal')) - element.style.zoom = 1; - - var filter = element.getStyle('filter'), style = element.style; - if (value == 1 || value === '') { - (filter = stripAlpha(filter)) ? - style.filter = filter : style.removeAttribute('filter'); - return element; - } else if (value < 0.00001) value = 0; - style.filter = stripAlpha(filter) + - 'alpha(opacity=' + (value * 100) + ')'; - return element; - }; - - Element._attributeTranslations = { - read: { - names: { - 'class': 'className', - 'for': 'htmlFor' - }, - values: { - _getAttr: function(element, attribute) { - return element.getAttribute(attribute, 2); - }, - _getAttrNode: function(element, attribute) { - var node = element.getAttributeNode(attribute); - return node ? node.value : ""; - }, - _getEv: function(element, attribute) { - attribute = element.getAttribute(attribute); - return attribute ? attribute.toString().slice(23, -2) : null; - }, - _flag: function(element, attribute) { - return $(element).hasAttribute(attribute) ? attribute : null; - }, - style: function(element) { - return element.style.cssText.toLowerCase(); - }, - title: function(element) { - return element.title; - } - } - } - }; - - Element._attributeTranslations.write = { - names: Object.extend({ - cellpadding: 'cellPadding', - cellspacing: 'cellSpacing' - }, Element._attributeTranslations.read.names), - values: { - checked: function(element, value) { - element.checked = !!value; - }, - - style: function(element, value) { - element.style.cssText = value ? value : ''; - } - } - }; - - Element._attributeTranslations.has = {}; - - $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + - 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { - Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; - Element._attributeTranslations.has[attr.toLowerCase()] = attr; - }); - - (function(v) { - Object.extend(v, { - href: v._getAttr, - src: v._getAttr, - type: v._getAttr, - action: v._getAttrNode, - disabled: v._flag, - checked: v._flag, - readonly: v._flag, - multiple: v._flag, - onload: v._getEv, - onunload: v._getEv, - onclick: v._getEv, - ondblclick: v._getEv, - onmousedown: v._getEv, - onmouseup: v._getEv, - onmouseover: v._getEv, - onmousemove: v._getEv, - onmouseout: v._getEv, - onfocus: v._getEv, - onblur: v._getEv, - onkeypress: v._getEv, - onkeydown: v._getEv, - onkeyup: v._getEv, - onsubmit: v._getEv, - onreset: v._getEv, - onselect: v._getEv, - onchange: v._getEv - }); - })(Element._attributeTranslations.read.values); -} - -else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1) ? 0.999999 : - (value === '') ? '' : (value < 0.00001) ? 0 : value; - return element; - }; -} - -else if (Prototype.Browser.WebKit) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - - if (value == 1) - if(element.tagName.toUpperCase() == 'IMG' && element.width) { - element.width++; element.width--; - } else try { - var n = document.createTextNode(' '); - element.appendChild(n); - element.removeChild(n); - } catch (e) { } - - return element; - }; - - // Safari returns margins on body which is incorrect if the child is absolutely - // positioned. For performance reasons, redefine Element#cumulativeOffset for - // KHTML/WebKit only. - Element.Methods.cumulativeOffset = function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - if (element.offsetParent == document.body) - if (Element.getStyle(element, 'position') == 'absolute') break; - - element = element.offsetParent; - } while (element); - - return Element._returnOffset(valueL, valueT); - }; -} - -if (Prototype.Browser.IE || Prototype.Browser.Opera) { - // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements - Element.Methods.update = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - - content = Object.toHTML(content); - var tagName = element.tagName.toUpperCase(); - - if (tagName in Element._insertionTranslations.tags) { - $A(element.childNodes).each(function(node) { element.removeChild(node) }); - Element._getContentFromAnonymousElement(tagName, content.stripScripts()) - .each(function(node) { element.appendChild(node) }); - } - else element.innerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; -} - -if ('outerHTML' in document.createElement('div')) { - Element.Methods.replace = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - element.parentNode.replaceChild(content, element); - return element; - } - - content = Object.toHTML(content); - var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); - - if (Element._insertionTranslations.tags[tagName]) { - var nextSibling = element.next(); - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - parent.removeChild(element); - if (nextSibling) - fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); - else - fragments.each(function(node) { parent.appendChild(node) }); - } - else element.outerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; -} - -Element._returnOffset = function(l, t) { - var result = [l, t]; - result.left = l; - result.top = t; - return result; -}; - -Element._getContentFromAnonymousElement = function(tagName, html) { - var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; - if (t) { - div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); - } else div.innerHTML = html; - return $A(div.childNodes); -}; - -Element._insertionTranslations = { - before: function(element, node) { - element.parentNode.insertBefore(node, element); - }, - top: function(element, node) { - element.insertBefore(node, element.firstChild); - }, - bottom: function(element, node) { - element.appendChild(node); - }, - after: function(element, node) { - element.parentNode.insertBefore(node, element.nextSibling); - }, - tags: { - TABLE: ['
     '+(serie.label || String.fromCharCode(65+i))+'
    ', '
    ', 1], - TBODY: ['', '
    ', 2], - TR: ['', '
    ', 3], - TD: ['
    ', '
    ', 4], - SELECT: ['', 1] - } -}; - -(function() { - Object.extend(this.tags, { - THEAD: this.tags.TBODY, - TFOOT: this.tags.TBODY, - TH: this.tags.TD - }); -}).call(Element._insertionTranslations); - -Element.Methods.Simulated = { - hasAttribute: function(element, attribute) { - attribute = Element._attributeTranslations.has[attribute] || attribute; - var node = $(element).getAttributeNode(attribute); - return !!(node && node.specified); - } -}; - -Element.Methods.ByTag = { }; - -Object.extend(Element, Element.Methods); - -if (!Prototype.BrowserFeatures.ElementExtensions && - document.createElement('div')['__proto__']) { - window.HTMLElement = { }; - window.HTMLElement.prototype = document.createElement('div')['__proto__']; - Prototype.BrowserFeatures.ElementExtensions = true; -} - -Element.extend = (function() { - if (Prototype.BrowserFeatures.SpecificElementExtensions) - return Prototype.K; - - var Methods = { }, ByTag = Element.Methods.ByTag; - - var extend = Object.extend(function(element) { - if (!element || element._extendedByPrototype || - element.nodeType != 1 || element == window) return element; - - var methods = Object.clone(Methods), - tagName = element.tagName.toUpperCase(), property, value; - - // extend methods for specific tags - if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); - - for (property in methods) { - value = methods[property]; - if (Object.isFunction(value) && !(property in element)) - element[property] = value.methodize(); - } - - element._extendedByPrototype = Prototype.emptyFunction; - return element; - - }, { - refresh: function() { - // extend methods for all tags (Safari doesn't need this) - if (!Prototype.BrowserFeatures.ElementExtensions) { - Object.extend(Methods, Element.Methods); - Object.extend(Methods, Element.Methods.Simulated); - } - } - }); - - extend.refresh(); - return extend; -})(); - -Element.hasAttribute = function(element, attribute) { - if (element.hasAttribute) return element.hasAttribute(attribute); - return Element.Methods.Simulated.hasAttribute(element, attribute); -}; - -Element.addMethods = function(methods) { - var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; - - if (!methods) { - Object.extend(Form, Form.Methods); - Object.extend(Form.Element, Form.Element.Methods); - Object.extend(Element.Methods.ByTag, { - "FORM": Object.clone(Form.Methods), - "INPUT": Object.clone(Form.Element.Methods), - "SELECT": Object.clone(Form.Element.Methods), - "TEXTAREA": Object.clone(Form.Element.Methods) - }); - } - - if (arguments.length == 2) { - var tagName = methods; - methods = arguments[1]; - } - - if (!tagName) Object.extend(Element.Methods, methods || { }); - else { - if (Object.isArray(tagName)) tagName.each(extend); - else extend(tagName); - } - - function extend(tagName) { - tagName = tagName.toUpperCase(); - if (!Element.Methods.ByTag[tagName]) - Element.Methods.ByTag[tagName] = { }; - Object.extend(Element.Methods.ByTag[tagName], methods); - } - - function copy(methods, destination, onlyIfAbsent) { - onlyIfAbsent = onlyIfAbsent || false; - for (var property in methods) { - var value = methods[property]; - if (!Object.isFunction(value)) continue; - if (!onlyIfAbsent || !(property in destination)) - destination[property] = value.methodize(); - } - } - - function findDOMClass(tagName) { - var klass; - var trans = { - "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", - "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", - "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", - "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", - "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": - "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": - "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": - "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": - "FrameSet", "IFRAME": "IFrame" - }; - if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName.capitalize() + 'Element'; - if (window[klass]) return window[klass]; - - window[klass] = { }; - window[klass].prototype = document.createElement(tagName)['__proto__']; - return window[klass]; - } - - if (F.ElementExtensions) { - copy(Element.Methods, HTMLElement.prototype); - copy(Element.Methods.Simulated, HTMLElement.prototype, true); - } - - if (F.SpecificElementExtensions) { - for (var tag in Element.Methods.ByTag) { - var klass = findDOMClass(tag); - if (Object.isUndefined(klass)) continue; - copy(T[tag], klass.prototype); - } - } - - Object.extend(Element, Element.Methods); - delete Element.ByTag; - - if (Element.extend.refresh) Element.extend.refresh(); - Element.cache = { }; -}; - -document.viewport = { - getDimensions: function() { - var dimensions = { }, B = Prototype.Browser; - $w('width height').each(function(d) { - var D = d.capitalize(); - if (B.WebKit && !document.evaluate) { - // Safari <3.0 needs self.innerWidth/Height - dimensions[d] = self['inner' + D]; - } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { - // Opera <9.5 needs document.body.clientWidth/Height - dimensions[d] = document.body['client' + D] - } else { - dimensions[d] = document.documentElement['client' + D]; - } - }); - return dimensions; - }, - - getWidth: function() { - return this.getDimensions().width; - }, - - getHeight: function() { - return this.getDimensions().height; - }, - - getScrollOffsets: function() { - return Element._returnOffset( - window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, - window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); - } -}; -/* Portions of the Selector class are derived from Jack Slocum's DomQuery, - * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style - * license. Please see http://www.yui-ext.com/ for more information. */ - -var Selector = Class.create({ - initialize: function(expression) { - this.expression = expression.strip(); - - if (this.shouldUseSelectorsAPI()) { - this.mode = 'selectorsAPI'; - } else if (this.shouldUseXPath()) { - this.mode = 'xpath'; - this.compileXPathMatcher(); - } else { - this.mode = "normal"; - this.compileMatcher(); - } - - }, - - shouldUseXPath: function() { - if (!Prototype.BrowserFeatures.XPath) return false; - - var e = this.expression; - - // Safari 3 chokes on :*-of-type and :empty - if (Prototype.Browser.WebKit && - (e.include("-of-type") || e.include(":empty"))) - return false; - - // XPath can't do namespaced attributes, nor can it read - // the "checked" property from DOM nodes - if ((/(\[[\w-]*?:|:checked)/).test(e)) - return false; - - return true; - }, - - shouldUseSelectorsAPI: function() { - if (!Prototype.BrowserFeatures.SelectorsAPI) return false; - - if (!Selector._div) Selector._div = new Element('div'); - - // Make sure the browser treats the selector as valid. Test on an - // isolated element to minimize cost of this check. - try { - Selector._div.querySelector(this.expression); - } catch(e) { - return false; - } - - return true; - }, - - compileMatcher: function() { - var e = this.expression, ps = Selector.patterns, h = Selector.handlers, - c = Selector.criteria, le, p, m; - - if (Selector._cache[e]) { - this.matcher = Selector._cache[e]; - return; - } - - this.matcher = ["this.matcher = function(root) {", - "var r = root, h = Selector.handlers, c = false, n;"]; - - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - p = ps[i]; - if (m = e.match(p)) { - this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : - new Template(c[i]).evaluate(m)); - e = e.replace(m[0], ''); - break; - } - } - } - - this.matcher.push("return h.unique(n);\n}"); - eval(this.matcher.join('\n')); - Selector._cache[this.expression] = this.matcher; - }, - - compileXPathMatcher: function() { - var e = this.expression, ps = Selector.patterns, - x = Selector.xpath, le, m; - - if (Selector._cache[e]) { - this.xpath = Selector._cache[e]; return; - } - - this.matcher = ['.//*']; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - if (m = e.match(ps[i])) { - this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : - new Template(x[i]).evaluate(m)); - e = e.replace(m[0], ''); - break; - } - } - } - - this.xpath = this.matcher.join(''); - Selector._cache[this.expression] = this.xpath; - }, - - findElements: function(root) { - root = root || document; - var e = this.expression, results; - - switch (this.mode) { - case 'selectorsAPI': - // querySelectorAll queries document-wide, then filters to descendants - // of the context element. That's not what we want. - // Add an explicit context to the selector if necessary. - if (root !== document) { - var oldId = root.id, id = $(root).identify(); - e = "#" + id + " " + e; - } - - results = $A(root.querySelectorAll(e)).map(Element.extend); - root.id = oldId; - - return results; - case 'xpath': - return document._getElementsByXPath(this.xpath, root); - default: - return this.matcher(root); - } - }, - - match: function(element) { - this.tokens = []; - - var e = this.expression, ps = Selector.patterns, as = Selector.assertions; - var le, p, m; - - while (e && le !== e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - p = ps[i]; - if (m = e.match(p)) { - // use the Selector.assertions methods unless the selector - // is too complex. - if (as[i]) { - this.tokens.push([i, Object.clone(m)]); - e = e.replace(m[0], ''); - } else { - // reluctantly do a document-wide search - // and look for a match in the array - return this.findElements(document).include(element); - } - } - } - } - - var match = true, name, matches; - for (var i = 0, token; token = this.tokens[i]; i++) { - name = token[0], matches = token[1]; - if (!Selector.assertions[name](element, matches)) { - match = false; break; - } - } - - return match; - }, - - toString: function() { - return this.expression; - }, - - inspect: function() { - return "#"; - } -}); - -Object.extend(Selector, { - _cache: { }, - - xpath: { - descendant: "//*", - child: "/*", - adjacent: "/following-sibling::*[1]", - laterSibling: '/following-sibling::*', - tagName: function(m) { - if (m[1] == '*') return ''; - return "[local-name()='" + m[1].toLowerCase() + - "' or local-name()='" + m[1].toUpperCase() + "']"; - }, - className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", - id: "[@id='#{1}']", - attrPresence: function(m) { - m[1] = m[1].toLowerCase(); - return new Template("[@#{1}]").evaluate(m); - }, - attr: function(m) { - m[1] = m[1].toLowerCase(); - m[3] = m[5] || m[6]; - return new Template(Selector.xpath.operators[m[2]]).evaluate(m); - }, - pseudo: function(m) { - var h = Selector.xpath.pseudos[m[1]]; - if (!h) return ''; - if (Object.isFunction(h)) return h(m); - return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); - }, - operators: { - '=': "[@#{1}='#{3}']", - '!=': "[@#{1}!='#{3}']", - '^=': "[starts-with(@#{1}, '#{3}')]", - '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", - '*=': "[contains(@#{1}, '#{3}')]", - '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", - '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" - }, - pseudos: { - 'first-child': '[not(preceding-sibling::*)]', - 'last-child': '[not(following-sibling::*)]', - 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0)]", - 'checked': "[@checked]", - 'disabled': "[(@disabled) and (@type!='hidden')]", - 'enabled': "[not(@disabled) and (@type!='hidden')]", - 'not': function(m) { - var e = m[6], p = Selector.patterns, - x = Selector.xpath, le, v; - - var exclusion = []; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in p) { - if (m = e.match(p[i])) { - v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); - exclusion.push("(" + v.substring(1, v.length - 1) + ")"); - e = e.replace(m[0], ''); - break; - } - } - } - return "[not(" + exclusion.join(" and ") + ")]"; - }, - 'nth-child': function(m) { - return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); - }, - 'nth-last-child': function(m) { - return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); - }, - 'nth-of-type': function(m) { - return Selector.xpath.pseudos.nth("position() ", m); - }, - 'nth-last-of-type': function(m) { - return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); - }, - 'first-of-type': function(m) { - m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); - }, - 'last-of-type': function(m) { - m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); - }, - 'only-of-type': function(m) { - var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); - }, - nth: function(fragment, m) { - var mm, formula = m[6], predicate; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - if (mm = formula.match(/^(\d+)$/)) // digit only - return '[' + fragment + "= " + mm[1] + ']'; - if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (mm[1] == "-") mm[1] = -1; - var a = mm[1] ? Number(mm[1]) : 1; - var b = mm[2] ? Number(mm[2]) : 0; - predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + - "((#{fragment} - #{b}) div #{a} >= 0)]"; - return new Template(predicate).evaluate({ - fragment: fragment, a: a, b: b }); - } - } - } - }, - - criteria: { - tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', - className: 'n = h.className(n, r, "#{1}", c); c = false;', - id: 'n = h.id(n, r, "#{1}", c); c = false;', - attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', - attr: function(m) { - m[3] = (m[5] || m[6]); - return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); - }, - pseudo: function(m) { - if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); - return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); - }, - descendant: 'c = "descendant";', - child: 'c = "child";', - adjacent: 'c = "adjacent";', - laterSibling: 'c = "laterSibling";' - }, - - patterns: { - // combinators must be listed first - // (and descendant needs to be last combinator) - laterSibling: /^\s*~\s*/, - child: /^\s*>\s*/, - adjacent: /^\s*\+\s*/, - descendant: /^\s/, - - // selectors follow - tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, - id: /^#([\w\-\*]+)(\b|$)/, - className: /^\.([\w\-\*]+)(\b|$)/, - pseudo: -/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, - attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, - attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ - }, - - // for Selector.match and Element#match - assertions: { - tagName: function(element, matches) { - return matches[1].toUpperCase() == element.tagName.toUpperCase(); - }, - - className: function(element, matches) { - return Element.hasClassName(element, matches[1]); - }, - - id: function(element, matches) { - return element.id === matches[1]; - }, - - attrPresence: function(element, matches) { - return Element.hasAttribute(element, matches[1]); - }, - - attr: function(element, matches) { - var nodeValue = Element.readAttribute(element, matches[1]); - return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); - } - }, - - handlers: { - // UTILITY FUNCTIONS - // joins two collections - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - a.push(node); - return a; - }, - - // marks an array of nodes for counting - mark: function(nodes) { - var _true = Prototype.emptyFunction; - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = _true; - return nodes; - }, - - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = undefined; - return nodes; - }, - - // mark each child node with its position (for nth calls) - // "ofType" flag indicates whether we're indexing for nth-of-type - // rather than nth-child - index: function(parentNode, reverse, ofType) { - parentNode._countedByPrototype = Prototype.emptyFunction; - if (reverse) { - for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { - var node = nodes[i]; - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - } else { - for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - }, - - // filters out duplicates and extends all nodes - unique: function(nodes) { - if (nodes.length == 0) return nodes; - var results = [], n; - for (var i = 0, l = nodes.length; i < l; i++) - if (!(n = nodes[i])._countedByPrototype) { - n._countedByPrototype = Prototype.emptyFunction; - results.push(Element.extend(n)); - } - return Selector.handlers.unmark(results); - }, - - // COMBINATOR FUNCTIONS - descendant: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName('*')); - return results; - }, - - child: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) { - for (var j = 0, child; child = node.childNodes[j]; j++) - if (child.nodeType == 1 && child.tagName != '!') results.push(child); - } - return results; - }, - - adjacent: function(nodes) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - var next = this.nextElementSibling(node); - if (next) results.push(next); - } - return results; - }, - - laterSibling: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, Element.nextSiblings(node)); - return results; - }, - - nextElementSibling: function(node) { - while (node = node.nextSibling) - if (node.nodeType == 1) return node; - return null; - }, - - previousElementSibling: function(node) { - while (node = node.previousSibling) - if (node.nodeType == 1) return node; - return null; - }, - - // TOKEN FUNCTIONS - tagName: function(nodes, root, tagName, combinator) { - var uTagName = tagName.toUpperCase(); - var results = [], h = Selector.handlers; - if (nodes) { - if (combinator) { - // fastlane for ordinary descendant combinators - if (combinator == "descendant") { - for (var i = 0, node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName(tagName)); - return results; - } else nodes = this[combinator](nodes); - if (tagName == "*") return nodes; - } - for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() === uTagName) results.push(node); - return results; - } else return root.getElementsByTagName(tagName); - }, - - id: function(nodes, root, id, combinator) { - var targetNode = $(id), h = Selector.handlers; - if (!targetNode) return []; - if (!nodes && root == document) return [targetNode]; - if (nodes) { - if (combinator) { - if (combinator == 'child') { - for (var i = 0, node; node = nodes[i]; i++) - if (targetNode.parentNode == node) return [targetNode]; - } else if (combinator == 'descendant') { - for (var i = 0, node; node = nodes[i]; i++) - if (Element.descendantOf(targetNode, node)) return [targetNode]; - } else if (combinator == 'adjacent') { - for (var i = 0, node; node = nodes[i]; i++) - if (Selector.handlers.previousElementSibling(targetNode) == node) - return [targetNode]; - } else nodes = h[combinator](nodes); - } - for (var i = 0, node; node = nodes[i]; i++) - if (node == targetNode) return [targetNode]; - return []; - } - return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; - }, - - className: function(nodes, root, className, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - return Selector.handlers.byClassName(nodes, root, className); - }, - - byClassName: function(nodes, root, className) { - if (!nodes) nodes = Selector.handlers.descendant([root]); - var needle = ' ' + className + ' '; - for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { - nodeClassName = node.className; - if (nodeClassName.length == 0) continue; - if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) - results.push(node); - } - return results; - }, - - attrPresence: function(nodes, root, attr, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var results = []; - for (var i = 0, node; node = nodes[i]; i++) - if (Element.hasAttribute(node, attr)) results.push(node); - return results; - }, - - attr: function(nodes, root, attr, value, operator, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var handler = Selector.operators[operator], results = []; - for (var i = 0, node; node = nodes[i]; i++) { - var nodeValue = Element.readAttribute(node, attr); - if (nodeValue === null) continue; - if (handler(nodeValue, value)) results.push(node); - } - return results; - }, - - pseudo: function(nodes, name, value, root, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - if (!nodes) nodes = root.getElementsByTagName("*"); - return Selector.pseudos[name](nodes, value, root); - } - }, - - pseudos: { - 'first-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.previousElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'last-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.nextElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'only-child': function(nodes, value, root) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) - results.push(node); - return results; - }, - 'nth-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root); - }, - 'nth-last-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true); - }, - 'nth-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, false, true); - }, - 'nth-last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true, true); - }, - 'first-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, false, true); - }, - 'last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, true, true); - }, - 'only-of-type': function(nodes, formula, root) { - var p = Selector.pseudos; - return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); - }, - - // handles the an+b logic - getIndices: function(a, b, total) { - if (a == 0) return b > 0 ? [b] : []; - return $R(1, total).inject([], function(memo, i) { - if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); - return memo; - }); - }, - - // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type - nth: function(nodes, formula, root, reverse, ofType) { - if (nodes.length == 0) return []; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - var h = Selector.handlers, results = [], indexed = [], m; - h.mark(nodes); - for (var i = 0, node; node = nodes[i]; i++) { - if (!node.parentNode._countedByPrototype) { - h.index(node.parentNode, reverse, ofType); - indexed.push(node.parentNode); - } - } - if (formula.match(/^\d+$/)) { // just a number - formula = Number(formula); - for (var i = 0, node; node = nodes[i]; i++) - if (node.nodeIndex == formula) results.push(node); - } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (m[1] == "-") m[1] = -1; - var a = m[1] ? Number(m[1]) : 1; - var b = m[2] ? Number(m[2]) : 0; - var indices = Selector.pseudos.getIndices(a, b, nodes.length); - for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { - for (var j = 0; j < l; j++) - if (node.nodeIndex == indices[j]) results.push(node); - } - } - h.unmark(nodes); - h.unmark(indexed); - return results; - }, - - 'empty': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - // IE treats comments as element nodes - if (node.tagName == '!' || node.firstChild) continue; - results.push(node); - } - return results; - }, - - 'not': function(nodes, selector, root) { - var h = Selector.handlers, selectorType, m; - var exclusions = new Selector(selector).findElements(root); - h.mark(exclusions); - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node._countedByPrototype) results.push(node); - h.unmark(exclusions); - return results; - }, - - 'enabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled && (!node.type || node.type !== 'hidden')) - results.push(node); - return results; - }, - - 'disabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.disabled) results.push(node); - return results; - }, - - 'checked': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.checked) results.push(node); - return results; - } - }, - - operators: { - '=': function(nv, v) { return nv == v; }, - '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, - '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, - '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, - '$=': function(nv, v) { return nv.endsWith(v); }, - '*=': function(nv, v) { return nv.include(v); }, - '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + - '-').include('-' + (v || "").toUpperCase() + '-'); } - }, - - split: function(expression) { - var expressions = []; - expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { - expressions.push(m[1].strip()); - }); - return expressions; - }, - - matchElements: function(elements, expression) { - var matches = $$(expression), h = Selector.handlers; - h.mark(matches); - for (var i = 0, results = [], element; element = elements[i]; i++) - if (element._countedByPrototype) results.push(element); - h.unmark(matches); - return results; - }, - - findElement: function(elements, expression, index) { - if (Object.isNumber(expression)) { - index = expression; expression = false; - } - return Selector.matchElements(elements, expression || '*')[index || 0]; - }, - - findChildElements: function(element, expressions) { - expressions = Selector.split(expressions.join(',')); - var results = [], h = Selector.handlers; - for (var i = 0, l = expressions.length, selector; i < l; i++) { - selector = new Selector(expressions[i].strip()); - h.concat(results, selector.findElements(element)); - } - return (l > 1) ? h.unique(results) : results; - } -}); - -if (Prototype.Browser.IE) { - Object.extend(Selector.handlers, { - // IE returns comment nodes on getElementsByTagName("*"). - // Filter them out. - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - if (node.tagName !== "!") a.push(node); - return a; - }, - - // IE improperly serializes _countedByPrototype in (inner|outer)HTML. - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node.removeAttribute('_countedByPrototype'); - return nodes; - } - }); -} - -function $$() { - return Selector.findChildElements(document, $A(arguments)); -} -var Form = { - reset: function(form) { - $(form).reset(); - return form; - }, - - serializeElements: function(elements, options) { - if (typeof options != 'object') options = { hash: !!options }; - else if (Object.isUndefined(options.hash)) options.hash = true; - var key, value, submitted = false, submit = options.submit; - - var data = elements.inject({ }, function(result, element) { - if (!element.disabled && element.name) { - key = element.name; value = $(element).getValue(); - if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && - submit !== false && (!submit || key == submit) && (submitted = true)))) { - if (key in result) { - // a key is already present; construct an array of values - if (!Object.isArray(result[key])) result[key] = [result[key]]; - result[key].push(value); - } - else result[key] = value; - } - } - return result; - }); - - return options.hash ? data : Object.toQueryString(data); - } -}; - -Form.Methods = { - serialize: function(form, options) { - return Form.serializeElements(Form.getElements(form), options); - }, - - getElements: function(form) { - return $A($(form).getElementsByTagName('*')).inject([], - function(elements, child) { - if (Form.Element.Serializers[child.tagName.toLowerCase()]) - elements.push(Element.extend(child)); - return elements; - } - ); - }, - - getInputs: function(form, typeName, name) { - form = $(form); - var inputs = form.getElementsByTagName('input'); - - if (!typeName && !name) return $A(inputs).map(Element.extend); - - for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { - var input = inputs[i]; - if ((typeName && input.type != typeName) || (name && input.name != name)) - continue; - matchingInputs.push(Element.extend(input)); - } - - return matchingInputs; - }, - - disable: function(form) { - form = $(form); - Form.getElements(form).invoke('disable'); - return form; - }, - - enable: function(form) { - form = $(form); - Form.getElements(form).invoke('enable'); - return form; - }, - - findFirstElement: function(form) { - var elements = $(form).getElements().findAll(function(element) { - return 'hidden' != element.type && !element.disabled; - }); - var firstByIndex = elements.findAll(function(element) { - return element.hasAttribute('tabIndex') && element.tabIndex >= 0; - }).sortBy(function(element) { return element.tabIndex }).first(); - - return firstByIndex ? firstByIndex : elements.find(function(element) { - return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); - }); - }, - - focusFirstElement: function(form) { - form = $(form); - form.findFirstElement().activate(); - return form; - }, - - request: function(form, options) { - form = $(form), options = Object.clone(options || { }); - - var params = options.parameters, action = form.readAttribute('action') || ''; - if (action.blank()) action = window.location.href; - options.parameters = form.serialize(true); - - if (params) { - if (Object.isString(params)) params = params.toQueryParams(); - Object.extend(options.parameters, params); - } - - if (form.hasAttribute('method') && !options.method) - options.method = form.method; - - return new Ajax.Request(action, options); - } -}; - -/*--------------------------------------------------------------------------*/ - -Form.Element = { - focus: function(element) { - $(element).focus(); - return element; - }, - - select: function(element) { - $(element).select(); - return element; - } -}; - -Form.Element.Methods = { - serialize: function(element) { - element = $(element); - if (!element.disabled && element.name) { - var value = element.getValue(); - if (value != undefined) { - var pair = { }; - pair[element.name] = value; - return Object.toQueryString(pair); - } - } - return ''; - }, - - getValue: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - return Form.Element.Serializers[method](element); - }, - - setValue: function(element, value) { - element = $(element); - var method = element.tagName.toLowerCase(); - Form.Element.Serializers[method](element, value); - return element; - }, - - clear: function(element) { - $(element).value = ''; - return element; - }, - - present: function(element) { - return $(element).value != ''; - }, - - activate: function(element) { - element = $(element); - try { - element.focus(); - if (element.select && (element.tagName.toLowerCase() != 'input' || - !['button', 'reset', 'submit'].include(element.type))) - element.select(); - } catch (e) { } - return element; - }, - - disable: function(element) { - element = $(element); - element.disabled = true; - return element; - }, - - enable: function(element) { - element = $(element); - element.disabled = false; - return element; - } -}; - -/*--------------------------------------------------------------------------*/ - -var Field = Form.Element; -var $F = Form.Element.Methods.getValue; - -/*--------------------------------------------------------------------------*/ - -Form.Element.Serializers = { - input: function(element, value) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - return Form.Element.Serializers.inputSelector(element, value); - default: - return Form.Element.Serializers.textarea(element, value); - } - }, - - inputSelector: function(element, value) { - if (Object.isUndefined(value)) return element.checked ? element.value : null; - else element.checked = !!value; - }, - - textarea: function(element, value) { - if (Object.isUndefined(value)) return element.value; - else element.value = value; - }, - - select: function(element, value) { - if (Object.isUndefined(value)) - return this[element.type == 'select-one' ? - 'selectOne' : 'selectMany'](element); - else { - var opt, currentValue, single = !Object.isArray(value); - for (var i = 0, length = element.length; i < length; i++) { - opt = element.options[i]; - currentValue = this.optionValue(opt); - if (single) { - if (currentValue == value) { - opt.selected = true; - return; - } - } - else opt.selected = value.include(currentValue); - } - } - }, - - selectOne: function(element) { - var index = element.selectedIndex; - return index >= 0 ? this.optionValue(element.options[index]) : null; - }, - - selectMany: function(element) { - var values, length = element.length; - if (!length) return null; - - for (var i = 0, values = []; i < length; i++) { - var opt = element.options[i]; - if (opt.selected) values.push(this.optionValue(opt)); - } - return values; - }, - - optionValue: function(opt) { - // extend element because hasAttribute may not be native - return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; - } -}; - -/*--------------------------------------------------------------------------*/ - -Abstract.TimedObserver = Class.create(PeriodicalExecuter, { - initialize: function($super, element, frequency, callback) { - $super(callback, frequency); - this.element = $(element); - this.lastValue = this.getValue(); - }, - - execute: function() { - var value = this.getValue(); - if (Object.isString(this.lastValue) && Object.isString(value) ? - this.lastValue != value : String(this.lastValue) != String(value)) { - this.callback(this.element, value); - this.lastValue = value; - } - } -}); - -Form.Element.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); - -/*--------------------------------------------------------------------------*/ - -Abstract.EventObserver = Class.create({ - initialize: function(element, callback) { - this.element = $(element); - this.callback = callback; - - this.lastValue = this.getValue(); - if (this.element.tagName.toLowerCase() == 'form') - this.registerFormCallbacks(); - else - this.registerCallback(this.element); - }, - - onElementEvent: function() { - var value = this.getValue(); - if (this.lastValue != value) { - this.callback(this.element, value); - this.lastValue = value; - } - }, - - registerFormCallbacks: function() { - Form.getElements(this.element).each(this.registerCallback, this); - }, - - registerCallback: function(element) { - if (element.type) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - Event.observe(element, 'click', this.onElementEvent.bind(this)); - break; - default: - Event.observe(element, 'change', this.onElementEvent.bind(this)); - break; - } - } - } -}); - -Form.Element.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); -if (!window.Event) var Event = { }; - -Object.extend(Event, { - KEY_BACKSPACE: 8, - KEY_TAB: 9, - KEY_RETURN: 13, - KEY_ESC: 27, - KEY_LEFT: 37, - KEY_UP: 38, - KEY_RIGHT: 39, - KEY_DOWN: 40, - KEY_DELETE: 46, - KEY_HOME: 36, - KEY_END: 35, - KEY_PAGEUP: 33, - KEY_PAGEDOWN: 34, - KEY_INSERT: 45, - - cache: { }, - - relatedTarget: function(event) { - var element; - switch(event.type) { - case 'mouseover': element = event.fromElement; break; - case 'mouseout': element = event.toElement; break; - default: return null; - } - return Element.extend(element); - } -}); - -Event.Methods = (function() { - var isButton; - - if (Prototype.Browser.IE) { - var buttonMap = { 0: 1, 1: 4, 2: 2 }; - isButton = function(event, code) { - return event.button == buttonMap[code]; - }; - - } else if (Prototype.Browser.WebKit) { - isButton = function(event, code) { - switch (code) { - case 0: return event.which == 1 && !event.metaKey; - case 1: return event.which == 1 && event.metaKey; - default: return false; - } - }; - - } else { - isButton = function(event, code) { - return event.which ? (event.which === code + 1) : (event.button === code); - }; - } - - return { - isLeftClick: function(event) { return isButton(event, 0) }, - isMiddleClick: function(event) { return isButton(event, 1) }, - isRightClick: function(event) { return isButton(event, 2) }, - - element: function(event) { - event = Event.extend(event); - - var node = event.target, - type = event.type, - currentTarget = event.currentTarget; - - if (currentTarget && currentTarget.tagName) { - // Firefox screws up the "click" event when moving between radio buttons - // via arrow keys. It also screws up the "load" and "error" events on images, - // reporting the document as the target instead of the original image. - if (type === 'load' || type === 'error' || - (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' - && currentTarget.type === 'radio')) - node = currentTarget; - } - if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; - return Element.extend(node); - }, - - findElement: function(event, expression) { - var element = Event.element(event); - if (!expression) return element; - var elements = [element].concat(element.ancestors()); - return Selector.findElement(elements, expression, 0); - }, - - pointer: function(event) { - var docElement = document.documentElement, - body = document.body || { scrollLeft: 0, scrollTop: 0 }; - return { - x: event.pageX || (event.clientX + - (docElement.scrollLeft || body.scrollLeft) - - (docElement.clientLeft || 0)), - y: event.pageY || (event.clientY + - (docElement.scrollTop || body.scrollTop) - - (docElement.clientTop || 0)) - }; - }, - - pointerX: function(event) { return Event.pointer(event).x }, - pointerY: function(event) { return Event.pointer(event).y }, - - stop: function(event) { - Event.extend(event); - event.preventDefault(); - event.stopPropagation(); - event.stopped = true; - } - }; -})(); - -Event.extend = (function() { - var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { - m[name] = Event.Methods[name].methodize(); - return m; - }); - - if (Prototype.Browser.IE) { - Object.extend(methods, { - stopPropagation: function() { this.cancelBubble = true }, - preventDefault: function() { this.returnValue = false }, - inspect: function() { return "[object Event]" } - }); - - return function(event) { - if (!event) return false; - if (event._extendedByPrototype) return event; - - event._extendedByPrototype = Prototype.emptyFunction; - var pointer = Event.pointer(event); - Object.extend(event, { - target: event.srcElement, - relatedTarget: Event.relatedTarget(event), - pageX: pointer.x, - pageY: pointer.y - }); - return Object.extend(event, methods); - }; - - } else { - Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; - Object.extend(Event.prototype, methods); - return Prototype.K; - } -})(); - -Object.extend(Event, (function() { - var cache = Event.cache; - - function getEventID(element) { - if (element._prototypeEventID) return element._prototypeEventID[0]; - arguments.callee.id = arguments.callee.id || 1; - return element._prototypeEventID = [++arguments.callee.id]; - } - - function getDOMEventName(eventName) { - if (eventName && eventName.include(':')) return "dataavailable"; - return eventName; - } - - function getCacheForID(id) { - return cache[id] = cache[id] || { }; - } - - function getWrappersForEventName(id, eventName) { - var c = getCacheForID(id); - return c[eventName] = c[eventName] || []; - } - - function createWrapper(element, eventName, handler) { - var id = getEventID(element); - var c = getWrappersForEventName(id, eventName); - if (c.pluck("handler").include(handler)) return false; - - var wrapper = function(event) { - if (!Event || !Event.extend || - (event.eventName && event.eventName != eventName)) - return false; - - Event.extend(event); - handler.call(element, event); - }; - - wrapper.handler = handler; - c.push(wrapper); - return wrapper; - } - - function findWrapper(id, eventName, handler) { - var c = getWrappersForEventName(id, eventName); - return c.find(function(wrapper) { return wrapper.handler == handler }); - } - - function destroyWrapper(id, eventName, handler) { - var c = getCacheForID(id); - if (!c[eventName]) return false; - c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); - } - - function destroyCache() { - for (var id in cache) - for (var eventName in cache[id]) - cache[id][eventName] = null; - } - - - // Internet Explorer needs to remove event handlers on page unload - // in order to avoid memory leaks. - if (window.attachEvent) { - window.attachEvent("onunload", destroyCache); - } - - // Safari has a dummy event handler on page unload so that it won't - // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" - // object when page is returned to via the back button using its bfcache. - if (Prototype.Browser.WebKit) { - window.addEventListener('unload', Prototype.emptyFunction, false); - } - - return { - observe: function(element, eventName, handler) { - element = $(element); - var name = getDOMEventName(eventName); - - var wrapper = createWrapper(element, eventName, handler); - if (!wrapper) return element; - - if (element.addEventListener) { - element.addEventListener(name, wrapper, false); - } else { - element.attachEvent("on" + name, wrapper); - } - - return element; - }, - - stopObserving: function(element, eventName, handler) { - element = $(element); - var id = getEventID(element), name = getDOMEventName(eventName); - - if (!handler && eventName) { - getWrappersForEventName(id, eventName).each(function(wrapper) { - element.stopObserving(eventName, wrapper.handler); - }); - return element; - - } else if (!eventName) { - Object.keys(getCacheForID(id)).each(function(eventName) { - element.stopObserving(eventName); - }); - return element; - } - - var wrapper = findWrapper(id, eventName, handler); - if (!wrapper) return element; - - if (element.removeEventListener) { - element.removeEventListener(name, wrapper, false); - } else { - element.detachEvent("on" + name, wrapper); - } - - destroyWrapper(id, eventName, handler); - - return element; - }, - - fire: function(element, eventName, memo) { - element = $(element); - if (element == document && document.createEvent && !element.dispatchEvent) - element = document.documentElement; - - var event; - if (document.createEvent) { - event = document.createEvent("HTMLEvents"); - event.initEvent("dataavailable", true, true); - } else { - event = document.createEventObject(); - event.eventType = "ondataavailable"; - } - - event.eventName = eventName; - event.memo = memo || { }; - - if (document.createEvent) { - element.dispatchEvent(event); - } else { - element.fireEvent(event.eventType, event); - } - - return Event.extend(event); - } - }; -})()); - -Object.extend(Event, Event.Methods); - -Element.addMethods({ - fire: Event.fire, - observe: Event.observe, - stopObserving: Event.stopObserving -}); - -Object.extend(document, { - fire: Element.Methods.fire.methodize(), - observe: Element.Methods.observe.methodize(), - stopObserving: Element.Methods.stopObserving.methodize(), - loaded: false -}); - -(function() { - /* Support for the DOMContentLoaded event is based on work by Dan Webb, - Matthias Miller, Dean Edwards and John Resig. */ - - var timer; - - function fireContentLoadedEvent() { - if (document.loaded) return; - if (timer) window.clearInterval(timer); - document.fire("dom:loaded"); - document.loaded = true; - } - - if (document.addEventListener) { - if (Prototype.Browser.WebKit) { - timer = window.setInterval(function() { - if (/loaded|complete/.test(document.readyState)) - fireContentLoadedEvent(); - }, 0); - - Event.observe(window, "load", fireContentLoadedEvent); - - } else { - document.addEventListener("DOMContentLoaded", - fireContentLoadedEvent, false); - } - - } else { - document.write("\n\n\n\n" + + return request.make_response(resp,{ + 'Cache-Control': 'no-cache', + 'Content-Type': 'text/html; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET', + }) + + @http.route('/hw_proxy/status', type='json', auth='admin') + def status_json(self): + return request.make_response(simplejson.dumps(self.get_status()),{ + 'Cache-Control': 'no-cache', + 'Content-Type': 'text/html; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET', + }) @http.route('/hw_proxy/scan_item_success', type='json', auth='admin') def scan_item_success(self, ean): diff --git a/addons/hw_scanner/controllers/main.py b/addons/hw_scanner/controllers/main.py index cd70a353372..3e487cab404 100644 --- a/addons/hw_scanner/controllers/main.py +++ b/addons/hw_scanner/controllers/main.py @@ -26,7 +26,9 @@ except ImportError: class Scanner(Thread): def __init__(self): Thread.__init__(self) + self.status = {'status':'connecting', 'messages':[]} self.input_dir = '/dev/input/by-id/' + self.barcodes = Queue() self.keymap = { 2: ("1","!"), 3: ("2","@"), @@ -84,6 +86,24 @@ class Scanner(Thread): 57:(" "," "), } + def set_status(self, status, message = None): + if status == self.status['status']: + if message != None and message != self.status['messages'][-1]: + self.status['messages'].append(message) + else: + self.status['status'] = status + if message: + self.status['messages'] = [message] + else: + self.status['messages'] = [] + + if status == 'error' and message: + _logger.error('Barcode Scanner Error: '+message) + elif status == 'disconnected' and message: + _logger.warning('Disconnected Barcode Scanner: '+message) + + + def get_device(self): try: if not evdev: @@ -92,14 +112,16 @@ class Scanner(Thread): keyboards = [ device for device in devices if 'kbd' in device ] scanners = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower()) ] if len(scanners) > 0: + self.set_status('connected','Connected to '+scanners[0]) return evdev.InputDevice(join(self.input_dir,scanners[0])) elif len(keyboards) > 0: + self.set_status('connected','Connected to '+keyboards[0]) return evdev.InputDevice(join(self.input_dir,keyboards[0])) else: - _logger.error('Could not find the Barcode Scanner') + self.set_status('disconnected','Barcode Scanner Not Found') return None except Exception as e: - _logger.error('Found the Barcode Scanner, but unable to access it\n Exception: ' + str(e)) + self.set_status('error',str(e)) return None @http.route('/hw_proxy/Vis_scanner_connected', type='json', auth='admin') @@ -120,6 +142,9 @@ class Scanner(Thread): return barcode except Empty: return '' + + def get_status(self): + return self.status def run(self): """ This will start a loop that catches all keyboard events, parse barcode @@ -138,7 +163,7 @@ class Scanner(Thread): try: device.ungrab() except Exception as e: - _logger.error('Unable to release barcode scanner\n Exception:'+str(e)) + self.set_status('error',str(e)) device = self.get_device() if not device: time.sleep(5) # wait until a suitable device is plugged @@ -173,17 +198,13 @@ class Scanner(Thread): shift = False except Exception as e: - _logger.error('Could not read Barcode Scanner Events:\n Exception: '+str(e)) + self.set_status('error',str(e)) s = Scanner() +hw_proxy.drivers['scanner'] = s + class ScannerDriver(hw_proxy.Proxy): - @http.route('/hw_proxy/is_scanner_connected', type='json', auth='admin') - def is_scanner_connected(self): - if not s.isAlive(): - s.start() - return s.get_device() != None - @http.route('/hw_proxy/scanner', type='json', auth='admin') def scanner(self): if not s.isAlive(): From 7e535526cb8938e6f2b384667ee0fe70e57d1c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Sun, 26 Jan 2014 20:40:55 +0100 Subject: [PATCH 321/335] [FIX] hw_proxy: fix harmless exception on proxy restart bzr revid: fva@openerp.com-20140126194055-pietbyi7ezvdm9ch --- addons/hw_escpos/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py index 5d5808208df..36e655cd465 100644 --- a/addons/hw_escpos/controllers/main.py +++ b/addons/hw_escpos/controllers/main.py @@ -32,7 +32,7 @@ _logger = logging.getLogger(__name__) class EscposDriver(Thread): def __init__(self): Thread.__init__(self) - self.queue = None + self.queue = Queue() self.status = {'status':'connecting', 'messages':[]} self.supported_printers = [ From 1e09d3b3f507223d1cc2216dfa06cf59a9c38152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Sun, 26 Jan 2014 22:55:28 +0100 Subject: [PATCH 322/335] [IMP] point_of_sale: Realtime printer & scanner online status bzr revid: fva@openerp.com-20140126215528-q32v2q8keh6m0ufo --- addons/hw_proxy/controllers/main.py | 13 ++-- addons/point_of_sale/static/src/css/pos.css | 9 ++- addons/point_of_sale/static/src/js/devices.js | 74 ++++++++++++++----- addons/point_of_sale/static/src/js/models.js | 17 +++-- addons/point_of_sale/static/src/js/widgets.js | 30 +++++++- addons/point_of_sale/static/src/xml/pos.xml | 5 +- 6 files changed, 112 insertions(+), 36 deletions(-) diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py index 1888c2304d1..3ea2de4424b 100644 --- a/addons/hw_proxy/controllers/main.py +++ b/addons/hw_proxy/controllers/main.py @@ -42,6 +42,10 @@ class Proxy(http.Controller): 'Access-Control-Allow-Methods': 'GET', }) + @http.route('/hw_proxy/handshake', type='json', auth='admin') + def handshake(self): + return True + @http.route('/hw_proxy/status', type='http', auth='admin') def status_http(self): resp = '\n\n

    Hardware Proxy Status

    \n' @@ -71,14 +75,9 @@ class Proxy(http.Controller): 'Access-Control-Allow-Methods': 'GET', }) - @http.route('/hw_proxy/status', type='json', auth='admin') + @http.route('/hw_proxy/status_json', type='json', auth='admin') def status_json(self): - return request.make_response(simplejson.dumps(self.get_status()),{ - 'Cache-Control': 'no-cache', - 'Content-Type': 'text/html; charset=utf-8', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET', - }) + return self.get_status() @http.route('/hw_proxy/scan_item_success', type='json', auth='admin') def scan_item_success(self, ean): diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 41fc5030519..80515952317 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -379,13 +379,16 @@ td { width:20px; height:16px; color: white; } -.pos .oe_icon.oe_red{ +.pos .oe_status .oe_red, +.pos .oe_icon.oe_red { color: rgb(197, 52, 0); } -.pos .oe_icon.oe_green{ +.pos .oe_statu .oe_green, +.pos .oe_icon.oe_green { color: rgb(94, 185, 55); } -.pos .oe_icon.oe_orange{ +.pos .oe_status .oe_orange, +.pos .oe_icon.oe_orange { color: rgb(239, 153, 65); } diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index e86bb7c368e..f31fb89cf0e 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -105,38 +105,80 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.bypass_proxy = false; this.connection = null; - this.connected = new $.Deferred(); - this.status = 'disconnected'; + this.connected = null; + this.host = ''; + this.keptalive = false; + this.status = {}; - window.proxy = this; + this.status_callback = options.status_callback || null; + this.set_connection_status('disconnected'); + + window.hw_proxy = this; }, - close: function(){ - if(this.status !== 'disconnected'){ + set_connection_status: function(status,drivers){ + this.status.status = status; + this.status.drivers = status === 'disconnected' ? {} : this.status.drivers; + this.status.drivers = drivers ? drivers : this.status.drivers; + if(this.status_callback){ + this.status_callback(status,drivers); + } + }, + disconnect: function(){ + if(this.status.status !== 'disconnected'){ this.connection.destroy(); - this.status = 'disconnected'; + this.set_connection_status('disconnected'); } }, connect : function(url){ var self = this; this.connection = new instance.web.Session(undefined,url); - this.status = 'connecting'; + this.host = url; + this.set_connection_status('connecting',{}); + if(this.connected){ + this.connected.reject(); + } + this.connected = new $.Deferred(); - return this.message('handshake').then(function(){ - self.status = 'connected'; - localStorage['hw_proxy_url'] = url; - self.connected.resolve(); + return this.message('handshake').then(function(response){ + if(response){ + self.set_connection_status('connected'); + localStorage['hw_proxy_url'] = url; + self.connected.resolve(); + self.keepalive(); + }else{ + self.set_connection_status('disconnected'); + self.connected.reject(); + console.error('Connection refused by the Proxy'); + } },function(){ - self.status = 'disconnected'; + self.set_connection_status('disconnected'); self.connected.reject(); console.error('Could not connect to the Proxy'); }); }, + keepalive: function(){ + var self = this; + if(!this.keptalive){ + this.keptalive = true; + function status(){ + self.connection.rpc('/hw_proxy/status_json',{}) + .then(function(driver_status){ + self.set_connection_status('connected',driver_status); + },function(){ + self.set_connection_status('disconnected'); + }).always(function(){ + setTimeout(status,5000); + }); + } + status(); + }; + }, message : function(name,params){ var callbacks = this.notifications[name] || []; for(var i = 0; i < callbacks.length; i++){ callbacks[i](params); } - if(this.status !== 'disconnected'){ + if(this.status.status !== 'disconnected'){ return this.connection.rpc('/hw_proxy/' + name, params || {}); }else{ return (new $.Deferred()).reject(); @@ -245,8 +287,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal } this.notifications[name].push(callback); }, - - //a product has been scanned and recognized with success // ean is a parsed ean object @@ -702,7 +742,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal disconnect_from_proxy: function(){ this.remote_scanning = false; }, - connect_to_proxy: function(){ + connect_to_proxy: function(){ //FIXME prevent double connections var self = this; this.remote_scanning = true; @@ -714,13 +754,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal if(!self.remote_scanning){ return; } - self.pos.set('proxy_status','connected'); self.scan(barcode); waitforbarcode(); }, function(){ setTimeout(waitforbarcode,5000); - self.pos.set('proxy_status','disconnected'); }); }); }, diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 58f87b6f4d3..aae0263de7b 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -55,7 +55,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal 'synch': { state:'connected', pending:0 }, 'orders': new module.OrderCollection(), 'selectedOrder': null, - 'proxy_status': 'connecting', + 'proxy_status': {status: 'connecting', drivers: {}}, }); this.bind('change:synch',function(pos,synch){ @@ -92,17 +92,24 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.barcode_reader.disconnect_from_proxy(); }, + set_proxy_status: function(status,drivers){ + this.set('proxy_status',{status:status,drivers:drivers || {}}); + }, connect_to_proxy: function(){ var self = this; this.barcode_reader.disconnect_from_proxy(); this.pos_widget.loading_message(_t('Connecting to the PosBox'),0); - this.set('proxy_status', 'connecting'); + this.set_proxy_status('connecting'); this.pos_widget.loading_skip(function(){ self.proxy.stop_searching(); - self.set('proxy_status', 'disconnected'); + this.set_proxy_status('disconnected'); }); + this.proxy.status_callback = function(status,driver_status){ + self.set_proxy_status(status,driver_status); + }; + return this.proxy.find_proxy({ force_ip: self.config.proxy_ip || undefined, progress: function(prog){ @@ -114,9 +121,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal if(self.config.iface_scan_via_proxy){ self.barcode_reader.connect_to_proxy(); } - self.set('proxy_status', 'connected'); + self.set_proxy_status('connected'); }else{ - self.set('proxy_status', 'disconnected'); + self.set_proxy_status('disconnected'); } }); }, diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 393b0b2aa8b..1bfda516578 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -856,13 +856,39 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa // this is used to notify the user if the pos is connected to the proxy module.ProxyStatusWidget = module.StatusWidget.extend({ template: 'ProxyStatusWidget', + set_smart_status: function(status){ + if(status.status === 'connected'){ + var warning = false; + var msg = '' + if(this.pos.config.iface_scan_via_proxy){ + var scanner = status.drivers.scanner ? status.drivers.scanner.status : false; + if( scanner != 'connected' && scanner != 'connecting'){ + warning = true; + msg += _t('Scanner'); + } + } + if( this.pos.config.iface_print_via_proxy || + this.pos.config.iface_cashdrawer ){ + var printer = status.drivers.escpos ? status.drivers.escpos.status : false; + if( printer != 'connected' && printer != 'connecting'){ + warning = true; + msg = msg ? msg + ' & ' : msg; + msg += _t('Printer'); + } + } + msg = msg ? msg + ' ' + _t('Offline') : msg; + this.set_status(warning ? 'warning' : 'connected', msg); + }else{ + this.set_status(status.status,''); + } + }, start: function(){ var self = this; - this.set_status(this.pos.get('proxy_status'),''); + this.set_smart_status(this.pos.get('proxy_status')); this.pos.bind('change:proxy_status', function(pos,status){ - self.set_status(status,''); + self.set_smart_status(status); }); this.$el.click(function(){ diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 179d4acb412..748fcbfee1b 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -117,13 +117,16 @@
    - +
    +
    + +
    From c9879fcd1ee5ac3bda28e64a5617cd30318c73f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 27 Jan 2014 01:22:03 +0100 Subject: [PATCH 323/335] [IMP] point_of_sale: refactor proxy to use event based status notifications bzr revid: fva@openerp.com-20140127002203-y2qnhwf0z708gbrx --- addons/point_of_sale/static/src/js/devices.js | 81 ++++++++++++------- addons/point_of_sale/static/src/js/models.js | 29 ++----- addons/point_of_sale/static/src/js/widgets.js | 6 +- 3 files changed, 61 insertions(+), 55 deletions(-) diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index f31fb89cf0e..926fc3f91b6 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -81,8 +81,9 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal // connected to the Point of Sale. As the communication only goes from the POS to the proxy, // methods are used both to signal an event, and to fetch information. - module.ProxyDevice = instance.web.Class.extend({ - init: function(options){ + module.ProxyDevice = instance.web.Class.extend(openerp.PropertiesMixin,{ + init: function(parent,options){ + openerp.PropertiesMixin.init.call(this,parent); var self = this; options = options || {}; url = options.url || 'http://localhost:8069'; @@ -105,57 +106,68 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.bypass_proxy = false; this.connection = null; - this.connected = null; this.host = ''; this.keptalive = false; - this.status = {}; - this.status_callback = options.status_callback || null; + this.set('status',{}); + this.set_connection_status('disconnected'); window.hw_proxy = this; }, set_connection_status: function(status,drivers){ - this.status.status = status; - this.status.drivers = status === 'disconnected' ? {} : this.status.drivers; - this.status.drivers = drivers ? drivers : this.status.drivers; - if(this.status_callback){ - this.status_callback(status,drivers); - } + oldstatus = this.get('status'); + newstatus = {}; + newstatus.status = status; + newstatus.drivers = status === 'disconnected' ? {} : oldstatus.drivers; + newstatus.drivers = drivers ? drivers : newstatus.drivers; + this.set('status',newstatus); }, disconnect: function(){ - if(this.status.status !== 'disconnected'){ + if(this.get('status').status !== 'disconnected'){ this.connection.destroy(); this.set_connection_status('disconnected'); } }, - connect : function(url){ + connect: function(url){ var self = this; this.connection = new instance.web.Session(undefined,url); this.host = url; this.set_connection_status('connecting',{}); - if(this.connected){ - this.connected.reject(); - } - this.connected = new $.Deferred(); return this.message('handshake').then(function(response){ if(response){ self.set_connection_status('connected'); localStorage['hw_proxy_url'] = url; - self.connected.resolve(); self.keepalive(); }else{ self.set_connection_status('disconnected'); - self.connected.reject(); console.error('Connection refused by the Proxy'); } },function(){ self.set_connection_status('disconnected'); - self.connected.reject(); console.error('Could not connect to the Proxy'); }); }, + autoconnect: function(opts){ + var self = this; + this.set_connection_status('connecting',{}); + var success = new $.Deferred(); + this.find_proxy(opts) + .then(function(proxies){ + if(proxies.length > 0){ + self.connect(proxies[0]) + .then(function(){ + success.resolve(); + },function(){ + success.reject(); + }); + }else{ + success.reject(); + } + }); + return success; + }, keepalive: function(){ var self = this; if(!this.keptalive){ @@ -178,7 +190,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal for(var i = 0; i < callbacks.length; i++){ callbacks[i](params); } - if(this.status.status !== 'disconnected'){ + if(this.get('status').status !== 'disconnected'){ return this.connection.rpc('/hw_proxy/' + name, params || {}); }else{ return (new $.Deferred()).reject(); @@ -205,6 +217,8 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal var threads = []; var progress = 0; + this.set_connection_status('connecting'); + if(options.force_ip){ urls.push(options.force_ip); }else{ @@ -277,6 +291,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal stop_searching: function(){ this.searching_for_proxy = false; + this.set_connection_status('disconnected'); }, // this allows the client to be notified when a proxy call is made. The notification @@ -505,6 +520,8 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.pos = attributes.pos; this.action_callback = {}; this.proxy = attributes.proxy; + this.remote_scanning = false; + this.remote_active = 0; this.action_callback_stack = []; @@ -513,6 +530,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.price_prefix_set = attributes.price_prefix_set || {'23':''}; this.cashier_prefix_set = attributes.cashier_prefix_set || {'041':''}; this.client_prefix_set = attributes.client_prefix_set || {'042':''}; + }, save_callbacks: function(){ @@ -742,25 +760,32 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal disconnect_from_proxy: function(){ this.remote_scanning = false; }, - connect_to_proxy: function(){ //FIXME prevent double connections + connect_to_proxy: function(){ //FIXME prevent connection losses var self = this; this.remote_scanning = true; + if(this.remote_active >= 1){ + return; + } + this.remote_active = 1; - this.proxy.connected.then(function waitforbarcode(){ - if(!self.remote_scanning){ - return; - } + function waitforbarcode(){ return self.proxy.connection.rpc('/hw_proxy/scanner',{}).then(function(barcode){ + self.scan(barcode); if(!self.remote_scanning){ + this.remote_active = 0; return; } - self.scan(barcode); waitforbarcode(); }, function(){ + if(!self.remote_scanning){ + this.remote_active = 0; + return; + } setTimeout(waitforbarcode,5000); }); - }); + } + waitforbarcode(); }, }); diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index aae0263de7b..f623b21374c 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -23,7 +23,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time this.pos_widget = attributes.pos_widget; - this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy + this.proxy = new module.ProxyDevice(this); // used to communicate to the hardware devices via a local proxy this.barcode_reader = new module.BarcodeReader({'pos': this, proxy:this.proxy}); // used to read barcodes this.proxy_queue = new module.JobQueue(); // used to prevent parallels communications to the proxy this.db = new module.PosDB(); // a local database used to search trough products and categories & store pending orders @@ -55,7 +55,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal 'synch': { state:'connected', pending:0 }, 'orders': new module.OrderCollection(), 'selectedOrder': null, - 'proxy_status': {status: 'connecting', drivers: {}}, }); this.bind('change:synch',function(pos,synch){ @@ -91,39 +90,21 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.barcode_reader.disconnect(); this.barcode_reader.disconnect_from_proxy(); }, - - set_proxy_status: function(status,drivers){ - this.set('proxy_status',{status:status,drivers:drivers || {}}); - }, connect_to_proxy: function(){ var self = this; this.barcode_reader.disconnect_from_proxy(); this.pos_widget.loading_message(_t('Connecting to the PosBox'),0); - this.set_proxy_status('connecting'); - this.pos_widget.loading_skip(function(){ self.proxy.stop_searching(); - this.set_proxy_status('disconnected'); }); - - this.proxy.status_callback = function(status,driver_status){ - self.set_proxy_status(status,driver_status); - }; - - return this.proxy.find_proxy({ + this.proxy.autoconnect({ force_ip: self.config.proxy_ip || undefined, progress: function(prog){ self.pos_widget.loading_progress(prog); }, - }).then(function(proxies){ - if(proxies.length > 0){ - self.proxy.connect(proxies[0]); - if(self.config.iface_scan_via_proxy){ - self.barcode_reader.connect_to_proxy(); - } - self.set_proxy_status('connected'); - }else{ - self.set_proxy_status('disconnected'); + }).then(function(){ + if(self.config.iface_scan_via_proxy){ + self.barcode_reader.connect_to_proxy(); } }); }, diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 1bfda516578..f887a537d39 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -885,10 +885,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa start: function(){ var self = this; - this.set_smart_status(this.pos.get('proxy_status')); + this.set_smart_status(this.pos.proxy.get('status')); - this.pos.bind('change:proxy_status', function(pos,status){ - self.set_smart_status(status); + this.pos.proxy.on('change:status',this,function(eh,status){ //FIXME remove duplicate changes + self.set_smart_status(status.newValue); }); this.$el.click(function(){ From 939ae651b4553cc39e1019b13b581525d285b16c Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Mon, 27 Jan 2014 09:20:38 +0100 Subject: [PATCH 324/335] [IMP] improves the account invoice reporting view, in graph view mode : puts the period in column, product category in rows (addon account) bzr revid: ged@openerp.com-20140127082038-sek8wnkzwundrxe6 --- addons/account/report/account_invoice_report_view.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/account/report/account_invoice_report_view.xml b/addons/account/report/account_invoice_report_view.xml index 5bdef88392d..67d568801b7 100644 --- a/addons/account/report/account_invoice_report_view.xml +++ b/addons/account/report/account_invoice_report_view.xml @@ -6,7 +6,8 @@ account.invoice.report - + + @@ -38,7 +39,7 @@ - + @@ -59,7 +60,7 @@ account.invoice.report form graph - {'search_default_period':1,'search_default_current':1, 'search_default_year': 1, 'search_default_category_product':1, 'search_default_customer':1, 'group_by':[], 'group_by_no_leaf':1,} + {'search_default_current':1, 'search_default_year': 1, 'search_default_customer':1, 'group_by':[], 'group_by_no_leaf':1,} From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs. From 737bb32e588eb9c64c11d7a7ff89791bd00ef675 Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Mon, 27 Jan 2014 10:23:08 +0100 Subject: [PATCH 325/335] [IMP] improves the HR recruitment analysis view (reporting) by using the new graph view in pivot mode (addon hr_recruitment) bzr revid: ged@openerp.com-20140127092308-g893knucca1ez4cq --- .../report/hr_recruitment_report.py | 19 ++++--------------- .../report/hr_recruitment_report_view.xml | 14 ++++++++------ 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/addons/hr_recruitment/report/hr_recruitment_report.py b/addons/hr_recruitment/report/hr_recruitment_report.py index 908b701d928..e83b0d2cc3e 100644 --- a/addons/hr_recruitment/report/hr_recruitment_report.py +++ b/addons/hr_recruitment/report/hr_recruitment_report.py @@ -34,18 +34,8 @@ class hr_recruitment_report(osv.Model): _columns = { 'user_id': fields.many2one('res.users', 'User', readonly=True), - 'nbr': fields.integer('# of Applications', readonly=True), - # TDE TODO: use MONTHS - 'month':fields.selection([('01', 'January'), ('02', 'February'), \ - ('03', 'March'), ('04', 'April'),\ - ('05', 'May'), ('06', 'June'), \ - ('07', 'July'), ('08', 'August'),\ - ('09', 'September'), ('10', 'October'),\ - ('11', 'November'), ('12', 'December')], 'Month', readonly=True), 'company_id': fields.many2one('res.company', 'Company', readonly=True), - 'day': fields.char('Day', size=128, readonly=True), - 'year': fields.char('Year', size=4, readonly=True), - 'date_create': fields.date('Create Date', readonly=True), + 'date_create': fields.datetime('Create Date', readonly=True), 'date_last_stage_update': fields.datetime('Last Stage Update', readonly=True), 'date_closed': fields.date('Closed', readonly=True), 'job_id': fields.many2one('hr.job', 'Applied Job',readonly=True), @@ -70,7 +60,7 @@ class hr_recruitment_report(osv.Model): create or replace view hr_recruitment_report as ( select min(s.id) as id, - date_trunc('day',s.create_date) as date_create, + s.create_date as date_create, date_trunc('day',s.date_closed) as date_closed, date_trunc('day',s.date_last_stage_update) as date_last_stage_update, to_char(s.create_date, 'YYYY') as year, @@ -94,9 +84,6 @@ class hr_recruitment_report(osv.Model): count(*) as nbr from hr_applicant s group by - to_char(s.create_date, 'YYYY'), - to_char(s.create_date, 'MM'), - to_char(s.create_date, 'YYYY-MM-DD') , date_trunc('day',s.create_date), date_trunc('day',s.date_closed), s.date_open, @@ -117,3 +104,5 @@ class hr_recruitment_report(osv.Model): """) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + + diff --git a/addons/hr_recruitment/report/hr_recruitment_report_view.xml b/addons/hr_recruitment/report/hr_recruitment_report_view.xml index 56d0e61a1f6..c214b2efa75 100644 --- a/addons/hr_recruitment/report/hr_recruitment_report_view.xml +++ b/addons/hr_recruitment/report/hr_recruitment_report_view.xml @@ -6,8 +6,9 @@ hr.recruitment.report - - + + + @@ -42,9 +43,10 @@ - - - + + + + @@ -55,7 +57,7 @@ hr.recruitment.report form graph - {'search_default_year':1,'search_default_month':1,'search_default_department':1,'group_by_no_leaf':1,'group_by':[]} + {'group_by_no_leaf':1,'group_by':[]} From f6fec727974c1cf4094f044fbf88184d93603ae5 Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Mon, 27 Jan 2014 10:49:18 +0100 Subject: [PATCH 326/335] [IMP] improves the Event reporting view by using the new graph view in pivot mode (addon event) bzr revid: ged@openerp.com-20140127094918-52w9xt1nj1ae93fn --- addons/event/report/report_event_registration.py | 13 ++----------- .../report/report_event_registration_view.xml | 14 ++++++++------ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/addons/event/report/report_event_registration.py b/addons/event/report/report_event_registration.py index 0e33e892432..23ab2f26305 100644 --- a/addons/event/report/report_event_registration.py +++ b/addons/event/report/report_event_registration.py @@ -27,12 +27,7 @@ class report_event_registration(osv.osv): _description = "Events Analysis" _auto = False _columns = { - 'event_date': fields.char('Event Start Date', size=64, readonly=True), - 'year': fields.char('Year', size=4, readonly=True), - 'month': fields.selection([ - ('01','January'), ('02','February'), ('03','March'), ('04','April'), - ('05','May'), ('06','June'), ('07','July'), ('08','August'), - ('09','September'), ('10','October'), ('11','November'), ('12','December')], 'Month',readonly=True), + 'event_date': fields.datetime('Event Date', readonly=True), 'event_id': fields.many2one('event.event', 'Event', required=True), 'draft_state': fields.integer(' # No of Draft Registrations', size=20), 'confirm_state': fields.integer(' # No of Confirmed Registrations', size=20), @@ -65,9 +60,7 @@ class report_event_registration(osv.osv): r.name AS name_registration, e.company_id AS company_id, e.main_speaker_id AS speaker_id, - to_char(e.date_begin, 'YYYY-MM-DD') AS event_date, - to_char(e.date_begin, 'YYYY') AS year, - to_char(e.date_begin, 'MM') AS month, + e.date_begin AS event_date, count(e.id) AS nbevent, CASE WHEN r.state IN ('draft') THEN r.nb_register ELSE 0 END AS draft_state, CASE WHEN r.state IN ('open','done') THEN r.nb_register ELSE 0 END AS confirm_state, @@ -92,8 +85,6 @@ class report_event_registration(osv.osv): event_state, e.company_id, e.main_speaker_id, - year, - month, e.register_max, name_registration ) diff --git a/addons/event/report/report_event_registration_view.xml b/addons/event/report/report_event_registration_view.xml index 378fc667362..c6c2191c9c0 100644 --- a/addons/event/report/report_event_registration_view.xml +++ b/addons/event/report/report_event_registration_view.xml @@ -7,8 +7,10 @@ report.event.registration - - + + + + @@ -40,9 +42,9 @@ - - - + + + form graph - {"search_default_year":1,"search_default_this_month":1,"search_default_365day":1, "search_default_invoiced":1, "search_default_event":1, "group_by_no_leaf":1, "group_by":[]} + {"group_by_no_leaf":1, "group_by":[]} From 9cd84a583457d1052e2cbba9f5b178dc3c68a32f Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Mon, 27 Jan 2014 11:34:08 +0100 Subject: [PATCH 327/335] [IMP] replace the Leaves analysis views in reporting by the new pivot graph view (addon hr_holidays) bzr revid: ged@openerp.com-20140127103408-8a1fi88gdhl2sud5 --- .../report/available_holidays_view.xml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/addons/hr_holidays/report/available_holidays_view.xml b/addons/hr_holidays/report/available_holidays_view.xml index e7441060204..b288db30572 100644 --- a/addons/hr_holidays/report/available_holidays_view.xml +++ b/addons/hr_holidays/report/available_holidays_view.xml @@ -2,13 +2,26 @@ + + hr.holidays.report_graph + hr.holidays + 20 + + + + + + + + + + Leaves Analysis hr.holidays form - tree,form + graph - {'search_default_year':1, 'search_default_This Month':1, 'search_default_group_employee': 1, 'search_default_group_type': 1} [('holiday_type','=','employee')] From 567be50d0aa650366d8db47e4c2422f9edac2ab7 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Mon, 27 Jan 2014 11:38:16 +0100 Subject: [PATCH 328/335] [FIX] crm: mass assign were not always deduplicating bzr revid: dle@openerp.com-20140127103816-t9pga6sohbxaw5ux --- addons/crm/wizard/crm_lead_to_opportunity.py | 2 +- addons/crm/wizard/crm_lead_to_opportunity_view.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index e2fb70bfa56..21a553a59ff 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -257,7 +257,7 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory): if data.name == 'convert' and data.deduplicate: merged_lead_ids = [] remaining_lead_ids = [] - for lead in data.opportunity_ids: + for lead in self.pool['crm.lead'].browse(cr, uid, context.get('active_ids', []), context=context): duplicated_lead_ids = self._get_duplicated_leads(cr, uid, lead.partner_id.id, lead.partner_id and lead.partner_id.email or lead.email_from) if len(duplicated_lead_ids) > 1: lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, duplicated_lead_ids, False, False, context=context) diff --git a/addons/crm/wizard/crm_lead_to_opportunity_view.xml b/addons/crm/wizard/crm_lead_to_opportunity_view.xml index 130aa4192d6..58fbe244ba4 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity_view.xml +++ b/addons/crm/wizard/crm_lead_to_opportunity_view.xml @@ -59,9 +59,9 @@ - @@ -55,7 +56,7 @@ graph - {'search_default_year':1,'search_default_month':1,'search_default_User':1,'group_by_no_leaf':1,'group_by':[]} + {'search_default_sales':1, 'group_by_no_leaf':1,'group_by':[]} This report performs analysis on your quotations and sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. From 85daaccb619c2da8e3f05b72468d99bf24e060fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 27 Jan 2014 13:36:14 +0100 Subject: [PATCH 330/335] [FIX] point_of_sale: prevent barcode scanner connection loss bzr revid: fva@openerp.com-20140127123614-vjrcwgh7s8rs2y2u --- addons/point_of_sale/static/src/js/devices.js | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 926fc3f91b6..57b23afd49f 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -129,6 +129,8 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.set_connection_status('disconnected'); } }, + + // connects to the specified url connect: function(url){ var self = this; this.connection = new instance.web.Session(undefined,url); @@ -149,35 +151,43 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal console.error('Could not connect to the Proxy'); }); }, - autoconnect: function(opts){ + + // find a proxy and connects to it. for options see find_proxy + autoconnect: function(options){ var self = this; this.set_connection_status('connecting',{}); var success = new $.Deferred(); - this.find_proxy(opts) + this.find_proxy(options) .then(function(proxies){ if(proxies.length > 0){ self.connect(proxies[0]) .then(function(){ success.resolve(); },function(){ + self.set_connection_status('disconnected'); success.reject(); }); }else{ + self.set_connection_status('disconnected'); success.reject(); } }); return success; }, + + // starts a loop that updates the connection status keepalive: function(){ var self = this; if(!this.keptalive){ this.keptalive = true; function status(){ - self.connection.rpc('/hw_proxy/status_json',{}) + self.connection.rpc('/hw_proxy/status_json',{},{timeout:500}) .then(function(driver_status){ self.set_connection_status('connected',driver_status); },function(){ - self.set_connection_status('disconnected'); + if(self.get('status').status !== 'connecting'){ + self.set_connection_status('disconnected'); + } }).always(function(){ setTimeout(status,5000); }); @@ -185,6 +195,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal status(); }; }, + message : function(name,params){ var callbacks = this.notifications[name] || []; for(var i = 0; i < callbacks.length; i++){ @@ -197,18 +208,15 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal } }, - test_connection: function(host,timeout){ - return $.ajax({ - url: host + '/hw_proxy/hello', - method: 'GET', - timeout: timeout || 10000, - }); - }, - + // returns as a deferred a list of valid hosts urls that can be used as proxy. + // options: + // - port: what port to listen to (default 8069) + // - force_ip : limit the search to the specified ip + // - progress(fac) : callback for search progress ( fac in [0,1] ) find_proxy: function(options){ options = options || {}; var self = this; - var port = ':8069'; + var port = ':' + (options.port || '8069'); var urls = []; var found = false; var proxies = []; @@ -249,8 +257,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal if(!url){ done.resolve(); } - var c = self.test_connection(url, 300) - .done(function(){ + var c = $.ajax({ + url: url + '/hw_proxy/hello', + method: 'GET', + timeout: 300, + }).done(function(){ found = true; update_progress(); proxies.push(url); @@ -757,10 +768,10 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal disconnect: function(){ $('body').off('keypress', this.handler) }, - disconnect_from_proxy: function(){ - this.remote_scanning = false; - }, - connect_to_proxy: function(){ //FIXME prevent connection losses + + // the barcode scanner will listen on the hw_proxy/scanner interface for + // scan events until disconnect_from_proxy is called + connect_to_proxy: function(){ var self = this; this.remote_scanning = true; if(this.remote_active >= 1){ @@ -769,17 +780,18 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.remote_active = 1; function waitforbarcode(){ - return self.proxy.connection.rpc('/hw_proxy/scanner',{}).then(function(barcode){ - self.scan(barcode); + return self.proxy.connection.rpc('/hw_proxy/scanner',{},{timeout:7500}) + .then(function(barcode){ if(!self.remote_scanning){ - this.remote_active = 0; + self.remote_active = 0; return; } + self.scan(barcode); waitforbarcode(); }, function(){ if(!self.remote_scanning){ - this.remote_active = 0; + self.remote_active = 0; return; } setTimeout(waitforbarcode,5000); @@ -787,6 +799,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal } waitforbarcode(); }, + + // the barcode scanner will stop listening on the hw_proxy/scanner remote interface + disconnect_from_proxy: function(){ + this.remote_scanning = false; + }, }); } From 68a466e8a876238e4cb3422ccfd10fddfe050585 Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Mon, 27 Jan 2014 14:03:51 +0100 Subject: [PATCH 331/335] [IMP] improves the Timesheet Analysis reporting view : changes the axis, adds a 'this month' filter using the new pivot graph view (addon hr_timesheet_sheet) bzr revid: ged@openerp.com-20140127130351-iu422fkjnu8059wd --- .../hr_timesheet_sheet/report/hr_timesheet_report.py | 8 -------- .../report/hr_timesheet_report_view.xml | 10 ++++++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/addons/hr_timesheet_sheet/report/hr_timesheet_report.py b/addons/hr_timesheet_sheet/report/hr_timesheet_report.py index 61c2b9e53da..e15ae99ab5a 100644 --- a/addons/hr_timesheet_sheet/report/hr_timesheet_report.py +++ b/addons/hr_timesheet_sheet/report/hr_timesheet_report.py @@ -29,11 +29,6 @@ class hr_timesheet_report(osv.osv): _description = "Timesheet" _auto = False _columns = { - 'year': fields.char('Year',size=64,required=False, readonly=True), - 'day': fields.char('Day', size=128, readonly=True), - 'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), - ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), - ('10','October'), ('11','November'), ('12','December')], 'Month',readonly=True), 'date': fields.date('Date', readonly=True), 'name': fields.char('Description', size=64,readonly=True), 'product_id' : fields.many2one('product.product', 'Product',readonly=True), @@ -53,9 +48,6 @@ class hr_timesheet_report(osv.osv): select min(t.id) as id, l.date as date, - to_char(l.date, 'YYYY-MM-DD') as day, - to_char(l.date,'YYYY') as year, - to_char(l.date,'MM') as month, sum(l.amount) as cost, sum(l.unit_amount) as quantity, l.account_id as account_id, diff --git a/addons/hr_timesheet_sheet/report/hr_timesheet_report_view.xml b/addons/hr_timesheet_sheet/report/hr_timesheet_report_view.xml index 5c0da80b528..5e2ca249d7d 100644 --- a/addons/hr_timesheet_sheet/report/hr_timesheet_report_view.xml +++ b/addons/hr_timesheet_sheet/report/hr_timesheet_report_view.xml @@ -7,6 +7,7 @@ + @@ -16,6 +17,7 @@ hr.timesheet.report + @@ -31,9 +33,9 @@ - - - + + + @@ -44,7 +46,7 @@ hr.timesheet.report form graph - {'search_default_year':1,'search_default_month':1,'search_default_group_user_id':1,'group_by_no_leaf':1,'group_by':[]} + {'search_default_month':1,'search_default_group_user_id':1,'group_by_no_leaf':1,'group_by':[]}

    This report performs analysis on timesheets created by your From a584ba9ed2e2d921d16b4a5c69f62bbc5e43d219 Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Mon, 27 Jan 2014 14:20:20 +0100 Subject: [PATCH 332/335] [IMP] removes 'Employee Timesheet' and 'Employees Timesheet' reports in HR reporting (addon hr_timesheet) bzr revid: ged@openerp.com-20140127132020-qhxgkygxx38vecab --- addons/hr_timesheet/__openerp__.py | 2 - addons/hr_timesheet/wizard/__init__.py | 2 - .../wizard/hr_timesheet_print_employee.py | 66 ------------------- .../hr_timesheet_print_employee_view.xml | 40 ----------- .../wizard/hr_timesheet_print_users.py | 56 ---------------- .../wizard/hr_timesheet_print_users_view.xml | 46 ------------- 6 files changed, 212 deletions(-) delete mode 100644 addons/hr_timesheet/wizard/hr_timesheet_print_employee.py delete mode 100644 addons/hr_timesheet/wizard/hr_timesheet_print_employee_view.xml delete mode 100644 addons/hr_timesheet/wizard/hr_timesheet_print_users.py delete mode 100644 addons/hr_timesheet/wizard/hr_timesheet_print_users_view.xml diff --git a/addons/hr_timesheet/__openerp__.py b/addons/hr_timesheet/__openerp__.py index f2e589d969e..17a40f64189 100644 --- a/addons/hr_timesheet/__openerp__.py +++ b/addons/hr_timesheet/__openerp__.py @@ -49,8 +49,6 @@ up a management by affair. 'hr_timesheet_report.xml', 'hr_timesheet_wizard.xml', 'process/hr_timesheet_process.xml', - 'wizard/hr_timesheet_print_employee_view.xml', - 'wizard/hr_timesheet_print_users_view.xml', 'wizard/hr_timesheet_sign_in_out_view.xml', 'hr_timesheet_installer.xml', 'hr_timesheet_data.xml' diff --git a/addons/hr_timesheet/wizard/__init__.py b/addons/hr_timesheet/wizard/__init__.py index 5f2bc1443e3..8b73cf62f43 100644 --- a/addons/hr_timesheet/wizard/__init__.py +++ b/addons/hr_timesheet/wizard/__init__.py @@ -20,8 +20,6 @@ ############################################################################## import hr_timesheet_sign_in_out -import hr_timesheet_print_employee -import hr_timesheet_print_users # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hr_timesheet/wizard/hr_timesheet_print_employee.py b/addons/hr_timesheet/wizard/hr_timesheet_print_employee.py deleted file mode 100644 index efffa6acd5f..00000000000 --- a/addons/hr_timesheet/wizard/hr_timesheet_print_employee.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -import datetime - -from openerp.osv import fields, osv -from openerp.tools.translate import _ - -class analytical_timesheet_employee(osv.osv_memory): - _name = 'hr.analytical.timesheet.employee' - _description = 'Print Employee Timesheet & Print My Timesheet' - _columns = { - 'month': fields.selection([(1,'January'), (2,'February'), (3,'March'), (4,'April'), - (5,'May'), (6,'June'), (7,'July'), (8,'August'), (9,'September'), - (10,'October'), (11,'November'), (12,'December')], 'Month', required=True), - 'year': fields.integer('Year', required=True), - 'employee_id': fields.many2one('hr.employee', 'Employee', required=True) - - } - - def _get_user(self, cr, uid, context=None): - - emp_obj = self.pool.get('hr.employee') - emp_id = emp_obj.search(cr, uid, [('user_id', '=', uid)], context=context) - if not emp_id: - raise osv.except_osv(_("Warning!"), _("Please define employee for this user!")) - return emp_id and emp_id[0] or False - - _defaults = { - 'month': lambda *a: datetime.date.today().month, - 'year': lambda *a: datetime.date.today().year, - 'employee_id': _get_user - } - - def print_report(self, cr, uid, ids, context=None): - data = self.read(cr, uid, ids, context=context)[0] - data['employee_id'] = data['employee_id'][0] - datas = { - 'ids': [], - 'model': 'hr.employee', - 'form': data - } - return { - 'type': 'ir.actions.report.xml', - 'report_name': 'hr.analytical.timesheet', - 'datas': datas, - } - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hr_timesheet/wizard/hr_timesheet_print_employee_view.xml b/addons/hr_timesheet/wizard/hr_timesheet_print_employee_view.xml deleted file mode 100644 index 3603809990b..00000000000 --- a/addons/hr_timesheet/wizard/hr_timesheet_print_employee_view.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - hr.analytical.timesheet.employee.form - hr.analytical.timesheet.employee - -

    - - - - - -
    -
    -
    -
    - - - - Employee Timesheet - hr.analytical.timesheet.employee - form - tree,form - - new - - - - - - diff --git a/addons/hr_timesheet/wizard/hr_timesheet_print_users.py b/addons/hr_timesheet/wizard/hr_timesheet_print_users.py deleted file mode 100644 index d7dda927111..00000000000 --- a/addons/hr_timesheet/wizard/hr_timesheet_print_users.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import datetime - -from openerp.osv import fields, osv - -class analytical_timesheet_employees(osv.osv_memory): - _name = 'hr.analytical.timesheet.users' - _description = 'Print Employees Timesheet' - _columns = { - 'month': fields.selection([(1,'January'), (2,'February'), (3,'March'), (4,'April'), - (5,'May'), (6,'June'), (7,'July'), (8,'August'), (9,'September'), - (10,'October'), (11,'November'), (12,'December')], 'Month', required=True), - 'year': fields.integer('Year', required=True), - 'employee_ids': fields.many2many('hr.employee', 'timesheet_employee_rel', 'timesheet_id', 'employee_id', 'employees', required=True) - } - - _defaults = { - 'month': lambda *a: datetime.date.today().month, - 'year': lambda *a: datetime.date.today().year, - } - - def print_report(self, cr, uid, ids, context=None): - data = self.read(cr, uid, ids, context=context)[0] - datas = { - 'ids': [], - 'model': 'hr.employee', - 'form': data - } - return { - 'type': 'ir.actions.report.xml', - 'report_name': 'hr.analytical.timesheet_users', - 'datas': datas, - } - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hr_timesheet/wizard/hr_timesheet_print_users_view.xml b/addons/hr_timesheet/wizard/hr_timesheet_print_users_view.xml deleted file mode 100644 index 93015a76503..00000000000 --- a/addons/hr_timesheet/wizard/hr_timesheet_print_users_view.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - hr.analytical.timesheet.users.form - hr.analytical.timesheet.users - -
    - - - - - - - -
    -
    -
    -
    -
    - - - Employees Timesheet - hr.analytical.timesheet.users - form - tree,form - - new - - - - -
    -
    From cdde84f821a78ad4948e4a87ba760c359a908caa Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Mon, 27 Jan 2014 15:04:30 +0100 Subject: [PATCH 333/335] [FIX] remove the yml tests hr_timesheet_report (it should have been removed when I removed the Employee(s) timesheet reports from the reporting menu) (addon hr_timesheet) bzr revid: ged@openerp.com-20140127140430-49i2dqyq67qc19qm --- addons/hr_timesheet/__openerp__.py | 1 - .../hr_timesheet/test/hr_timesheet_report.yml | 23 ------------------- 2 files changed, 24 deletions(-) delete mode 100644 addons/hr_timesheet/test/hr_timesheet_report.yml diff --git a/addons/hr_timesheet/__openerp__.py b/addons/hr_timesheet/__openerp__.py index 17a40f64189..6105271be77 100644 --- a/addons/hr_timesheet/__openerp__.py +++ b/addons/hr_timesheet/__openerp__.py @@ -57,7 +57,6 @@ up a management by affair. 'test': [ 'test/hr_timesheet_users.yml', 'test/test_hr_timesheet.yml', - 'test/hr_timesheet_report.yml', 'test/hr_timesheet_demo.yml', ], 'installable': True, diff --git a/addons/hr_timesheet/test/hr_timesheet_report.yml b/addons/hr_timesheet/test/hr_timesheet_report.yml deleted file mode 100644 index ec8a8954cca..00000000000 --- a/addons/hr_timesheet/test/hr_timesheet_report.yml +++ /dev/null @@ -1,23 +0,0 @@ -- - Print the HR Timesheet By Employee report through the wizard -- - !python {model: hr.employee}: | - import os, time - from openerp import netsvc, tools - ctx={} - ctx.update({'model': 'hr.employee','active_ids': []}) - data_dict = {'month': time.gmtime()[1], 'year': time.gmtime()[0], 'employee_id' : ref('hr.employee_fp')} - from openerp.tools import test_reports - test_reports.try_report_action(cr, uid, 'action_hr_timesheet_employee',wiz_data=data_dict, context=ctx, our_module='hr_timesheet') - -- - Print the HR Timesheet By Employee report through the wizard -- - !python {model: hr.employee}: | - import os, time - from openerp import netsvc, tools - ctx={} - ctx.update({'model': 'hr.employee','active_ids': []}) - data_dict = {'month': time.gmtime()[1], 'year': time.gmtime()[0], 'employee_ids' : [(6,0,[ref('hr.employee_fp'),ref('hr.employee_qdp'),ref('hr.employee_al')])]} - from openerp.tools import test_reports - test_reports.try_report_action(cr, uid, 'action_hr_timesheet_users',wiz_data=data_dict, context=ctx, our_module='hr_timesheet') From e4cb7e5aa14b8d5d5e759ff1997f2164852cd76c Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Mon, 27 Jan 2014 15:32:19 +0100 Subject: [PATCH 334/335] [FIX] change the name of the sales filter to Sales -> this is the name required in some other places that inherits it (addon sale) bzr revid: ged@openerp.com-20140127143219-2ljcrieylv3pdz8c --- addons/sale/report/sale_report_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/sale/report/sale_report_view.xml b/addons/sale/report/sale_report_view.xml index 40af5281012..14b719ebd7a 100644 --- a/addons/sale/report/sale_report_view.xml +++ b/addons/sale/report/sale_report_view.xml @@ -22,7 +22,7 @@ - + @@ -56,7 +56,7 @@ graph - {'search_default_sales':1, 'group_by_no_leaf':1,'group_by':[]} + {'search_default_Sales':1, 'group_by_no_leaf':1,'group_by':[]} This report performs analysis on your quotations and sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. From f38017faecc1bf6e745ca1a330c584da7ac061af Mon Sep 17 00:00:00 2001 From: Gery Debongnie Date: Mon, 27 Jan 2014 16:23:24 +0100 Subject: [PATCH 335/335] [FIX] fixes a crash happening when the context has a groupby attributes which is an array (addon web_graph) bzr revid: ged@openerp.com-20140127152324-n1jeyfjvii9dfeoh --- addons/web_graph/static/src/js/graph_widget.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/addons/web_graph/static/src/js/graph_widget.js b/addons/web_graph/static/src/js/graph_widget.js index 49ddb9388a0..454af202593 100644 --- a/addons/web_graph/static/src/js/graph_widget.js +++ b/addons/web_graph/static/src/js/graph_widget.js @@ -79,10 +79,18 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({ return g instanceof openerp.web.search.GroupbyGroup; }); - var filters = _.flatten(_.pluck(groupbygroups, 'filters'), true); + var filters = _.flatten(_.pluck(groupbygroups, 'filters'), true), + groupbys = _.flatten(_.map(filters, function (filter) { + var groupby = py.eval(filter.attrs.context).group_by; + if (!(groupby instanceof Array)) { groupby = [groupby]; } + return _.map(groupby, function(g) { + return {field: g, filter: filter}; + }); + })); - return _.uniq(_.map(filters, function (filter) { - var field = py.eval(filter.attrs.context).group_by, + return _.uniq(_.map(groupbys, function (groupby) { + var field = groupby.field, + filter = groupby.filter, raw_field = field.split(':')[0], string = (field === raw_field) ? filter.attrs.string : self.fields[raw_field].string; @@ -465,7 +473,6 @@ openerp.web_graph.Graph = openerp.web.Widget.extend({ make_measure_row: function() { var self = this, cols = this.pivot.cols.headers, - measure_cells, measure_row = $(''); measure_row.append($(''));